2367 字
约 7 分钟
17
S21-X11 · Swarm 式交接与图式编排:两种控制流

先说结论:Swarm 把控制流交给了模型,图式把控制流交给了代码。 前者写起来快、跑起来飘;后者写起来慢、跑起来稳。选哪个,取决于你能不能接受"这次请求走了哪条路"事后才知道。


1. Swarm 的核心:交接就是一个返回值

OpenAI 的 Swarm 是一个实验性、教学向的轻量框架(后来演化为 Agents SDK 的 handoff 机制)。它只有两个原语:Agent 和 handoff。没有图、没有节点、没有边,一个循环反复执行:拿当前 Agent 生成回复,如果回复里触发了 handoff,就把"当前 Agent"换成下一个,继续循环。

from swarm import Swarm, Agent

def to_billing():
    """处理账单、退款、发票相关问题。"""
    return billing_agent

triage = Agent(
    name="triage",
    instructions="你是客服分流。判断用户问题属于账单、技术还是其他,需要时转交。",
    functions=[to_billing, to_tech],
)
billing_agent = Agent(name="billing", instructions="只处理账单与退款,不聊技术。")

client = Swarm()
resp = client.run(agent=triage, messages=[{"role": "user", "content": "我上个月被多扣了钱"}])
print(resp.agent.name)   # 交接后变成 billing

注意 handoff 的实现方式:它就是一个返回 Agent 对象的函数。模型通过 function calling 调用它,框架看到返回值是个 Agent,就切换当前执行者。没有协议、没有状态机,只有一个"下一步谁来说话"的指针。


2. 与图式编排的根本差异

维度 Swarm 式交接 图式编排(LangGraph 等)
控制流写在哪 模型的 function call 里 代码的边与条件函数里
转移是否可枚举 否,取决于模型当次判断 是,边是有限集合
状态存哪 调用栈 + 消息列表 显式 State + checkpoint
一段对话能走多远 由模型自行决定 由边与终止条件决定
中断/审批 需自己加 原生 interrupt
失败重放 从头再来 断点续跑

一句话概括:Swarm 的转移是隐式的(你看代码看不出可能走向哪),图的转移是显式的(读 add_conditional_edges 就知道全部出口)。


3. 状态保存在哪:这是最大的工程差异

方案 状态位置 恢复能力 代价
Swarm 内存对象 进程内存里的 messages 列表 无,进程挂了就没了 零成本
Swarm + 自己落盘 每轮把 messages 序列化 可恢复到"某一轮之后" 要自己设计断点
Agents SDK + Session 会话存储 按会话恢复 依赖 SDK 的存储抽象
图 + checkpointer 数据库里的 State 快照 任意节点恢复、可改状态再继续 要设计 State 与 reducer

Swarm 的 messages 列表是线性追加的,这意味着"回到第 3 轮重来"这件事在它里面没有语义——你只能把消息截断到第 3 轮,然后指望模型重新走一遍相同的路径。图式编排里这是一次 update_state 的事。


4. 什么时候 Swarm 是对的

场景 为什么合适
客服分流 问题类型决定去哪个专席,路径本身就是"看情况"
意图路由 一个入口分发给多个下游能力,每次只走一条
多 persona 切换 同一个会话里换"说话的人",不换流程
原型验证 几十行就能跑起来,验证"分工是否有效"

这类场景的共同点:一次请求只走一条路径,且路径选择本身就是模糊判断。 用图来写,你会得到一堆 if 和不必要的状态字段。


5. 什么时候必须用图

场景 为什么不能只靠交接
需要严格顺序(先校验再执行) 顺序必须由代码保证,不能由模型"记得"
需要审计每一步 图有显式节点,日志天然按步分片
需要人工审批 审批点必须是可暂停、可恢复的
需要失败重试与补偿 断点续跑依赖 checkpoint
有并行分支 交接模型里没有"并行"这个概念

一个实用的判断:如果出了事故你要向别人解释"为什么走了这一步",而你说不出规则,那就该用图。


6. 同一件事的两种写法

图式版本里,交接变成了一条显式边:

from langgraph.graph import StateGraph, START, END

def route(state) -> str:
    intent = classify(state["messages"][-1])
    return {"billing": "billing", "tech": "tech"}.get(intent, "general")

g = StateGraph(State)
g.add_node("triage", triage_node)
g.add_node("billing", billing_node)
g.add_node("tech", tech_node)
g.add_edge(START, "triage")
g.add_conditional_edges("triage", route,
                        {"billing": "billing", "tech": "tech", "general": END})
g.add_edge("billing", END)
app = g.compile(checkpointer=checkpointer)

区别不在于功能,而在于出口是有限集合:读这段代码你能列出所有可能的走向;而在 Swarm 里,to_billing 和 to_tech 什么时候被调用,只有跑起来才知道。

Agents SDK 里的 handoff 更接近工程可用形态,转移仍然是工具调用,但转移目标被声明为类型:

from agents import Agent, handoff

billing = Agent(name="billing", instructions="处理账单与退款")
triage = Agent(
    name="triage",
    instructions="按问题类型转交,不要自己回答账单问题",
    handoffs=[handoff(billing, tool_name_override="转交账单组")],
)

tool_name_override 值得注意:交接工具的名字就是模型看到的选项。写成 transfer_to_billing 不如写"转交账单组",后者在中文语境下被正确触发的概率明显更高。


7. 迁移到生产要补的四件事

补充项 做法 不加的后果
审计日志 每次交接记录 from、to、触发理由、消息摘要 出事无法归因
权限边界 每个 Agent 独立工具集与凭证 分流到低级 Agent 却有高级权限
成本上限 每会话交接次数上限 + 预算阈值 在两个 Agent 之间来回弹
终止判据 显式 end 条件,不让模型自己收尾 会话永不结束
MAX_HANDOFFS = 5

def guarded_handoff(state, target):
    if state["handoffs"] >= MAX_HANDOFFS:
        return {"messages": [("ai", "本次已转交多次,请描述具体问题以便直接处理")],
                "handoffs": state["handoffs"]}
    log.info("handoff from=%s to=%s reason=%s",
             state["current"], target, state["last_reason"])
    return {"current": target, "handoffs": state["handoffs"] + 1}

交接日志应该长成这样,一行一次转移(示意格式):

2025-11-03T09:12:41Z from=triage to=billing reason="用户提到退款" handoffs=1 ms=410
2025-11-03T09:13:20Z from=billing to=tech   reason="账单已确认,但问到接口报错" handoffs=2 ms=380
2025-11-03T09:13:52Z from=tech   to=end     reason="已给出排查步骤" handoffs=2 ms=295

有了这三行,"为什么最后是技术组在回话"这个问题就有答案了;没有它,只能把整段对话重放一遍再猜。

"无限互踢"是 Swarm 式系统最经典的失控:两个 Agent 都觉得该对方处理,来回交接,每一轮都烧一次完整推理。加一个计数器是最低成本的止损。


8. 排错

现象 原因 处理
该转交却没转 交接工具的名字/描述不清晰 用业务语言命名,描述里写触发条件
不该转交却转了 当前 Agent 的 instructions 没划清边界 明确"你不处理 X,遇到 X 必须转交"
两个 Agent 互踢 职责重叠 + 无次数上限 计数器 + 单向交接
会话跑了几十轮 模型自己决定继续 加显式终止条件与步数上限
无法复现同一路径 路由由模型每次重判 记录每次分类结果,必要时固化规则
转交后上下文丢失 只传了最后一条消息 交接时显式携带必要的状态摘要

9. 边界

场景 选择 理由
客服分流、意图路由 Swarm 式交接 路径短、每次只走一条
多步审批流程 图 需要暂停、恢复、审计
需要并行处理 图 交接模型没有并行语义
快速原型 Swarm 式 数十行即可验证分工
强合规、要出报告 图 每一步都得能被引用

还有一条中间路线值得知道:用图管主干,用交接管局部。 主流程用图保证顺序与审计,某个"分流"节点内部用 handoff 决定谁来回话。两者不是互斥的技术选型,而是同一系统里不同粒度的工具。


10. 自测

  1. Swarm 里 handoff 的技术实现是什么?为什么它不需要"协议"?
  2. "转移是隐式的"具体指什么?它给调试带来什么困难?
  3. 为什么在 Swarm 里"回到第 3 轮重来"没有语义?
  4. 哪一类场景最适合交接式控制流?举两个。
  5. "无限互踢"的最低成本止损手段是什么?
  6. 图管主干、交接管分流,这种混合形态的分界线怎么划?
S21-X11 · Swarm 式交接与图式编排:两种控制流
https://www.clxhxhhr.top/posts/5694/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。