人机协作
本指南介绍 SDK 中基于审批的人机协作流程。当工具调用需要审批时,SDK 会暂停运行并返回 interruptions,您可以稍后从同一个 RunState 恢复运行。
该审批机制适用于整个运行,而不仅限于当前的顶层智能体。无论工具属于当前智能体、通过交接转入的智能体,还是嵌套的 agent.asTool() 执行,都适用相同的模式。对于嵌套的 agent.asTool(),中断仍会在外层运行中呈现,因此您需要在外层的 result.state 上批准或拒绝该中断,然后恢复原始的根运行。
使用 agent.asTool() 时,审批可能发生在两个不同层级:智能体工具本身可以通过 asTool({ needsApproval }) 要求审批;嵌套智能体开始运行后,其中的工具也可以提出各自的审批请求。这两种情况都通过同一个外层运行中断流程处理。
本页重点介绍通过 interruptions 完成的手动审批流程。如果您的应用可以通过代码作出决定,某些工具类型还支持程序化审批回调,使运行无需暂停即可继续。如果您正在设置 agent.asTool() 本身,请参阅工具;本页介绍该运行层级结构中的任意工具需要审批后会发生什么。
您可以将 needsApproval 选项设置为 true,或设置为返回布尔值的异步函数,以定义需要审批的工具。
import { tool } from '@openai/agents';import z from 'zod';
const sensitiveTool = tool({ name: 'cancelOrder', description: 'Cancel order', parameters: z.object({ orderId: z.number(), }), // always requires approval needsApproval: true, execute: async ({ orderId }, args) => { // prepare order return },});
const sendEmail = tool({ name: 'sendEmail', description: 'Send an email', parameters: z.object({ to: z.string(), subject: z.string(), body: z.string(), }), needsApproval: async (_context, { subject }) => { // check if the email is spam return subject.includes('spam'); }, execute: async ({ to, subject, body }, args) => { // send email },});- 即将执行工具调用时,SDK 会评估其审批规则(
needsApproval或托管 MCP 中的对应设置)。 - 如果需要审批且尚未存储相关决定,工具调用不会执行。运行会改为记录一个
RunToolApprovalItem。 - 在该轮结束时,运行会暂停,并在执行结果的
interruptions数组中返回所有待审批项。这包括嵌套agent.asTool()运行中提出的审批请求。 - 使用
result.state.approve(interruption)或result.state.reject(interruption)处理每个待审批项。如果希望同一工具在该运行的剩余时间内始终获得批准或拒绝,请传入{ alwaysApprove: true }或{ alwaysReject: true }。拒绝时,您还可以传入{ message: '...' },以控制针对该特定工具调用返回给模型的拒绝文本。 - 将更新后的
result.state传回runner.run(agent, state)以恢复运行,其中agent是该运行最初的顶层智能体。SDK 会从中断处继续执行,包括嵌套的智能体工具执行。
默认情况下,函数工具的输入护栏仅在审批后、工具即将执行前运行。如果您希望在显示待审批项之前,使用相同的输入护栏验证本地函数工具调用,请向 run() 或 Runner 传入 toolExecution: { preApprovalInputGuardrails: true }。如果审批前护栏拒绝调用,SDK 会将护栏消息作为工具输出返回给模型,而不会创建审批中断。如果护栏允许调用,运行仍会暂停以等待审批;审批后,输入护栏还会再次运行,以防工具调用在等待期间变得不安全。
当 needsApproval 是函数时,SDK 仅会在工具参数已解析为可检查对象后调用它。格式错误的 JSON 和非对象值会按保守方式处理:SDK 会请求审批,但不会调用该回调或执行工具。即使批准该调用,工具仍不会执行;调用会继续进入常规的参数解析错误处理流程。实时函数工具遵循相同规则,并会针对无效调用发出 tool_approval_requested。
使用 { alwaysApprove: true } 或 { alwaysReject: true } 创建的持久决定会存储在运行状态中,因此稍后通过 toString() / fromString() 恢复同一个已暂停运行时,这些决定仍会保留。
在正式发布(GA)模型上,计算机工具中断可以表示单个 computer_call 中的一批操作。SDK 会在执行前按操作评估 needsApproval,因此一个待审批项可以涵盖移动和点击等操作序列。如果您通过检查 interruption.rawItem 来呈现 UI,请同时处理 GA 版本的 actions 数组和旧版的单个 action 字段。
序列化的 RunState 还会同时保留当前 computer 工具名称和旧版 computer_use_preview 名称对应的计算机操作审批,因此在从预览版迁移到 GA 版本期间,已暂停的运行可以顺利恢复。
如果您未提供 message,SDK 会依次回退到已配置的 toolErrorFormatter(如果有),然后使用默认拒绝文本。
您不必在同一次处理中解决所有待审批项。如果仅批准或拒绝部分项目后重新运行,已处理的调用可以继续,而未处理的项目会保留在 interruptions 中,并再次暂停运行。
自动审批决策
Section titled “自动审批决策”手动处理 interruptions 是最通用的模式,但并非唯一方式:
- 本地
shellTool()和applyPatchTool()可以使用onApproval,直接在代码中批准或拒绝。 - 托管 MCP 工具可以结合使用
requireApproval和onApproval,以相同方式通过程序作出决定。 - 普通函数工具使用本页介绍的手动中断流程。
当这些回调返回决定时,运行会继续,而无需暂停等待人工响应。对于实时会话 API,请参阅构建实时智能体中的审批流程。
流式传输与会话
Section titled “流式传输与会话”相同的中断流程也适用于流式运行。流式运行暂停后,请等待 stream.completed,读取 stream.interruptions 并处理这些项目;如果希望恢复后的输出继续采用流式传输,请使用 { stream: true } 再次调用 run()。有关此模式的流式版本,请参阅流式传输中的人机协作。
如果您还在使用 session,从 RunState 恢复时,请继续传入同一个 session。恢复后的轮次随后会追加到会话记忆中,而无需重新准备输入。有关会话生命周期的详细信息,请参阅会话。
下面是一个更完整的人机协作流程示例。它会在终端中请求审批,并将状态临时存储在文件中。
import { z } from 'zod';import readline from 'node:readline/promises';import fs from 'node:fs/promises';import { Agent, run, tool, RunState, RunResult } from '@openai/agents';
const getWeatherTool = tool({ name: 'get_weather', description: 'Get the weather for a given city', parameters: z.object({ location: z.string(), }), needsApproval: async (_context, { location }) => { // forces approval to look up the weather in San Francisco return location === 'San Francisco'; }, execute: async ({ location }) => { return `The weather in ${location} is sunny`; },});
const dataAgentTwo = new Agent({ name: 'Data agent', instructions: 'You are a data agent', handoffDescription: 'You know everything about the weather', tools: [getWeatherTool],});
const agent = new Agent({ name: 'Basic test agent', instructions: 'You are a basic agent', handoffs: [dataAgentTwo],});
async function confirm(question: string) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, });
const answer = await rl.question(`${question} (y/n): `); const normalizedAnswer = answer.toLowerCase(); rl.close(); return normalizedAnswer === 'y' || normalizedAnswer === 'yes';}
async function main() { let result: RunResult<unknown, Agent<unknown, any>> = await run( agent, 'What is the weather in Oakland and San Francisco?', ); let hasInterruptions = result.interruptions?.length > 0; while (hasInterruptions) { // Store the current run state await fs.writeFile( 'result.json', JSON.stringify(result.state, null, 2), 'utf-8', );
// At this point, another process could review the saved state
// Read the saved state later const storedState = await fs.readFile('result.json', 'utf-8'); const state = await RunState.fromString(agent, storedState);
for (const interruption of result.interruptions) { const confirmed = await confirm( `Agent ${interruption.agent.name} would like to use the tool ${interruption.name} with "${interruption.arguments}". Do you approve?`, );
if (confirmed) { state.approve(interruption); } else { state.reject(interruption); } }
// Resume execution from the restored state result = await run(agent, state); hasInterruptions = result.interruptions?.length > 0; }
console.log(result.finalOutput);}
main().catch((error) => { console.dir(error, { depth: null });});有关可实际运行的端到端版本,请参阅完整示例脚本。
长时间审批的处理
Section titled “长时间审批的处理”人机协作流程经过专门设计,可长时间中断,而无需让服务器持续运行。如果您需要结束请求并稍后继续,可以序列化状态,并在之后恢复。
您可以使用 result.state.toString()(或 JSON.stringify(result.state))序列化状态,之后再将序列化状态传入 RunState.fromString(agent, serializedState) 以恢复运行,其中 agent 是触发整个运行的智能体实例。
序列化 RunState 时,SDK 会记录交接和 Agent.asTool() 图中稳定的智能体标识。这样,只要恢复运行的进程重新构建相同的智能体图,即使不同的智能体具有相同的 name,已暂停的运行也可以恢复。
传入 RunState.fromString(agent, serializedState) 的 agent 是重新构建图的根节点。反序列化期间,SDK 会遍历该智能体的交接和 Agent.asTool() 引用,然后针对重新构建的图解析状态中的每个序列化智能体引用。其中包括当前智能体,以及生成项、已处理的模型响应和排队的后续步骤所持有的嵌套引用。
如果您需要使用替换后的图恢复运行,例如智能体的模型或工具已由其他运行时封装,请先使用原始图反序列化状态,再次将其序列化,然后使用替换后的根智能体反序列化该字符串。调用 state.setCurrentAgent(agent) 只会更改当前活动的智能体,不会重写反序列化期间已经解析的嵌套引用。
如果恢复运行的进程需要注入新的上下文对象,请改用 RunState.fromStringWithContext(agent, serializedState, context, { contextStrategy })。
contextStrategy: 'merge'(默认)会保留所提供的RunContext,合并序列化的审批状态,并在新上下文尚未定义toolInput时恢复序列化的toolInput。contextStrategy: 'replace'会原样使用所提供的RunContext重新构建运行。
序列化的运行状态包含应用上下文以及由 SDK 管理的运行时元数据,例如审批、用量、嵌套的 toolInput 和待恢复的嵌套智能体工具。如果您计划存储或传输序列化状态,请将 runContext.context 视为持久化数据;除非您明确希望密钥随状态一起传输,否则请避免在其中放置密钥。
默认情况下,追踪 API 密钥不会包含在序列化状态中,以免意外持久化密钥。仅当您确实需要随状态一起转移追踪凭据时,才应传入 result.state.toString({ includeTracingApiKey: true })。
这样,您就可以将序列化状态存储在数据库中,或与请求一起存储。
待处理任务的版本管理
Section titled “待处理任务的版本管理”如果审批请求需要较长时间处理,并且您计划以有实质意义的方式对智能体定义进行版本管理,或升级 Agents SDK版本,我们目前建议您使用软件包别名并行安装两个 Agents SDK版本,以实现自己的分支逻辑。
在实践中,这意味着为您自己的代码分配版本号,将其与序列化状态一起存储,并引导反序列化过程使用正确的代码版本。