OpenAI Agents API 的 JSON 資料結構詳解:Agent、Tool Call、Tool Result、Handoff 如何透過 JSON 連接起來?
公測後最容易糊的不是「會不會調工具」,而是這四個物件怎麼釘在同一條鏈上。官方沒有一個叫 handoff 的 item。連接靠 ID:session_id、turn_id、call_id、agent_id。本文只拆這條 JSON 鏈。
閱讀前速覽
| 物件 | 官方形狀 | 釘子 |
|---|---|---|
| Agent | 模型、工具表、是否開 multi_agent | session 建立時寫入 |
| Tool Call | required_actions 裡的 function_call | turn_id + call_id |
| Tool Result | agent.session.input.tool_result | 同一對 ID 回寫 |
| Handoff | create / send / wait / interrupt items | agent_id / subagent_id |
歷史裡出現 function_call,不等於還在等結果。等不等,看 required_actions。
四個物件靠 ID 相連,不靠一段自然語言
Agents API 裡,Agent 是建立 session 時寫下的設定。Tool Call 是 session 停住時,required_actions 裡的 function_call。Tool Result 是你打回事件流的 agent.session.input.tool_result。所謂 Handoff,在這套 API 裡是一組協調 item,不是 SDK 裡那個 handoff 物件。
- 同一對 turn_id / call_id:呼叫和結果必須對得上。對不上,harness 不會接著轉。
- 歷史不夠:items 裡有 function_call,只說明曾經調過。還在不在等,讀
required_actions。 - Handoff 不是一個 type:看
create_subagent_call和subagent_id,不要找名為 handoff 的欄位。
工具怎麼調,見 OpenAI Agents API 完整解析。長任務怎麼續,見 長時間執行的 Agents API。循環該不該託管,見 Agents API vs Responses API。若 Agent 和 Function Calling 還糊在一起,先讀 AI Agent 完整指南。
Agent:設定物件,不是一次回覆
建立 session 時一次寫進模型、指令、工具和 multi_agent。這是後面所有 item 的主語。官方口是 POST /v1/agents/sessions,header 帶 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." }] }]
}
Agent 設定跟著 session 走。改模型、推理力度,要更新這份 session 的設定;已儲存的 Agent 範本只作用於新 session。環境是另一份資源:openai_hosted 或自建沙箱,刪 session 不會自動清 sandbox。
Tool Call:只信 required_actions
需要函數結果時,session 發 agent.session.requires_action。待處理的呼叫在 required_actions 裡,形狀是帶 turn_id / call_id 的 function_call:
{
"type": "function_call",
"turn_id": "turn_2",
"call_id": "call_3c91",
"name": "lookup_order",
"arguments": { "order_id": "A-1042" }
}
參數必須過 Schema。寬 object 等於沒有門。口徑見 AI 為什麼需要 JSON Schema。items 歷史裡同樣可能出現 function_call;官方寫明:單憑歷史不能斷定還在等結果。
Tool Result:同一對 ID,寫回事件流
執行函數後,向 session 的 events 端點發送 agent.session.input.tool_result。turn_id 和 call_id 從待處理動作原樣抄過來。成功寫 success: true 和字串 output;失敗寫 success: false 和 error:
{
"type": "agent.session.input.tool_result",
"turn_id": "turn_2",
"call_id": "call_3c91",
"success": true,
"output": "{\"order_id\":\"A-1042\",\"status\":\"paid\"}"
}
物件要先序列化成字串。斷線後若已經跑過函數,用同一對 ID 再交一次儲存的結果。不要新開 session「重試」。harness 收齊所需結果才繼續這一 turn。
Handoff:協調 item,不是一個欄位名
打開 multi_agent.enabled 後,harness 自帶建立、傳話、等待、打斷。你不用宣告這些工具。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:派出去。item 上的
agent_id是誰請求的。完成不等於子任務結束。 - send / wait / interrupt:傳話、等待、打斷。另有
agent_message承載可見的對內文本,流不會給你完整對話稿。 - SDK 的 handoff 物件:那是你行程裡的 runner。和這些 item 不是同一份資源。
真正交給下一個主語的,應該是可審計的 JSON:訂單號、意圖、依據。怎麼收成 Schema,見 JSON Schema 與 AI Agent 工作流。
釘子對齊,整條鏈才成立
把四個物件壓成你本地能重建的鏈。虛構訂單,不要貼真實金鑰:
{
"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" }
]
}
| 物件 | 你看見的 type | 連到誰 | 常見錯位 |
|---|---|---|---|
| Agent | session.agent | session_id | 另開 session 當重試 |
| Tool Call | function_call | turn_id + call_id | 只看 items 歷史 |
| Tool Result | input.tool_result | 同一對 ID | output 仍是物件不是字串 |
| Handoff | create_subagent_call | agent_id / subagent_id | 去找名為 handoff 的欄位 |
| Turn | turn.completed / failed | turn_id;根 Agent 的 subagent_id 為空 | 空閒當成成功 |
MCP 那一行只是另一張工具表,不另造一套 ID。邊界見 MCP Tool Schema vs Function Calling。
複盤這條鏈,先攤開兩段 JSON
對不上,多半是釘子漂了,不是模型變笨。
- 把 required_actions 和回寫事件 格式化,確認
call_id一字不差。 - 用 JSONPath 提取 抽出
turn_id、call_id、subagent_id。 - 同一筆訂單的呼叫和結果 JSON Diff。漂了,先看 ID,再怪模型。
- 主 Agent 的 items 和某個 subagent 的 items 分開列。混在一頁,協調會對錯人。
- 訂單、session、內網 MCP 不要貼進公共格式化站。JSONSort 在瀏覽器本地跑。
兩段軌跡都能按 ID 重建,你才是在讀結構,不是在讀口號。
常見誤區
- 把 items 裡的 function_call 當成還在等。等不等,看 required_actions。
- 回寫時換了一對 ID。harness 對不上,這一 turn 就停在那。
- output 直接丟物件。官方要字串或受支援的 content 陣列。
- 去 payload 裡找 type: handoff。Agents API 用的是 create / send / wait / interrupt。
- 讓 subagent 跑你的 function。子代理不支援 function 工具。查單留在主 Agent。
- 把 SDK handoff 和 session items 當成同一份。刪一份不會自動清另一份。
FAQ
Tool Call 和 Tool Result 必須成對出現嗎?
待處理的呼叫必須回寫。已經結束的歷史 function_call 不必再交結果。判斷標準是 required_actions,不是 items 列表長度。
Agents API 有沒有名為 handoff 的 JSON 欄位?
沒有作為一等 item type。協調看 create_subagent_call、send_subagent_input_call、wait_for_subagents_call、interrupt_subagent_call。SDK 的 handoff 是另一套物件。
call_id 和 item_id 有什麼差別?
call_id 把一次函數呼叫和它的結果釘在一起。item_id 標識儲存下來的那條 item。事件流用 item_id 把增量貼回同一段內容。兩套釘子,不要混用。
為什麼 turn 完成了,工具卻失敗了?
官方寫明:completed 不等於每個工具都成功。還要看 tool_result 的 success,以及該 turn 的 items。
能不能自己發明一個 handoff JSON 在應用裡傳?
能,而且該傳。那是你的業務物件:訂單號、意圖、依據。不要指望官方 item 替你記住金額。把它寫成 Schema,和 session items 分開存。
結論
Agent、Tool Call、Tool Result、Handoff 不是四段散文。它們是同一條 JSON 鏈上的四個物件,釘子是 ID。官方把回寫寫成事件,把交接寫成協調 item。
先寫清你能不能只憑 call_id 把呼叫和結果對上。能,結構才算連起來。模型會換;釘子應該先穩定。
延伸阅读
- OpenAI Agents API 完整解析 Session、Tool Calling、Schema 怎麼接
- 长时间运行的 Agents API Session、compact、Subagents
- Agents API vs Responses API 循環該由誰跑
- AI 为什么需要 JSON Schema Schema / Function Calling / Structured Output
- JSON Schema 与 Agent 工作流 把工具和狀態收成 Schema
- JSONSort 本地工具箱 格式化、校驗、Diff
- 全部文章 更多 JSON 與 AI 開發筆記
更新紀錄: 首次發布