
Ag2 Structured Output
- 33 installs
- 8 repo stars
- Updated July 27, 2026
- ag2ai/ag2-skills
ag2-structured-output is a Claude Code skill that configures an AG2 beta Agent to return a typed, validated Python value via response_schema= instead of free text.
About
This skill shows how to get a typed, validated Python value back from an AG2 beta Agent instead of free text. A developer passes response_schema= (a Pydantic model, dataclass, primitive, union, ResponseSchema, or @response_schema validator) and reads the parsed result via await reply.content(). It is used for classification, extraction, scoring, and normalisation where downstream code parses the reply, and it covers validation retries and PromptedSchema for providers without native structured output.
- Return typed Python values from an AG2 beta Agent via response_schema= instead of parsing free text
- Supports Pydantic models, dataclasses, primitives, unions, ResponseSchema, and @response_schema validators
- Handles validation retries and PromptedSchema for providers without native structured output
Ag2 Structured Output by the numbers
- 33 all-time installs (skills.sh)
- Ranked #8,968 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 1, 2026 (Skillselion catalog sync)
ag2-structured-output capabilities & compatibility
Free skill; requires an LLM provider API key (e.g. OpenAI) to run the agent.
- Capabilities
- structured output · schema validation · data extraction · classification
- Use cases
- data analysis
- Pricing
- Bring your own API key
What ag2-structured-output says it does
Get a typed Python value back from an AG2 beta `Agent` instead of free text.
reply.body` is still the raw model text; `await reply.content()` runs validation and returns the parsed value.
npx skills add https://github.com/ag2ai/ag2-skills --skill ag2-structured-outputAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 33 |
|---|---|
| repo stars | ★ 8 |
| Last updated | July 27, 2026 |
| Repository | ag2ai/ag2-skills ↗ |
What it does
Configure an AG2 beta agent to return a validated Pydantic model or typed value for classification, extraction, or scoring.
Who is it for?
Developers building AG2 beta agents that need validated structured output (classification, extraction, scoring).
Skip if: Multi-agent orchestration or plain string replies.
When should I use this skill?
The user wants a Pydantic model, dataclass, dict, primitive, or union back from an AG2 agent rather than a string.
What you get
The agent returns a typed, validation-checked value with automatic retry on failure.
- AG2 Agent configured with response_schema
- typed parsed reply via reply.content()
By the numbers
- 8 schema types listed in the schema-types table
Files
Structured output
When to use
- The user wants a Pydantic model, dataclass, dict, primitive, or union back — not a string.
- They're doing classification, extraction, scoring, normalisation, or anything where downstream code parses the reply.
- They want automatic retry on validation failure.
60-second recipe
from pydantic import BaseModel, Field
from typing import Annotated
from autogen.beta import Agent
from autogen.beta.config import OpenAIConfig
class TicketTriage(BaseModel):
category: Annotated[str, Field(description="e.g. billing, bug, account_access")]
urgency: Annotated[str, Field(description="low, medium, or high")]
summary_one_line: Annotated[str, Field(description="Max 120 characters", max_length=120)]
agent = Agent(
"triage",
prompt="You triage support messages. Be conservative with urgency.",
config=OpenAIConfig(model="gpt-4o-mini"),
response_schema=TicketTriage,
)
reply = await agent.ask("I was charged twice and can't export reports. Quarter close blocked.")
triage = await reply.content() # → typed TicketTriage
print(triage.category, triage.urgency)reply.body is still the raw model text; await reply.content() runs validation and returns the parsed value. If validation fails, content() raises (e.g. pydantic.ValidationError).
Schema types you can pass
| Type | What you get |
|---|---|
Primitive (int, float, bool) | Bare value, framework wraps in {"data": ...} for the API |
dataclass | Instance of the dataclass |
Pydantic BaseModel | Instance of the model |
| Union (`int \ | str, (int, str)`) |
dict[K, V], TypedDict | Validated dict |
ResponseSchema(...) | Same as above, with explicit name / description for the provider |
@response_schema callable | Custom validation/parsing logic |
PromptedSchema(inner) | Schema injected into the system prompt for providers without native structured output |
ResponseSchema — name your payload
Helps the provider treat the structured output as a named contract:
from autogen.beta import Agent, ResponseSchema
schema = ResponseSchema(int | str, name="ByteWidth", description="Number of bits in one byte.")
agent = Agent("assistant", config=config, response_schema=schema)@response_schema — custom validation
For clamping, regex cleanup, decoding wrapped JSON, or combining fields:
from autogen.beta import Agent, response_schema
@response_schema
def parse_rating(content: str) -> int:
"""Parse a rating and clamp to 1–5."""
return max(1, min(5, int(content)))
agent = Agent("assistant", config=config, response_schema=parse_rating)Multi-parameter form synthesises a JSON object schema from the parameter names:
from typing import Annotated
from pydantic import Field
from autogen.beta import response_schema
@response_schema
def extract_listing(
title: Annotated[str, Field(description="Product name")],
price_usd: Annotated[float, Field(description="Price in USD", ge=0)],
in_stock: Annotated[bool, Field(description="True if it ships now")],
) -> dict:
return {"title": title, "price_usd": price_usd, "in_stock": in_stock}The function also participates in dependency injection — Context, Variable, Inject, Depends work the same way as in tools (and don't appear in the JSON schema).
Async validators are supported:
import json
@response_schema
async def fetch_and_validate(content: str) -> dict:
data = json.loads(content)
data["validated"] = True
return dataPromptedSchema — for providers without native structured output
Injects the JSON schema into the system prompt instead of using response_format:
from autogen.beta import Agent, PromptedSchema
agent = Agent("assistant", config=config, response_schema=PromptedSchema(int))Wraps any inner schema (type, ResponseSchema, @response_schema callable). The validation logic stays the same; only the wire format changes.
Custom prompt template:
PromptedSchema(int, prompt_template="Reply with JSON matching this schema:\n{schema}")Per-turn override
agent = Agent("assistant", config=config)
turn = await agent.ask("How many seconds in a minute?", response_schema=int)
print(await turn.content()) # 60
turn2 = await turn.ask("Say hello.") # back to default (no schema)Pass response_schema=None to drop a schema set on the agent for one call.
Retries
When validation fails, automatically re-ask the model:
result = await reply.content(retries=3) # initial + up to 3 re-asks
result = await reply.content(retries=math.inf) # interactive only — could loop foreverThe validation error is sent back to the model as a follow-up so it can correct itself.
Primitive embedding (embed)
Bare primitives (int, float, bool, list[T], primitive unions) get wrapped in {"data": ...} by default — most structured-output APIs handle objects more reliably than bare values. content() transparently unwraps. Opt out:
ResponseSchema(int, name="RawInt", embed=False) # model must produce a bare 42
@response_schema(embed=False)
def parse_rating(value: int) -> int: ...Going deeper
- Working starter:
assets/recipe_builder.py(mirrorscode_examples/02) — Pydantic model +@tool+response_schema=. - Full reference:
website/docs/beta/structured_output.mdx— covers every schema type, multi-param@response_schema,Fieldconstraints,PromptedSchema, retries, embedding semantics.
Common pitfalls
- Reading `reply.body` when you wanted typed output —
reply.bodyis the raw text.await reply.content()does the parsing. - Forgetting `await` on `content()` — it's async; you'll get a coroutine, not the value.
- No `description` in the Pydantic field — the LLM may guess what to put in each field. Add a
Field(description=...)for every non-obvious key. - Provider doesn't support native structured output — wrap with
PromptedSchema(...)rather than fighting the API. - `retries=math.inf` in production — will loop forever on a model that can't comply. Use a finite count.
- Per-turn override is single-turn — passing
response_schema=intto oneask()doesn't change the agent's default. The next turn returns to whatever was set on the constructor.
"""Recipe builder — tools and structured output.
Mirrors website/docs/beta/code_examples/02_recipe_builder.mdx. Demonstrates
two core Agent features on top of the bare loop:
1. A custom @tool function the LLM can call (scale_ingredient).
2. A Pydantic response_schema so the final reply is a typed object.
Run::
python recipe_builder.py
"""
import asyncio
from pydantic import BaseModel, Field
from autogen.beta import Agent
from autogen.beta.config import GeminiConfig
def section(title: str) -> None:
print(f"\n── {title} ───")
class Ingredient(BaseModel):
name: str
quantity: float
unit: str
class Recipe(BaseModel):
title: str = Field(description="Short human title for the recipe.")
servings: int = Field(description="How many portions this recipe yields.")
ingredients: list[Ingredient]
steps: list[str] = Field(description="Ordered preparation steps.")
def scale_ingredient(quantity: float, factor: float) -> float:
"""Return ``quantity`` multiplied by ``factor``, rounded to 2 decimals.
The model uses this any time it needs to rescale a recipe for a
different number of servings.
"""
return round(quantity * factor, 2)
async def main() -> None:
config = GeminiConfig(model="gemini-3-flash-preview", temperature=0)
section("Recipe builder — scale an existing dish for 6 servings")
agent = Agent(
"chef",
prompt=(
"You are a culinary assistant. When asked to rescale a recipe, "
"use the scale_ingredient tool for every ingredient to compute the "
"new quantity. Return a complete Recipe object."
),
config=config,
tools=[scale_ingredient],
response_schema=Recipe,
)
reply = await agent.ask(
"Start from classic carbonara for 2 servings: 200g spaghetti, 2 eggs, "
"100g guanciale, 50g pecorino romano. Rescale it for 6 servings and "
"produce the full Recipe."
)
recipe: Recipe | None = await reply.content(retries=1)
if recipe is None:
print("Model returned no body — try again.")
return
print(f"{recipe.title} ({recipe.servings} servings)")
print()
print("Ingredients:")
for ing in recipe.ingredients:
print(f" - {ing.quantity} {ing.unit} {ing.name}")
print()
print("Steps:")
for i, step in enumerate(recipe.steps, 1):
print(f" {i}. {step}")
if __name__ == "__main__":
asyncio.run(main())
Related skills
FAQ
How do I get a typed value from an AG2 agent?
Pass response_schema= (a Pydantic model, dataclass, primitive, union, ResponseSchema, or @response_schema validator) and read the parsed result via await reply.content().
What happens if validation fails?
content() raises (e.g. pydantic.ValidationError); you can pass retries= to automatically re-ask the model with the validation error as a follow-up.