没有主管,Agent 之间怎么自己"转接电话"?
上一篇 Supervisor 是"项目经理派活",这篇 Swarm 是"同事之间互相转接"——没有中央调度,谁发现自己搞不定,就把控制权交给更合适的人。
一、先搞清:Swarm 到底解决什么问题
Supervisor 模式里,主管是单点瓶颈。每次路由都要问主管"下一步找谁",多一轮 LLM 调用就多一笔开销。
Swarm 的思路更轻:每个 Agent 自带"转接工具",自己决定要不要移交、移交给谁。 底层靠 Command(goto=agent_name, graph=Command.PARENT) 实现子图间跳转。
适用场景:
- 多轮对话中用户需求会"跑题"(订机票 → 顺便订酒店 → 查天气)
- 流程难以预先画完整路由图
- 需要降低 LLM 调用次数(省掉主管那一跳)
代价:调试更难,链路分散,容易互相推诿或死循环。
二、从零实现:旅行规划 Swarm
1. 安装依赖
pip install -U langgraph langgraph-swarm langchain-openai
langgraph-swarm 封装了 create_handoff_tool 和 create_swarm,是官方推荐的快捷方式。
2. 定义工具
from langchain_core.tools import tool
@tool
def search_flights(from_city: str, to_city: str, date: str) -> str:
"""搜索航班。当用户需要查询或预订航班时调用。"""
return f"找到 {from_city}→{to_city} {date} 的航班,最低 ¥1200。"
@tool
def book_flight(flight_id: str) -> str:
"""预订航班。当用户确认预订时调用。"""
return f"航班 {flight_id} 预订成功。"
@tool
def search_hotels(city: str, check_in: str, check_out: str) -> str:
"""搜索酒店。当用户需要查询或预订酒店时调用。"""
return f"找到 {city} {check_in} 至 {check_out} 的酒店,最低 ¥450/晚。"
@tool
def book_hotel(hotel_id: str) -> str:
"""预订酒店。当用户确认预订时调用。"""
return f"酒店 {hotel_id} 预订成功。"
关键点:每个工具的 docstring 是写给模型看的,不是写给人看的。描述不清,模型就选错工具。
3. 创建移交工具
from langgraph_swarm import create_handoff_tool
# 机票助手可以转给酒店助手
transfer_to_hotel = create_handoff_tool(
agent_name="hotel_assistant",
description="当用户需要查询或预订酒店时,转给酒店助手"
)
# 酒店助手可以转给机票助手
transfer_to_flight = create_handoff_tool(
agent_name="flight_assistant",
description="当用户需要查询或预订航班时,转给机票助手"
)
4. 创建两个专家 Agent
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o", temperature=0)
flight_assistant = create_react_agent(
llm,
tools=[search_flights, book_flight, transfer_to_hotel],
name="flight_assistant",
prompt="你是航班预订专家。只处理航班相关请求,遇到酒店需求就转接。"
)
hotel_assistant = create_react_agent(
llm,
tools=[search_hotels, book_hotel, transfer_to_flight],
name="hotel_assistant",
prompt="你是酒店预订专家。只处理酒店相关请求,遇到航班需求就转接。"
)
5. 组建 Swarm
from langgraph_swarm import create_swarm
from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver()
swarm = create_swarm(
agents=[flight_assistant, hotel_assistant],
default_active_agent="flight_assistant", # 默认启动谁
)
app = swarm.compile(checkpointer=checkpointer)
create_swarm 会自动扫描每个 Agent 身上的 handoff 工具,构建路由表,无需手动写调度逻辑。
6. 运行
config = {"configurable": {"thread_id": "trip-001"}, "recursion_limit": 15}
for chunk in app.stream(
{"messages": [{"role": "user", "content": "帮我订一张北京到上海下周五的机票"}]},
config=config,
stream_mode="messages"
):
print(chunk[0].content, end="", flush=True)
输出轨迹大致是:
flight_assistant: 调用 search_flights → 找到航班
flight_assistant: 调用 book_flight → 预订成功
flight_assistant: 需要转接 → 调用 transfer_to_hotel
hotel_assistant: 收到转接,等待用户输入酒店需求
用户接着说"再帮我订一家外滩附近的酒店":
hotel_assistant: 调用 search_hotels → 找到酒店
hotel_assistant: 调用 book_hotel → 预订成功
三、Handoff 底层机制拆解
很多人用 create_handoff_tool 但不知道它干了什么。简化版源码逻辑如下:
from langgraph.types import Command
from langchain_core.tools import tool
from typing import Annotated
from langgraph.prebuilt import InjectedState
def create_handoff_tool(*, agent_name: str, description: str):
@tool(f"transfer_to_{agent_name}", description=description)
def handoff_tool(
task_description: Annotated[str, "描述下一个 Agent 应该做什么"],
state: Annotated[dict, InjectedState],
) -> Command:
# 只传 task_description,不传完整历史
task_msg = {"role": "user", "content": task_description}
return Command(
goto=agent_name, # 跳到目标 Agent
update={"messages": [task_msg]}, # 只传摘要
graph=Command.PARENT, # 在父图层面跳转
)
return handoff_tool
三个关键点:
graph=Command.PARENT:告诉 LangGraph 在父图(Swarm 图)层面跳转,而不是在当前 Agent 的子图里跳。task_description参数:强制 LLM 把移交意图总结成一句话,而不是把整个对话历史原样扔过去。这是性能优化的核心。InjectedState:让工具能读取当前 State,但不直接修改,修改权交给Command.update。
四、一个常见坑:默认 Handoff 传全量历史
create_handoff_tool 的默认行为是把整个 conversation history 打包传给下一个 Agent。对话长了之后会:
- 序列化慢
- 下一个 Agent 被无关信息干扰
- Token 费用爆炸
优化方案:用上面自定义的 handoff 工具,强制 LLM 输出 task_description,只传摘要。实测长对话场景下移交速度提升 40% 以上。
五、防失控:Swarm 的三道保险
| 保险 | 做法 | 说明 |
|---|---|---|
| 递归限制 | config={"recursion_limit": 15} | 不设的话,两个 Agent 互相转接会跑到 API 账单爆炸 |
| 白名单校验 | 自定义 handoff 时校验 agent_name | 防止 LLM 幻觉跳到不存在的节点 |
| 活跃 Agent 追踪 | SwarmState.active_agent | 新消息自动路由到上次干活的 Agent,避免"金鱼记忆" |
六、Swarm vs Supervisor 选型指南
| 维度 | Supervisor | Swarm |
|---|---|---|
| 控制方式 | 中心化,主管统筹 | 去中心化,互相转交 |
| 可预测性 | 高,路径清晰 | 较低,动态灵活 |
| 调试难度 | 低,日志集中 | 高,链路分散 |
| LLM 调用 | 多(每轮都要问主管) | 少(直接交接,省一跳) |
| 适用场景 | 客服工单分派、报告生成 | 多轮对话转接、专家接力 |
| 推荐 Agent 数 | 3-10 | 5-15 |
一句话:要可控选 Supervisor,要灵活选 Swarm。实际生产中最常见的是混合架构——团队内部用 Swarm,团队之间用 Supervisor 串起来。
七、生产级 Checklist
- 安装
langgraph-swarm并用create_swarm组建 - 每个 Agent 的 handoff 工具 description 写清楚触发条件
- 设
recursion_limit,防止互相转接死循环 - 开启 Checkpointer,保证多轮对话连续性
- 自定义 handoff 工具时只传
task_description,不传全量历史 - 用 LangSmith 分布式追踪排查链路问题