SpyBara
Go Premium

agent-sdk/structured-outputs.md 2026-11-12 23:02 UTC to 2026-11-13 00:00 UTC

No changes for this document in this history range.

2026
Tue 10 21:57

Obtener salida estructurada de agentes

Devuelve JSON validado desde flujos de trabajo de agentes usando JSON Schema, Zod o Pydantic. Obtén datos estructurados seguros en tipos después del uso de herramientas de múltiples turnos.

Las salidas estructuradas le permiten definir la forma exacta de los datos que desea recuperar de un agente. El agente puede usar cualquier herramienta que necesite para completar la tarea, y aún así obtiene JSON validado que coincide con su esquema al final. Defina un JSON Schema para la estructura que necesita, y el SDK valida la salida en su contra, re-solicitando en caso de discrepancia. Si la validación no tiene éxito dentro del límite de reintentos, el resultado es un error en lugar de datos estructurados; consulte Manejo de errores.

Para una seguridad de tipos completa, usa Zod (TypeScript) o Pydantic (Python) para definir tu esquema y obtener objetos fuertemente tipados.

¿Por qué usar salidas estructuradas?

Los agentes devuelven texto libre de forma predeterminada, lo cual funciona para un chat, pero no cuando necesitas usar la salida de forma programática. Las salidas estructuradas te dan datos tipados que puedes pasar directamente a la lógica de tu aplicación, a tu base de datos o a tus componentes de UI.

Piensa en una aplicación de recetas donde un agente busca en la web y trae recetas. Sin salidas estructuradas, obtienes texto libre que tendrías que analizar por tu cuenta. Con salidas estructuradas, defines la forma que quieres y obtienes datos tipados que puedes usar directamente en tu aplicación.

[accordion] Sin salidas estructuradas

Here's a classic chocolate chip cookie recipe!

**Chocolate Chip Cookies**
Prep time: 15 minutes | Cook time: 10 minutes

Ingredients:
- 2 1/4 cups all-purpose flour
- 1 cup butter, softened
...

Para usar esto en tu aplicación, tendrías que extraer el título, convertir "15 minutes" en un número, separar los ingredientes de las instrucciones y manejar el formato inconsistente entre respuestas.

[accordion] Con salidas estructuradas

{
  "name": "Chocolate Chip Cookies",
  "prep_time_minutes": 15,
  "cook_time_minutes": 10,
  "ingredients": [
    { "item": "all-purpose flour", "amount": 2.25, "unit": "cups" },
    { "item": "butter, softened", "amount": 1, "unit": "cup" }
    // ...
  ],
  "steps": ["Preheat oven to 375°F", "Cream butter and sugar" /* ... */]
}

Datos tipados que puedes usar directamente en tu UI.

Inicio rápido

Para usar salidas estructuradas, define un JSON Schema que describa la forma de los datos que quieres y luego pásalo a query() mediante la opción outputFormat (TypeScript) o la opción output_format (Python). Cuando el agente termina, el mensaje de resultado incluye un campo structured_output con datos validados que coinciden con tu esquema.

Antes de ejecutar los ejemplos de esta página, instala el Claude Agent SDK siguiendo la guía de inicio rápido. El siguiente ejemplo le pide al agente que investigue Anthropic y devuelva el nombre de la empresa, el año de fundación y la sede como salida estructurada.

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

// Define la forma de los datos que quieres recibir
const schema = {
  type: "object",
  properties: {
    company_name: { type: "string" },
    founded_year: { type: "number" },
    headquarters: { type: "string" }
  },
  required: ["company_name"]
};

try {
  for await (const message of query({
    prompt: "Research Anthropic and provide key company information",
    options: {
      outputFormat: {
        type: "json_schema",
        schema: schema
      }
    }
  })) {
    // El mensaje de resultado contiene structured_output con datos validados
    if (message.type === "result" && message.subtype === "success" && !message.is_error && message.structured_output) {
      console.log(message.structured_output);
      // { company_name: "Anthropic", founded_year: 2021, headquarters: "San Francisco, CA" }
    }
  }
} catch (error) {
  // Un query() de una sola ejecución lanza una excepción después de producir un resultado de error, como
  // error_max_structured_output_retries; consulta la sección de manejo de errores.
  console.error(`Session ended with an error: ${error}`);
}
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage

# Define la forma de los datos que quieres recibir
schema = {
    "type": "object",
    "properties": {
        "company_name": {"type": "string"},
        "founded_year": {"type": "number"},
        "headquarters": {"type": "string"},
    },
    "required": ["company_name"],
}


async def main():
    try:
        async for message in query(
            prompt="Research Anthropic and provide key company information",
            options=ClaudeAgentOptions(
                output_format={"type": "json_schema", "schema": schema}
            ),
        ):
            # El mensaje de resultado contiene structured_output con datos validados
            if (
                isinstance(message, ResultMessage)
                and not message.is_error
                and message.structured_output
            ):
                print(message.structured_output)
                # {'company_name': 'Anthropic', 'founded_year': 2021, 'headquarters': 'San Francisco, CA'}
    except Exception as error:
        # Un query() de una sola ejecución lanza una excepción después de producir un resultado de error, como
        # error_max_structured_output_retries; consulta la sección de manejo de errores.
        print(f"Session ended with an error: {error}")


asyncio.run(main())

Esquemas seguros en tipos con Zod y Pydantic

En lugar de escribir JSON Schema a mano, puede usar Zod (TypeScript) o Pydantic (Python) para definir su esquema. Estas bibliotecas generan el JSON Schema para usted y le permiten analizar la respuesta en un objeto completamente tipado que puede usar en todo su código con autocompletado y verificación de tipos.

El ejemplo a continuación define un esquema para un plan de implementación de características con un resumen, lista de pasos (cada uno con nivel de complejidad) y riesgos potenciales. El agente planifica la característica y devuelve un objeto FeaturePlan tipado. Luego puede acceder a propiedades como plan.summary e iterar sobre plan.steps con seguridad de tipos completa.

El SDK valida esquemas con JSON Schema draft-07, por lo que se rechazan los esquemas que declaran una versión más nueva. Zod apunta a draft 2020-12 de forma predeterminada, así que pase target: "draft-7" al convertir su esquema.

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

// Define schema with 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())
});

type FeaturePlan = z.infer<typeof FeaturePlan>;

// Convert to JSON Schema using the draft-07 target the SDK expects
const schema = z.toJSONSchema(FeaturePlan, { target: "draft-7" });

// Use in query
try {
  for await (const message of query({
    prompt:
      "Plan how to add dark mode support to a React app. Break it into implementation steps.",
    options: {
      outputFormat: {
        type: "json_schema",
        schema: schema
      }
    }
  })) {
    if (message.type === "result" && message.subtype === "success" && !message.is_error && message.structured_output) {
      // Validate and get fully typed result
      const parsed = FeaturePlan.safeParse(message.structured_output);
      if (parsed.success) {
        const plan: FeaturePlan = parsed.data;
        console.log(`Feature: ${plan.feature_name}`);
        console.log(`Summary: ${plan.summary}`);
        plan.steps.forEach((step) => {
          console.log(`${step.step_number}. [${step.estimated_complexity}] ${step.description}`);
        });
      }
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result, such as
  // error_max_structured_output_retries; see the Error handling section.
  console.error(`Session ended with an error: ${error}`);
}
import asyncio
from pydantic import BaseModel
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


class Step(BaseModel):
    step_number: int
    description: str
    estimated_complexity: str  # 'low', 'medium', 'high'


class FeaturePlan(BaseModel):
    feature_name: str
    summary: str
    steps: list[Step]
    risks: list[str]


async def main():
    try:
        async for message in query(
            prompt="Plan how to add dark mode support to a React app. Break it into implementation steps.",
            options=ClaudeAgentOptions(
                output_format={
                    "type": "json_schema",
                    "schema": FeaturePlan.model_json_schema(),
                }
            ),
        ):
            if (
                isinstance(message, ResultMessage)
                and not message.is_error
                and message.structured_output
            ):
                # Validate and get fully typed result
                plan = FeaturePlan.model_validate(message.structured_output)
                print(f"Feature: {plan.feature_name}")
                print(f"Summary: {plan.summary}")
                for step in plan.steps:
                    print(
                        f"{step.step_number}. [{step.estimated_complexity}] {step.description}"
                    )
    except Exception as error:
        # A single-shot query() raises after yielding an error result, such as
        # error_max_structured_output_retries; see the Error handling section.
        print(f"Session ended with an error: {error}")


asyncio.run(main())

Configuración del formato de salida

La opción outputFormat (TypeScript) u output_format (Python) acepta un objeto con:

  • type: Establezca en "json_schema" para salidas estructuradas
  • schema: Un objeto JSON Schema que define su estructura de salida. Puede generar esto a partir de un esquema Zod con z.toJSONSchema(schema, { target: "draft-7" }) o un modelo Pydantic con .model_json_schema()

El SDK admite características estándar de JSON Schema, incluidos todos los tipos básicos (objeto, matriz, cadena, número, booleano, nulo), enum, const, required, objetos anidados y definiciones $ref. Para la lista completa de características admitidas y limitaciones, consulte Limitaciones de JSON Schema.

Un esquema que no es un JSON Schema válido falla la ejecución al inicio con un error que nombra el problema. Antes de v2.1.205, un esquema inválido se ignoraba silenciosamente y el agente devolvía texto no estructurado.

La palabra clave format, como "format": "email", se acepta como una anotación y no se aplica mediante el validador del SDK. Antes de v2.1.205, cualquier esquema que contenga format se trataba como inválido.

Ejemplo: agente de seguimiento de TODO

Este ejemplo demuestra cómo funcionan las salidas estructuradas con el uso de herramientas en varios pasos. El agente necesita encontrar comentarios TODO en la base de código y luego buscar la información de git blame para cada uno. Decide de forma autónoma qué herramientas usar (Grep para buscar, Bash para ejecutar comandos de git) y combina los resultados en una única respuesta estructurada.

El esquema incluye campos opcionales (author y date) ya que la información de git blame podría no estar disponible para todos los archivos. El agente completa lo que puede encontrar y omite el resto.

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

// Define la estructura para la extracción de TODO
const todoSchema = {
  type: "object",
  properties: {
    todos: {
      type: "array",
      items: {
        type: "object",
        properties: {
          text: { type: "string" },
          file: { type: "string" },
          line: { type: "number" },
          author: { type: "string" },
          date: { type: "string" }
        },
        required: ["text", "file", "line"]
      }
    },
    total_count: { type: "number" }
  },
  required: ["todos", "total_count"]
};

// El agente usa Grep para encontrar TODO y Bash para obtener la información de git blame
try {
  for await (const message of query({
    prompt: "Find all TODO comments in this codebase and identify who added them",
    options: {
      outputFormat: {
        type: "json_schema",
        schema: todoSchema
      }
    }
  })) {
    if (message.type === "result" && message.subtype === "success" && !message.is_error && message.structured_output) {
      const data = message.structured_output as { total_count: number; todos: Array<{ file: string; line: number; text: string; author?: string; date?: string }> };
      console.log(`Found ${data.total_count} TODOs`);
      data.todos.forEach((todo) => {
        console.log(`${todo.file}:${todo.line} - ${todo.text}`);
        if (todo.author) {
          console.log(`  Added by ${todo.author} on ${todo.date}`);
        }
      });
    }
  }
} catch (error) {
  // Un query() de una sola ejecución lanza una excepción después de emitir un resultado de error, como
  // error_max_structured_output_retries; consulta la sección de manejo de errores.
  console.error(`Session ended with an error: ${error}`);
}
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage

# Define la estructura para la extracción de TODO
todo_schema = {
    "type": "object",
    "properties": {
        "todos": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "text": {"type": "string"},
                    "file": {"type": "string"},
                    "line": {"type": "number"},
                    "author": {"type": "string"},
                    "date": {"type": "string"},
                },
                "required": ["text", "file", "line"],
            },
        },
        "total_count": {"type": "number"},
    },
    "required": ["todos", "total_count"],
}


async def main():
    # El agente usa Grep para encontrar TODO y Bash para obtener la información de git blame
    try:
        async for message in query(
            prompt="Find all TODO comments in this codebase and identify who added them",
            options=ClaudeAgentOptions(
                output_format={"type": "json_schema", "schema": todo_schema}
            ),
        ):
            if (
                isinstance(message, ResultMessage)
                and not message.is_error
                and message.structured_output
            ):
                data = message.structured_output
                print(f"Found {data['total_count']} TODOs")
                for todo in data["todos"]:
                    print(f"{todo['file']}:{todo['line']} - {todo['text']}")
                    if "author" in todo:
                        print(f"  Added by {todo['author']} on {todo['date']}")
    except Exception as error:
        # Un query() de una sola ejecución lanza una excepción después de emitir un resultado de error, como
        # error_max_structured_output_retries; consulta la sección de manejo de errores.
        print(f"Session ended with an error: {error}")


asyncio.run(main())

Reconocer la llamada a herramienta StructuredOutput

Cuando configuras outputFormat en TypeScript o output_format en Python, tu flujo de mensajes incluye una llamada a herramienta llamada StructuredOutput que nunca definiste. Claude Code agrega esta herramienta a la sesión, y el agente la llama para devolver su respuesta. El mensaje de inicialización del sistema la incluye en el array tools, y cada llamada llega como dos bloques:

  • Un bloque tool_use llamado StructuredOutput, cuya entrada es el objeto que el agente produjo para tu esquema
  • Un bloque tool_result que dice Structured output provided successfully

Cuando ese objeto no coincide con tu esquema, el tool_result incluye is_error: true en su lugar y el agente vuelve a llamar a StructuredOutput, por lo que tu flujo puede contener varias llamadas para una sola consulta. Lee los datos validados desde structured_output en el mensaje de resultado en lugar de hacerlo desde un bloque tool_use, y consulta Manejo de errores para ver el resultado que obtienes cuando ningún intento tiene éxito.

Si tu aplicación muestra o registra llamadas a herramientas, omite las que se llaman StructuredOutput para que el mecanismo no aparezca como un paso que el agente eligió. Un bloque tool_result no incluye ningún nombre de herramienta, solo el tool_use_id de la llamada a la que responde, así que compáralo con el id de cada llamada que omitiste.

Manejo de errores

La generación de salida estructurada puede fallar cuando el agente no puede producir JSON válido que coincida con tu esquema. Esto típicamente sucede cuando el esquema es demasiado complejo para la tarea, la tarea en sí es ambigua, o el agente alcanza su límite de reintentos intentando corregir errores de validación. También puede suceder sin ninguna falla de validación: una alternativa de modelo puede retraer una salida ya completada a mitad de la transmisión, y si ningún reintento la reemplaza, la ejecución termina con el mismo error. Revisa la lista errors en el mensaje de resultado de error para distinguir las dos causas antes de depurar tu esquema.

Revisa el subtype del mensaje de resultado:

Subtype Significado
success La ejecución terminó sin un subtype de error
error_max_structured_output_retries Ninguna salida válida sobrevivió después de múltiples intentos (fallos de validación, o una retracción de alternativa de modelo sin reintento exitoso)

Un subtype success no significa por sí solo que la ejecución haya producido una respuesta. Trata un resultado como un fallo cuando el is_error del resultado es true o cuando structured_output está ausente:

  • is_error en el resultado es true: por ejemplo, la solicitud final al modelo terminó en un error de API o en un rechazo. No uses un valor structured_output de un resultado así.
  • structured_output está ausente: para encontrar la causa, consulta structured_output es None pero el resultado dice success

El ejemplo a continuación aplica estas comprobaciones:

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

const contactSchema = {
  type: "object",
  properties: {
    name: { type: "string" },
    email: { type: "string" }
  },
  required: ["name"]
};

try {
  for await (const msg of query({
    prompt: "Extract contact info from the document",
    options: {
      outputFormat: {
        type: "json_schema",
        schema: contactSchema
      }
    }
  })) {
    if (msg.type === "result") {
      if (msg.subtype === "success" && !msg.is_error && msg.structured_output) {
        // Use the validated output
        console.log(msg.structured_output);
      } else if (msg.subtype === "error_max_structured_output_retries") {
        console.error("Could not produce valid output");
      } else {
        console.error("Run ended with an error or without a structured output");
      }
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result. If the
  // failure was an error result, the error subtype branches above have
  // already run; connection or process failures yield no result message.
  console.log(`Session ended with an error: ${error}`);
}
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage

contact_schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "email": {"type": "string"},
    },
    "required": ["name"],
}


async def main():
    try:
        async for message in query(
            prompt="Extract contact info from the document",
            options=ClaudeAgentOptions(
                output_format={"type": "json_schema", "schema": contact_schema}
            ),
        ):
            if isinstance(message, ResultMessage):
                if (
                    message.subtype == "success"
                    and not message.is_error
                    and message.structured_output
                ):
                    # Use the validated output
                    print(message.structured_output)
                elif message.subtype == "error_max_structured_output_retries":
                    print("Could not produce valid output")
                else:
                    print("Run ended with an error or without a structured output")
    except Exception as error:
        # A single-shot query() raises after yielding an error result. If the
        # failure was an error result, the error subtype branches above have
        # already run; connection or process failures yield no result message.
        print(f"Session ended with an error: {error}")


asyncio.run(main())

Consejos para evitar errores:

  • Mantenga esquemas enfocados. Los esquemas profundamente anidados con muchos campos requeridos son más difíciles de satisfacer. Comience simple y agregue complejidad según sea necesario.
  • Haga coincidir el esquema con la tarea. Si la tarea podría no tener toda la información que su esquema requiere, haga esos campos opcionales.
  • Use solicitudes claras. Las solicitudes ambiguas hacen que sea más difícil para el agente saber qué salida producir.

Recursos relacionados

  • Documentación de JSON Schema: aprenda la sintaxis de JSON Schema para definir esquemas complejos con objetos anidados, matrices, enumeraciones y restricciones de validación
  • API Structured Outputs: use salidas estructuradas con la API de Claude directamente para solicitudes de un solo turno sin uso de herramientas
  • Custom tools: proporcione a su agente herramientas personalizadas para llamar durante la ejecución antes de devolver salida estructurada