Structured Output
Client-side validation with Zod and provider-native JSON Schema mode
With Zod
Remember to install zod first!
pnpm add zodnpm install zodyarn add zodPass a schema and get a typed, validated result. Works with any validator exposing safeParse (Zod v3/v4).
import { z } from 'zod';
const CandidateSchema = z.object({
name: z.string(),
skills: z.array(z.string()),
});
// result is typed as z.infer<typeof CandidateSchema>
const result = await llm.call({
systemPrompt: 'Extract the candidate name and skills as JSON.',
userContent: resumeText,
schema: CandidateSchema,
});On a schema mismatch, call throws LLMError('validation') with .issues set to the validator's
error object, without burning a retry. Validation failures are deterministic.
schema only runs if the call actually parses JSON, which happens when jsonMode is true (the
default) or jsonSchema is set. If you explicitly pass jsonMode: false alongside schema
without also setting jsonSchema, the schema is silently skipped: you get the raw string back
with no validation, cast as your schema's type with no runtime guarantee behind it. In practice
this only comes up if you set jsonMode: false on purpose while still passing schema, which
isn't a combination you'd reach for intentionally, but worth knowing if you're toggling jsonMode
dynamically per call.
Provider-native JSON Schema mode
schema validates client-side, after the model has already responded. jsonSchema sends the schema to the provider (OpenAI/Groq response_format: { type: 'json_schema' }), constraining generation itself.
const result = await llm.call({
systemPrompt: 'Extract the candidate name and skills.',
userContent: resumeText,
jsonSchema: {
name: 'Candidate',
schema: {
type: 'object',
properties: {
name: { type: 'string' },
skills: { type: 'array', items: { type: 'string' } },
},
required: ['name', 'skills'],
},
// strict?: boolean, defaults to true
// description?: string, OpenAI-shaped providers only, see note below
},
schema: CandidateSchema, // optional client-side type/validation on top
});jsonSchemaimplies (and overrides) JSON mode. Even an explicitjsonMode: falseis overridden ifjsonSchemais also set; the request is still sent, and parsed, as JSON.strictdefaults totrue. This is an OpenAI-specific flag; providers that don't recognizeresponse_format.json_schema.strictjust ignore it.descriptiondoesn't reach every provider. OpenAI-shaped providers get it as part of thejson_schemaobject. The Anthropic adapter only embedsnameandschemainto its system-prompt instruction;descriptionis dropped for Anthropic specifically.
The Anthropic adapter has no native structured-output equivalent, so it embeds the schema in the
system prompt as an instruction instead of provider-enforcing it. Combining jsonSchema with a
client-side schema is especially worth doing on Anthropic, since the provider-side constraint
there is a suggestion, not a guarantee, and the client-side safeParse is your actual safety net.