交接与例行程序——无状态编排 本节摘要:OpenAI 的 Swarm(2024/10)把多智能体编排蒸馏成两个原语:例行程序(Routine)= 指令 + 工具作为系统提示;交接(Handoff)= 一个返回另一个 Agent 的工具。没有状态机、没有分支 DSL——LLM 通过调用正确的交接工具来路由。OpenAI Agents SDK(2025/03)是它的生产继任者,加了会话管理、护栏、追踪,同时保留交接原语。Swarm 本身仍是最干净的概念参考——全部源码几百行。这套模式之所以病毒式传播,因为 API 面大概就是「agent = 提示 + 工具;handoff = 返回 agent 的函数」。局限:无状态,所以记忆是调用者的问题。
本节摘要:OpenAI 的 Swarm(2024/10)把多智能体编排蒸馏成两个原语:例行程序(Routine)= 指令 + 工具作为系统提示;交接(Handoff)= 一个返回另一个 Agent 的工具。没有状态机、没有分支 DSL——LLM 通过调用正确的交接工具来路由。OpenAI Agents SDK(2025/03)是它的生产继任者,加了会话管理、护栏、追踪,同时保留交接原语。Swarm 本身仍是最干净的概念参考——全部源码几百行。这套模式之所以病毒式传播,因为 API 面大概就是「agent = 提示 + 工具;handoff = 返回 agent 的函数」。局限:无状态,所以记忆是调用者的问题。本节用标准库从零实现 Swarm,讲透交接如何用工具调用实现控制转移,以及它何时胜过又何时败给 GroupChat。
阅读完本节,你应当能够:
每个多智能体框架都要你学它的 DSL:LangGraph 的节点与边、CrewAI 的 crew 与 task、AutoGen 的 GroupChat 与 manager。这些 DSL 是真抽象,但让事情显得比必要的更重。
Swarm 反向推进:用模型已有的工具调用能力。交接变成工具调用;编排者就是当前持有会话的那个 Agent;状态机隐式地活在 Agent 的系统提示里。
例行程序:定义 Agent 角色与可用工具的系统提示。可看作一组作用域指令:「你是分诊 Agent;用户问退款就交给退款 Agent。」
交接:Agent 可调用的、返回一个新 Agent 对象的工具。Swarm 运行时检测到 Agent 返回值,就把下一轮的活跃 Agent 切换过去。
这就是全部抽象。
def transfer_to_refunds(): return refund_agent # Swarm 见 Agent 返回 → 切换活跃 Agent triage_agent = Agent( name="triage", instructions="把用户路由到对的专家。", functions=[transfer_to_refunds, transfer_to_sales, transfer_to_support], )
分诊 Agent 的系统提示让它按用户消息选对交接。LLM 的工具调用完成路由。
Swarm 在运行间明确无状态。框架在一次运行内保留消息历史,但不持久化任何东西。记忆、连续性、长时任务——全是调用者的问题。
生产中(OpenAI Agents SDK,2025/03)这是主要变化之一:SDK 加了内置会话管理、护栏、追踪,同时保留交接原语。
⚠️ 无状态是 Swarm 的设计核心,也是它最大的局限:它把协调简化到了极致(工具调用即路由),代价是把状态管理的活全甩给调用者。 这正是「协调难点」的另一种切法——要么你管状态(LangGraph),要么调用者管状态(Swarm)。
生产继任者加:会话状态(跨运行持久线程)、护栏(输入/输出校验钩子)、追踪(每次工具调用与交接都记日志)、交接过滤器(控制交接时转移什么上下文)。交接原语存活;生产工效在其之上添加。
两者都用 LLM 驱动路由,但在谁选下一个上不同:
Swarm 是「Agent 决定下一个」;GroupChat 是「manager 决定下一个」。Swarm 的决策活在活跃 Agent 的工具调用里;GroupChat 的活在 GroupChatManager 里。
code/main.py 从零实现 Swarm:Agent 数据类、交接机制(工具返回 Agent)、检测 Agent 切换的运行循环。
class Agent: def __init__(self, name, instructions, functions=None): self.name = name; self.instructions = instructions self.functions = functions or [] def run_swarm(active_agent, user_message, history): history.append({"role": "user", "content": user_message}) while True: # LLM 决定调用哪个工具(这里脚本化) tool_call = active_agent.decide_tool(history) result = tool_call() if isinstance(result, Agent): # 交接! active_agent = result continue history.append({"role": "assistant", "content": result}) return result, active_agent
triage = Agent("triage", "路由用户到专家。", functions=[transfer_to_refunds, transfer_to_sales, transfer_to_support]) refund = Agent("refund", "处理退款。", functions=[process_refund]) # 用户说「我要退款」→ triage 调 transfer_to_refunds → 返回 refund_agent # 运行循环检测 Agent 返回 → 切换活跃 Agent → refund 接管
设计要点:运行循环里那个
isinstance(result, Agent)检查是 Swarm 的全部魔法——它把「控制转移」变成「工具返回值的类型判断」。这种极简是它易于理解也易于被提示注入攻击的根源(练习 4 会探讨)。
| 维度 | Swarm(概念参考) | OpenAI Agents SDK(生产) | LangGraph | GroupChat |
|---|---|---|---|---|
| 编排者 | 当前活跃 Agent | 当前活跃 Agent + SDK 运行时 | 显式图 | GroupChatManager |
| 路由机制 | 工具返回 Agent | 工具返回 Agent + 过滤器 | 图边条件 | 选择器函数/LLM |
| 状态 | 无(调用者管) | 内置会话/追踪/护栏 | 内置 checkpoint | 全量消息池 |
| 何时选 | 短会话、分诊 | 生产客服、需护栏审计 | 确定性、可重放 | 涌现式会话 |
💡 心法:Swarm 的交接是「内聚路由」(谁拿着话谁决定下一个),GroupChat 是「外聚路由」(外部 manager 决定)。 同样是 LLM 驱动,决策位置不同决定了可调试性与攻击面不同。
outputs/skill-handoff-designer.md:为给定任务设计交接拓扑——存在哪些 Agent、各自能调哪些交接、什么上下文随交接转移。
code/main.py,分诊到退款 Agent。确认第二轮活跃 Agent 是 refund。下一节,我们深入第 03 节提到的 A2A 协议,把它当作一个完整的工程规范来讲——Agent Card、任务生命周期、流式、协商,以及它在跨组织协作中的落地。