AI 开发

OpenAI Agents API 如何处理长时间运行的 AI Agent?Context、Tools、Subagents 与 JSON 状态管理完整教程

阅读约 16 分钟

9 月 10 日公测的卖点不是「再开一个 chat」。官方把 Codex 上跑了几天的 harness 交出来:Session 续着,上下文会被 compact,subagent 自带一份上下文。本文只写长任务怎么活:你存什么 JSON,OpenAI 替你压什么,断线后看哪段 items。

阅读前速览

对象 干什么 你该存什么
Session 可续的实例,跨小时、跨窗口 session_id
Turn 一次输入启动的异步工作 turn_id / 完成事件
Items 已保存的消息和工具调用 断线后回放用
Compact 接近窗口上限时自动压缩 关键字段另存
Subagent 独立上下文,并行干活 并发上限

空闲的 session 不等于这一轮成功。看 turn 事件和 items,不要看最后一句自然语言。

长任务活在 Session 里,不活在更长的 prompt 里

Agents API 处理长时间运行的办法很具体:你建一个 durable session,后续输入都打到同一个 session_id。接近上下文上限时,harness 自动 compact 早先的内容。独立子任务可以派给 subagent。你不用自己拼完整聊天记录。

  • 续跑:把 session_id 跟业务会话绑在一起。同一条会话,不要新建第二个 session。
  • 断线:事件流不重放。把 session 和 items 拉回来,不要假设客户端还记得上一跳。
  • 钱和单号:compact 会丢掉细节。订单号、金额、审批结果写在你自己的 JSON 里。

Session 和工具 JSON 怎么接,见 OpenAI Agents API 完整解析。循环该不该交给托管,见 Agents API vs Responses API。若 Agent 和 Function Calling 还糊在一起,先读 AI Agent 完整指南。

Session 续着,Turn 异步跑

创建时一次声明模型、工具、环境和是否开 multi_agent。官方创建口是 POST /v1/agents/sessions,header 带 OpenAI-Beta: agents=v1。托管沙箱写 openai_hosted:

{
  "agent": {
    "model": "gpt-6-astra",
    "instructions": "Look up orders. Confirm before any refund. Delegate independent lookups to subagents.",
    "tools": [
      {
        "type": "function",
        "name": "lookup_order",
        "parameters": {
          "type": "object",
          "properties": {
            "order_id": { "type": "string", "pattern": "^[A-Z]-\\d{4}$" }
          },
          "required": ["order_id"],
          "additionalProperties": false
        }
      },
      { "type": "web_search" }
    ],
    "multi_agent": { "enabled": true, "max_concurrent_subagents": 3 }
  },
  "environment": { "type": "openai_hosted", "workspace_directory": "/workspace" },
  "input": [
    {
      "role": "user",
      "content": [{ "type": "input_text", "text": "Look up A-1042 and keep the case open." }]
    }
  ]
}

同一条 session 上再发 agent.session.input.message。空闲就开新 turn;还在跑就转向当前 turn。取消用 agent.session.input.cancel,session 和已保存工作还在:

{
  "events": [
    {
      "type": "agent.session.input.message",
      "input": [
        {
          "role": "user",
          "content": [
            { "type": "input_text", "text": "Warehouse marked A-1042 packed. Continue." }
          ]
        }
      ]
    }
  ]
}

看结局,不要看「现在闲不闲」。官方事件是 agent.session.turn.completed、failed、cancelled。turn 完成也不等于每个工具都成功。需要函数结果时,去读 required_actions。

工具链跨小时,回写还是 JSON

长任务里,模型仍然只填参数。轮到你的 function,harness 停住,你按 turn_id / call_id 交结果。四小时后回写,形状不变:

{
  "type": "tool_result",
  "turn_id": "turn_4",
  "call_id": "call_3c91",
  "success": true,
  "output": "{\"order_id\":\"A-1042\",\"status\":\"packed\"}"
}

function 要你执行;MCP 和 web_search 可以由 harness 自己走。subagent 继承 MCP 和网页搜索,不继承 function 工具。需要查自家订单的副作用,留在主 Agent。

门还是 Schema。宽 object 在长任务里更危险:compact 之后模型更容易填错单号。MCP 那一行只是另一张工具表,边界见 MCP Tool Schema vs Function Calling。

Compact 压的是窗口,不是你的账本

官方写明:接近上下文上限时,Agents API 自动 compact 早先内容,让工作跨多个窗口。你不必自己实现压缩。这和 Responses 上的 context_management / /responses/compact 不是同一层。

  • Agents API:session 自己压。你拿回 items,核对单号还在不在。
  • Responses:要自己开 compact,或调用 compact 端点。加密的 compaction item 当下一窗的输入,不要手剪。
  • 两边都成立:压缩丢细节。金额、确认、放行仍由宿主写。

把 order_id 只活在对话里,等于把账本交给摘要。Schema 怎么当门,见 AI 为什么需要 JSON Schema。

Subagent:并行,但上下文分开

打开 multi_agent.enabled 后,harness 自带创建、传话、等待、打断。你不用自己声明这些工具。items 里会出现协调动作:

{
  "data": [
    {
      "type": "function_call",
      "turn_id": "turn_4",
      "call_id": "call_3c91",
      "name": "lookup_order",
      "arguments": "{\"order_id\":\"A-1042\"}"
    },
    {
      "type": "create_subagent_call",
      "agent_id": "agent_main",
      "task": "Compare A-1042 packing notes with the invoice."
    }
  ]
}
  • 独立上下文:每个 subagent 自己一份。主 Agent 用 create / message / wait / interrupt 收口。
  • 并发要封顶:默认最多 6 个(不含协调者)。生产里写死更小的数。
  • 完成项 ≠ 子任务结束:create_subagent_call 完成,只说明派出去了。看 agent.session.subagent.created 和该 subagent 自己的 items。

handoff 该是可审计的 JSON,不是一句「请交给账单组」。怎么收成 Schema,见 JSON Schema 与 AI Agent 工作流。

你持有的状态,不要指望 compact 替你记

OpenAI 替你存 session 配置、对话和已保存工作。你至少还要一份业务状态,断线也能重建:

{
  "session_id": "sess_8f21",
  "order_id": "A-1042",
  "last_turn_id": "turn_4",
  "last_call_id": "call_3c91",
  "awaiting": "tool_result",
  "compact_checkpoint": "after_turn_3"
}

这份对象跟 Session、SDK session、Responses conversation、sandbox 都不是同一个资源。删一份不会自动清另一份。数据驻留目前只在美国;自建沙箱也不等于 Zero Data Retention。

五步把跨窗口任务跑完

不要从演示视频倒推。按对象走:

  1. 创建一次 session,把 session_id 写进你的会话。工具表和 Schema 一次收紧。
  2. 订事件再发后续输入。先订流,再打 agent.session.input.message,否则早期事件会丢。
  3. 停在 required_actions 就回写。用同一对 turn_id / call_id,不要另开 session 重试。
  4. 断线拉 items。流不重放。按 order=asc 列出,对上单号和等待中的调用。
  5. compact 之后再核一次账本。摘要里没有 A-1042,下一跳就会填错。

五步都落在对象上,长任务才是可恢复的。少一步,你就是在赌窗口还没满。

复盘长任务,先摊开两段 JSON

出问题的多半不是模型变笨,是对象对不上。

  • 把 session items 和你本地状态 格式化,确认 session_id / call_id 对得上。
  • 用 JSONPath 提取 抽出 order_id、turn_id、subagent 的 agent_id。
  • compact 前后的 items JSON Diff。单号漂了,先看压缩,再怪模型。
  • 主 Agent 的 items 和某个 subagent 的 items 分开列。混在一页里,协调动作会对错人。
  • 订单、session、内网 MCP 不要贴进公共格式化站。JSONSort 在浏览器本地跑。

两段轨迹都能重建,你才知道任务是续上的,不是重新猜的。

常见误区

  • 以为空闲就是成功。看 turn.completed / failed / cancelled,再看工具结果。
  • 断线后等流重放。官方写明流不重放。拉 session 和 items。
  • 把单号只活在对话里。compact 之后,摘要可能丢掉 A-1042。
  • 开 multi_agent 却不设并发上限。默认 6 个也会扇出 token 和副作用。
  • 让 subagent 去跑你的 function。子代理不支持 function 工具。查单留在主 Agent。
  • 把 Session 和 Conversation 当成同一份。删一份不会自动清另一份。

FAQ

长时间运行是不是把 prompt 写得更长?

不是。靠的是 durable session、自动 compact、以及你自己存的业务 JSON。更长的系统提示解决不了窗口上限。

Agents API 的 compact 和 Responses 的 compact 能混用吗?

不要混。Agents session 自己压上下文。Responses 用 context_management 或 /responses/compact。两套资源,两套下一窗输入。

断线后怎么恢复?

用存下来的 session_id 取回 session 和 items。流不会重放错过的事件。先对齐 last_turn_id 和未完成的 call_id,再决定回写还是续消息。

subagent 能不能调用我的 lookup_order?

不能。官方写明 subagent 不支持 function 工具。它们继承 MCP、网页搜索和环境里的文件 / 命令。查自家订单,留在主 Agent。

自建沙箱是不是就能做 Zero Data Retention?

不能。官方写明 Agents API 目前只支持美国数据驻留,也不支持 ZDR。选 self-hosted 沙箱不会让它变成 ZDR。

结论

长时间运行不是更聪明的模型。它是一份可续的 Session、一次自动 compact、以及你自己握住的 JSON 状态。工具参数、turn 事件、subagent items,都只是这条链上的对象。

先写清你断线后还能不能重建 A-1042。能,任务才算活着。模型会换;session_id 和 Schema 应该先稳定。

延伸阅读

更新记录: 首次发布