Skip to content
Changelog

ClaudeAgent and CodexAgent return typed objects with output_schema

October 12, 20261 min read

Agno's ClaudeAgent sends the schema to Claude Code as a json_schema output format and parses the object Claude delivers through its structured output tool, a call that stays out of the run's tool history. CodexAgent turns a Pydantic model into a strict schema, with additionalProperties: false and every field required, and parses only Codex's final answer, skipping its commentary. Both set content_type as Agent does, to the model's class name or to dict for a JSON Schema.

A per-run output_schema overrides the agent's, and the output_schema form field on the AgentOS runs endpoint now reaches external agents too. When the answer doesn't fit the schema, the run keeps the text and Agno logs a warning.

from pydantic import BaseModel
from agno.agents.claude import ClaudeAgent
 
class Capital(BaseModel):
    city: str
    country: str
 
agent = ClaudeAgent(id="claude", output_schema=Capital)
run = agent.run("Capital of France?")
print(repr(run.content), repr(run.content_type))

Against the fake Claude SDK from the release's tests, standing in for Claude Code:

Capital(city='Paris', country='France') 'Capital'

Code that read Codex's dict schema output as a string needs one change. Earlier releases returned the raw JSON text, and CodexAgent now returns the parsed dict. Here is the same run on both, with the release's fake Codex SDK answering:

schema = {
    "type": "object",
    "properties": {"title": {"type": "string"}, "year": {"type": "integer"}},
    "required": ["title", "year"],
}
run = CodexAgent(id="codex", output_schema=schema).run("Invent a movie set on Mars.")
 
# Before
# content: '{"title": "Red Dust", "year": 2031}'
# content_type: 'str'
 
# Now
# content: {'title': 'Red Dust', 'year': 2031}
# content_type: 'dict'

The schema has to describe a JSON object. Agno checks the root when you build the agent and again on each run, and refuses an array or scalar before any SDK call. With both SDKs installed:

from typing import List
from pydantic import RootModel
 
class Names(RootModel[List[str]]):
    pass
 
ClaudeAgent(id="claude", output_schema=Names)
# ValueError: ClaudeAgent output_schema must describe a JSON object (got type 'array'); wrap a list or scalar in a model field
 
CodexAgent(id="codex", output_schema={"type": "array", "items": {"type": "string"}})
# ValueError: CodexAgent output_schema must describe a JSON object (got type 'array'); wrap a list or scalar in a model field

Earlier releases built that CodexAgent without complaint and left the array schema for the API to reject.

See the cookbook, and learn more about structured output for Claude and Codex in the documentation.

Frequently asked questions

Set output_schema to a Pydantic model or a JSON Schema dict, on the agent or in run(). A completed run's content is an instance of the model, or a dict for a JSON Schema, and content_type names which.

Claude Code delivers structured output through a tool whose input must be an object, and Codex's response format needs an object root. Agno raises a ValueError before the run starts, so wrap the list in a field of a model.

No. A JSON Schema dict now returns a parsed dict with content_type "dict", as Agent does. Remove any json.loads(run.content) call from code written for earlier releases.

Shipped around the same time