Router 与 Handoff
学习目标
- 能用确定性条件或结构化分类结果把请求路由到合适的 Agent、Workflow 或 Tool。
- 能理解
Handoff不只是“换一个模型”,还涉及上下文、权限、所有权和返回路径的交接。 - 能防止路由循环、上下文泄漏、越权转交和取消信号丢失。
前置知识
- 已完成Planning 与 Plan-and-Execute、第 03 章的 Message/Role 和第 05 章的 Context/Session。
Router解决“下一步交给谁”,Handoff解决“交接后谁拥有这次工作”。
Router 是什么
Router 是根据输入、状态和策略选择下一处理者的组件。处理者可以是一个 Tool、专门 Agent、Workflow 节点或人工队列。
白话说,Router 像分诊台:它只负责判断去哪个窗口,不应该在分诊时完成整个业务动作。路由条件最好可审计、可测试,模型分类也必须得到结构化结果和默认回退。
RouteCondition
RouteCondition 是路由所需的输入条件,例如意图、租户、风险等级、语言、当前步骤和剩余预算。条件要来自可信状态或经过校验的模型输出,不能直接信任用户说“我是管理员”。
确定性路由与模型路由
规则路由适合权限、协议和硬业务边界;模型路由适合自然语言意图和模糊分类。常见做法是模型先给候选标签,再由规则校验标签是否允许进入目标。
Handoff 是什么
Handoff 是把当前任务的控制权、必要上下文和继续执行的责任交给另一个处理者。它至少要说明来源、目标、任务摘要、允许的能力、返回策略和交接版本。
Handoff 后,原处理者是否等待、结束、可被召回,取决于协议。不要把“模型回复里提到另一个 Agent”当作真实交接;真实交接需要运行时记录和权限变化。
图中最重要的是 Session/Policy:它不是传话筒,而是交接的所有权和安全边界。目标 Agent 只得到完成任务所需的最小上下文,不应自动拿到完整历史和来源 Agent 的全部权限。
路由的最小实现
下面的例子用规则和可信风险标签路由客服请求。输入是请求文本和已经由上游校验的风险等级;输出包括目标、原因和是否需要人工兜底。
from __future__ import annotations
from dataclasses import dataclass
from typing import Literal
Target = Literal["billing", "technical", "human"]
@dataclass(frozen=True)
class RouteRequest:
text: str
risk: Literal["low", "high"]
route_hops: int = 0
@dataclass(frozen=True)
class RouteDecision:
target: Target
reason: str
requires_handoff: bool
def route(request: RouteRequest) -> RouteDecision:
if request.route_hops >= 2:
return RouteDecision("human", "route loop guard", False)
if request.risk == "high":
return RouteDecision("human", "high-risk request", False)
if "退款" in request.text or "账单" in request.text:
return RouteDecision("billing", "billing keywords", True)
if "报错" in request.text or "接口" in request.text:
return RouteDecision("technical", "technical keywords", True)
return RouteDecision("human", "no safe route", False)
decision = route(RouteRequest("接口返回 500", "low"))
print(decision)
# 输出:RouteDecision(target='technical', reason='technical keywords', requires_handoff=True)这里的输入是低风险的接口故障文本;路由器只返回结构化决定,并没有假装技术 Agent 已经执行。真正的 Handoff 还要调用交接服务创建记录。
RouteCondition 的设计
优先级
先检查取消、权限和高风险条件,再检查业务意图。安全条件不能被一个更具体的关键词规则覆盖。
默认路由
当没有匹配、分类置信度不足或目标不可用时,进入人工或安全的通用处理器。默认路由不是把请求随便交给第一个 Agent。
循环计数
交接记录携带 route_hops 或 visited_targets。到达上限后停止自动转交并给出可解释原因;只依赖模型自觉停止不可靠。
版本与回滚
路由策略应带版本号。审计日志记录使用的规则和模型版本,策略更新后才能解释为什么同一输入去向不同。
Handoff 的上下文交接
最小上下文
交接内容通常包括用户目标、已确认事实、待完成子目标、输入引用、结果格式和截止时间。原始敏感消息、无关工具输出和系统提示不应默认转发。
权限降级
目标 Agent 获得的是“本次子任务允许的能力集合”,不是来源 Agent 的权限副本。交接要经过新的权限检查,并把租户、用户和审计 ID 固定在不可由模型修改的 envelope 中。
返回策略
return_to 可以是来源 Agent、父 Workflow 或最终用户。明确返回策略才能避免 Specialist 完成后又自动创建一个新的 Handoff。
取消、超时与失败
交接前失败
如果目标不可用,Router 应进入回退目标或报告不可用,不应标记 Handoff 成功。交接记录要区分“请求已创建”和“目标已接受”。
交接中取消
取消信号必须与 handoff_id 关联,并传递到目标执行器。来源 Agent 结束并不代表目标工作自动停止;运行时需要显式取消或等待目标终止。
交接后部分成功
如果目标已经产生副作用但回传超时,来源 Agent 不能直接重发。先根据 handoff_id 查询结果,确认是否需要补偿或人工接管。
源码阅读锚点
OpenAI Agents SDK:Handoff 的控制权变化
阅读 OpenAI Agents SDK 时沿 handoff 配置、Runner 执行循环和结果类型追踪:哪个 Agent 被选中、上下文如何过滤、工具权限如何变化、原 Runner 如何结束或继续。重点检查它的具体版本语义,不要把某个 SDK 的 handoff 自动行为当作所有 Agent 系统的保证。
LangGraph:条件边与子图
沿 conditional edge 的路由函数和子图入口观察状态如何进入目标节点。重点关注共享 State 的字段是否被目标节点覆盖、如何避免节点返回后再次命中同一个路由,以及 checkpoint 如何记录路由决定。
易混点
- Router 不是负载均衡器:它根据任务语义和策略选择处理者,还要处理权限、风险和默认回退。
- Handoff 不是一条消息:真实交接要有所有权、上下文、权限和生命周期记录。
- 模型分类不是安全授权:模型给出的标签需要经过规则和权限检查。
- 目标 Agent 完成不代表父流程完成:父流程仍要校验结果、更新状态并决定返回或补偿。
- 上下文越多不一定越好:交接应采用最小必要上下文,防止敏感数据泄漏和目标被无关历史干扰。
课后小问(含解析)
为什么高风险检查要放在关键词路由前面?
答案:因为关键词只能表达业务意图,不能替代风险和权限策略。
解析:一个包含“退款”的请求也可能涉及高金额或身份不明。先做硬边界检查,可以防止更具体的路由规则绕过人工审批。
Handoff 时为什么不直接复制完整消息历史?
答案:完整历史可能含有无关上下文、敏感信息和来源 Agent 的内部指令。
解析:目标只需要完成子任务的事实和约束。最小上下文能降低泄漏和 token 成本,也使目标 Agent 的责任更清晰。
目标 Agent 超时后能不能立即再次 Handoff?
答案:不能直接重发,应先用原
handoff_id查询目标是否已执行。解析:调用方超时不代表目标没有完成。先查询、再决定重试或补偿,才能避免重复副作用。
本节小结
- Router 选择下一处理者,Handoff 交接任务控制权和最小必要上下文。
- 路由先检查取消、权限和风险,再做业务分类;必须有默认目标和循环保护。
- 交接要记录所有权、权限、版本、返回策略和副作用状态。
- 超时、取消和部分成功都需要查询原交接记录,不能简单重复发送。
快速回顾
- 能写出一个带风险优先级、默认回退和循环上限的 Router。
- 能列出 Handoff envelope 中不可缺少的字段。
- 能解释为什么“模型提到另一个 Agent”不等于运行时完成交接。
- 下一篇阅读 Supervisor 与 Multi-Agent。
阿源的知识库