跳到主要内容

例程与交接:每个可靠多智能体系统背后的两个基本原语

· 阅读需 8 分钟
Tian Pan
Software Engineer

大多数多智能体系统的失败,不是因为模型出了问题,而是因为"管道"存在漏洞。智能体在任务执行中途丢失上下文,将任务移交给错误的专家,或者因为不知道如何退出而陷入无限循环。根本原因几乎总是相同的:系统设计只关注每个智能体能做什么,却没有清晰定义工作如何在它们之间流转

两个原语可以解决大部分问题:例程(routines)和交接(handoffs)。它们看似简单,但把它们做对,是一个能演示的系统和一个能上线的系统之间的关键区别。

例程到底是什么

例程不是工作流模板,也不是提示词组成的有向无环图。它更加简洁:一套自然语言指令集,加上执行这些指令所需的工具。

把它理解为智能体的标准操作程序。面对一个客户投诉,一个支持例程可能会这样说:

检查订单状态。如果订单延迟,道歉并提供 10 美元抵扣券。如果商品从未发货,升级至履约团队。如果客户咨询的是退货问题,转接至退货智能体。

智能体通过工具调用来执行这些指令——get_order_statusapply_creditescalate_to_fulfillment——直到任务完成或触发交接条件。

关键洞察在于:LLM 在遵循顺序化自然语言流程方面表现出色,尤其是当每个步骤都对应一个具体工具时。指令不需要是严格的状态机。模型会处理歧义并填补空白,这正是你应对那些工作流未预料到的真实场景时所需要的。

好的例程具备以下特征:

  • 步骤是动作而非描述("检查订单状态"vs."智能体应了解订单状态")
  • 每个条件分支都以清晰的结果结束:解决、升级或交接
  • 工具与智能体可执行的离散动作一一对应
  • 例程是有边界的——它处理一个领域,而不是所有事情

一个试图同时处理账单、退货、技术支持和账户变更的例程,最终会走向混乱。保持例程的精简和可组合性。

交接:显式优于隐式

交接发生在一个智能体将当前会话——连同其完整上下文——转移给另一个更适合继续的智能体时。

朴素的实现方式是在最开始就基于意图分类进行路由:检测用户意图,然后分派给正确的智能体。但这很快就会失效。意图会在对话中途改变。用户可能从退货咨询开始,却发现自己其实有账单方面的问题。分诊智能体往往在对话充分进行之前,无法判断需要哪位专家。

更好的模式是让智能体自己发起交接。每个智能体获得一组 transfer_to_X 工具——每个可交接的智能体对应一个。当模型判断自己触及了边界,就调用这个工具。运行时切换当前活跃的智能体,并使用新智能体的指令继续执行循环。

def transfer_to_billing_agent():
"""将对话转接给账单专家。"""
return billing_agent

def transfer_to_returns_agent():
"""将对话转接给退货退款专家。"""
return returns_agent

当工具返回的是智能体对象而非字符串时,运行时会将其解读为交接并更新活跃上下文。对话历史随之传递——接收方智能体知道此前发生的一切。

这种方式有几个强大之处:

  • 由模型决定何时交接,而非规则引擎。它读取对话并做出判断。
  • 交接可以链式进行。分诊智能体可以交接给专家,专家可以在必要时再交接给升级智能体。
  • 用户无需重复自己。上下文在整个链路中得以保留。

执行循环

将例程和交接串联起来的运行时简单到一屏就能容下:

  1. 用当前智能体的指令和工具调用模型
  2. 从响应中解析工具调用
  3. 执行函数——如果函数返回智能体对象,则切换至该智能体
  4. 将结果追加到对话历史
  5. 重复,直到模型返回不含工具调用的纯文本响应

就这样。循环在智能体没有更多事情要做时终止。系统中的每个智能体都只是一个指令集和一个工具列表——相同的结构,不同的内容。

这种一致性对可维护性至关重要。当你需要添加新的专家智能体时,无需触碰路由逻辑。写一个新例程,在需要访问该专家的智能体上添加 transfer_to_new_specialist 函数,就完成了。

交接图的失效点

简单的链式结构运行良好。路径众多的深层图则调试成本高昂。

交接边界处的上下文丢失是最常见的故障模式。即使对话历史得以保留,每个新智能体也会从自己的指令出发。如果这些指令假设了历史记录中并不存在的特定上下文——内部工单 ID、已确认的身份验证、缓存的查询结果——接收方智能体可能会在信息不完整的情况下做出决策。

解决方法是在交接工具中保持显式。不只是转移,而是携带摘要进行转移:

def transfer_to_escalation_agent(reason: str, case_summary: str):
"""
在问题无法在一线解决时,转接至升级智能体。

Args:
reason: 需要升级的原因
case_summary: 问题的简要摘要及已尝试的措施
"""
escalation_agent.context = {"reason": reason, "summary": case_summary}
return escalation_agent

强制转移方智能体明确说明为何交接、尝试了什么,能显著改善接收方的连续性。

协调延迟会累积。每次交接在下一个智能体开始处理之前都会增加 100–500 毫秒的开销。一个需要十次跳转的工作流,仅路由开销就可能高达五秒。设计智能体拓扑时要尽量减少不必要的跳转。如果两个智能体频繁相互交接,可能需要将它们合并为一个更广泛的例程。

调试隐式交接非常痛苦。当用户进入了错误的智能体,你需要重建决策路径。从一开始就把追踪机制内建进去:记录哪个智能体处于活跃状态、调用了哪个工具、以及模型在每个交接点的推理过程。没有这些,生产事故排查会变成一场考古挖掘。

设计智能体拓扑

多智能体系统有三种常见形态,各自适配不同场景。

线性链适合顺序管道,其中每个步骤都有清晰的输出传递给下一步。接收 → 分类 → 丰富 → 响应。协调开销低,易于追踪。

轮毂辐条式将分诊或编排智能体置于中心,路由至各专家并收集结果。这是面向用户系统的正确形态——当你事先不知道用户会落在哪个领域时。

对等网络允许任意智能体向任意其他智能体交接。这很灵活,但规模扩大后难以推理。将对等网络保留给规模小且互动模式已知的智能体集合——三到五个智能体。

一个实用规则:从轮毂辐条式开始。分诊智能体成为添加可观测性、限速和护栏的天然接入点。对有确定性工作流的场景,可以扁平化为线性链。在穷尽更简单的选项之前,避免使用对等网络。

生产环境中的好系统长什么样

一个设计良好的例程加交接系统,有几个特征使其区别于脆弱的演示系统:

智能体是窄域专家,而非通才。每个智能体把一件事做好。账单智能体不兼管退货。单个智能体的复杂度保持有界。

交接是声明的,而非发现的。每个智能体有一份明确的可转接智能体列表。路由从不是临时决定的。这使系统可审计——你可以画出交接图,并验证它与你的意图相符。

边界处的上下文是显式的。交接函数携带结构化摘要,而非仅有原始历史。接收方智能体获得的是立即行动所需的信息,而非需要解析的对话记录。

循环有终止条件。每个例程都有清晰的退出条件:解决、升级或交接。一个无法完成任务又无处交接的智能体,应该给出清晰的错误信息而非无限循环。

底层模型——一个指令集、一个工具列表、一个执行循环——能从双智能体系统扩展到数十个专家智能体。原语保持不变;你只是在添加更多节点。

智能体编排归根结底是一个软件设计问题,只不过包裹着概率性的外衣。模型处理歧义,你的工作是确保围绕它们的结构,与你在生产环境中运行的任何其他分布式系统一样显式且可调试。

Let's stay in touch and Follow me for more thoughts and updates