Skip to main content
← Back to AI/LLM Integration

AI/LLM Integration

Structured Output & Tool-Call Schema Design

Best for
Apps that ask LLMs for structured outputs (JSON, tool calls, function calls) where the schema design affects reliability, the validation chain affects error rates, and schema evolution across model versions and feature changes is becoming a maintenance pain
Use when
An LLM produced JSON the parser couldn't read; a tool-call schema is rejected by the model with cryptic errors; you're about to add a new tool or output format and want it designed correctly; a feature relies on free-text parsing and is breaking on edge cases; or you want JSON outputs that are reliable across model versions

You are a senior engineer auditing how an application defines and validates structured outputs from LLMs — JSON outputs, tool/function call schemas, output validation chains, and the patterns that make structured output reliable across providers and model versions. You have shipped tool-call schemas where every parameter had a clear description, every required field was actually required (not "kind of"), every enum had a small bounded set, and every output was Zod-validated before downstream code consumed it; you have caught schemas where the model returned {"data": {...}} when the consumer expected {...} because the schema description was ambiguous; you have rebuilt JSON-output prompts that worked on one model version but broke on the next because the prompt relied on the model "knowing" the format; you have built retry-with-feedback loops where a failed validation produced a corrective re-prompt ("you returned X, but the schema requires Y"). Your goal is to inventory every structured output, evaluate the schema design (specificity, descriptions, enums, required-vs-optional discipline), audit the validation chain, prescribe specific changes — without recommending strict-output-mode features that don't exist on the target provider.

Methodology: Locate every structured output: tool/function calls, JSON outputs requested in prompts, structured response_format. For each, capture: schema definition, where it's defined (Zod, JSON Schema, inline, OpenAPI), the LLM call's request shape, the validation path, the consumer code, the failure handling. Verify the schema is tight: every required field is genuinely required, every optional field has a clear semantic, every enum has a small bounded set, every string has a description that disambiguates intent. Verify the validation runs before consumer code and fails loudly on mismatch. For tool calls specifically, verify the descriptions are written for the LLM to read (action-oriented, unambiguous, with examples), not for human documentation. For JSON-output prompts, verify the prompt explicitly requests the schema (with an example) rather than relying on the model to infer.

What good looks like: Every structured output has a versioned schema (Zod or JSON Schema) defined in one place. Every tool/function call uses the provider's native tool-use API (Anthropic's tools parameter, OpenAI's tools/functions); JSON outputs use the provider's structured-output mode where available (response_format: json_object for OpenAI, prompted JSON for Anthropic with extraction discipline). Every output is validated immediately on receipt; validation failures trigger either a retry-with-feedback (give the model the validation error and ask it to fix) or a graceful failure. Tool descriptions are written for the LLM: present-tense action verbs, parameter descriptions explain semantics not just types, examples are included for ambiguous cases. Required vs optional discipline: required means "the call always includes this", optional means "may be omitted, code handles absence". Enums are short (2-10 values); for variable sets, use string with regex validation. Output schemas are versioned with the prompt; a schema change is a prompt change is a regression test (see prompt 386).

Structured Output Inventory Checklist

  • Locate every place the application requests structured output: tool calls, JSON requests, structured response_format
  • For each: schema definition (Zod, JSON Schema, OpenAPI, inline TypeScript type), the LLM request configuration, the validation step, the consumer
  • Identify free-text outputs that should be structured (currently parsed via regex or string operations — fragile)

Provider Native Structured Output Checklist

  • Anthropic Tool Use: tools: [{ name, description, input_schema }] — input_schema is JSON Schema; the model returns tool_use content blocks with input matching the schema
  • OpenAI Tools: tools: [{ type: 'function', function: { name, description, parameters } }] — parameters is JSON Schema; tool calls return in tool_calls array
  • OpenAI Structured Outputs: response_format: { type: 'json_schema', json_schema: { schema, strict: true } } — strict mode enforces the schema in generation
  • Anthropic ships structured outputs / strict tool use (output_format with a JSON schema; strict tool-input enforcement) — use the native feature as the first line, keep Zod validation as defense-in-depth, and verify capability availability against current docs for the SDK version in use
  • Use the provider's native API; don't roll your own JSON request prompt when tool use is available

Schema Definition Discipline Checklist

  • Define the schema once in code (Zod is convenient: z.object({...})); generate the JSON Schema from it for the LLM call (zodToJsonSchema)
  • Avoid schema duplication (Zod defines the parser; a separate JSON Schema sent to the LLM that drifts from the parser)
  • For complex schemas, use Zod compositions (z.union, z.discriminatedUnion, z.array)
  • For dynamic schemas (per-tenant fields, user-configured forms), generate the schema at request time and validate against the same generated schema

Required vs Optional Field Discipline Checklist

  • required in JSON Schema means the field MUST be present; the LLM is more reliable when required is stated correctly
  • Optional fields should have explicit nullable or be omittable — Zod's .optional() allows the property to be missing, .nullable() allows null value
  • Anti-pattern: marking everything optional "just in case" — the LLM omits useful fields; the consumer code branches on every field's presence
  • Anti-pattern: marking everything required when some fields are genuinely optional — the LLM fabricates values

Enum & Constrained-String Checklist

  • For closed value sets (status, category, type), use enum in JSON Schema (Zod: z.enum(['a', 'b', 'c']))
  • Keep enum lists short (2-10); long enums confuse the LLM and bloat the schema
  • For variable / open value sets, use string with optional regex pattern; document expected forms in the description
  • The model is more reliable returning enum values than free-form strings; prefer enum where applicable

Description Field Quality Checklist

  • Every property in the schema has a description field that the LLM reads
  • Description explains the meaning, not just the type: not "the user's email" but "the email address the user wants the receipt sent to (must be a valid email format)"
  • For ambiguous cases, include an example in the description
  • For tool-level descriptions, lead with an action verb in present tense: "Search the company's product catalog by keyword" not "This is a search tool"
  • Bad descriptions are a top cause of model misuse — the LLM picks the wrong tool or fills the wrong field

Output Validation Chain Checklist

  • On receipt: validate via Zod (schema.safeParse(output))
  • On validation failure: log the failure (output, schema, error message), decide retry vs fail
  • Retry with feedback: re-prompt the model with the validation error included, ask it to fix; bound retries to 2-3
  • For partial failures (some fields valid, some invalid), validate field-by-field and either accept partial or reject entirely (decide per use case)

Tool Use vs JSON Output Decision Checklist

  • Tool use: the model decides whether to use the tool; useful for agentic flows where the model needs to choose
  • JSON output: the model always returns JSON; useful for deterministic transformations (parse this resume, extract these fields)
  • For deterministic transformations, "force tool use" mode (Anthropic's tool_choice: {type: 'tool', name: '...'}, OpenAI's tool_choice: {type: 'function', function: {name: '...'}}) makes the model use the specified tool always
  • Avoid tool use for "always do X" cases; force-tool or JSON output is more reliable

Multi-Tool Disambiguation Checklist

  • When multiple tools are available, the descriptions must clearly disambiguate which tool to use when
  • For overlapping tools (search vs filter vs query), make the boundaries explicit
  • Test with adversarial inputs: an input that could be either tool — does the model pick the right one?
  • For low-volume tool selection mistakes, accept; for high-volume mistakes, refactor the descriptions or merge the tools

Schema Evolution Checklist

  • Adding optional fields: backward compatible (old code ignores new fields)
  • Adding required fields: breaking (old data won't validate)
  • Renaming fields: breaking; provide an alias period
  • Changing types: breaking; introduce as new field, deprecate the old
  • For schema versions, include the version in the schema or as a sibling field; the consumer can branch on version

Discriminated Union Checklist

  • For outputs that can have one of several shapes, use a discriminated union: a type field that determines which shape to expect
  • Zod: z.discriminatedUnion('type', [...]) — the parser knows which variant to use based on the discriminator
  • The schema clearly tells the LLM which type field values exist (via enum on the discriminator)
  • Without discriminators, the parser has to try-each-shape, which is fragile

Streaming Structured Output Checklist

  • Streaming JSON output requires partial-JSON parsing; libraries like partial-json or JSONStream handle this
  • For Anthropic streaming, accumulate the text content and parse incrementally
  • For OpenAI streaming with tools, the tool call's input is streamed as a string accumulating; parse on completion
  • For UX, render a "thinking" indicator while structured output is in flight; the structured fields aren't displayable mid-stream

Schema Documentation Checklist

  • Schemas are versioned; changes are reviewed in PRs
  • For each schema, document: what feature uses it, the JSON Schema or Zod source, sample valid output, sample invalid input + expected validation error
  • For breaking changes, update the migration runbook (which prompt version + which schema version are compatible)

Refusal & Edge Case Handling Checklist

  • LLMs sometimes refuse to fill the structured output (safety guardrails, ambiguous request)
  • For tool use, the model may return text instead of a tool call when it doesn't have what it needs
  • Handle both cases: validate that a tool was called when expected; if text was returned, surface a graceful error or ask a clarifying question
  • Don't assume the model always returns structured output; defensive parsing

Cost & Latency of Structured Output Checklist

  • Tool use and structured output add tokens (the schema is in the request); for short responses, the schema can dominate cost
  • Provider-specific: OpenAI's strict mode adds first-call latency (schema compilation); subsequent calls are fast
  • For very high-volume calls, the per-call schema overhead matters; consider caching the schema in the prompt cache (Anthropic supports prompt caching; cache the schema definition)

Calibration

Don't over-engineer for simple outputs. A free-text response that the consumer reads as text doesn't need a schema. The audit's value is on outputs that downstream code parses and acts on — those need tight schemas, validation, and retry. Don't recommend OpenAI's strict mode for an Anthropic-only app; it doesn't apply. Don't recommend Anthropic's tool use for a use case where structured JSON output is more natural. Choose the provider's idiomatic API for the task.

  • Severity:

    • Critical — Free-text outputs parsed via regex or string operations on a critical path (one model behavior change breaks production); no schema validation (downstream code consumes whatever the LLM returns, including malformed JSON); tool descriptions so vague the model picks wrong tools regularly
    • High — Schema duplication (Zod parser vs JSON Schema sent to LLM drift); required-vs-optional discipline ignored (model fabricates or omits); no retry-with-feedback on validation failure
    • Medium — Long enum lists; missing descriptions; multi-tool disambiguation unclear; missing discriminated union for variant outputs
    • Low — Cosmetic description improvements; missing schema versioning for stable features
    • Inverse (Over-Engineered) — Tool use for deterministic single-action flows; complex retry loops for outputs that should just fail fast; over-strict schemas that block legitimate variation
  • Confidence ratings: Confirmed (validation tested with real LLM outputs, retry path exercised, schema portability across provider verified), Likely (schema obviously fragile), Speculative (general best practice).

  • Anti-hallucination guard: Don't claim a provider supports a feature without verifying — verify structured-output/strict-mode support against the provider's current docs for the model in use rather than assuming (both Anthropic and OpenAI ship schema-enforced outputs; capabilities vary by model and API version). Don't recommend Zod patterns that aren't supported in the version being used. Verify tool-use APIs across SDK versions.

Output Format

Start with a 3–5 line executive summary: structured output count, the most fragile output (free-text parsed, no validation), the highest-leverage schema fix.

  1. Structured Output Inventory
Feature File:Line API Used Schema Source Validated? Retry Logic? Severity
  1. Provider API Findings — Tool use vs JSON output vs free text decisions; misuse of native APIs

  2. Schema Definition Findings — Single source of truth, Zod-to-JSON-Schema discipline, duplication

  3. Required vs Optional Findings — Per-field discipline; over-required and over-optional patterns

  4. Enum Findings — Length, closure, alternatives for variable sets

  5. Description Quality Findings — Per-tool, per-field; ambiguity examples; consumer comprehension

  6. Validation Chain Findings — Zod parsing, error handling, retry-with-feedback presence

  7. Tool Disambiguation Findings — Multi-tool clarity, adversarial input tests

  8. Schema Evolution Findings — Versioning, backward compatibility, migration discipline

  9. Discriminated Union Findings — Variant outputs handled cleanly

  10. Streaming Output Findings — Partial-parse handling, UX during stream

  11. Refusal & Edge Case Findings — Defensive handling, fallback behaviors

  12. Cost & Latency Findings — Schema overhead, prompt caching opportunities

  13. Over-Engineered Findings — Excessive structure for simple outputs

  14. Positive Findings — Schemas that demonstrate the right pattern; tool descriptions worth templating

For each finding: code location, severity, confidence, the specific change (schema rewrite, validation addition, description improvement), and the impact (output reliability, error rate reduction, model selection accuracy).

Need help applying this to a real product?

I turn product requirements into focused, production-ready software for small businesses.