LlamaIndex image generation tool with the Sume Image API

Wrap POST /v1/images in a LlamaIndex FunctionTool: send sume/auto and a prompt, return URLs on a 200, and hand back the job id on a 202.

5 min readSume
All posts

To build a LlamaIndex image generation tool, wrap a Python function that calls Sume's POST /v1/images in FunctionTool.from_defaults() and give it to a FunctionAgent. The function sends model: "sume/auto" and the prompt: a 200 returns image URLs in data[].url, and a 202 returns a job envelope whose id a second tool checks with GET /v1/jobs/{id}/status.

LlamaIndex facts come from its Tools and Agents guides; Sume facts come from the Image API, Jobs and results, and Authentication pages and the Sume API reference. All were read on 2026-09-27. Sume has no LlamaIndex integration or package: the tool is a plain HTTPS call made with requests. Reference images and model choice are covered in Image generation API with reference images.

How do I write the image generation tool?

FunctionTool wraps any Python function, sync or async. By default the tool name is the function name and the description is the docstring, and LlamaIndex notes that the name and description strongly shape how the model calls a tool, so write both for the model. FunctionAgent runs tools through your LLM provider's tool calling, so pass an LLM that supports it. Keep SUME_API_KEY in the server environment the agent runs in: Sume's docs say keys belong on trusted servers, never in frontend JavaScript.

import os, requests
from llama_index.core.agent.workflow import FunctionAgent
from llama_index.core.tools import FunctionTool
API = "https://api.sume.com/v1"
AUTH = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}

def generate_image(prompt: str) -> str:
    """Generate an image from a text prompt. Returns image URLs, or a job id to check later."""
    r = requests.post(f"{API}/images", headers=AUTH, timeout=45,  # Sume holds up to 30 s
                      json={"model": "sume/auto", "prompt": prompt})
    r.raise_for_status()  # 4xx, and 502 when the generation failed inside the wait
    if r.status_code == 202:  # still rendering: a job envelope, not images
        return f"Still rendering. Call check_image_job with job_id {r.json()['data']['job']['id']}."
    return "\n".join(image["url"] for image in r.json()["data"])

def check_image_job(job_id: str) -> str:
    """Check an image job started by generate_image. Returns its status, or image URLs."""
    s = requests.get(f"{API}/jobs/{job_id}/status", headers=AUTH, timeout=30).json()["data"]
    if not s["result_ready"]:
        return f"Job status: {s['sume_status']}"
    res = requests.get(s["result_url"], headers=AUTH, timeout=30).json()["data"]
    return "\n".join(a["url"] for a in res["result"]["artifacts"])

tools = [FunctionTool.from_defaults(generate_image), FunctionTool.from_defaults(check_image_job)]
agent = FunctionAgent(tools=tools, llm=llm)  # llm: any LlamaIndex LLM with tool calling

Why does the tool branch on 200 and 202?

POST /v1/images holds the request for up to 30 seconds, and most catalog models finish inside it and answer 200 with the image body. If the generation is still running when that budget expires, Sume answers 202 with a job envelope instead; 4K, high quality, and a large n are the slow configurations most likely to get one. Sume's rule is to check the status code, not the body shape.

  • Set the requests timeout above 30 seconds. Requests never times out unless you pass timeout, and a smaller value can raise before Sume answers.
  • After a 202, poll GET /v1/jobs/{id}/status until result_ready is true, then read result_url. The images arrive in the standard job result shape, under result.artifacts[], not the image body.
  • A generation that fails inside the wait returns 502, and raise_for_status() turns any 4xx or 5xx into an HTTPError.
  • The route also accepts an optional Idempotency-Key header. A retry that reuses the key with the same payload adopts the first job instead of paying for a second.

Which request fields can the tool send?

The tool keeps the body to two fields. Most others are catalog-gated, so read a model's supported_parameters from GET /v1/images/models before exposing them to the agent.

From the Image API page, read 2026-09-27.
FieldRule from the docsThis tool
modelA catalog id, or sume/auto to let Sume pick the family. Sume never discloses which family ran, and sume/auto is not listed in GET /v1/images/models."sume/auto"
promptRequired text description of the image.The agent's prompt
n1 to 10 images per call; per-model ceilings are lower.Omitted
aspect_ratio, resolution, qualityOnly the values a model's catalog descriptors list. A parameter the model does not list is 400 unsupported_parameter.Omitted
seed, streamIn the schema but not served in v1: seed is 400 unsupported_parameter, and stream: true is 400 streaming_not_supported.Never sent
modesync by default on this route; async or webhook returns 202 at once.Default

What should the agent do with the image URLs?

The Image API describes data[].url as Sume-hosted and signed, and Sume's authentication docs say to treat signed download URLs as temporary secrets. So do not publish them or keep them as your record: download the images you want to keep. Do AI-generated video URLs expire? compares URL types across Sume's outputs.

Billing is all-or-nothing: a completed generation is billed in full, usage.cost on the 200 body is the USD amount billed, and a failed generation is not billed. If your agent stack speaks remote MCP, Sume's hosted MCP server also works, but Sume's docs say it is not part of the primary path today, which is why this tool calls the REST API.

Sources

Related posts

More in Integrations

All Integrations posts

Written by Sume