Handoffs 与多 Agent 协作
AI-03: Handoffs 与多 Agent 协作
学习目标
- 理解 Handoff 作为工具调用的本质
- 掌握
HandoffInputFilter转换对话历史的方法 - 理解
nest_handoff_history嵌套压缩机制 - 区分 “Handoffs” 与 “Agents as Tools” 两种协作模式
前置知识
本章涉及多 Agent 协作的通用原理,建议先阅读:
下文直接聚焦 OpenAI Agents SDK 的具体实现。
项目实践
Handoff 的本质:工具调用
Handoff 在 SDK 中被建模为一种特殊的工具:
handoff() 工厂函数从目标 Agent 自动生成:
tool_name=transfer_to_{agent.name}(函数风格命名)tool_description= “Handoff to the {agent.name} agent…”(模型选择依据)on_invoke_handoff:调用时返回目标 Agent 实例
from agents import Agent, handoff, Runner
researcher = Agent(name="Researcher", instructions="你负责研究。")writer = Agent(name="Writer", instructions="你负责写作。", handoffs=[handoff(researcher)])
result = await Runner.run(writer, "请写一篇关于量子计算的文章")# Writer 模型决定需要研究 → 调用 handoff → Researcher 接管HandoffInputFilter:控制新 Agent 看到什么
HandoffInputFilter 接收完整的对话状态 HandoffInputData,返回修改后的版本。这是控制信息传递的关键点:
| 字段 | 含义 |
|---|---|
input_history | 传给 Runner.run() 的原始输入 |
pre_handoff_items | 当前 Agent turn 之前生成的 items |
new_items | 当前 turn 生成的 items(含 handoff 触发项) |
SDK 提供两个内置 filter 工具:
from agents.handoffs import HandoffInputDatafrom agents.extensions.handoff_filters import remove_all_tools
def my_filter(data: HandoffInputData) -> HandoffInputData: # 1. 移除所有工具调用记录 data = remove_all_tools(data) # 2. 截取历史(跳过前两条消息) history = tuple(data.input_history[2:]) if isinstance(data.input_history, tuple) else data.input_history return HandoffInputData( input_history=history, pre_handoff_items=tuple(data.pre_handoff_items), new_items=tuple(data.new_items), )
spanish_handoff = handoff(spanish_agent, input_filter=my_filter)关键区分:
new_items→ 用于 session history(完整审计轨迹)input_items→ 用于下一个 Agent 的模型输入(可被 filter 裁剪)
nest_handoff_history:嵌套历史压缩
当多轮 handoff 链过长时,上下文窗口会爆炸。nest_handoff_history 将历史摘要化:
- 将
input_history + pre_handoff_items + new_items展平 - 通过
HandoffHistoryMapper生成单条 assistant 消息 - 格式:
<CONVERSATION HISTORY>1. user: ... 2. assistant: ... 3. function_call: ...</CONVERSATION HISTORY> - 支持多级嵌套——递归提取已摘要的
<CONVERSATION HISTORY>标记
默认行为:function_call、function_call_output、reasoning 类型的 item 被摘要化,其余原样转发。
Handoffs vs Agents as Tools
| 维度 | Handoff | Agent.as_tool() |
|---|---|---|
| 控制权 | 永久转移给新 Agent | 工具调用,调用方拿回结果继续 |
| 历史传递 | 新 Agent 接收完整对话历史 | 新 Agent 仅接收工具参数 |
| 返回值 | 无(控制权不再返回) | 有(FunctionTool 结果) |
| 适用场景 | 领域路由、分诊 | 子任务、查找操作、封装推理 |
问题与规避
| 问题 | 规避方案 |
|---|---|
| Handoff input filter 修改后的输入绕过新 Agent guardrails | 在 filter 函数中内嵌验证,或将安全策略放在起始 Agent |
| Server-managed conversation 与 handoff filter 冲突 | 使用 conversation_id 时 handoff filter 会抛出 UserError,需禁用 filter |
| 多个 handoff 同时触发 | 仅第一个执行,其余被忽略——在 Agent instructions 中明确 handoff 优先级 |
设计取舍
为什么 handoff 是工具而不是独立协议
将 handoff 建模为工具调用有几个好处:(1) 复用现有的 tool calling 基础设施——模型天然知道何时 “调用工具”;(2) handoff 的触发条件可以被 tool_use_behavior 控制;(3) 模型可以在同一次响应中同时输出 handoff 和工具调用,SDK 统一规划执行。
代价:handoff 的语义被嵌入在 tool call 中,需要解析 ProcessedResponse.handoffs 才能区分。
为什么 handoff 后不运行新 Agent 的 input guardrails
避免多 Agent 链路中的重复验证。但这意味着如果 handoff filter 修改了输入,新 Agent 的输入可能未经其自身 guardrails 检查。