Structured output

End-to-end path: Zod schema → IR → model JSON → Rust parse. Use model(provider).generateObject when you need a typed object, not free text.

Happy path

structured.ts
import { model, mock } from "@monorch/ai";
import { z } from "zod";

const handle = model(
  mock([{ text: JSON.stringify({ city: "Lisbon", tempC: 22 }) }]),
);

const Weather = z.object({
  city: z.string(),
  tempC: z.number(),
});

const weather = await handle.generateObject({
  prompt: "Weather in Lisbon as JSON",
  output: Weather,
});
// weather: { city: "Lisbon", tempC: 22 }

Pipeline

1. Zod
You pass a Zod schema as output. Supported checks include string email / uuid / url / regex and number int. Exotic refine / transform / pipe throw SCHEMA_UNSUPPORTED — keep schemas in the common subset (objects, primitives, arrays, enums, optional, union + those checks).
2. zodToIr
Monorch compiles the schema to IR for the Rust validator (zodToIr is also a public export if you need IR for tools).
3. Model text
The provider is asked for JSON-only. Empty text → EMPTY_OUTPUT. Unparseable → INVALID_JSON (including markdown fences that still do not contain valid JSON — never a raw SyntaxError).
4. Rust parse
getRuntime().parse(ir, json) validates. Failure → VALIDATION_FAILED with details.errors.

Failure cases

structured-fail.ts
import { AiError, model, mock } from "@monorch/ai";
import { z } from "zod";

const Schema = z.object({ n: z.number() });

try {
  await model(mock([{ text: "not-json" }])).generateObject({
    prompt: "x",
    output: Schema,
  });
} catch (e) {
  // e instanceof AiError && e.code === "INVALID_JSON"
}

try {
  await model(mock([{ text: '{"n":"nope"}' }])).generateObject({
    prompt: "x",
    output: Schema,
  });
} catch (e) {
  // e instanceof AiError && e.code === "VALIDATION_FAILED"
}

Gaps vs “full” structured output

This path validates after generation. It does not bind provider-native JSON-schema / tool-choice constrained decoding. For OpenAI-compatible hosts that support response formats, you can still pass provider options on raw generate / stream and then validate with the same Zod schema yourself — generateObject is the batteries-included loop for mock + OpenAI-compatible text JSON.

Error catalog: Errors & failure modes.

FAQ