Agent as Tool 与 Handoff
调用后返回,还是移交控制权。
一句话理解:Agent as Tool 是“请同事帮忙查一下,结果还给我,我继续负责”;Handoff 是“这个客户转给你了,后面你来跟”。两者的根本区别是:控制权和用户对话最后归谁。
在多 Agent 系统里,“一个 Agent 找另一个 Agent”只有两种本质不同的方式。一种像函数调用:调用方把一个明确的子任务交出去,拿回结果,继续自己的工作。另一种像转接电话:当前 Agent 把整个对话和执行权交出去,自己退场。
选错方式的代价很具体。本该 Handoff 的场景用了 Agent as Tool,用户会感觉永远在和一个“传话筒”对话,专家 Agent 的回答被转述得走了样;本该 Agent as Tool 的场景用了 Handoff,就没人负责把几份结果综合成一个答案,出了错也不知道该找谁。
两种机制对比
Agent as Tool:调用后返回。
控制权的流向如图 1:
Handoff:移交控制权。
控制权的流向如图 2:
| 维度 | Agent as Tool | Handoff |
|---|---|---|
| 控制权 | 始终在调用方 | 转移给接手方 |
| 谁面对用户 | 调用方 | 接手方 |
| 对方看到什么 | 调用方传入的参数(一段任务描述或结构化输入) | 默认通常是之前的对话历史,可以过滤 |
| 结束后 | 结果返回给调用方继续处理 | 调用方不再参与 |
| 最终答案负责人 | 调用方 | 接手方 |
| 类比 | 请同事协助查资料 | 把客户转给另一个部门 |
| 适合 | 有界的子任务,需要综合多个专家结果 | 专家需要直接与用户多轮沟通、切换指令与权限 |
在主流 SDK 中长什么样
OpenAI Agents SDK 同时提供了两种机制,文档对它们的区分是:Agent as Tool 让一个 Manager Agent 保持控制,通过 Agent.as_tool() 调用专家 Agent,适合由一个 Agent 负责最终答案、综合多个专家的输出;Handoff 让分诊 Agent 把对话路由给专家,专家成为当前活跃的 Agent 并直接回复。
from agents import Agent, handoff
from agents.extensions import handoff_filters
history_agent = Agent(
name="History analyst",
instructions="分析用户的历史工单,只返回同类问题与判断依据。",
)
refund_agent = Agent(
name="Refund agent",
instructions="按退款政策处理用户的退款请求。",
)
# Agent as Tool:主 Agent 保持控制,历史工单分析结果返回给它
order_agent = Agent(
name="Order support agent",
instructions="诊断订单售后问题并回复用户。",
tools=[
history_agent.as_tool(
tool_name="summarize_history",
tool_description="分析指定订单相关的历史工单并返回同类问题",
)
],
)
# Handoff:分诊 Agent 可以把对话整个交给订单 Agent 或退款 Agent
triage_agent = Agent(
name="Triage agent",
instructions="判断用户意图,交给合适的专家。",
handoffs=[
order_agent,
handoff(refund_agent, input_filter=handoff_filters.remove_all_tools),
],
)
几个值得注意的细节(以 SDK 当前文档为准):
- 对模型来说,Handoff 也表现为一个工具,名字默认形如
transfer_to_refund_agent。 - 发生 Handoff 时,接手的 Agent 默认能看到之前的完整对话历史;可以用
input_filter过滤,例如remove_all_tools会去掉历史中所有与工具调用相关的条目。 handoff()还支持on_handoff回调(交接时触发,例如预先拉取数据)和input_type(让模型在交接时填写结构化参数,例如交接原因)。- SDK 的追踪会为 Handoff 单独记录 Span,方便排查“为什么被转走”。
Claude Agent SDK 的 Subagent 属于 Agent as Tool 这一类:主 Agent 通过 Agent 工具调用子 Agent,子 Agent 在新的上下文中运行(除非显式使用 fork),父 Agent 传给它的只有调用时的那段提示词,子 Agent 结束后只把最终消息作为工具结果返回。下一课「Subagent 与上下文隔离」会详细展开。
客服场景:两种方式一起用
用户在客服窗口输入:“订单 A1024 怎么还没处理好?再不处理我就要退款了。”
图 3 画出了这次对话中 Agent 之间的关系:
为什么这样分:
- 历史工单分析用 Agent as Tool:它是一个有界的子任务,结论要由订单 Agent 综合判断;历史工单原文很长,放在独立上下文里能保护订单 Agent 的上下文。
- 退款用 Handoff:退款需要和用户多轮确认(金额、原因、退回方式),遵循完全不同的政策指令,而且需要不同的写权限。让订单 Agent 转述退款对话既慢又容易出错。
交接包:Handoff 时到底传什么
Handoff 最大的风险是信息丢失或信息过载:完整历史里可能有大量与退款无关的工具结果,只传一句“用户要退款”又会丢掉关键事实。更稳妥的做法是除了对话历史外,传递一份结构化的交接包:
{
"handoff_reason": "user_requested_refund",
"customer_intent": "因订单处理超时申请全额退款",
"verified_facts": [
"订单 A1024 已支付,金额 ¥328",
"订单 11 月 3 日起停留在人工审核环节"
],
"unverified_claims": ["用户称已多次联系客服但无人回复"],
"commitments_made": ["已告知用户预计明天处理完成"],
"open_questions": ["用户是否接受部分补偿代替退款"],
"constraints": ["退款金额超过 ¥200 需要人工审批"]
}
注意 commitments_made 这一项:前一个 Agent 对用户做过的承诺,接手的 Agent 必须知道,否则就会出现“刚说明天处理完成,转眼又说无法确认”的前后矛盾。
常见误区
- “Handoff 之后前一个 Agent 还能兜底。” 控制权已经转移,除非设计了回交机制,否则前一个 Agent 不再参与。需要统一兜底,应该用 Agent as Tool。
- “Agent as Tool 时,子 Agent 自然知道上下文。” 子 Agent 只看到你传给它的输入。任务目标、已知事实、输出格式都必须写进调用参数里。
- “交接时把全部历史传过去最保险。” 过长的历史会稀释接手 Agent 的注意力,工具结果里还可能包含不可信内容。按需过滤,并附上结构化摘要。
- “用户察觉不到交接。” 语气、能力、权限变化用户都能感受到。界面上最好让用户知道“现在由退款专员为你处理”。
产品经理检查清单
- 每一次“Agent 找 Agent”,是需要结果返回(Agent as Tool),还是需要转移对话(Handoff)?
- 最终对用户负责的是哪个 Agent?出错时找谁?
- Agent as Tool 的调用参数里,是否包含了子任务目标、必要事实和输出格式?
- Handoff 时传递哪些历史、过滤哪些内容?是否有结构化交接包?
- 接手 Agent 的权限是否与其职责匹配,而不是继承前一个 Agent 的全部权限?
- 用户能否感知到交接?交接后能否回到之前的 Agent 或转人工?
练习
为一个电商客服系统设计 Agent 之间的调用关系(30–45 分钟):
- 列出分诊、订单查询、退款、发票四个 Agent,画出它们之间的箭头,并在每个箭头上标注“Tool”或“Handoff”。
- 为每个标注写一句理由。
- 为“订单查询 → 退款”这次 Handoff 写一份交接包 JSON,至少包含已验证事实、已做承诺和约束三项。
延伸阅读
- OpenAI Agents SDK:Handoffs
- OpenAI Agents SDK:Tools(含 Agents as tools)
- OpenAI Agents SDK:Agent orchestration
- Claude Agent SDK:Subagents
下一步
Agent as Tool 最常见、也最实用的形态是 Subagent。下一课「Subagent 与上下文隔离」讲如何用子 Agent 处理探索性任务,同时保护主 Agent 的上下文。