
从开发者最常见的「一个 Agent 什么都干、什么都不精」困惑切入,系统拆解 Handoffs 机制:Handoff 与普通工具调用的本质区别、handoff() 工厂函数核心参数(含 input_type / input_filter / is_enabled 三大机制代码示例)、单向流转 vs Triage 分发两种架构对比,以及 4 个高频踩坑点(防护栏覆盖范围、状态传递误用、nest_handoff_history 测试版警告、Streaming 追踪技巧)。结尾附 3 条可立即执行的实践建议,预告 #5 Memory 篇。

handoffs 参数:from agents import Agent
billing_agent = Agent(name="Billing Agent", instructions="...")
refund_agent = Agent(name="Refund Agent", instructions="...")
triage_agent = Agent(
name="Triage Agent",
instructions="根据用户问题,转给对应的专业 Agent。",
handoffs=[billing_agent, refund_agent],
)handoff() 工厂函数:from agents import handoff, Agent
def on_escalation(ctx, input_data):
print(f"升级原因:{input_data.reason}")
escalation_agent = Agent(name="Escalation Agent", instructions="...")
from pydantic import BaseModel
class EscalationData(BaseModel):
reason: str
custom_handoff = handoff(
agent=escalation_agent,
on_handoff=on_escalation,
input_type=EscalationData,
tool_description_override="当用户需要升级投诉时调用",
)handoff() 支持的核心参数2:| 参数 | 类型 | 作用 |
|---|---|---|
agent | Agent | 目标 Agent(必填) |
tool_name_override | str | 覆盖自动生成的工具名 |
tool_description_override | str | 覆盖工具描述,影响 LLM 路由决策 |
on_handoff | Callable | Handoff 发生时的回调,可接收 input_type 数据 |
input_type | Pydantic BaseModel | 定义 LLM 在调用 handoff 时需要提供的结构化数据 |
input_filter | Callable | 控制传给下一个 Agent 的上下文内容 |
is_enabled | bool | Callable | 运行时动态启用/禁用此 handoff |
class EscalationData(BaseModel):
reason: str
def on_escalation(ctx, input_data: EscalationData):
# input_data.reason 是 LLM 生成的转移原因
logger.info(f"Handoff reason: {input_data.reason}")input_type 是 LLM 在做路由决策时生成的元数据,用途是「告诉目标 Agent 发生了什么」。它不是传递应用状态的通道——应用状态应该通过 RunContextWrapper.context 传递,两者职责分开。from agents.extensions.handoff_filters import remove_all_tools
clean_handoff = handoff(
agent=billing_agent,
input_filter=remove_all_tools, # 移除历史中的所有工具调用记录
)input_history:完整的历史对话pre_handoff_items:handoff 前产生的所有条目new_items:本轮新产生的条目input_items:真正传给下一个 Agent 的内容(这里改,上面的不动)input_items 相当于「过滤模型输入,但 session history 还是完整保留的」。is_enabled 支持传一个 Callable:2def only_for_premium(ctx, agent):
return ctx.context.user_tier == "premium"
premium_handoff = handoff(
agent=premium_support_agent,
is_enabled=only_for_premium,
)# Triage Agent 持有所有可能的 handoff 目标
triage_agent = Agent(
name="Triage Agent",
instructions=(
RECOMMENDED_PROMPT_PREFIX # 帮助 LLM 更好理解 handoff 机制
+ "\n根据用户问题,转给合适的专业 Agent。"
),
handoffs=[billing_agent, refund_agent, tech_support_agent],
)agents.extensions.handoff_prompt.RECOMMENDED_PROMPT_PREFIX——SDK 官方提供的 prompt 片段,内嵌后能让 LLM 更准确地理解「什么时候该触发 handoff」。1on_handoff 或 input_filter 里加检查。input_type 是给 LLM 填的元数据,不是状态容器。用户 ID、数据库连接、会话配置这类东西请走 RunContext——Runner 的 context 参数在整个 run 内共享,所有 Agent 都能访问。nest_handoff_history 还在测试阶段。思路很好——把前一个 Agent 的完整对话折叠成一条摘要 message 传给下一个 Agent,解决「历史越来越长、token 越来越贵」的问题。4 测试版,需要显式开启:from agents import RunConfig
result = await Runner.run(
triage_agent,
input="...",
run_config=RunConfig(nest_handoff_history=True),
)Runner.run_streamed() 时,handoff_requested 和 handoff_occured 这两个事件能让你实时看到 Agent 切换时机5。顺带一提:occured 官方 API 里故意拼错了,向后兼容历史遗留,不是你看错了。handoff_filters.py 的模块文档字符串问题。如果用的是旧版,建议升级。RECOMMENDED_PROMPT_PREFIX。这是官方积累的最佳实践,能让 LLM 更稳定地理解何时触发 handoff。一行代码,效果明显:from agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX
triage_agent = Agent(
instructions=RECOMMENDED_PROMPT_PREFIX + "\n你的具体指令...",
...
)input_filter 主动控制历史传递。上下文不会自动帮你缩减。在合适的节点用 remove_all_tools 清理工具调用历史,或者自定义 filter 只保留「用户原始问题 + 关键摘要」。省 token 是一方面,更重要的是:干净的上下文让目标 Agent 不容易跑偏。openai.github.io
openai.github.io
openai.github.io
openai.github.io
openai.github.io
github.com
blog.csdn.net
This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.