跳转到内容

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 HandoffInputData
from 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 将历史摘要化:

  1. input_history + pre_handoff_items + new_items 展平
  2. 通过 HandoffHistoryMapper 生成单条 assistant 消息
  3. 格式:<CONVERSATION HISTORY>1. user: ... 2. assistant: ... 3. function_call: ...</CONVERSATION HISTORY>
  4. 支持多级嵌套——递归提取已摘要的 <CONVERSATION HISTORY> 标记

默认行为function_callfunction_call_outputreasoning 类型的 item 被摘要化,其余原样转发。

Handoffs vs Agents as Tools

维度HandoffAgent.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 检查。

参考来源