SpyBara
Go Premium

agent-sdk/structured-outputs.md 2026-11-10 21:57 UTC to 2026-11-11 22:59 UTC

No changes for this document in this history range.

2026
Tue 10 21:57

Ottenere output strutturati dagli agenti

Restituire JSON convalidato dai flussi di lavoro degli agenti utilizzando JSON Schema, Zod o Pydantic. Ottenere dati strutturati e type-safe dopo l'uso di strumenti multi-turno.

Gli output strutturati consentono di definire la forma esatta dei dati che si desidera ottenere da un agente. L'agente può utilizzare qualsiasi strumento necessario per completare l'attività e si ottiene comunque JSON convalidato corrispondente allo schema alla fine. Definire uno JSON Schema per la struttura necessaria e l'SDK convalida l'output rispetto ad esso, ripetendo la richiesta in caso di mancata corrispondenza. Se la convalida non ha successo entro il limite di tentativi, il risultato è un errore anziché dati strutturati; vedere Gestione degli errori.

Per la sicurezza dei tipi completa, utilizza Zod (TypeScript) o Pydantic (Python) per definire il tuo schema e ottenere oggetti fortemente tipizzati.

Perché gli output strutturati?

Per impostazione predefinita, gli agenti restituiscono testo in formato libero, il che funziona per la chat ma non quando devi usare l'output a livello di programmazione. Gli output strutturati ti forniscono dati tipizzati che puoi passare direttamente alla logica della tua applicazione, al database o ai componenti dell'interfaccia utente.

Considera un'app di ricette in cui un agente cerca sul web e restituisce ricette. Senza output strutturati, ottieni testo in formato libero che dovresti analizzare da solo. Con gli output strutturati, definisci la forma che desideri e ottieni dati tipizzati che puoi usare direttamente nella tua app.

[accordion] Senza output strutturati

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
...

Per usarlo nella tua app, dovresti estrarre il titolo, convertire "15 minutes" in un numero, separare gli ingredienti dalle istruzioni e gestire la formattazione incoerente tra le risposte.

[accordion] Con output strutturati

{
  "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" /* ... */]
}

Dati tipizzati che puoi usare direttamente nella tua interfaccia utente.

Guida rapida

Per usare gli output strutturati, definisci uno JSON Schema che descriva la forma dei dati che desideri, quindi passalo a query() tramite l'opzione outputFormat (TypeScript) o l'opzione output_format (Python). Quando l'agente termina, il messaggio di risultato include un campo structured_output con dati validati conformi al tuo schema.

Prima di eseguire gli esempi di questa pagina, installa il Claude Agent SDK seguendo la guida rapida. L'esempio seguente chiede all'agente di fare ricerche su Anthropic e di restituire il nome dell'azienda, l'anno di fondazione e la sede come output strutturato.

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

// Definisci la forma dei dati che vuoi ricevere
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
      }
    }
  })) {
    // Il messaggio di risultato contiene structured_output con dati validati
    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) {
  // Una query() a singolo colpo genera un'eccezione dopo aver prodotto un risultato di errore, come
  // error_max_structured_output_retries; consulta la sezione Gestione degli errori.
  console.error(`Session ended with an error: ${error}`);
}
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage

# Definisci la forma dei dati che vuoi ricevere
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}
            ),
        ):
            # Il messaggio di risultato contiene structured_output con dati validati
            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:
        # Una query() a singolo colpo solleva un'eccezione dopo aver prodotto un risultato di errore, come
        # error_max_structured_output_retries; consulta la sezione Gestione degli errori.
        print(f"Session ended with an error: {error}")


asyncio.run(main())

Schemi type-safe con Zod e Pydantic

Invece di scrivere JSON Schema a mano, puoi usare Zod (TypeScript) o Pydantic (Python) per definire il tuo schema. Queste librerie generano il JSON Schema per te e ti permettono di analizzare la risposta in un oggetto completamente tipizzato che puoi usare in tutto il tuo codebase con autocompletamento e controllo dei tipi.

L'esempio seguente definisce uno schema per un piano di implementazione di una funzionalità con un riepilogo, un elenco di passaggi (ciascuno con un livello di complessità) e i potenziali rischi. L'agente pianifica la funzionalità e restituisce un oggetto FeaturePlan tipizzato. Puoi quindi accedere a proprietà come plan.summary e iterare su plan.steps con piena sicurezza dei tipi.

L'SDK convalida gli schemi con JSON Schema draft-07, quindi gli schemi che dichiarano una versione più recente vengono rifiutati. Zod usa come target predefinito la draft 2020-12, quindi passa target: "draft-7" quando converti il tuo schema.

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

// Definisci lo schema con 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>;

// Converti in JSON Schema usando il target draft-07 previsto dall'SDK
const schema = z.toJSONSchema(FeaturePlan, { target: "draft-7" });

// Usa 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) {
      // Valida e ottieni il risultato completamente tipizzato
      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) {
  // Una query() single-shot genera un'eccezione dopo aver restituito un risultato di errore, come
  // error_max_structured_output_retries; consulta la sezione Gestione degli errori.
  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
            ):
                # Valida e ottieni il risultato completamente tipizzato
                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:
        # Una query() single-shot solleva un'eccezione dopo aver restituito un risultato di errore, come
        # error_max_structured_output_retries; consulta la sezione Gestione degli errori.
        print(f"Session ended with an error: {error}")


asyncio.run(main())

Configurazione del formato di output

L'opzione outputFormat (TypeScript) o output_format (Python) accetta un oggetto con:

  • type: Impostare su "json_schema" per gli output strutturati
  • schema: Un oggetto JSON Schema che definisce la struttura di output. È possibile generarlo da uno schema Zod con z.toJSONSchema(schema, { target: "draft-7" }) o da un modello Pydantic con .model_json_schema()

L'SDK supporta le funzionalità standard di JSON Schema inclusi tutti i tipi di base (object, array, string, number, boolean, null), enum, const, required, oggetti annidati e definizioni $ref. Per l'elenco completo delle funzionalità supportate e delle limitazioni, vedere Limitazioni di JSON Schema.

Uno schema che non è un JSON Schema valido non riesce all'avvio con un errore che nomina il problema. Prima della v2.1.205, uno schema non valido veniva silenziosamente ignorato e l'agente restituiva testo non strutturato.

La parola chiave format, come "format": "email", viene accettata come annotazione e non viene applicata dal validatore dell'SDK. Prima della v2.1.205, qualsiasi schema contenente format veniva trattato come non valido.

Esempio: agente per il tracciamento dei TODO

Questo esempio mostra come funzionano gli output strutturati con l'uso di strumenti in più passaggi. L'agente deve trovare i commenti TODO nel codebase, quindi cercare le informazioni di git blame per ciascuno di essi. Decide autonomamente quali strumenti usare (Grep per cercare, Bash per eseguire i comandi git) e combina i risultati in un'unica risposta strutturata.

Lo schema include campi opzionali (author e date) perché le informazioni di git blame potrebbero non essere disponibili per tutti i file. L'agente compila ciò che riesce a trovare e omette il resto.

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

// Definisci la struttura per l'estrazione dei 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"]
};

// L'agente usa Grep per trovare i TODO e Bash per ottenere le informazioni di 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) {
  // Una query() a singola esecuzione genera un'eccezione dopo aver prodotto un risultato di errore, come
  // error_max_structured_output_retries; consulta la sezione sulla gestione degli errori.
  console.error(`Session ended with an error: ${error}`);
}
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage

# Definisci la struttura per l'estrazione dei 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():
    # L'agente usa Grep per trovare i TODO e Bash per ottenere le informazioni di 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:
        # Una query() a singola esecuzione solleva un'eccezione dopo aver prodotto un risultato di errore, come
        # error_max_structured_output_retries; consulta la sezione sulla gestione degli errori.
        print(f"Session ended with an error: {error}")


asyncio.run(main())

Riconoscere la chiamata allo strumento StructuredOutput

Quando imposti outputFormat in TypeScript o output_format in Python, il tuo flusso di messaggi contiene una chiamata a uno strumento denominato StructuredOutput che non hai mai definito. Claude Code aggiunge questo strumento alla sessione e l'agente lo chiama per restituire la sua risposta. Il messaggio di sistema init lo elenca nell'array tools e ogni chiamata arriva sotto forma di due blocchi:

  • Un blocco tool_use denominato StructuredOutput, il cui input è l'oggetto prodotto dall'agente per il tuo schema
  • Un blocco tool_result con il testo Structured output provided successfully

Quando quell'oggetto non corrisponde al tuo schema, il tool_result riporta invece is_error: true e l'agente chiama di nuovo StructuredOutput, quindi il tuo flusso può contenere diverse chiamate per una singola query. Leggi i dati convalidati da structured_output nel messaggio di risultato anziché da un blocco tool_use e consulta Gestione degli errori per il risultato che ottieni quando nessun tentativo va a buon fine.

Se la tua applicazione mostra o registra nei log le chiamate agli strumenti, salta quelle denominate StructuredOutput in modo che il meccanismo non appaia come un passaggio scelto dall'agente. Un blocco tool_result non riporta alcun nome di strumento, ma solo il tool_use_id della chiamata a cui risponde, quindi confrontalo con l'id di ogni chiamata che hai saltato.

Gestione degli errori

La generazione di output strutturati può non riuscire quando l'agente non può produrre JSON valido corrispondente al tuo schema. Questo accade in genere quando lo schema è troppo complesso per l'attività, l'attività stessa è ambigua o l'agente raggiunge il limite di tentativi cercando di correggere gli errori di convalida. Può anche accadere senza alcun errore di convalida: un fallback del modello può ritirare un output già completato a metà flusso, e se nessun nuovo tentativo lo sostituisce, l'esecuzione termina con lo stesso errore. Controlla l'elenco errors sul messaggio di risultato per distinguere le due cause prima di eseguire il debug del tuo schema.

Controlla il subtype del messaggio di risultato:

Subtype Significato
success L'esecuzione è terminata senza un subtype di errore
error_max_structured_output_retries Nessun output valido è rimasto dopo più tentativi (errori di convalida, o un ritiro dovuto a fallback del modello senza un nuovo tentativo riuscito)

Un subtype success non significa di per sé che l'esecuzione abbia prodotto una risposta. Tratta un risultato come un errore quando is_error del risultato è true oppure structured_output è assente:

  • is_error sul risultato è true: ad esempio, la richiesta finale al modello è terminata con un errore API o un rifiuto. Non usare un valore structured_output su un risultato di questo tipo.
  • structured_output è assente: per trovare la causa, consulta structured_output è None ma il risultato dice success

L'esempio seguente applica questi controlli:

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())

Suggerimenti per evitare errori:

  • Mantenere gli schema focalizzati. Gli schema profondamente annidati con molti campi obbligatori sono più difficili da soddisfare. Iniziare in modo semplice e aggiungere complessità secondo le necessità.
  • Abbinare lo schema all'attività. Se l'attività potrebbe non avere tutte le informazioni richieste dallo schema, rendere questi campi facoltativi.
  • Utilizzare prompt chiari. I prompt ambigui rendono più difficile per l'agente sapere quale output produrre.

Risorse correlate

  • Documentazione di JSON Schema: imparare la sintassi di JSON Schema per definire schema complessi con oggetti annidati, array, enum e vincoli di convalida
  • API Structured Outputs: utilizzare gli output strutturati direttamente con l'API Claude per richieste single-turn senza uso di strumenti
  • Custom tools: fornire all'agente strumenti personalizzati da chiamare durante l'esecuzione prima di restituire l'output strutturato