Client SDK · Chat

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.

Python
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:

Python
@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.

Python
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 returned

MatildaObjectResponse

Extends MatildaChatResponse with one additional field:

Fieldtypedescription
objectT | NoneThe 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:

Python
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 JSON

With 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 with strict: true server-side; the strict and name fields you supply are re-wrapped downstream).
  • { "type": "json_object" } — guarantees valid JSON output without a schema (OpenAI JSON mode).
Note

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.

Caution

Truncation throws. If the stream is truncated before the JSON completes, both helpers raise MatildaObjectParseError with the partial text in .raw. See Error handling.

Caution

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}").