Get structured output¶
Pass schema= to chat() or generate() to get a validated, typed object back instead of
a string. schema may be a dataclass (dependency-free) or a Pydantic v2 model.
import aimu
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
client = aimu.client("openai:gpt-4.1")
person = client.chat("Extract the person: Ada Lovelace, 36.", schema=Person)
# person -> Person(name="Ada Lovelace", age=36)
How it works: native, with a parse fallback¶
AIMU uses the best method the model supports, automatically:
- Native enforcement when the model has
supports_structured_output=True: the provider constrains generation to the schema (OpenAIresponse_format, Ollamaformat=, Anthropic forced-tool). Check it withclient.supports_structured_output. - Prompt-and-parse otherwise: the schema is appended to the prompt and the response is parsed.
Either way you get a validated instance or a ValueError (parsing failed). The choice is
based on the model's static capability, not on catching a runtime error, so a genuine
provider failure surfaces rather than silently downgrading.
aimu.client("openai:gpt-4.1").supports_structured_output # True (native)
aimu.client("ollama:qwen3.5:9b").supports_structured_output # True (Ollama format=)
aimu.client("hf:Qwen/Qwen3-8B").supports_structured_output # False (prompt-and-parse)
Works on generate() too¶
Or one-shot:
(generate_json is the older prompt-and-parse convenience with retries; schema= on
chat/generate is the newer path that prefers native enforcement.)
Pydantic¶
from pydantic import BaseModel
class Invoice(BaseModel):
vendor: str
total: float
paid: bool
invoice = client.chat("Acme, $1,250.00, unpaid", schema=Invoice) # -> Invoice
Pydantic is optional; dataclasses work without it.
With tools¶
schema= composes with tool calling on OpenAI-compatible and parse-path providers: tools
run in the loop, and the final answer obeys the schema.
Anthropic is the exception. Its native structured output is a forced tool, which
conflicts with offering action tools, so combining schema= with active tools= raises a
ValueError. Drop the tools (or use_tools=False), or use a provider whose response_format
composes with tools.
Streaming structured output¶
schema= combines with stream=True. The call returns an iterator of StreamChunks so you can
show thinking / generation live, and the validated object is delivered two ways:
- a terminal
StreamChunkwithphase == DONEwhosecontentis{"result": <object>}, and client.last_structured, set once the stream is fully consumed (mirrorsclient.last_usage).
for chunk in client.chat("Is 17 prime? Think, then answer.", schema=Verdict, stream=True):
if chunk.phase is StreamingContentType.THINKING:
print(chunk.content, end="") # reasoning tokens, live
elif chunk.is_done():
verdict = chunk.content["result"] # validated Verdict
# ...or just: verdict = client.last_structured (after the loop)
An include= filter still applies to the thinking/generation phases, but the terminal result chunk
is always emitted so the payload is never filtered away.
Anthropic streams the JSON, not thinking. Its structured mode is a forced tool, which the API forbids alongside extended thinking, so an Anthropic structured stream yields the answer JSON as it is built (GENERATING chunks) with no THINKING. Ollama and OpenAI-compatible thinking models stream thinking normally. This matches the non-streaming behavior (Anthropic structured output has never produced thinking).
Constraints¶
self.messagesstays plain. The typed object is a return value only; the assistant turn is stored as the plain JSON string, so conversation history remains provider-portable.
Async¶
Identical on the async surface:
from aimu import aio
async def main():
client = aio.client("openai:gpt-4.1")
person = await client.chat("Ada Lovelace, 36", schema=Person)
Which models enforce natively¶
supports_structured_output=True (native) on the OpenAI, Gemini, Ollama (all models), and
Anthropic catalogs; Client.STRUCTURED_MODELS lists them per provider. HuggingFace and
llama-cpp use the prompt-and-parse path. See parse helpers for
parse_json_response / generate_json, which back the coercion.
See also¶
- Stream output: the general streaming surface;
schema=combines with it - Switch providers:
supports_structured_outputvaries by provider