Obtenir une sortie structurée des agents
Retourner du JSON validé à partir de workflows d'agents en utilisant JSON Schema, Zod ou Pydantic. Obtenir des données structurées et type-safe après une utilisation multi-tour d'outils.
Les sorties structurées vous permettent de définir la forme exacte des données que vous souhaitez récupérer d'un agent. L'agent peut utiliser tous les outils dont il a besoin pour accomplir la tâche, et vous obtenez toujours du JSON validé correspondant à votre schéma à la fin. Définissez un JSON Schema pour la structure dont vous avez besoin, et le SDK valide la sortie par rapport à celui-ci, en relançant des invites en cas de non-correspondance. Si la validation n'aboutit pas dans la limite de tentatives, le résultat est une erreur au lieu de données structurées ; voir Gestion des erreurs.
Pour une sécurité de type complète, utilisez Zod (TypeScript) ou Pydantic (Python) pour définir votre schéma et obtenir des objets fortement typés en retour.
Pourquoi des sorties structurées ?
Par défaut, les agents renvoient du texte libre, ce qui convient au chat, mais pas lorsque vous devez exploiter la sortie de manière programmatique. Les sorties structurées vous fournissent des données typées que vous pouvez transmettre directement à la logique de votre application, à votre base de données ou à vos composants d'interface utilisateur.
Prenons l'exemple d'une application de recettes dans laquelle un agent effectue des recherches sur le web et renvoie des recettes. Sans sorties structurées, vous obtenez du texte libre que vous devez analyser vous-même. Avec les sorties structurées, vous définissez la forme souhaitée et obtenez des données typées que vous pouvez utiliser directement dans votre application.
[accordion] Sans sorties structurées
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 ...Pour utiliser ce résultat dans votre application, vous devriez extraire le titre, convertir « 15 minutes » en nombre, séparer les ingrédients des instructions et gérer les incohérences de mise en forme d'une réponse à l'autre.
[accordion] Avec sorties structurées
{ "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" /* ... */] }Des données typées que vous pouvez utiliser directement dans votre interface utilisateur.
Démarrage rapide
Pour utiliser les sorties structurées, définissez un JSON Schema décrivant la forme des données que vous souhaitez, puis passez-le à query() via l'option outputFormat (TypeScript) ou output_format (Python). Quand l'agent termine, le message de résultat inclut un champ structured_output avec des données validées correspondant à votre schéma.
Avant d'exécuter les exemples de cette page, installez le Claude Agent SDK en suivant le démarrage rapide. L'exemple ci-dessous demande à l'agent de rechercher Anthropic et de retourner le nom de l'entreprise, l'année de fondation et le siège social en tant que sortie structurée.
import { query } from "@anthropic-ai/claude-agent-sdk";
// Définir la forme des données que vous souhaitez récupérer
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
}
}
})) {
// Le message de résultat contient structured_output avec des données validées
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) {
// Une requête query() unique lève une exception après avoir produit un résultat d'erreur, tel que
// error_max_structured_output_retries ; voir la section Gestion des erreurs.
console.error(`Session ended with an error: ${error}`);
}
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
# Définir la forme des données que vous souhaitez récupérer
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}
),
):
# Le message de résultat contient structured_output avec des données validées
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:
# Une requête query() unique lève une exception après avoir produit un résultat d'erreur, tel que
# error_max_structured_output_retries ; voir la section Gestion des erreurs.
print(f"Session ended with an error: {error}")
asyncio.run(main())
Schémas type-safe avec Zod et Pydantic
Au lieu d'écrire JSON Schema à la main, vous pouvez utiliser Zod (TypeScript) ou Pydantic (Python) pour définir votre schéma. Ces bibliothèques génèrent le JSON Schema pour vous et vous permettent d'analyser la réponse en un objet entièrement typé que vous pouvez utiliser dans tout votre codebase avec l'autocomplétion et la vérification de type.
L'exemple ci-dessous définit un schéma pour un plan d'implémentation de fonctionnalité avec un résumé, une liste d'étapes (chacune avec un niveau de complexité) et les risques potentiels. L'agent planifie la fonctionnalité et retourne un objet FeaturePlan typé. Vous pouvez ensuite accéder à des propriétés comme plan.summary et itérer sur plan.steps avec une sécurité de type complète.
Le SDK valide les schémas avec JSON Schema draft-07, donc les schémas qui déclarent une version plus récente sont rejetés. Zod cible draft 2020-12 par défaut, donc passez target: "draft-7" lors de la conversion de votre schéma.
import { z } from "zod";
import { query } from "@anthropic-ai/claude-agent-sdk";
// Définir le schéma avec 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>;
// Convertir en JSON Schema en utilisant la cible draft-07 que le SDK attend
const schema = z.toJSONSchema(FeaturePlan, { target: "draft-7" });
// Utiliser dans la requête
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) {
// Valider et obtenir un résultat entièrement typé
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) {
// Une requête query() unique lève une exception après avoir produit un résultat d'erreur, tel que
// error_max_structured_output_retries ; voir la section Gestion des erreurs.
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
):
# Valider et obtenir un résultat entièrement typé
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:
# Une requête query() unique lève une exception après avoir produit un résultat d'erreur, tel que
# error_max_structured_output_retries ; voir la section Gestion des erreurs.
print(f"Session ended with an error: {error}")
asyncio.run(main())
Configuration du format de sortie
L'option outputFormat (TypeScript) ou output_format (Python) accepte un objet avec :
type: Défini sur"json_schema"pour les sorties structuréesschema: Un objet JSON Schema définissant votre structure de sortie. Vous pouvez générer ceci à partir d'un schéma Zod avecz.toJSONSchema(schema, { target: "draft-7" })ou d'un modèle Pydantic avec.model_json_schema()
Le SDK supporte les fonctionnalités JSON Schema standard incluant tous les types de base (object, array, string, number, boolean, null), enum, const, required, les objets imbriqués et les définitions $ref. Pour la liste complète des fonctionnalités supportées et des limitations, voir Limitations JSON Schema.
Un schéma qui n'est pas un JSON Schema valide échoue l'exécution au démarrage avec une erreur nommant le problème. Avant la v2.1.205, un schéma invalide était silencieusement ignoré et l'agent retournait du texte non structuré.
Le mot-clé format, tel que "format": "email", est accepté comme annotation et n'est pas appliqué par le validateur du SDK. Avant la v2.1.205, tout schéma contenant format était traité comme invalide.
Exemple : agent de suivi des TODO
Cet exemple démontre comment les sorties structurées fonctionnent avec l'utilisation multi-étapes d'outils. L'agent doit trouver des commentaires TODO dans la base de code, puis rechercher les informations de git blame pour chacun. Il décide de manière autonome quels outils utiliser (Grep pour rechercher, Bash pour exécuter les commandes git) et combine les résultats en une seule réponse structurée.
Le schéma inclut des champs optionnels (author et date) puisque les informations de git blame pourraient ne pas être disponibles pour tous les fichiers. L'agent remplit ce qu'il peut trouver et omet le reste.
import { query } from "@anthropic-ai/claude-agent-sdk";
// Définir la structure pour l'extraction 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"]
};
// L'agent utilise Grep pour trouver les TODOs, Bash pour obtenir les informations 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) {
// Une requête query() unique lève une exception après avoir produit un résultat d'erreur, tel que
// error_max_structured_output_retries ; voir la section Gestion des erreurs.
console.error(`Session ended with an error: ${error}`);
}
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
# Définir la structure pour l'extraction 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():
# L'agent utilise Grep pour trouver les TODOs, Bash pour obtenir les informations 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:
# Une requête query() unique lève une exception après avoir produit un résultat d'erreur, tel que
# error_max_structured_output_retries ; voir la section Gestion des erreurs.
print(f"Session ended with an error: {error}")
asyncio.run(main())
Reconnaître l'appel d'outil StructuredOutput
Lorsque vous définissez outputFormat en TypeScript ou output_format en Python, votre flux de messages contient un appel d'outil nommé StructuredOutput que vous n'avez jamais défini. Claude Code ajoute cet outil à la session, et l'agent l'appelle pour renvoyer sa réponse. Le message d'initialisation système le liste dans le tableau tools, et chaque appel arrive sous forme de deux blocs :
- Un bloc
tool_usenomméStructuredOutput, dont l'entrée est l'objet produit par l'agent pour votre schéma - Un bloc
tool_resultindiquantStructured output provided successfully
Lorsque cet objet ne correspond pas à votre schéma, le tool_result contient à la place is_error: true et l'agent appelle à nouveau StructuredOutput, de sorte que votre flux peut contenir plusieurs appels pour une seule requête. Lisez les données validées depuis structured_output sur le message de résultat plutôt que depuis un bloc tool_use, et consultez Gestion des erreurs pour connaître le résultat obtenu lorsqu'aucune tentative n'aboutit.
Si votre application affiche ou journalise les appels d'outils, ignorez ceux nommés StructuredOutput afin que ce mécanisme n'apparaisse pas comme une étape choisie par l'agent. Un bloc tool_result ne contient aucun nom d'outil, seulement le tool_use_id de l'appel auquel il répond : comparez-le donc à l'id de chaque appel que vous avez ignoré.
Gestion des erreurs
La génération de sortie structurée peut échouer quand l'agent ne peut pas produire du JSON valide correspondant à votre schéma. Cela se produit généralement quand le schéma est trop complexe pour la tâche, la tâche elle-même est ambiguë, ou l'agent atteint sa limite de tentatives en essayant de corriger les erreurs de validation. Cela peut aussi se produire sans aucune erreur de validation : un basculement vers un modèle de secours peut rétracter une sortie déjà complétée en cours de flux, et si aucune nouvelle tentative ne la remplace, l'exécution se termine avec la même erreur. Vérifiez la liste errors du message de résultat d'erreur pour distinguer les deux causes avant de déboguer votre schéma.
Vérifiez le subtype du message de résultat :
| Subtype | Signification |
|---|---|
success |
L'exécution s'est terminée sans subtype d'erreur |
error_max_structured_output_retries |
Aucune sortie valide ne subsistait après plusieurs tentatives (erreurs de validation, ou une rétraction due au basculement vers un modèle de secours sans nouvelle tentative réussie) |
Un subtype success ne signifie pas à lui seul que l'exécution a produit une réponse. Traitez un résultat comme un échec quand le champ is_error du résultat vaut true ou que structured_output est absent :
is_errorvauttruesur le résultat : par exemple, la dernière requête au modèle s'est terminée par une erreur d'API ou un refus. N'utilisez pas la valeurstructured_outputd'un tel résultat.structured_outputest absent : pour en trouver la cause, consultez structured_output is None but the result says success
L'exemple ci-dessous applique ces vérifications :
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) {
// Utiliser la sortie validée
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) {
// Une requête query() unique lève une exception après avoir produit un résultat d'erreur. Si
// l'échec était un résultat d'erreur, les branches de subtype d'erreur ci-dessus ont
// déjà été exécutées ; les défaillances de connexion ou de processus ne produisent aucun message de résultat.
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
):
# Utiliser la sortie validée
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:
# Une requête query() unique lève une exception après avoir produit un résultat d'erreur. Si
# l'échec était un résultat d'erreur, les branches de subtype d'erreur ci-dessus ont
# déjà été exécutées ; les défaillances de connexion ou de processus ne produisent aucun message de résultat.
print(f"Session ended with an error: {error}")
asyncio.run(main())
Conseils pour éviter les erreurs :
- Gardez les schémas ciblés. Les schémas profondément imbriqués avec de nombreux champs requis sont plus difficiles à satisfaire. Commencez simple et ajoutez de la complexité au besoin.
- Faites correspondre le schéma à la tâche. Si la tâche pourrait ne pas avoir toutes les informations que votre schéma nécessite, rendez ces champs optionnels.
- Utilisez des prompts clairs. Les prompts ambigus rendent plus difficile pour l'agent de savoir quelle sortie produire.
Ressources connexes
- Documentation JSON Schema : apprenez la syntaxe JSON Schema pour définir des schémas complexes avec des objets imbriqués, des tableaux, des énumérations et des contraintes de validation
- Sorties structurées de l'API : utilisez les sorties structurées avec l'API Claude directement pour les requêtes à un seul tour sans utilisation d'outils
- Outils personnalisés : donnez à votre agent des outils personnalisés à appeler pendant l'exécution avant de retourner une sortie structurée