Structured output.
Constrain a chat response to a pydantic model and get a validated object back.
Structured output constrains the model's response to a JSON Schema, server-side (grammar-constrained decoding), and then validates it client-side against your pydantic model. Pass a pydantic model class (or a plain JSON-schema dict), receive a validated object — no prompt engineering, no brittle JSON extraction. The SDK duck-types on model_json_schema, so any pydantic v2 version works and pydantic stays an optional dependency.
chat.stream_object(schema, *, input=...)
Streams exactly like chat.stream() — you receive every ChatEvent — plus one final ObjectEvent with the parsed, schema-validated object.
from pydantic import BaseModel
class Recipe(BaseModel):
name: str
prep_time_minutes: float
ingredients: list[str]
async def main():
async with MatildaClient(token="...") as client:
async for event in client.chat.stream_object(
Recipe,
input="Give me a recipe for pavlova.",
):
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True) # raw JSON streaming in
if event.type == "object":
recipe = event.object # a validated Recipe instance
print(f"\nValidated: {recipe.name}")The final event:
@dataclass(frozen=True)
class ObjectEvent[T]:
object: T # your pydantic model (or parsed JSON for dict schemas)
# type = "object"chat.create_object(schema, *, input=...)
Non-streaming convenience. Returns a MatildaObjectResponse — everything chat.create() returns, plus the validated object.
class Invoice(BaseModel):
total: float
currency: str
async def main():
async with MatildaClient(token="...") as client:
response = await client.chat.create_object(
Invoice,
input="Extract the invoice total: $1,250.00 AUD due 30 Sep.",
conversation_id=conversation_id,
)
print(response.object.total) # 1250.0 (float)
print(response.object.currency) # "AUD" (str)
print(response.output_text) # raw JSON text as returnedMatildaObjectResponse
Extends MatildaChatResponse with one additional field:
| Field | type | description |
|---|---|---|
| object | T | None | The response text parsed as JSON and validated against your schema (a pydantic model instance for model classes; the parsed JSON for plain dict schemas). |
Raw JSON Schema via response_schema
If you don't want pydantic validation, pass a stringified JSON Schema directly as response_schema on any chat call:
import json
async def main():
async with MatildaClient(token="...") as client:
response = await client.chat.create(
input="List three Australian birds.",
response_schema=json.dumps({
"type": "object",
"properties": {"birds": {"type": "array", "items": {"type": "string"}}},
"required": ["birds"],
"additionalProperties": False,
}),
)
data = json.loads(response.output_text) # guaranteed valid, schema-conforming JSONWith response_schema set, the response text is guaranteed to be valid JSON conforming to the schema — but parsing and validation are up to you.
Local $ref/$defs (pydantic's nested-model output) are inlined client-side before the schema is sent — the server's grammar compiler does not resolve #/$defs/... pointers. A genuinely recursive shape degrades to unconstrained at the recursion point, which client-side pydantic validation still catches.
OpenAI-compatible endpoint
The OpenAI-compatible endpoint (POST /api/v1/chat/completions) also honours structured output via the standard response_format parameter, so the OpenAI Python SDK's structured-output option works against Matilda as-is:
{ "type": "json_schema", "json_schema": { "name": "...", "schema": {...} } }— grammar-constrained to your schema (the schema is applied withstrict: trueserver-side; thestrictandnamefields you supply are re-wrapped downstream).{ "type": "json_object" }— guarantees valid JSON output without a schema (OpenAI JSON mode).
Safety replace and structured output. If the server replaces the output mid-stream (safety filter), stream_object and create_object raise SafetyReplaceError — the replacement text is in str(err) and the triggering categories in .categories. Deltas already yielded to your consumer are not rolled back; if you render streamed JSON, handle response.output_text.replace events (or choose non-streaming create_object) to avoid showing half-rendered output that is later discarded.
Truncation throws. If the stream is truncated before the JSON completes, both helpers raise MatildaObjectParseError with the partial text in .raw. See Error handling.
Stream errors throw. If the server emits an error event mid-stream, both helpers raise MatildaStreamError with the server's error code and message (f"{code}: {message}").