1"use strict";(self.webpackChunkinnovation_labs=self.webpackChunkinnovation_labs||[]).push([[2162],{61246:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>r,metadata:()=>s,toc:()=>d});const s=JSON.parse('{"id":"examples/chat-protocol/image-analysis-agent","title":"Image Analysis Agent","description":"This guide shows how to create a hosted Image Analysis Agent that accepts an image attachment plus a text prompt over the Agent Chat Protocol, then returns a natural-language analysis. The agent is compatible with ASI:One Chat.","source":"@site/versioned_docs/version-1.0.5/examples/chat-protocol/image-analysis-agent.md","sourceDirName":"examples/chat-protocol","slug":"/examples/chat-protocol/image-analysis-agent","permalink":"/resources/docs/examples/chat-protocol/image-analysis-agent","draft":false,"unlisted":false,"tags":[],"version":"1.0.5","frontMatter":{"id":"image-analysis-agent","title":"Image Analysis Agent"},"sidebar":"tutorialSidebar","previous":{"title":"Create an ASI:One Compatible Agent Using the Chat Protocol","permalink":"/resources/docs/examples/chat-protocol/asi-compatible-uagents"},"next":{"title":"Image Generation Agent","permalink":"/resources/docs/examples/chat-protocol/image-generation-agent"}}');var a=t(74848),i=t(28453);const r={id:"image-analysis-agent",title:"Image Analysis Agent"},o="Image Analysis Agent",l={},d=[{value:"Prerequisites",id:"prerequisites",level:2},{value:"Overview",id:"overview",level:2},{value:"Message Flow",id:"message-flow",level:2},{value:"Implementation",id:"implementation",level:2},{value:"Agentverse secrets",id:"agentverse-secrets",level:3},{value:"1. Image analysis helper (<code>image_analysis.py</code>)",id:"1-image-analysis-helper-image_analysispy",level:3},{value:"2. Hosted agent (<code>agent.py</code>)",id:"2-hosted-agent-agentpy",level:3},{value:"Key features",id:"key-features",level:4},{value:"3. Local run (optional)",id:"3-local-run-optional",level:3},{value:"Adding a README to your agent",id:"adding-a-readme-to-your-agent",level:2},{value:"Query your agent (ASI Chat)",id:"query-your-agent-asi-chat",level:2},{value:"Environment variables",id:"environment-variables",level:2},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"Full example repository",id:"full-example-repository",level:2}];function c(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(n.header,{children:(0,a.jsx)(n.h1,{id:"image-analysis-agent",children:"Image Analysis Agent"})}),"\n",(0,a.jsxs)(n.p,{children:["This guide shows how to create a hosted ",(0,a.jsx)(n.strong,{children:"Image Analysis Agent"})," that accepts an image attachment plus a text prompt over the ",(0,a.jsx)(n.a,{href:"/docs/agent-communication/agent-chat-protocol",children:"Agent Chat Protocol"}),", then returns a natural-language analysis. The agent is compatible with ",(0,a.jsxs)(n.a,{href:"https://chat.asi1.ai/",children:["ASI",":One"," Chat"]}),"."]}),"\n",(0,a.jsxs)(n.p,{children:["Live profile (reference layout for handle, README, and ",(0,a.jsx)(n.strong,{children:"Chat with Agent"}),"): ",(0,a.jsx)(n.a,{href:"https://agentverse.ai/agents/details/agent1q23d43ql6wxfaf4ahgtm98u2fktc5hqp7q29psujdvz728y34xy5uxqkklk/profile",children:"Image Analysis Agent Profile"}),"."]}),"\n",(0,a.jsxs)(n.p,{children:["For a basic ASI",":One-compatible"," agent without attachments, start with ",(0,a.jsxs)(n.a,{href:"/docs/examples/chat-protocol/asi-compatible-uagents",children:["ASI",":One"," Compatible Agent"]}),"."]}),"\n",(0,a.jsx)(n.h2,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["An ",(0,a.jsx)(n.a,{href:"https://agentverse.ai/",children:"Agentverse"})," account (this guide is ",(0,a.jsx)(n.strong,{children:"hosted-first"}),")."]}),"\n",(0,a.jsxs)(n.li,{children:["An ",(0,a.jsx)(n.a,{href:"https://platform.openai.com/api-keys",children:"OpenAI API key"})," with access to a multimodal model."]}),"\n",(0,a.jsxs)(n.li,{children:["Familiarity with the ",(0,a.jsx)(n.a,{href:"/docs/agent-communication/agent-chat-protocol",children:"Agent Chat Protocol"})," (",(0,a.jsx)(n.code,{children:"ChatMessage"}),", ",(0,a.jsx)(n.code,{children:"ChatAcknowledgement"}),", ",(0,a.jsx)(n.code,{children:"ResourceContent"}),", ",(0,a.jsx)(n.code,{children:"StartSessionContent"}),", ",(0,a.jsx)(n.code,{children:"MetadataContent"}),")."]}),"\n",(0,a.jsx)(n.li,{children:"Optional, for a local clone only:"}),"\n"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:'pip install uagents==0.25.5 uagents-core==0.4.9 "openai>=1.40.0" python-dotenv\n'})}),"\n",(0,a.jsx)(n.p,{children:"Hosted agents on Agentverse already provide uAgents. You still need to paste both Python files below and set secrets."}),"\n",(0,a.jsx)(n.h2,{id:"overview",children:"Overview"}),"\n",(0,a.jsx)(n.p,{children:"You will build a uAgent that:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["Accepts text plus image ",(0,a.jsx)(n.strong,{children:"attachments"})," through the chat protocol."]}),"\n",(0,a.jsxs)(n.li,{children:["Sends the prompt and an HTTPS image URL to the ",(0,a.jsx)(n.strong,{children:"OpenAI multimodal Responses API"})," (default model ",(0,a.jsx)(n.code,{children:"gpt-4.1-mini"}),")."]}),"\n",(0,a.jsx)(n.li,{children:"Returns a description or analysis in chat."}),"\n",(0,a.jsxs)(n.li,{children:["Forwards whatever image URL the Chat Interface attached. The agent does ",(0,a.jsx)(n.strong,{children:"not"})," validate file formats or sizes; those limits come from the Chat Interface and OpenAI (typically common raster types such as JPEG, PNG, WebP, and GIF)."]}),"\n"]}),"\n",(0,a.jsx)(n.h2,{id:"message-flow",children:"Message Flow"}),"\n",(0,a.jsxs)(n.p,{children:["The communication between the user, Chat Interface, and ",(0,a.jsx)(n.strong,{children:"Image Analysis Agent"})," proceeds as follows. Message types are defined in the ",(0,a.jsx)(n.a,{href:"/docs/agent-communication/agent-chat-protocol",children:"Agent Chat Protocol"}),"."]}),"\n",(0,a.jsxs)(n.ol,{children:["\n",(0,a.jsxs)(n.li,{children:["\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"User query"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["The user submits a prompt and an image through ",(0,a.jsxs)(n.a,{href:"https://chat.asi1.ai/",children:["ASI",":One"," Chat"]}),"."]}),"\n"]}),"\n"]}),"\n",(0,a.jsxs)(n.li,{children:["\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Image upload and query forwarding"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.strong,{children:"2.1"}),": The Chat Interface may upload the image to Agent Storage internally."]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.strong,{children:"2.2"}),": The Chat Interface forwards the user's query plus ",(0,a.jsx)(n.code,{children:"ResourceContent"})," (an attachment URL) to the Image Analysis Agent as a ",(0,a.jsx)(n.code,{children:"ChatMessage"}),"."]}),"\n"]}),"\n"]}),"\n",(0,a.jsxs)(n.li,{children:["\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Acknowledgement (on receipt)"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["The agent immediately sends a ",(0,a.jsx)(n.code,{children:"ChatAcknowledgement"})," for the incoming ",(0,a.jsx)(n.code,{children:"msg_id"}),". Analysis happens ",(0,a.jsx)(n.strong,{children:"after"})," this ack."]}),"\n"]}),"\n"]}),"\n",(0,a.jsxs)(n.li,{children:["\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Image URL extraction"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["The agent extracts a valid ",(0,a.jsx)(n.code,{children:"http"}),"/",(0,a.jsx)(n.code,{children:"https"})," image URL from ",(0,a.jsx)(n.code,{children:"ResourceContent"})," (URI or metadata). It does ",(0,a.jsx)(n.strong,{children:"not"})," download the file from Agent Storage; Chat Interface storage is opaque to the agent."]}),"\n"]}),"\n"]}),"\n",(0,a.jsxs)(n.li,{children:["\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Image analysis"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.strong,{children:"5.
11"}),": The agent passes the text prompt and image URL to ",(0,a.jsx)(n.code,{children:"get_image_analysis()"}),"."]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.strong,{children:"5.2"}),": That function calls the OpenAI Responses API and returns analysis text."]}),"\n"]}),"\n"]}),"\n",(0,a.jsxs)(n.li,{children:["\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Response"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["The agent sends the analysis back as a ",(0,a.jsx)(n.code,{children:"ChatMessage"})," with ",(0,a.jsx)(n.code,{children:"TextContent"}),". It does ",(0,a.jsx)(n.strong,{children:"not"})," send ",(0,a.jsx)(n.code,{children:"EndSessionContent"}),", so the session stays open for follow-up questions about the same image."]}),"\n"]}),"\n"]}),"\n",(0,a.jsxs)(n.li,{children:["\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"User receives the response"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"The Chat Interface delivers the analysis to the user."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.img,{alt:"ASI Chat Protocol Flow",src:t(80342).A+"",width:"1878",height:"1210"})}),"\n",(0,a.jsx)(n.p,{children:"If the static image still shows acknowledgement after the analysis reply, follow the numbered steps: the code acks first, then analyzes, then replies."}),"\n",(0,a.jsx)(n.h2,{id:"implementation",children:"Implementation"}),"\n",(0,a.jsxs)(n.p,{children:["Create the agent and its files on ",(0,a.jsx)(n.a,{href:"https://agentverse.ai/",children:"Agentverse"})," so it can talk to the ",(0,a.jsx)(n.a,{href:"https://agentverse.ai/",children:"Chat Interface"})," over the chat protocol."]}),"\n",(0,a.jsxs)(n.ol,{children:["\n",(0,a.jsxs)(n.li,{children:["Follow the ",(0,a.jsx)(n.a,{href:"/docs/agent-creation/uagent-creation#hosted-agents",children:"Hosted Agents"})," steps to create a hosted agent named ",(0,a.jsx)(n.strong,{children:"Image Analysis Agent"}),"."]}),"\n",(0,a.jsx)(n.li,{children:"In the editor, add two files (see the directory listing below)."}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"To create a new file on Agentverse:"}),"\n",(0,a.jsxs)(n.ol,{children:["\n",(0,a.jsx)(n.li,{children:"Click the New File icon."}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.img,{alt:"New File",src:t(18799).A+"",width:"2597",height:"783"})}),"\n",(0,a.jsxs)(n.ol,{start:"2",children:["\n",(0,a.jsx)(n.li,{children:"Assign a name to the file."}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.img,{alt:"Rename File",src:t(90899).A+"",width:"2597",height:"1096"})}),"\n",(0,a.jsxs)(n.ol,{start:"3",children:["\n",(0,a.jsx)(n.li,{children:"Confirm the directory structure."}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.img,{alt:"Directory Structure",src:t(80027).A+"",width:"2596",height:"734"})}),"\n",(0,a.jsxs)(n.p,{children:["You need ",(0,a.jsx)(n.strong,{children:"both"})," of these files on the hosted agent:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-text",metastring:'title="directory"',children:"agent.py\nimage_analysis.py\n"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"agent.py"})," \u2014 chat protocol handlers for ",(0,a.jsx)(n.code,{children:"ChatMessage"})," and ",(0,a.jsx)(n.code,{children:"ChatAcknowledgement"}),"."]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"image_analysis.py"})," \u2014 OpenAI Responses API helper."]}),"\n"]}),"\n",(0,a.jsx)(n.h3,{id:"agentverse-secrets",children:"Agentverse secrets"}),"\n",(0,a.jsxs)(n.p,{children:[(0,a.jsx)(n.code,{children:"image_analysis.py"})," reads ",(0,a.jsx)(n.code,{children:"OPENAI_API_KEY"})," from the environment. On a hosted agent, set it in ",(0,a.jsx)(n.strong,{children:"Agent Secrets"})," (not a local ",(0,a.jsx)(n.code,{children:".env"})," file). ",(0,a.jsx)(n.code,{children:"load_dotenv()"})," in the sample is only useful for ",(0,a.jsx)(n.strong,{children:"local"})," runs."]}),"\n",(0,a.jsxs)(n.ol,{children:["\n",(0,a.jsxs)(n.li,{children:["\n",(0,a.jsxs)(n.p,{children:["In the agent editor, open the ",(0,a.jsx)(n.strong,{children:"Build"})," tab and ",(0,a.jsx)(n.strong,{children:"Agent Secrets"})," (shield icon)."]}),"\n"]}),"\n",(0,a.jsxs)(n.li,{children:["\n",(0,a.jsxs)(n.p,{children:["Click ",(0,a.jsx)(n.strong,{children:"+ New Secret"}),"."]}),"\n"]}),"\n",(0,a.jsxs)(n.li,{children:["\n",(0,a.jsx)(n.p,{children:"Add:"}),"\n",(0,a.jsxs)(n.table,{children:[(0,a.jsx)(n.thead,{children:(0,a.jsxs)(n.tr,{children:[(0,a.jsx)(n.th,{children:"Name"}),(0,a.jsx)(n.th,{children:"Required"}),(0,a.jsx)(n.th,{children:"Default"}),(0,a.jsx)(n.th,{children:"Description"})]})}),(0,a.jsxs)(n.tbody,{children:[(0,a.jsxs)(n.tr,{children:[(0,a.jsx)(n.td,{children:(0,a.jsx)(n.code,{children:"OPENAI_API_KEY"})}),(0,a.jsx)(n.td,{children:"Yes"}),(0,a.jsx)(n.td,{children:"\u2014"}),(0,a.jsx)(n.td,{children:"OpenAI API key"})]}),(0,a.jsxs)(n.tr,{children:[(0,a.jsx)(n.td,{children:(0,a.jsx)(n.code,{children:"MAX_TOKENS"})}),(0,a.jsx)(n.td,{children:"No"}),(0,a.jsx)(n.td,{children:(0,a.jsx)(n.code,{children:"1024"})}),(0,a.jsx)(n.td,{children:"Max output tokens for the Responses API"})]}),(0,a.jsxs)(n.tr,{children:[(0,a.jsx)(n.td,{children:(0,a.jsx)(n.code,{children:"IMAGE_MODEL_ENGINE"})}),(0,a.jsx)(n.td,{children:"No"}),(0,a.jsx)(n.td,{children:(0,a.jsx)(n.code,{children:"gpt-4.1-mini"})}),(0,a.jsx)(n.td,{children:"Multimodal model name"})]})]})]}),"\n"]}),"\n",(0,a.jsxs)(n.li,{children:["\n",(0,a.jsx)(n.p,{children:"Save. Restart or start the agent so it picks up the secrets."}),"\n"]}),"\n"]}),"\n",(0,a.jsxs)(n.p,{children:["Without ",(0,a.jsx)(n.code,{children:"OPENAI_API_KEY"}),", import fails with ",(0,a.jsx)(n.code,{children:"OPENAI_API_KEY is required"}),"."]}
1),"\n",(0,a.jsxs)(n.h3,{id:"1-image-analysis-helper-image_analysispy",children:["1. Image analysis helper (",(0,a.jsx)(n.code,{children:"image_analysis.py"}),")"]}),"\n",(0,a.jsxs)(n.p,{children:["This module sends text and image inputs to the OpenAI Responses API. It accepts HTTPS image URLs (what the hosted agent sends) or optional base64 ",(0,a.jsx)(n.code,{children:"resource"})," payloads."]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",metastring:'title="image_analysis.py"',children:'import os\nfrom typing import Any\n\nfrom dotenv import load_dotenv\nfrom openai import OpenAI\n\nload_dotenv()\n\nMAX_TOKENS = int(os.getenv("MAX_TOKENS", "1024"))\nMODEL_ENGINE = os.getenv("IMAGE_MODEL_ENGINE", "gpt-4.1-mini")\nOPENAI_API_KEY = os.getenv("OPENAI_API_KEY")\n\nif not OPENAI_API_KEY:\n raise ValueError(\n "OPENAI_API_KEY is required. Create one at https://platform.openai.com/api-keys"\n )\n\nclient = OpenAI(api_key=OPENAI_API_KEY)\n\ndef get_image_analysis(content: list[dict[str, Any]]) -> str:\n processed_content: list[dict[str, Any]] = []\n\n for item in content:\n item_type = item.get("type")\n if item_type == "text":\n text = item.get("text", "")\n if text:\n processed_content.append({"type": "input_text", "text": text})\n elif item_type == "resource":\n mime_type = item.get("mime_type", "")\n image_b64 = item.get("contents", "")\n if not mime_type.startswith("image/"):\n return f"Unsupported mime type: {mime_type}"\n if not image_b64:\n return "Image content is empty."\n processed_content.append(\n {\n "type": "input_image",\n "image_url": f"data:{mime_type};base64,{image_b64}",\n }\n )\n elif item_type == "resource_url":\n image_url = item.get("url", "")\n if not image_url:\n return "Image URL is empty."\n processed_content.append(\n {\n "type": "input_image",\n "image_url": image_url,\n }\n )\n\n if not processed_content:\n return "Please send a text prompt and an image attachment."\n\n try:\n response = client.responses.create(\n model=MODEL_ENGINE,\n input=[{"role": "user", "content": processed_content}],\n max_output_tokens=MAX_TOKENS,\n )\n if response.output_text:\n return response.output_text\n return "I could not generate an analysis for this image."\n except Exception as err:\n return f"An error occurred while analyzing the image: {err}"\n'})}),"\n",(0,a.jsxs)(n.h3,{id:"2-hosted-agent-agentpy",children:["2. Hosted agent (",(0,a.jsx)(n.code,{children:"agent.py"}),")"]}),"\n",(0,a.jsxs)(n.p,{children:["On Agentverse, ",(0,a.jsxs)(n.strong,{children:["omit ",(0,a.jsx)(n.code,{children:"agent.run()"})]}),". The platform starts the process. The hosted snippet ends after ",(0,a.jsx)(n.code,{children:"agent.include(...)"}),"."]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",metastring:'title="agent.py"',children:'from datetime import datetime, timezone\nfrom typing import Any\nfrom urllib.parse import urlparse\nfrom uuid import uuid4\n\nfrom uagents import Agent, Context, Protocol\nfrom uagents_core.contrib.protocols.chat import (\n ChatAcknowledgement,\n ChatMessage,\n MetadataContent,\n ResourceContent,\n StartSessionContent,\n TextContent,\n chat_protocol_spec,\n)\n\nfrom image_analysis import get_image_analysis\n\nagent = Agent()\nchat_proto = Protocol(spec=chat_protocol_spec)\n\ndef create_text_chat(text: str) -> ChatMessage:\n return ChatMessage(\n timestamp=datetime.now(timezone.utc),\n msg_id=uuid4(),\n content=[TextContent(type="text", text=text)],\n )\n\ndef create_metadata_chat(metadata: dict[str, str]) -> ChatMessage:\n return ChatMessage(\n timestamp=datetime.now(timezone.utc),\n msg_id=uuid4(),\n content=[MetadataContent(type="metadata", metadata=metadata)],\n )\n\ndef extract_image_url(item: ResourceContent) -> str | None:\n resources = item.resource if isinstance(item.resource, list) else [item.resource]\n for resource in resources:\n uri = getattr(resource, "uri", None)\n if isinstance(uri, str):\n parsed = urlparse(uri)\n if parsed.scheme in {"http", "https"} and parsed.netloc:\n return uri\n\n metadata = getattr(resource, "metadata", None) or {}\n if isinstance(metadata, dict):\n for key in ("url", "uri", "source", "image_url"):\n candidate = metadata.get(key)\n if isinstance(candidate, str):\n parsed = urlparse(candidate)\n if parsed.scheme in {"http", "https"}
1 and parsed.netloc:\n return candidate\n return None\n\n@chat_proto.on_message(ChatMessage)\nasync def handle_message(ctx: Context, sender: str, msg: ChatMessage):\n ctx.logger.info(f"Got a message from {sender}")\n\n await ctx.send(\n sender,\n ChatAcknowledgement(\n acknowledged_msg_id=msg.msg_id,\n timestamp=datetime.now(timezone.utc),\n ),\n )\n\n prompt_content: list[dict[str, Any]] = []\n\n for item in msg.content:\n if isinstance(item, StartSessionContent):\n ctx.logger.info(f"Got a start session message from {sender}")\n await ctx.send(sender, create_metadata_chat({"attachments": "true"}))\n elif isinstance(item, TextContent):\n ctx.logger.info(f"Got text content from {sender}: {item.text}")\n prompt_content.append({"type": "text", "text": item.text})\n elif isinstance(item, ResourceContent):\n ctx.logger.info(f"Got resource content from {sender}")\n image_url = extract_image_url(item)\n if not image_url:\n await ctx.send(\n sender,\n create_text_chat(\n "Attachment URL not found. Please re-upload the image and try again."\n ),\n )\n return\n ctx.logger.info(f"Using image URL={image_url}")\n prompt_content.append({"type": "resource_url", "url": image_url})\n\n if not prompt_content:\n await ctx.send(\n sender, create_text_chat("Please send a question and attach an image.")\n )\n return\n\n try:\n response = get_image_analysis(prompt_content)\n await ctx.send(sender, create_text_chat(response))\n except Exception as err:\n ctx.logger.error(f"Image analysis error: {err}")\n await ctx.send(\n sender,\n create_text_chat("Sorry, I couldn\'t analyze the image. Please try again later."),\n )\n\n@chat_proto.on_message(ChatAcknowledgement)\nasync def handle_ack(ctx: Context, sender: str, msg: ChatAcknowledgement):\n ctx.logger.info(\n f"Got an acknowledgement from {sender} for {msg.acknowledged_msg_id}"\n )\n\nagent.include(chat_proto, publish_manifest=True)\n'})}),"\n",(0,a.jsx)(n.h4,{id:"key-features",children:"Key features"}),"\n",(0,a.jsxs)(n.ol,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.strong,{children:"Integrated architecture"}),": Chat protocol handlers live in ",(0,a.jsx)(n.code,{children:"agent.py"}),"."]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.strong,{children:"Attachment URL extraction"}),": HTTPS URLs are read from ",(0,a.jsx)(n.code,{children:"ResourceContent"})," URIs and metadata. Chat Interface storage is opaque to the agent."]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.strong,{children:"OpenAI Responses API"}),": Text plus image URL (or optional base64) go to the multimodal model."]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.strong,{children:"Error handling"}),": Missing URLs, empty prompts, and OpenAI failures return user-facing chat text."]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.strong,{children:"Multi-turn sessions"}),": ",(0,a.jsx)(n.code,{children:"StartSessionContent"})," advertises ",(0,a.jsx)(n.code,{children:'"attachments": "true"'}),". Replies omit ",(0,a.jsx)(n.code,{children:"EndSessionContent"})," so ASI",":One"," Chat can keep the session open for follow-ups."]}),"\n"]}),"\n",(0,a.jsx)(n.h3,{id:"3-local-run-optional",children:"3. Local run (optional)"}),"\n",(0,a.jsxs)(n.p,{children:["Use this only if you clone the sample and run outside Agentverse. Replace ",(0,a.jsx)(n.code,{children:"Agent()"})," with mailbox/port settings and add a guarded ",(0,a.jsx)(n.code,{children:"run()"}),". Keep the handlers from the hosted snippet."]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-python",metastring:'title="agent_local.py"',children:'agent = Agent(\n name="image-analysis-agent",\n seed="<your-agent-seedphrase>",\n port=8001,\n mailbox=True,\n publish_agent_details=True,\n)\n\n# ... same chat_proto handlers as the hosted agent.py ...\n\nagent.include(chat_proto, publish_manifest=True)\n\nif __name__ == "__main__":\n agent.run()\n'})}),"\n",(0,a.jsxs)(n.p,{children:["Create a ",(0,a.jsx)(n.code,{children:".env"})," in the project directory (or export variables in your shell). ",(0,a.jsx)(n.code,{children:"load_dotenv()"})," in ",(0,a.jsx)(n.code,{children:"image_analysis.py"})," loads that file locally; Agentverse secrets replace it when hosted."]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"macOS / Linux:"})}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:'python3 -m venv .venv\nsource .venv/bin/activate\npip install uagents==0.25.5 uagents-core==0.4.9 "openai>=1.40.0" python-dotenv\nexport OPENAI_API_KEY="<your-openai-api-key>"\npython agent.py\n'})}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Windows (PowerShell):"})}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-powershell",children:'python -m venv .venv\n.\\.venv\\Scripts\\Activate.ps1\npip install uagents==0.25.5 uagents-core==0.4.9 "openai>=1.40.0" python-dotenv\n$env:OPENAI_API_KEY = "<your-openai-api-key>"\npython agent.py\n'})}),"\n",(0,a.jsxs)(n.p,{children:["After start, connect the mailbox from the Agent Inspector as in the ",(0,a.jsxs)(n.a,{href:"/docs/examples/chat-protocol/asi-compatible-uagents",children:["ASI",":One"," C
1ompatible Agent"]})," guide, then use ",(0,a.jsx)(n.strong,{children:"Chat with Agent"}),"."]}),"\n",(0,a.jsx)(n.h2,{id:"adding-a-readme-to-your-agent",children:"Adding a README to your agent"}),"\n",(0,a.jsxs)(n.ol,{children:["\n",(0,a.jsxs)(n.li,{children:["Open the ",(0,a.jsx)(n.strong,{children:"Overview"})," section in the editor."]}),"\n",(0,a.jsxs)(n.li,{children:["Click ",(0,a.jsx)(n.strong,{children:"Edit"})," and add a searchable description so ASI",":One"," can find the agent. See ",(0,a.jsx)(n.a,{href:"/docs/agentverse/searching#importance-of-good-readme",children:"Importance of a Good README"}),"."]}),"\n",(0,a.jsxs)(n.li,{children:["Confirm the agent publishes ",(0,a.jsx)(n.code,{children:"AgentChatProtocol"}),"."]}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.img,{alt:"Chat Protocol version",src:t(69141).A+"",width:"1698",height:"1666"})}),"\n",(0,a.jsxs)(n.h2,{id:"query-your-agent-asi-chat",children:["Query your agent (ASI",":One"," Chat)"]}),"\n",(0,a.jsxs)(n.ol,{children:["\n",(0,a.jsxs)(n.li,{children:["In Agentverse, ",(0,a.jsx)(n.strong,{children:"Start"})," the hosted agent (or keep your local agent running with mailbox connected)."]}),"\n",(0,a.jsxs)(n.li,{children:["Open the agent ",(0,a.jsx)(n.strong,{children:"Inspector"}),", then ",(0,a.jsx)(n.strong,{children:"Agent Profile"}),". Edit the handle and README if needed. Use the ",(0,a.jsx)(n.a,{href:"https://agentverse.ai/agents/details/agent1q23d43ql6wxfaf4ahgtm98u2fktc5hqp7q29psujdvz728y34xy5uxqkklk/profile",children:"live profile example"})," as a layout reference."]}),"\n",(0,a.jsxs)(n.li,{children:["Click ",(0,a.jsx)(n.strong,{children:"Chat with Agent"}),". That opens ",(0,a.jsxs)(n.a,{href:"https://chat.asi1.ai/",children:["ASI",":One"," Chat"]})," scoped to your agent."]}),"\n",(0,a.jsxs)(n.li,{children:["Use the attach control, upload an image, and send a prompt such as ",(0,a.jsx)(n.code,{children:"Please analyze this image"}),"."]}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.img,{alt:"ASI Chat Attach Button",src:t(59710).A+"",width:"2396",height:"1608"})}),"\n",(0,a.jsxs)(n.ol,{start:"5",children:["\n",(0,a.jsx)(n.li,{children:"The agent returns the analysis in the same chat."}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.img,{alt:"ASI Image Analysis Result",src:t(93118).A+"",width:"2442",height:"1616"})}),"\n",(0,a.jsx)(n.h2,{id:"environment-variables",children:"Environment variables"}),"\n",(0,a.jsxs)(n.table,{children:[(0,a.jsx)(n.thead,{children:(0,a.jsxs)(n.tr,{children:[(0,a.jsx)(n.th,{children:"Variable"}),(0,a.jsx)(n.th,{children:"Required"}),(0,a.jsx)(n.th,{children:"Default"}),(0,a.jsx)(n.th,{children:"Where to set"})]})}),(0,a.jsxs)(n.tbody,{children:[(0,a.jsxs)(n.tr,{children:[(0,a.jsx)(n.td,{children:(0,a.jsx)(n.code,{children:"OPENAI_API_KEY"})}),(0,a.jsx)(n.td,{children:"Yes"}),(0,a.jsx)(n.td,{children:"\u2014"}),(0,a.jsxs)(n.td,{children:["Agentverse ",(0,a.jsx)(n.strong,{children:"Secrets"}),", or local env / ",(0,a.jsx)(n.code,{children:".env"})]})]}),(0,a.jsxs)(n.tr,{children:[(0,a.jsx)(n.td,{children:(0,a.jsx)(n.code,{children:"MAX_TOKENS"})}),(0,a.jsx)(n.td,{children:"No"}),(0,a.jsx)(n.td,{children:(0,a.jsx)(n.code,{children:"1024"})}),(0,a.jsx)(n.td,{children:"Same"})]}),(0,a.jsxs)(n.tr,{children:[(0,a.jsx)(n.td,{children:(0,a.jsx)(n.code,{children:"IMAGE_MODEL_ENGINE"})}),(0,a.jsx)(n.td,{children:"No"}),(0,a.jsx)(n.td,{children:(0,a.jsx)(n.code,{children:"gpt-4.1-mini"})}),(0,a.jsx)(n.td,{children:"Same"})]})]})]}),"\n",(0,a.jsx)(n.h2,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,a.jsxs)(n.table,{children:[(0,a.jsx)(n.thead,{children:(0,a.jsxs)(n.tr,{children:[(0,a.jsx)(n.th,{children:"Symptom"}),(0,a.jsx)(n.th,{children:"What to try"})]})}),(0,a.jsxs)(n.tbody,{children:[(0,a.jsxs)(n.tr,{children:[(0,a.jsx)(n.td,{children:(0,a.jsx)(n.code,{children:"OPENAI_API_KEY is required"})}),(0,a.jsxs)(n.td,{children:["Add the secret in Agentverse ",(0,a.jsx)(n.strong,{children:"Secrets"})," (hosted) or export it before ",(0,a.jsx)(n.code,{children:"python agent.py"})," (local). ",(0,a.jsx)(n.code,{children:"load_dotenv()"})," does not run Agentverse secrets for you; the platform injects them."]})]}),(0,a.jsxs)(n.tr,{children:[(0,a.jsx)(n.td,{children:(0,a.jsx)(n.code,{children:"Attachment URL not found"})}),(0,a.jsxs)(n.td,{children:["Re-upload the image in ASI",":One"," Chat and resend. The agent only accepts ",(0,a.jsx)(n.code,{children:"http"}),"/",(0,a.jsx)(n.code,{children:"https"})," URLs on ",(0,a.jsx)(n.code,{children:"ResourceContent"}),"."]})]}),(0,a.jsxs)(n.tr,{children:[(0,a.jsx)(n.td,{children:"Empty or generic analysis"}),(0,a.jsxs)(n.td,{children:["Ask a more specific question, or set ",(0,a.jsx)(n.code,{children:"IMAGE_MODEL_ENGINE"})," to a stronger multimodal model you have access to."]})]}),(0,a.jsxs)(n.tr,{children:[(0,a.jsx)(n.td,{children:"Agent ignores images"}),(0,a.jsxs)(n.td,{children:["Confirm the README/session metadata advertises attachments, and that you clicked ",(0,a.jsx)(n.strong,{children:"Chat with Agent"})," for ",(0,a.jsx)(n.strong,{children:"this"})," agent (not a generic ASI",":One"," session)."]})]}),(0,a.jsxs)(n.tr,{children:[(0,a.jsx)(n.td,{children:"Hosted agent misbehaves after paste"}),(0,a.jsxs)(n.td,{children:["Confirm hosted ",(0,a.jsx)(n.code,{children:"agent.py"})," has ",(0,a.jsx)(n.strong,{children:"no"})," ",(0,a.jsx)(n.code,{children:"agent.run()"})," and that both files are present."]})]}),(0,a.jsxs)(n.tr,{children:[(0,a.jsxs)(n.td,{children:["Copy-paste ",(0,a.jsx)(n.code,{children:"SyntaxError"})]}),(0,a.jsxs)(n.td,{children:["Copy the titled ",(0,a.jsx)(n.code,{children:"image_analysis.py"})," and ",(0,a.jsx)(n.code,{children:"agent.py"})," fences as whole blocks (not a single line). Each statement must stay on its own line."]})]})]})]}),"\n",(0,a.jsx)(n.h2,{id:"full-example-repository",children:"Full example repository"}),"\n",(0,a.jsxs)(n.p,{children:["A community sample with the same tw
1o files lives at ",(0,a.jsx)(n.a,{href:"https://github.com/gautammanak1/image-analysis-agent",children:"gautammanak1/image-analysis-agent"}),". That repository is ",(0,a.jsx)(n.strong,{children:"third-party"})," (not Fetch-maintained). Last verified against this page: ",(0,a.jsx)(n.strong,{children:"2026-08-26"}),"."]}),"\n",(0,a.jsxs)(n.p,{children:["Expect drift: the sample ",(0,a.jsx)(n.code,{children:"requirements.txt"})," pins ",(0,a.jsx)(n.code,{children:"uagents==0.23.7"}),", while Innovation Lab docs use ",(0,a.jsx)(n.code,{children:"uagents==0.25.5"})," and ",(0,a.jsx)(n.code,{children:"uagents-core==0.4.9"}),". Prefer the snippets on this page for Agentverse. There is no official image-analysis example under ",(0,a.jsx)(n.code,{children:"fetchai/innovation-lab-examples"})," at the time of writing."]})]})}function h(e={}){const{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,a.jsx)(n,{...e,children:(0,a.jsx)(c,{...e})}):c(e)}},18799:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/New-File-2-57d76f79e15f6fc34f6f0a7e2bf7b022.png"},90899:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/Rename-File-2-eacb9410c619c3d95ffe35b1e6e23fb8.png"},93118:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/asi1-analysis-result-487d9b8b51b6149dca0bdbb01d3e3ea8.png"},59710:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/asi1-attach-button-2e546d0ae3f4550fc18f51549f548456.png"},80342:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/asi1-sequence-flow-fd889fd009012d06b4816dc29715aaf5.png"},80027:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/directory-structure-2-3877146c972ffa9bede4e32b244e081d.png"},69141:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/readme-68706d713ad510fec26934805c691aae.png"},28453:(e,n,t)=>{t.d(n,{R:()=>r,x:()=>o});var s=t(96540);const a={},i=s.createContext(a);function r(e){const n=s.useContext(i);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:r(e.components),s.createElement(i.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.