Structures JSON de l’API Agents OpenAI : comment Agent, Tool Call, Tool Result et Handoff se lient (2026)
Après la bêta, le dur n’est pas « peut-il appeler un outil ». C’est comment quatre objets tiennent sur une chaîne. Il n’existe pas d’item de type handoff. Les épingles sont des ID : session_id, turn_id, call_id, agent_id. Ce texte ne déplie que cette chaîne JSON.
Avant de lire
| Objet | Forme officielle | L’épingle |
|---|---|---|
| Agent | Modèle, table d’outils, multi_agent | Écrit à la création de session |
| Tool Call | function_call dans required_actions | turn_id + call_id |
| Tool Result | agent.session.input.tool_result | Le même couple renvoyé |
| Handoff | items create / send / wait / interrupt | agent_id / subagent_id |
Un function_call dans l’historique ne veut pas dire qu’un résultat est encore dû. Le travail en attente est dans required_actions.
Quatre objets se joignent sur des ID, pas sur un paragraphe
Dans l’API Agents, Agent est la config écrite à la création de session. Tool Call est un function_call dans required_actions quand la session s’arrête. Tool Result est agent.session.input.tool_result renvoyé sur le flux. « Handoff » ici est un ensemble d’items de coordination, pas l’objet handoff du SDK.
- Le même couple turn_id / call_id : appel et résultat doivent coller. Sinon le harness n’enchaîne pas.
- L’historique ne suffit pas : un function_call dans items dit seulement qu’il a eu lieu. Encore en attente ? Lire
required_actions. - Handoff n’est pas un type : regarder
create_subagent_calletsubagent_id. Ne pas chercher un champ nommé handoff.
Comment les outils sont appelés : Tour de l’API Agents OpenAI. Comment un long travail continue : API Agents longue durée. Qui doit posséder la boucle : Agents API vs Responses API. Si Agent et Function Calling ne font encore qu’un mot, commencer par Guide complet de l’agent IA.
Agent : un objet de config, pas une réponse
La création écrit modèle, consignes, outils et multi_agent une fois. Cet objet est le sujet de tous les items suivants. Officiel : POST /v1/agents/sessions avec OpenAI-Beta: agents=v1 :
{
"agent": {
"model": "gpt-6-astra",
"instructions": "Look up orders. Confirm before any refund.",
"tools": [
{
"type": "function",
"name": "lookup_order",
"parameters": {
"type": "object",
"properties": {
"order_id": { "type": "string", "pattern": "^[A-Z]-\\d{4}$" }
},
"required": ["order_id"],
"additionalProperties": false
}
}
],
"multi_agent": { "enabled": true, "max_concurrent_subagents": 3 }
},
"environment": { "type": "openai_hosted" },
"input": [{ "role": "user", "content": [{ "type": "input_text", "text": "Look up A-1042." }] }]
}
La config Agent reste avec la session. Changer modèle ou raisonnement met à jour cette session ; un modèle enregistré ne s’applique qu’aux nouvelles sessions. L’environnement est une autre ressource : openai_hosted ou votre sandbox. Supprimer la session ne vide pas le sandbox.
Tool Call : faire confiance à required_actions
Quand un résultat de fonction est requis, la session émet agent.session.requires_action. Les appels en attente sont dans required_actions comme function_call avec turn_id / call_id :
{
"type": "function_call",
"turn_id": "turn_2",
"call_id": "call_3c91",
"name": "lookup_order",
"arguments": { "order_id": "A-1042" }
}
Les arguments doivent passer le Schema. Un object large n’est pas une porte. Voir Pourquoi l’IA a besoin de JSON Schema. L’historique des items peut aussi montrer function_call ; docs officielles : l’historique seul ne prouve pas qu’un résultat est encore dû.
Tool Result : le même couple, écrit sur le flux
Après l’exécution, envoyer agent.session.input.tool_result à l’endpoint events. Copier turn_id et call_id depuis l’action en attente. Succès : success: true et un output chaîne. Échec : success: false et error :
{
"type": "agent.session.input.tool_result",
"turn_id": "turn_2",
"call_id": "call_3c91",
"success": true,
"output": "{\"order_id\":\"A-1042\",\"status\":\"paid\"}"
}
Sérialiser les objets en chaînes. Si la fonction a déjà tourné avant la coupure, renvoyer le résultat sauvé avec le même couple. Pas de nouvelle session comme « retry ». Le harness n’enchaîne le turn qu’une fois les résultats requis arrivés.
Handoff : items de coordination, pas un nom de champ
Avec multi_agent.enabled, le harness fournit create, message, wait et interrupt. Vous ne déclarez pas ces outils. La coordination apparaît en items :
{
"data": [
{
"type": "create_subagent_call",
"agent_id": "agent_main",
"task": "Compare A-1042 packing notes with the invoice."
},
{
"type": "send_subagent_input_call",
"subagent_id": "sub_7a12",
"input": "Focus on line items only."
},
{
"type": "agent_message",
"from": "sub_7a12",
"text": "Invoice lines match packing notes for A-1042."
}
]
}
- create_subagent_call : envoyer.
agent_idest qui a demandé. Terminé ne veut pas dire que la sous-tâche est finie. - send / wait / interrupt : parler, attendre, arrêter.
agent_messagepeut porter un texte inter-agents ; le flux n’est pas une transcription complète. - L’objet handoff du SDK : il vit dans votre runner de processus. Ce n’est pas la même ressource que ces items.
Ce que vous passez au sujet suivant doit être du JSON auditable : numéro de commande, intention, preuve. Comment le saisir en Schema : JSON Schema et flux d’agents.
La chaîne tient seulement si les épingles s’alignent
Plier les quatre objets en une chaîne reconstructible en local. Commande fictive — pas de secrets réels :
{
"session_id": "sess_8f21",
"links": [
{ "object": "agent", "id": "agent_main" },
{ "object": "turn", "id": "turn_2" },
{ "object": "function_call", "call_id": "call_3c91" },
{ "object": "tool_result", "call_id": "call_3c91" },
{ "object": "subagent", "id": "sub_7a12", "via": "create_subagent_call" }
]
}
| Objet | Type vu | Épinglé à | Décalage habituel |
|---|---|---|---|
| Agent | session.agent | session_id | Une nouvelle session comme « retry » |
| Tool Call | function_call | turn_id + call_id | Ne lire que l’historique d’items |
| Tool Result | input.tool_result | Le même couple | output encore un objet, pas une chaîne |
| Handoff | create_subagent_call | agent_id / subagent_id | Chercher un champ nommé handoff |
| Turn | turn.completed / failed | turn_id ; subagent_id racine est null | Lire idle comme un succès |
Une ligne MCP est une autre table d’outils. Elle n’invente pas un second schéma d’ID. Frontière : MCP Tool Schema vs Function Calling.
Relire la chaîne en étalant deux traces JSON
La plupart des décalages sont des épingles qui dérivent, pas un modèle plus bête.
- formater required_actions à côté de l’événement de retour. Le
call_iddoit coller exactement. - Avec extraire en JSONPath, extraire
turn_id,call_id,subagent_id. - JSON Diff l’appel et le résultat de la même commande. S’ils ont dérivé, regarder les ID d’abord.
- Lister séparément les items de l’agent principal et ceux d’un subagent. Mélangés, la coordination s’accroche au mauvais acteur.
- Ne pas coller commandes, sessions ou MCP intranet dans un formateur public. JSONSort tourne dans le navigateur, en local.
Si les deux traces se reconstruisent sur les ID, vous lisez une structure. Sinon des slogans.
Pièges courants
- Prendre un function_call dans items pour encore dû. L’attente est required_actions.
- Renvoyer un autre couple d’ID. Le harness ne peut pas apparier ; le turn cale.
- Envoyer output comme objet brut. Les docs veulent une chaîne ou un tableau de contenu pris en charge.
- Chercher type: handoff dans le payload. L’API Agents utilise create / send / wait / interrupt.
- Envoyer votre fonction à un subagent. Les subagents n’ont pas d’outils function. Les consultations restent sur l’agent principal.
- Traiter handoff SDK et items de session comme une ressource. En supprimer une ne vide pas l’autre.
FAQ
Chaque Tool Call doit-il avoir un Tool Result ?
Chaque appel en attente doit être réécrit. Un function_call historique déjà fini n’a pas besoin d’un autre résultat. Le test est required_actions, pas la longueur de la liste d’items.
Y a-t-il un champ JSON nommé handoff dans l’API Agents ?
Pas comme type d’item de premier rang. Coordination : create_subagent_call, send_subagent_input_call, wait_for_subagents_call, interrupt_subagent_call. Le handoff du SDK est un autre objet.
Quelle différence entre call_id et item_id ?
call_id pique un appel de fonction à son résultat. item_id nomme l’item sauvé. Le flux attache les deltas de texte au même morceau via item_id. Deux épingles — ne pas les mêler.
Comment un turn peut-il être completed si un outil a échoué ?
Docs officielles : completed ne veut pas dire que chaque outil a réussi. Lire aussi success sur le tool_result et les items de ce turn.
Puis-je inventer mon propre JSON de handoff dans l’app ?
Oui, et vous le devez. C’est votre objet métier : numéro de commande, intention, preuve. N’attendez pas que les items officiels retiennent les montants. Écrivez-le en Schema et stockez-le à part des items de session.
Conclusion
Agent, Tool Call, Tool Result et Handoff ne sont pas quatre paragraphes. Ce sont quatre objets sur une chaîne JSON, piqués par des ID. Le retour officiel est un événement ; le transfert officiel est un item de coordination.
Écrivez si vous pouvez apparier appel et résultat avec seul call_id. Si oui, la structure tient. Les modèles changent ; les épingles doivent d’abord tenir.
Pour aller plus loin
- API Agents OpenAI, tour complet Comment Session, tool calling et Schema se lient
- API Agents longue durée Session, compact, subagents
- Agents API vs Responses API Qui doit faire tourner la boucle
- Pourquoi l’IA a besoin de JSON Schema Schema / Function Calling / Structured Output
- JSON Schema et flux d’agents Saisir outils et état en Schema
- Boîte à outils locale JSONSort Formater, valider, Diff
- Tous les articles Plus de notes JSON et IA
Journal : Première publication