Structured output.
Constrain a chat response to a zod schema and get a typed 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 zod schema. Pass a zod schema, receive a fully-typed object — no prompt engineering, no brittle JSON extraction.
chat.streamObject(params, schema, options?)
Streams exactly like chat.stream() — you receive every MatildaChatStreamEvent — plus one final event with the parsed, schema-validated object. The schema argument is any zod schema (z is bundled with the SDK); the SDK converts it to JSON Schema and constrains generation server-side.
import { z } from 'zod';
const recipe = z.object({
name: z.string(),
prepTimeMinutes: z.number(),
ingredients: z.array(z.string()),
});
for await (const event of client.chat.streamObject(
{ input: 'Give me a recipe for pavlova.' },
recipe,
)) {
if (event.type === 'response.output_text.delta') {
process.stdout.write(event.delta); // raw JSON streaming in
}
if (event.type === 'object') {
console.log('\nValidated:', event.object); // typed as z.infer<typeof recipe>
}
}The final event:
{ type: 'object'; object: T } // T = z.infer<typeof schema>chat.createObject(params, schema, options?)
Non-streaming convenience. Returns a MatildaObjectResponse<T> — everything chat.create() returns, plus the validated object.
const response = await client.chat.createObject(
{ input: 'Extract the invoice total: $1,250.00 AUD due 30 Sep.', conversationId },
z.object({ total: z.number(), currency: z.string() }),
);
console.log(response.object.total); // 1250 (number)
console.log(response.object.currency); // "AUD" (string)
console.log(response.outputText); // raw JSON text as returnedMatildaObjectResponse<T>
Extends MatildaChatResponse with one additional field:
| Field | type | description |
|---|---|---|
| object | T | The response text parsed as JSON and validated against your schema. |
Raw JSON Schema via responseSchema
If you don't want zod validation, pass a stringified JSON Schema directly as responseSchema on any chat call:
const response = await client.chat.create({
input: 'List three Australian birds.',
responseSchema: JSON.stringify({
type: 'object',
properties: { birds: { type: 'array', items: { type: 'string' } } },
required: ['birds'],
additionalProperties: false,
}),
});
JSON.parse(response.outputText); // guaranteed valid, schema-conforming JSONWith responseSchema set, the response text is guaranteed to be valid JSON conforming to the schema — but parsing and validation are up to you.
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 JS 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), streamObject throws SafetyReplaceError — the replacement text is in .message 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 createObject) to avoid showing half-rendered output that is later discarded.
Truncation throws. If the stream is truncated before the JSON completes, both helpers throw MatildaObjectParseError with the partial text in .raw. See Error handling.
Stream errors throw. If the server emits an error event mid-stream, both helpers throw an Error with the server's error code and message (${code}: ${message}).