AI 开发

OpenAI Agents API 的 JSON 数据结构详解:Agent、Tool Call、Tool Result、Handoff 如何通过 JSON 连接起来?

阅读约 16 分钟

公测后最容易糊的不是「会不会调工具」,而是这四个对象怎么钉在同一条链上。官方没有一个叫 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 把调用和结果对上。能,结构才算连起来。模型会换;钉子应该先稳定。

延伸阅读

更新记录: 首次发布