Build / AI /

Structured Outputs

Enforce strict type safety and JSON Schema compliance on AI model outputs using Zod validation and structured decoding.

TL;DR

  1. Define strict response structures using standard zod object schemas.
  2. Enable guaranteed grammar compliance with response_format strict mode.
  3. Validate generated output strings with safeParse to guarantee type safety.

Zod Schema Definition

    Declare Target Schema

    Define type-safe entity schema with strict field descriptions.

    import { z } from 'zod';
    
    const UserProfileSchema = z.object({
      name: z.string().describe('Full name'),
      age: z.number().int().positive(),
      skills: z.array(z.string()),
    });
    type UserProfile = z.infer<typeof UserProfileSchema>;
    Enforce Strict Object

    Prevent models from generating extra unverified object keys.

    const StrictSchema = z.object({
      status: z.enum(['active', 'pending']),
      score: z.number().min(0).max(100),
    }).strict();
    Nested Complex Schemas

    Compose multi-level hierarchical structures for complex data.

    const ProjectSchema = z.object({
      title: z.string(),
      tasks: z.array(z.object({
        id: z.string(),
        done: z.boolean(),
      })),
    });

OpenAI Structured Outputs

    zodResponseFormat Helper

    Pass Zod schemas directly into chat completions request format.

    import { zodResponseFormat } from 'openai/helpers/zod';
    
    const fmt = zodResponseFormat(
      UserProfileSchema,
      'profile'
    );
    const res = await openai.beta.chat.completions.parse({
      model: 'gpt-4o-2024-08-06',
      messages: [{ role: 'user', content: 'John, 30' }],
      response_format: fmt,
    });
    Direct Parsed Access

    Read pre-validated typed object directly from completion message.

    const p: UserProfile = res.choices[0].message.parsed!;
    console.log(p.name, p.age);
    Refusal Handling

    Check if safety policies prevented structured generation.

    if (res.choices[0].message.refusal) {
      console.warn('Safety policy prevented generation');
    }

JSON Schema Native Mode

    Raw JSON Schema

    Declare raw schema parameters with strict enforcement enabled.

    const format = {
      type: 'json_schema' as const,
      json_schema: {
        name: 'user_data',
        strict: true,
        schema: { type: 'object', properties: {} }
      }
    };
    Anthropic Tool Extraction

    Use forced tool calls to extract structured data in Claude.

    const tool = {
      name: 'submit',
      input_schema: rawSchema
    };
    const res = await anthropic.messages.create({
      model: 'claude-3-5-sonnet-20241022',
      max_tokens: 500,
      tools: [tool],
      tool_choice: { type: 'tool', name: 'submit' },
      messages: [{ role: 'user', content: text }],
    });
    Extract Tool JSON

    Extract parsed tool input argument object from Claude response.

    const t = res.content.find(b => b.type === 'tool_use');
    const data = (t as any)?.input;

Validation And Fallbacks

    SafeParse Validation

    Validate raw generated JSON strings defensively with Zod.

    const parsed = JSON.parse(rawJson);
    const result = UserProfileSchema.safeParse(parsed);
    if (!result.success) {
      console.error(result.error.format());
    }
    Markdown Fence Stripping

    Clean unwanted markdown delimiters before JSON decoding.

    // Strip backtick fences without literal triggers
    function cleanJson(raw: string): string {
      const fence = String.fromCharCode(96).repeat(3);
      return raw.replaceAll(fence, '').trim();
    }
    Schema Retry Prompt

    Re-prompt model with specific validation errors for self-correction.

    const errMsg = result.error.message;
    const fixPrompt = `Fix JSON errors: ${errMsg}`;
    const fixed = await model.complete(fixPrompt);

Tips

  1. Set strict: true on JSON Schema definitions so models reject unspecified properties and follow exact types.
  2. Add descriptive describe() comments inside schema definitions to guide model field population accurately without bloating system prompts.

Warnings

  1. Do not rely solely on simple prompt instructions like return JSON; models frequently wrap outputs in markdown code fences.
  2. Avoid optional properties without explicit default fallbacks in your zod schema to prevent unexpected downstream runtime exceptions.

In Practice

FAQ