obsidian/wiki/agent-sdk/structured-outputs.md
2026-04-17 12:48:28 +01:00

5.6 KiB

title aliases tags sources created updated
Structured Outputs from Agents
structured-output
agent-json-output
validated-json-output
agent-sdk
structured-outputs
zod
pydantic
json-schema
typescript
python
raw/Get structured output from agents.md
2026-04-17 2026-04-17

Structured Outputs from Agents

Return validated, typed JSON from an agent workflow instead of free-form text. The agent still runs its full tool loop; structured output is enforced at the end.

Why Use It

  • Agents return free-form text by default — usable for chat, not for app logic
  • Structured outputs give you typed data ready for databases, UIs, APIs
  • SDK validates output against your schema; re-prompts on mismatch
  • If retries exceed the limit, result is an error (not bad data)

Quick Start

Pass outputFormat (TS) / output_format (Python) to query():

import { query } from "@anthropic-ai/claude-agent-sdk";

const schema = {
  type: "object",
  properties: {
    company_name: { type: "string" },
    founded_year: { type: "number" },
    headquarters: { type: "string" }
  },
  required: ["company_name"]
};

for await (const message of query({
  prompt: "Research Anthropic and provide key company information",
  options: {
    outputFormat: { type: "json_schema", schema }
  }
})) {
  if (message.type === "result" && message.subtype === "success" && message.structured_output) {
    console.log(message.structured_output);
    // { company_name: "Anthropic", founded_year: 2021, headquarters: "San Francisco, CA" }
  }
}

Validated data is on message.structured_output in the result message.

Type-Safe Schemas: Zod (TS) and Pydantic (Python)

Instead of hand-writing JSON Schema, use library types — they generate the schema and give you full autocomplete + runtime validation.

TypeScript with Zod:

import { z } from "zod";

const FeaturePlan = z.object({
  feature_name: z.string(),
  summary: z.string(),
  steps: z.array(z.object({
    step_number: z.number(),
    description: z.string(),
    estimated_complexity: z.enum(["low", "medium", "high"])
  })),
  risks: z.array(z.string())
});

const schema = z.toJSONSchema(FeaturePlan);   // generate JSON Schema
// pass schema to outputFormat, then:
const parsed = FeaturePlan.safeParse(message.structured_output);
if (parsed.success) {
  const plan = parsed.data;  // fully typed FeaturePlan
}

Benefits over raw JSON Schema:

  • Full type inference / type hints
  • Runtime validation with safeParse() or model_validate()
  • Better error messages
  • Composable, reusable schemas

outputFormat Config

outputFormat: {
  type: "json_schema",  // only supported type
  schema: <JSON Schema object>
}

Supported JSON Schema features: object, array, string, number, boolean, null, enum, const, required, nested objects, $ref.

Multi-Step Tool Use + Structured Output

The agent freely uses tools (Grep, Bash, etc.) during execution. Structured output applies only to the final result, not individual tool calls.

Example — TODO tracker agent:

  • Agent uses Grep to find TODO comments, Bash for git blame
  • Returns a single validated object with todos[] and total_count
  • Optional fields (author, date) handle cases where git info is unavailable
const todoSchema = {
  type: "object",
  properties: {
    todos: {
      type: "array",
      items: {
        type: "object",
        properties: {
          text: { type: "string" },
          file: { type: "string" },
          line: { type: "number" },
          author: { type: "string" },   // optional
          date: { type: "string" }       // optional
        },
        required: ["text", "file", "line"]
      }
    },
    total_count: { type: "number" }
  },
  required: ["todos", "total_count"]
};

Error Handling

Check message.subtype on the result message:

Subtype Meaning
success Output validated successfully
error_max_structured_output_retries Agent failed to produce valid output after retries
if (msg.type === "result") {
  if (msg.subtype === "success" && msg.structured_output) {
    // use data
  } else if (msg.subtype === "error_max_structured_output_retries") {
    // retry with simpler prompt, fall back to unstructured, etc.
  }
}

Tips for Avoiding Errors

  • Keep schemas focused — deeply nested + many required fields = harder to satisfy
  • Make fields optional when the task might not surface that data
  • Write clear prompts — ambiguity makes output unpredictable

Key Takeaways

  • Pass outputFormat: { type: "json_schema", schema } to query() to get validated JSON back
  • Use Zod (TS) or Pydantic (Python) to generate the schema and get strong types
  • The agent runs its full tool loop freely; structured output is enforced only at the end
  • Validation failures auto-retry; persistent failure → error_max_structured_output_retries
  • Optional fields are your friend — don't require data the agent might not find
  • For single-turn requests without tool use, the Claude API has its own structured outputs endpoint

Sources

  • raw/Get structured output from agents.md — official Agent SDK docs on structured outputs