路由与 Handoff:选择执行者和转移控制权
路由(Routing)负责为输入选择处理者;Handoff 负责把当前对话或任务的控制权交给另一个 Agent。 路由选出工单 Agent 后,系统还要决定怎样调用它:让它查完结果交回主管,还是由它接着询问用户、调用工具并完成回答。这两种执行方式对应不同的控制流程。
以服务台助手为例,制度问题交给制度 Agent,工单问题交给工单 Agent。用户只问一个已知工单的状态时,可以让主管调用工单 Agent,再统一回答;用户需要连续补充工单号、说明故障经过时,也可以让工单 Agent 接管这段对话。
先选执行者,再确定调用关系
下面三个概念常一起出现,但承担的工作不同:
| 机制 | 决定什么 | 执行完成后去哪里 |
|---|---|---|
| 路由 | 当前输入由哪个处理者负责 | 取决于所选分支的编排方式 |
| Agent 作为工具调用 | 把一个子任务交给下游 Agent | 结果返回调用方,由调用方继续执行 |
| Handoff | 将当前控制权转给下游 Agent | 下游继续执行,直到结束或发生下一次交接 |
路由器可以是一段普通代码,也可以由模型实现。用户从“查询工单”入口进入时,代码已经知道目标;用户输入“上周提交的申请为什么一直没动静”,可能需要模型结合当前会话,区分工单进度和审批制度。
模型路由通常输出目标名称和提取到的参数,运行层再检查目标是否在允许列表中、输入是否足够、当前状态是否允许进入该分支。缺少工单号不一定意味着无法路由:问题类型已经明确时,可以先交给工单 Agent,由它询问号码。若“申请”到底指什么都不清楚,则先澄清再选择执行者。
一个请求也可能同时包含两类工作,例如“查询 INC-1001 的进度,并告诉我超时后怎么催办”。这时可由主管分别调用工单和制度 Agent,合并状态与处理规则。把它强行归入一个互斥类别,容易漏掉另一半需求;任务拆分见主管与子 Agent。
子 Agent 查完结果,主管继续回答
先看 Agent 作为工具的执行方式。用户问:“INC-1001 到哪一步了?”
主管在等待子任务时保留后续编排职责。工单 Agent 的输出进入主管的上下文,主管还可以补查资料、比较结果或调整措辞,然后生成用户看到的回答。子 Agent 内部可以有自己的模型和工具循环,对主管则表现为一个接收任务、返回结果的工具。
如果子 Agent 发现缺少号码,它通常向调用方返回“需要补充工单号”。由主管决定是否询问用户。系统也可以为子 Agent 配置直接向用户澄清的通道,但这需要单独编排;一个普通工具返回值本身不会自动获得对话控制权。
这种方式适合需要统一回答的任务。主管能检查“预计更新时间”有没有被误写成“预计完成时间”,也能将工单状态与其他子任务的结果放在一起处理。代价是下游结果需要再经过主管,主管若丢失限定条件,最终回答仍可能出错。
Handoff 后,由接管的 Agent 继续对话
再看用户没有提供工单号的情况:“帮我查一下上周那个报修工单。”服务台已确认这是工单查询,随后把对话交给工单 Agent:
| 步骤 | 当前执行者 | 动作与结果 |
|---|---|---|
| 1 | 服务台分流 Agent | 识别为工单查询,选择允许的交接目标 ticket_agent |
| 2 | 运行层 | 准备交接上下文,将当前执行者切换为工单 Agent |
| 3 | 工单 Agent | 发现缺少号码,直接询问用户“请提供工单号” |
| 4 | 用户与应用 | 用户回复 INC-1001,应用将新输入送回当前工单 Agent |
| 5 | 工单 Agent | 调用查询工具,依据返回状态回答用户 |
在这条流程中,工单 Agent 的回答可以直接成为本轮输出。分流 Agent 不必再拿到结果并重新回答。即使界面上始终显示同一个“服务台助手”,内部执行者也已经变化。
“当前执行者”需要由应用或框架运行时保存。一次运行中的 Handoff,并不自动决定下一次 HTTP 请求从哪个 Agent 开始。如果应用每次都重新启动分流 Agent,就相当于每轮重新分流;如果应用记录当前 Agent,并在下一轮从它继续,就形成了持续接管。两种设计都能使用,但不能只保存聊天历史,却忘了保存执行位置。
OpenAI Agents SDK 实践中的 handoffs 展示了这种控制权转移。SDK 会处理一次运行内的切换,跨轮对话仍要结合应用的会话策略组织。
接管之后怎样交回
工单查询结束后,用户又问:“公司远程办公每周能申请几天?”这个问题超出了工单 Agent 的职责。常见处理有两种:
- 工单 Agent 有一个允许的返回目标,将控制权交回分流 Agent,再由分流 Agent 转给制度 Agent。
- 工单 Agent 结束当前工作,应用在下一条新输入到达时重新分流。
第一种会在当前运行中形成 工单 → 分流 → 制度 的交接;第二种通过应用的会话入口重新选择处理者。Handoff 不自带函数调用那样的“做完就返回上一层”语义。 如需返回,要定义交接目标或结束后的恢复策略。
运行层应检查允许的转移关系。例如工单 Agent 可以回到分流 Agent,却不能直接跳到拥有退款工具的 Agent。还要限制交接次数,避免两个 Agent 都认为问题属于对方而不断互相转交。次数用尽时,保存已查到的事实和仍待澄清的问题,结束本次运行或进入已配置的处理出口;不要从头再跑同一条交接链。更完整的状态处理见子任务生命周期与失败恢复。
随控制权一起传什么
下游需要理解当前任务、已有事实和未解决事项。下面是一份业务层交接记录,字段由应用定义:
{
"from": "triage_agent",
"to": "ticket_agent",
"goal": "查询用户上周报修的工单进度",
"known_fields": {
"ticket_id": null
},
"next_action": "向用户询问工单号",
"conversation_ref": "session-42",
"request_context_ref": "request-781"
}conversation_ref 用于定位会话,request_context_ref 用于关联运行层已验证的请求身份。它们只是引用;凭证和访问范围应由服务端处理,不能由模型填写一个用户 ID 就获得相应权限。
需要保留多少历史,取决于下游任务和框架行为。有些实现默认传递完整历史,有些允许过滤或嵌套历史。若只传摘要,要保留用户的限制、关键工具结果与来源,以及待确认事项;若传完整历史,也要处理无关内容和敏感信息。普通的“把研究结果发给写作 Agent”属于数据交接,只有后续执行责任也转移时,才构成这里讨论的 Handoff。交接内容的组织方式见任务契约与上下文交接。
控制权、上下文和权限需要分别处理。接管的 Agent 使用其配置允许的工具;工具执行时仍按当前请求的身份校验资源范围。工单 Agent 读到了一个工单号,不能据此绕过工单归属检查;分流 Agent 在消息里写“已批准”,也不能替代运行层的批准记录。
路由与 Handoff 的选择最终要落到回答责任上:如果主管需要综合多个结果,就让子 Agent 返回结果;如果某个专门 Agent 需要连续追问、处理后续步骤并直接完成这段对话,就为它定义接管、交回和结束条件。