Skip to main content
Version: 1.0.5

Image Generation Agent

This guide shows how to build a local Image Generation Agent that accepts a text prompt over the Agent Chat Protocol, calls the ASI:One image API, and returns a renderable image URL in ASI:One Chat.

For a first ASI:One-compatible agent (mailbox, inspector, chat), start with ASI:One Compatible Agents. For request payload, sizes, and auth, use the ASI:One Image Generation API page — this example does not duplicate that schema.

Overview

You will:

  • Accept a natural-language prompt through ChatMessage / TextContent
  • Call POST https://api.asi1.ai/v1/image/generate
  • Prefer an HTTPS image URL from the API; if the response is a base64 data URL, host a copy so chat can render ![alt](url)
  • Reply with one ChatMessage that contains both the markdown image and EndSessionContent (success and error paths)
How the image is returned

ASI:One Chat renders Markdown images inside TextContent (![Generated image](https://...)). This example uses that pattern so the picture appears inline in chat.

The Image Analysis Agent and some other samples use ResourceContent with Agentverse storage instead. If markdown shows as raw ![]() text in your client, switch to ResourceContent (see Google Gemini Image) or confirm the URL is a public https:// link, not a data: URI.

Message flow

  1. User query — The user describes the image in ASI:One Chat.
  2. Forward — The chat interface sends a ChatMessage to your agent.
  3. Ack — The agent sends a ChatAcknowledgement.
  4. Generate — The agent calls ASI:One image generation (see API docs).
  5. Host if needed — If the API returns a hosted HTTPS URL, use it. If it returns data:image/...;base64,... (common), upload PNG bytes so chat can load an https:// URL.
  6. Reply — One outbound ChatMessage with markdown plus EndSessionContent.
  7. Display — The chat UI shows the image to the user.

Overview

Prerequisites

Create a project folder and two Python files. You can create empty files in your editor; you do not need touch.

macOS / Linux

setup.sh
mkdir image-generation
cd image-generation
python3 -m venv .venv
source .venv/bin/activate

Windows (PowerShell)

setup.ps1
New-Item -ItemType Directory -Force -Path image-generation | Out-Null
Set-Location image-generation
python -m venv .venv
.\.venv\Scripts\Activate.ps1

Windows (Command Prompt)

setup.cmd
mkdir image-generation
cd image-generation
python -m venv .venv
.venv\Scripts\activate.bat

Pin dependencies (uAgents 0.25.5 is required for QuotaProtocol and current mailbox behaviour):

requirements.txt
uagents==0.25.5
requests
python-dotenv
install.sh
pip install -r requirements.txt

Environment variables

models.py reads the API key when generating an image. Create .env before you run the agent.

.env.example
ASI1_API_KEY=PASTE_YOUR_ASI1_API_KEY
AGENT_SEED=PASTE_YOUR_UNIQUE_SEED
AGENT_NAME=Image Generator Agent
VariableRequiredDescription
ASI1_API_KEYYesBearer token for https://api.asi1.ai. Get it from the ASI:One API keys dashboard. ASI_LLM_KEY is accepted as a legacy alias.
AGENT_SEEDRecommendedSecret phrase that determines your agent address. If omitted, the sample generates a random seed on each process start (a new address every run).
AGENT_NAMENoDisplay name (default Image Generator Agent).
Do not reuse a shared tutorial seed

A hardcoded seed such as image-generator-agent-seed-phrase-testing gives every copy-paste user the same agent address. Set your own AGENT_SEED and copy the address from the startup logs when you register on Agentverse.

Implementation

This example creates a local agent that talks over the chat protocol. Connect it to Agentverse with a mailbox. See Mailbox Agents for the inspector Connect flow.

1. Image generation (models.py)

The helper calls ASI:One, prefers a public HTTPS image URL, and only then uploads bytes to a temporary host.

Third-party hosting (tmpfiles.org)

When the API returns base64 instead of a public URL, this sample uploads PNG bytes to tmpfiles.org so Markdown in chat has an https:// link. That host is external, ephemeral, and not Fetch-operated. Do not use it for private or production images. Prefer an ASI-returned HTTPS URL when present, or Fetch-approved storage such as Agentverse ExternalStorage / ResourceContent. Retention is short; links expire.

models.py
import base64
import os
from urllib.parse import urlparse

import requests
from dotenv import load_dotenv
from uagents import Model

load_dotenv()

class ImageRequest(Model):
image_description: str

class ImageResponse(Model):
image_url: str

def get_asi1_api_key() -> str:
key = os.getenv("ASI1_API_KEY") or os.getenv("ASI_LLM_KEY")
if not key:
raise ValueError(
"ASI1_API_KEY is required. Create a key at "
"https://asi1.ai/dashboard/api-keys and put it in .env. "
"ASI_LLM_KEY is still accepted as an alias."
)
return key

def to_direct_tmpfiles_url(raw_url: str) -> str:
"""Turn a tmpfiles page URL into a direct download URL (http or https)."""
parsed = urlparse(raw_url)
host = (parsed.netloc or "").lower()
if host != "tmpfiles.org" and not host.endswith(".tmpfiles.org"):
return raw_url
path = parsed.path or ""
if path.startswith("/dl/"):
return f"https://{host}{path}"
return f"https://{host}/dl{path}"

def upload_to_tmpfiles(image_bytes: bytes, filename: str = "asi1_image.png") -> str:
try:
response = requests.post(
"https://tmpfiles.org/api/v1/upload",
files={"file": (filename, image_bytes, "image/png")},
timeout=120,
)
response.raise_for_status()
response_data = response.json()
except requests.RequestException as e:
raise RuntimeError(f"Tmpfiles upload failed: {e}") from e

raw_url = response_data.get("data", {}).get("url")
if not raw_url:
raise RuntimeError(f"Tmpfiles upload returned no URL: {response_data}")
return to_direct_tmpfiles_url(raw_url)

def _bytes_from_data_url(data_url: str) -> bytes:
header, _, encoded = data_url.partition(",")
if not encoded or "base64" not in header:
raise RuntimeError("ASI image API returned a data URL without base64 payload")
return base64.b64decode(encoded)

def _extract_image_url(response_data: dict) -> str | None:
for key in ("image_url", "url"):
value = response_data.get(key)
if isinstance(value, str) and value:
return value

images = response_data.get("images")
if isinstance(images, list) and images:
first = images[0] if isinstance(images[0], dict) else {}
value = first.get("url")
if isinstance(value, str) and value:
return value

data_items = response_data.get("data")
if isinstance(data_items, list) and data_items:
first = data_items[0] if isinstance(data_items[0], dict) else {}
value = first.get("url")
if isinstance(value, str) and value:
return value
b64 = first.get("b64_json")
if b64:
return upload_to_tmpfiles(base64.b64decode(b64))
return None

def generate_image(prompt: str) -> str:
url = "https://api.asi1.ai/v1/image/generate"
payload = {
"model": "asi1",
"prompt": prompt,
"size": "auto",
}
headers = {
"Authorization": f"Bearer {get_asi1_api_key()}",
"Content-Type": "application/json",
}

try:
response = requests.post(url, json=payload, headers=headers, timeout=60)
if not response.ok:
raise requests.HTTPError(
f"{response.status_code} Error from ASI image API: {response.text}",
response=response,
)
response_data = response.json()
except requests.RequestException as e:
raise RuntimeError(f"Image generation request failed: {e}") from e

image_url = _extract_image_url(response_data)
if not image_url:
raise RuntimeError(f"ASI image API returned no image URL: {response_data}")

if image_url.startswith("data:image/"):
return upload_to_tmpfiles(_bytes_from_data_url(image_url))
if image_url.startswith("http://") or image_url.startswith("https://"):
return to_direct_tmpfiles_url(image_url) if "tmpfiles.org" in image_url else image_url
raise RuntimeError(f"ASI image API returned an unsupported image value: {image_url[:80]}")

2. Agent (agent.py)

The agent includes the chat protocol and publish_agent_details=True so Agentverse can show a profile others can discover. Copy the agent address from the logs after start.

agent.py
import asyncio
import os
import secrets
from datetime import datetime, timezone
from uuid import uuid4

from dotenv import load_dotenv
from uagents import Agent, Context, Protocol
from uagents_core.contrib.protocols.chat import (
ChatAcknowledgement,
ChatMessage,
EndSessionContent,
StartSessionContent,
TextContent,
chat_protocol_spec,
)

from models import generate_image

load_dotenv()

AGENT_SEED = os.getenv("AGENT_SEED") or secrets.token_hex(16)
AGENT_NAME = os.getenv("AGENT_NAME", "Image Generator Agent")

PORT = 8000
agent = Agent(
name=AGENT_NAME,
seed=AGENT_SEED,
port=PORT,
mailbox=True,
publish_agent_details=True,
)

chat_proto = Protocol(spec=chat_protocol_spec)

def create_text_chat(text: str, end_session: bool = True) -> ChatMessage:
content = [TextContent(type="text", text=text)]
if end_session:
content.append(EndSessionContent(type="end-session"))
return ChatMessage(
timestamp=datetime.now(timezone.utc),
msg_id=uuid4(),
content=content,
)

@chat_proto.on_message(ChatMessage)
async def handle_message(ctx: Context, sender: str, msg: ChatMessage):
await ctx.send(
sender,
ChatAcknowledgement(
timestamp=datetime.now(timezone.utc),
acknowledged_msg_id=msg.msg_id,
),
)

for item in msg.content:
if isinstance(item, StartSessionContent):
ctx.logger.info(f"Got a start session message from {sender}")
continue
if isinstance(item, TextContent):
ctx.logger.info(f"Got a message from {sender}: {item.text}")
try:
image_url = await asyncio.to_thread(generate_image, item.text)
await ctx.send(
sender,
create_text_chat(
"Image generated successfully.\n\n"
f"![Generated image]({image_url})\n"
),
)
except Exception as err:
ctx.logger.error(err)
await ctx.send(
sender,
create_text_chat(
"Sorry, I couldn't process your request. Please try again later."
),
)
return
ctx.logger.info(f"Got unexpected content from {sender}")

@chat_proto.on_message(ChatAcknowledgement)
async def handle_ack(ctx: Context, sender: str, msg: ChatAcknowledgement):
ctx.logger.info(
f"Got an acknowledgement from {sender} for {msg.acknowledged_msg_id}"
)

agent.include(chat_proto, publish_manifest=True)

if __name__ == "__main__":
agent.run()

publish_agent_details=True publishes name and metadata to Agentverse so the agent is easier to find after the mailbox is connected (same pattern as ASI:One Compatible Agents).

Both the success reply and the error reply use a single ChatMessage with TextContent and EndSessionContent. That matches the ASI:One chat example and closes the session so clients do not wait for another turn.

Advanced: optional QuotaProtocol for direct agent-to-agent requests

The beginner path above is chat-only. Rate limiting is optional. If you also want other agents to send structured ImageRequest messages, add this after the chat handlers and include the protocol.

QuotaProtocol lives in uagents.experimental.quota (uAgents 0.25.5+). Older installs raise ImportError.

quota_protocol.py
from uagents.experimental.quota import QuotaProtocol, RateLimit
from uagents_core.models import ErrorMessage

from models import ImageRequest, ImageResponse, generate_image

proto = QuotaProtocol(
storage_reference=agent.storage,
name="Image-Generation-Protocol",
version="0.1.0",
default_rate_limit=RateLimit(window_size_minutes=60, max_requests=30),
)

@proto.on_message(ImageRequest, replies={ImageResponse, ErrorMessage})
async def handle_request(ctx: Context, sender: str, msg: ImageRequest):
ctx.logger.info("Received image generation request")
try:
image_url = await asyncio.to_thread(generate_image, msg.image_description)
await ctx.send(sender, ImageResponse(image_url=image_url))
except Exception as err:
ctx.logger.error(err)
await ctx.send(sender, ErrorMessage(error=str(err)))

agent.include(proto, publish_manifest=True)

This second protocol adds another Almanac manifest. Skip it until you need agent-to-agent image requests.

Run the agent

From the project directory, with the venv active and .env filled in:

run.sh
python agent.py

Copy the inspector URL from the logs, open it, click Connect, and choose Mailbox. Details: Mailbox Agents.

Agent logs

Agent Logs

Adding a README to your agent

  1. Start the agent and connect the mailbox using the inspector link in the logs (Mailbox Agents).
  2. Open Agent Profile and the Overview section. The agent appears under local agents on Agentverse.

Agent Profile

  1. Click Edit. Add a clear name, handle, and README so ASI:One can select the agent. See Importance of a Good README.
  2. Confirm the agent publishes AgentChatProtocol (publish_manifest=True on the chat protocol).

Chat Protocol version

Query your agent

Make the agent discoverable, then chat:

  1. Mailbox is connected and the agent is running.
  2. README, name, and handle are filled in (step 3 above).
  3. AgentChatProtocol appears on the profile (protocol manifest published).
  4. Find the agent under the Agents tab on Agentverse (or search).
  5. Open the profile and click Chat with Agent (same flow as ASI:One Compatible Agents).
  6. In ASI:One Chat, send a prompt such as: A serene landscape with mountains and a lake at sunset.
  7. Expect a markdown image in the reply. Confirm the picture renders, not raw ![Generated image](...) text.

Chat with agent

Chat UI

Troubleshooting

SymptomLikely causeWhat to do
401 / invalid_api_key from api.asi1.aiMissing or wrong keySet ASI1_API_KEY in .env from the dashboard. Restart the agent.
ValueError: ASI1_API_KEY is required.env not loaded or emptyPlace .env next to agent.py / models.py. Confirm python-dotenv is installed.
ModuleNotFoundError: uagents / requestsDeps not installedRecreate the venv and pip install -r requirements.txt.
ImportError for uagents.experimental.quotaOld uAgentsPin uagents==0.25.5. The beginner sample does not import QuotaProtocol.
Tmpfiles upload failed or empty data.urlThird-party host down or blockedRetry later, or host the PNG yourself / use Agentverse storage.
Image markdown does not renderClient expects ResourceContent, or URL is data: / http page linkConfirm the reply URL is https:// (tmpfiles rewrite handles both http:// and https:// page URLs). See the note in Overview.
Mailbox never connectsInspector not used, or agent not runningFollow Mailbox Agents; keep mailbox=True.
Agent not found in ASI:OneThin README or unpublished manifestComplete the discoverability checklist; set publish_agent_details=True.
Address changes every restartAGENT_SEED unsetSet a unique AGENT_SEED in .env and reuse it.
)