Structured Outputs from LLMs
Extract reliable structured data from LLMs using JSON mode, function calling, Instructor/Zod schemas, and output validation.
Structured outputs transform LLM free-text responses into typed, validated data structures. Without them, LLMs produce inconsistently formatted responses that require fragile string parsing. With them, LLMs become reliable data extraction and transformation engines.
Always use structured output mechanisms (JSON mode or function calling) when you need machine-parseable output. Validate with Zod. Retry on validation failure. Never rely on regex parsing of free-text LLM responses for production systems.
Workflow
- Use OpenAI structured outputs (strict mode)OpenAI's strict structured outputs guarantee 100% schema adherence - no need for parsing or retries.
- Use Instructor for multi-provider supportInstructor wraps multiple providers (OpenAI, Anthropic, Gemini) with validation + retry logic.
- Design effective schemasSchema design determines extraction quality - overly complex schemas produce worse results than focused, simple ones.
- Handle extraction from documentsDocument extraction (invoices, contracts, emails) is a primary LLM use case that requires reliable structure.
- Implement retry and fallback strategiesEven with JSON mode, complex schemas can fail on difficult inputs; retries with error feedback dramatically improve success rates.
Output Format
Zod schema defining expected output, Instructor or OpenAI structured outputs call, validation with error details, retry logic, TypeScript types inferred from schema.
Examples
OpenAI strict mode: `const { sentiment, score } = await openai.beta.chat.completions.parse({ model: "gpt-4o-mini", messages: [{ role: "user", content: `Analyze: ${text}` }], response_format: zodResponseFormat(SentimentSchema, "sentiment") }).then(r => r.choices[0].message.parsed)`Instructor with retry: `const result = await client.chat.completions.create({ model: "gpt-4o", messages, response_model: { schema: InvoiceSchema, name: "invoice" }, max_retries: 3 })`Field description: `const schema = z.object({ date: z.string().describe("ISO 8601 format, e.g. 2026-08-01"), amount: z.number().describe("Numeric value in USD, no currency symbol") })`Edge Cases & Gotchas
- Anthropic Claude: use tool_use mode (function calling) for structured outputs - no native JSON mode; Instructor handles this transparently
- Very large schemas: models perform worse with >20 fields; split into multiple focused extraction calls and merge results
- Nullable vs optional: use `z.string().nullable()` when a field may be explicitly null vs `z.string().optional()` when it may be absent; LLMs handle nullable better
When to Break These Rules
- For simple single-field extractions (yes/no, a single number), a constrained string match on the response is simpler than full schema validation.
Reference Files
OpenAI structured outputs: https://platform.openai.com/docs/guides/structured-outputsInstructor (Python + TS): https://python.useinstructor.com/Zod: https://zod.dev/Anthropic tool use: https://docs.anthropic.com/claude/docs/tool-use