[Ai Agent] 12 Swarm 与 Agents SDK —— 去中心化的多智能体协作

博客配套代码发布于github12 Agents SDK & Swarm 模式

相关Agent专栏:Ai Agent教学

本系列所有博客均配套Gihub开源代码,开箱即用,仅需配置API_KEY。

如果该Agent教学系列帮到了你,欢迎给我个Star⭐

知识点Swarm | Handoff | Context_variables | Agents SDK | 去中心化


前言、

在上一篇 M11 中,我们借助 MCP 协议,让 Agent 能够自由调用任意外部工具------无论是查天气、发飞书,还是调用高德地图,都变得像插 USB 一样简单。

但现实中的任务往往不是单打独斗,而是多人协作

比如"航空公司客服"系统:

  • 有人负责退票,
  • 有人处理改签,
  • 有人追踪行李。

面对这种需求,我们有两种主流解法:

  • 老办法(M09:LangGraph) :设一个"车间主任"(Supervisor),所有请求先汇总给它,由它决定派活给谁、何时汇总结果。这是一种中央控制式架构。
  • 新方法(本篇:Swarm 模式) :像一场接力赛 ------前台接待员处理不了,就直接把电话转接 给退票专员;如果退票专员也搞不定,再转给上级......整个过程无需总控,自主交接

本篇,我们将使用 OpenAI 官方推出的 Agents SDK,亲手实现这种轻量、灵活、高内聚的协作模式。

一、Langgraph vs Swarm:选对工具

要真正理解 Swarm 的价值,最好的方式是和 LangGraph 对比。

▶ LangGraph:适合"确定性流程"

当你用 LangGraph 构建多 Agent 系统时,本质上是在画一张状态转移图

  • 每个节点(Node)是一个 Agent 或工具;
  • 每条边(Edge)是一条路由规则(比如"如果用户说'改签',跳转到 ChangeAgent");
  • 通常还有一个 Supervisor 节点,作为"大脑"协调所有子 Agent。

这种方式强大但繁重

  • 你需要同时设计节点逻辑 + 路由逻辑;
  • 流程越复杂,图就越庞大,维护成本越高;
  • 但它能保证执行顺序确定、可审计、可回溯------适合金融审批、医疗诊断等强流程场景。

▶ Swarm:适合"自由协作"

Swarm 的设计哲学恰恰相反:去掉大脑,让手脚自己说话

它把 LangGraph 中 Supervisor 管理的那些子 Agent(a、b、c)直接释放出来,每个 Agent 自己决定:

"这事我干不了,应该交给谁?"

你不再需要定义 Node、Edge、条件跳转------只需专注每个 Agent 的核心能力与可转接对象(handoffs)

开发效率大幅提升,代码也极度简洁。

💡 一句话理解 Swarm

它就像是把 LangGraph 里的 Supervisor "拆掉",让原本被调度的子 Agent 获得自主权,彼此直接对话。

▶ 那么,该如何选择?

场景 推荐方案 原因
单 Agent + 工具调用 LangChain / 原生 LLM 调用 结构最简,无需引入多 Agent 框架
多 Agent 自由协作 (如客服、导购、分诊) Swarm 模式(Agents SDK) 角色清晰、转接灵活、开发高效
多 Agent 强流程控制 (如工单审批、合规检查) LangGraph 时间轴明确、状态可控、可追溯

没有"最好",只有"最合适"

Swarm 不是 LangGraph 的替代品,而是它的轻量化补充------专治"不需要复杂编排,只要几个专家能互相配合"的场景。

二、Swarm 与 Agents SDK:思想 vs 工程

前文我们花了较多篇幅对比 Swarm 与 LangGraph ,但从严格意义上讲,这种对比并不完全对等。

真正与 LangGraph 同级的,其实是 Agents SDK ------ 它才是一个完整的、可用于生产环境的多智能体开发框架。

那么,Swarm 到底是什么?

Swarm 并不是一个框架,而是一种设计思想(Design Pattern),其核心主张是:

去中心化协作 + 自主任务转接(Handoff)

它刻意不设中央控制器 ,也不显式定义流程图或状态机。每个 Agent 只需知道自己能做什么、以及"干不了时该转给谁"。代码极简,理念清爽,非常适合教学和原型验证。

但也正因如此,原生 Swarm 存在明显局限

  • 不支持异步并发;
  • 缺乏工具集成机制;
  • 没有上下文管理、安全护栏、可观测性等工程能力;
  • 难以支撑真实业务场景的稳定性与扩展性。

换句话说:Swarm 适合"讲清楚原理",但不适合"跑在生产环境"

Agents SDK 正是为了弥补这一 gap 而诞生的。

它是 OpenAI 对 Swarm 思想的系统化工程封装,不仅保留了"自主 Handoff"的核心体验,还全面增强了以下能力:

  • ✅ 原生异步支持(async/await)
  • ✅ MCP 协议深度集成(无缝调用你在 M10--M11 构建的工具服务)
  • ✅ 统一上下文管理(共享背包)
  • ✅ 安全护栏(Guardrails)、全链路追踪(Tracing)等企业级特性

正因如此,它被命名为 Agents SDK ,而非 "Swarm 2.0"------

因为它不只是 Swarm 的简单升级,而是一个融合了多智能体最佳实践的完整开发框架

更重要的是,Agents SDK 并非要取代 LangGraph。

相反,它在设计上借鉴了 LangGraph 对多 Agent 的结构化抽象 ,同时采纳了 Swarm 去中心化、自主转接(Handoff)的协作理念,在开发效率、系统灵活性与工程可靠性之间,找到了一条更实用的路径。

📌 一句话总结

Swarm 提出了一个好想法,Agents SDK 把它变成了可落地的工具。

接下来,我们将先基于 Swarm 的核心思想,用 Agents SDK 实现一个典型的多 Agent 协作场景;

随后再逐步展开它的高级能力------MCP 集成、安全护栏、上下文管理等,带你全面掌握这个新一代智能体开发平台。

三、核心机制:接力棒与背包

在 Agents SDK 中,实现 Swarm 式协作只需掌握两个核心概念:

1. 接力棒:Handoff(转接)

这是 Swarm 模式的灵魂------任务交接由 Agent 自主触发,无需外部调度

在代码中,转接非常简单:一个函数直接返回另一个 Agent 对象

python 复制代码
def transfer_to_sales():
    """用户想购票时,转接给销售专员"""
    return sales_agent

def transfer_to_refund():
    """用户申请退款或取消行程时,转接给退票专员"""
    return refund_agent

这些函数会被注册为 Agent 的"可选动作"。

当用户说"我要退钱",当前 Agent 会根据指令和上下文,自主决定调用 transfer_to_refund()

SDK 检测到返回值是一个 Agent,便立即把对话控制权"交棒"过去。

关键点

你通过代码定义了"有哪些路可走",而 LLM 根据对话内容动态选择走哪一条


2. 共享背包:Context Variables(上下文)

接力赛中,交棒的同时,信息也得跟着传递。

Agents SDK 通过一个全局的 字典 context_variables(即"共享背包")来实现这一点。

  • 用户刚接入时,系统将客户信息(如"姓名:张三,航班 CA1234")放入背包;
  • 前台 Agent 背着这个包接待;
  • 当发生 Handoff 时,背包自动传递给下一个 Agent
  • 退票专员拿到背包后,可在自己的提示词中动态引用客户信息,无需重复询问。
python 复制代码
def refund_instructions(context_variables):
    user_info = context_variables.get("user_info", "未知用户")
    return f"你是退票专员。当前客户:{user_info}。请按流程处理退款。"

理解了"接力棒"和"共享背包",你就掌握了 Swarm 协作的全部基础。

接下来,我们将在实战中用 Agents SDK 搭建一个完整的多 Agent 客服系统。

四、代码实战:航空公司客服集群

本节我们将用 Agents SDK 搭建一个由三个 Agent 组成的客服系统:

  • 前台分诊员:接收用户请求,判断意图;
  • 退票专员:处理退款;
  • 改签专员:处理航班变更。

它们之间通过 Handoff(转接) 自主协作,无需预设复杂路由逻辑------谁该接手,由当前 Agent 根据上下文实时决定。


1. 环境准备

bash 复制代码
# 仅需一行命令(要求 Python >= 3.10)
pip install openai-agents

2. 定义"共享背包"与"工具手脚"

  • 共享背包(Context):用于在所有 Agent 间传递用户信息;
  • 工具(Tools):Agent 可调用的业务函数,使用 @function_tool 装饰器注册。
python 复制代码
# tools
from agents import function_tool

# 全局上下文,所有 Agent 共享
context_variables = {
    "user_name":"张三(白金会员)",
    "flight_no":"CA1234"
}

# 业务工具
@function_tool
def execute_refund():
    """执行退款逻辑"""
    return "✅️ 退款申请已提交,预计3个工作日内到账。"

@function_tool
def check_seat():
    """查询座位余量"""
    return "✅ 明日航班尚有余票。"

💡 提示:工具写法与 LangChain/LangGraph 类似,只需将 @tool 替换为 @function_tool。

3. 创建并连接 Agent

我们先创建三个 Agent,再通过 handoffs 属性建立"转接网络"。

python 复制代码
# agent.py
from openai import AsyncOpenAI
from agents import Agent,OpenAIChatCompletionsModel,function_tool
from config import OPENAI_API_KEY
from m12_agents_sdk_swarm.tools import execute_refund,check_seat

client = AsyncOpenAI(api_key=OPENAI_API_KEY,base_url="https://api.deepseek.com") # 创建异步OpenAI客户端实例
model = OpenAIChatCompletionsModel(model="deepseek-chat",openai_client=client) # 创建模型实例


# === 1.创建所有Agent(先不指定handoffs) ===

# 退票专员
refund_agent = Agent(
    name="RefundAgent",
    instructions="得知用户想退票后,直接调用execute_refund退票工具,无需确认其他信息。如果用户想改签或咨询其他问题,请转接。",
    tools=[execute_refund],
    model=model
)

# 改签专员
change_agent = Agent(
    name="ChangeAgent",
    instructions="得知用户想改签后,直接调用check_seat改签工具,无需确认其他信息。如果用户想退票或咨询其他问题,请转接。",
    tools=[check_seat],
    model=model,
)

# 前台分诊员(动态提示词)
triage_agent = Agent(
    name="TriageAgent",
    # 动态指令:读取用户背包里的用户名,实现"千人千面"
    instructions=lambda context, __: (
        f"你是前台,客户名:{context.context.get('user_name')}。根据意图转接。"
    ),
    model=model
)
建立转接关系(织网)

为了避免循环引用,我们在所有 Agent 实例化后再设置 handoffs:

python 复制代码
# 前台可转给两位专员
triage_agent.handoffs = [refund_agent, change_agent]

# 专员之间也可互相转接,并能回退到前台
refund_agent.handoffs = [change_agent, triage_agent]
change_agent.handoffs = [refund_agent, triage_agent]

✅ 这样就形成了一个全联通的协作网络 :每个 Agent 都知道自己"能找谁帮忙",但具体找谁,由 LLM 动态决策

4. 启动引擎:事件驱动的流式对话

Agents SDK 采用事件驱动架构,运行时会持续推送各类事件(文本生成、Agent 切换、工具调用等)。我们需要监听这些事件,实时响应。

以下是精简后的主循环逻辑:

python 复制代码
import asyncio
from agents import Runner, set_tracing_disabled

# 关闭 OpenAI 官方 Tracing(避免报错)
set_tracing_disabled(True)

async def main():
    print("✈️ 航空公司客服系统启动...\n")
    messages = []
    current_agent = triage_agent

    while True:
        user_input = input("\n👤 用户: ")
        if user_input == "quit":
            break
        messages.append({"role": "user", "content": user_input})

        # 启动流式执行
        result = Runner.run_streamed(current_agent, input=messages, context=context_variables)
        
        agent_name = None
        is_printing = False

        async for event in result.stream_events():
            # 文本输出(逐 Token)
            if event.type == "raw_response_event":
                if not is_printing:
                    print(f"🤖 [{agent_name or '...'}] ", end="", flush=True)
                    is_printing = True
                print(event.data.delta, end="", flush=True)

            # Agent 切换
            elif event.type == "agent_updated_stream_event":
                new_name = event.new_agent.name
                if agent_name and agent_name != new_name:
                    if is_printing:
                        print()  # 换行
                    print(f"🔀 [转接] {agent_name} → {new_name}")
                    is_printing = False
                agent_name = new_name

            # 工具调用
            elif event.type == "run_item_stream_event":
                item = event.item.raw_item
                if event.name == "tool_called":
                    if is_printing:
                        print()
                    tool_type = "📞 [转接]" if item.name.startswith("transfer_") else "🔧 [工具]"
                    print(f"{tool_type} {item.name}")
                elif event.name == "tool_output":
                    print(f"✅ [结果] {event.item.output}")
                    is_printing = False

        if is_printing:
            print()

        # 更新状态
        messages = result.to_input_list()
        current_agent = result.last_agent

if __name__ == "__main__":
    asyncio.run(main())

📌 设计要点

  • 使用 run_streamed() 实现流式响应;
  • 通过事件类型区分文本、转接、工具调用;
  • 动态更新 current_agent,确保下一轮对话直接与最新 Agent 交互。

测试运行:

运行成功。如感觉展现过程略复杂,可根据实际需求做删改。

5. 实用注意事项(避坑指南)

Agents SDK 仍在快速迭代中,使用时需注意以下细节:

✅ 5.1 Agent 名称必须符合标识符规范

name 字段不支持中文或特殊字符,只能包含字母、数字和下划线(如 "RefundAgent" )。否则可能引发内部解析错误。

✅ 5.2 默认启用 OpenAI Tracing,需显式关闭或配置

SDK 默认尝试上报运行日志到 OpenAI 的追踪服务(类似 LangSmith)。若未提供 API Key,会频繁报错。

  • 不想用 -- 加一行关闭:

    python 复制代码
    from agents import set_tracing_disabled
    set_tracing_disabled(True)
  • 想用 -- https://platform.openai.com/account/api-keys:申请 OpenAI 平台 API Key 并配置:

    python 复制代码
    from agents import set_tracing_export_api_key
    set_tracing_export_api_key("your-openai-api-key")

🔍 Tracing 功能非常有用:可查看每一步推理、工具调用耗时、Token 消耗等,强烈建议在开发阶段开启。

五、深度架构观点:Agents SDK vs LangGraph

经过上文实战,我们可以更深入地探讨 Agents SDKLangGraph 在智能体协作架构上的本质差异,并回答一个关键问题:

为什么说 Agents SDK 的设计(原生异步、事件驱动、弱 Schema)比 LangGraph 更适合构建动态、响应式的多智能体系统?

我们从五个维度进行对比,用"LangGraph 的逻辑"对照"Agents SDK 的实现"。

5.1 运行模式:步骤(Steps) vs 事件流(Event Stream)

  • LangGraph(Step-based)

    本质是一个状态机。执行逻辑是"走一步,停一下":必须等一个 Node 完全执行完毕、状态更新后,才能进入下一个 Node。在涉及工具调用或长思考链的场景中,这会导致较高的 首字延迟(TTFT) ------用户只能看着加载圈,无法获得任何即时反馈。

  • Agents SDK(Event-Driven)

    基于 asyncio 的事件循环模型。通过 Runner.run_streamed() 返回一条实时事件流,即使 Agent 仍在推理或工具尚未返回,系统也能立即推送高层级事件(如"开始调用地图工具"、"即将转接至退票专员")到前端。

python 复制代码
# main.py:消费实时事件流
result = Runner.run_streamed(cur_agent, input=messages, context=context_variables)

async for event in result.stream_events():
    if event.type == "raw_response_event":
        print(event.data.delta, end="")  # 打字机式输出
    elif event.type == "agent_updated_stream_event":
        print(f"🔀 [系统]: {event.old_agent.name} → {event.new_agent.name}")
    elif event.type == "run_item_stream_event" and event.name == "tool_called":
        print(f"🔧 [工具]: {event.item.raw_item.name}")

优势:用户体验更流畅,系统行为透明可感知。


5.2 上下文管理:强类型 Schema vs 动态字典

  • LangGraph(Strict Schema)

    要求预先定义 TypedDict 或 Pydantic 模型来规范 State(如 class State(TypedDict): messages: list)。一旦业务需要新增字段(如 user_tier: str),就必须修改全局 Schema,影响所有节点,扩展成本高、耦合性强

  • Agents SDK(Flexible Context)

    使用标准 Python dict 作为上下文载体(context_variables),天然支持动态扩展。配合 lambda 提示词注入,Agent 可以按需读取任意字段,无需修改核心架构。

python 复制代码
# agent.py:动态上下文注入
context_variables = {"user_name": "张三(白金会员)", "flight_no": "CA1234"}

triage_agent = Agent(
    name="TriageAgent",
    instructions=lambda ctx, _: f"当前用户:{ctx.context.get('user_name')}",
    model=model
)

优势:符合 Python "Duck Typing"哲学,开发灵活,迭代敏捷。


5.3 可扩展性:图结构耦合 vs 配置化解耦

  • LangGraph(Graph)

    新增一个业务分支(如"酒店预订"),需三步:

      1. 定义新 Node;
      1. 在图中 add_node
      1. 最关键的 :修改前置节点的条件边逻辑(add_conditional_edges)。
        节点越多,图越复杂,路由逻辑高度耦合
  • Agents SDK(Handoff)

    只需在"通讯录"中新增一项。Agent 的 handoffs 列表即声明了所有可能的协作对象,路由由 SDK 自动处理,无需修改提示词或条件判断。

python 复制代码
# agent.py:仅需扩展 handoffs 列表
hotel_agent = Agent(name="HotelAgent", ...)

triage_agent.handoffs = [refund_agent, change_agent, hotel_agent]  # 一行搞定

优势:新增业务零侵入,系统可组合性极强。

5.4 MCP 工具集成:适配器转换 vs 原生一等公民

  • LangGraph

    工具被视为外部插件。要集成 MCP Server,通常需借助 langchain-mcp-adapters 等中间层:

      1. 启动 MCP 客户端;
      1. 将 MCP 工具描述转换为 LangChain Tool 格式;
      1. 再绑定到 LLM。
        这不仅增加代码复杂度,还引入额外的序列化/反序列化开销,本质上是"翻译"而非"集成"。 (我们在MCP篇学了非常久,很明显能感知到这点)
  • Agents SDK
    MCP Server 被视为与 Python 函数同级的一等公民。SDK 底层直接封装了 MCP 协议(JSON-RPC over stdio),开发者只需声明连接参数,工具自动注册、自动调用。

python 复制代码
# agent.py:声明式配置 MCP Server
amap_server = MCPServerStdio(
    name="amap",
    params=MCPServerStdioParams(
        command="npx",
        args=["-y", "@amap/amap-maps-mcp-server"],
        env={"AMAP_MAPS_API_KEY": "your_key"}
    )
)

# Agent 直接引用,无需转换
map_agent = Agent(name="地图助手", mcp_servers=[amap_server], model=model)
python 复制代码
# main.py:启动时连接
await amap_server.connect()

​ MCP链接效果如图所示。

优势零胶水代码,工具即服务,无缝融入 Agent 工作流。这也是为何在实战中,Agent 能直接调用 maps_text_search 并返回结构化结果------背后是 SDK 对 MCP 的原生支持,而非层层封装的适配器。
⚠️ 注:此前在P4部分的代码为了表述清晰,并未加入MCP服务。实际的main.py部分是含有MCP工具链接的,具体可参考仓库内原代码。

5.5 流式逻辑:底层事件 vs 业务语义事件

  • LangGraph 的流式输出

    虽然支持异步流式响应(如 astream_events),但它暴露的是底层执行过程中的原始事件 :包括模型生成的 token 片段、节点进入/退出信号等。

    开发者需要手动:

    • 过滤掉空或无效的文本块;
    • 将零散的 token 拼接成可读内容;
    • 并且无法直接获知当前是哪个 Agent 在响应------因为 LangGraph 本身没有"Agent 切换"这一高层事件,只能通过节点名称间接推测。

    这导致流式交互的实现成本较高,逻辑分散在多个判断分支中。

  • Agents SDK 的流式输出

    它对底层细节做了封装,直接提供面向业务场景的结构化事件

    • raw_response_event:代表 LLM 正在生成回复,delta 字段已过滤无效内容,可直接用于打字机式输出;
    • agent_updated_stream_event:明确通知 Agent 发生切换,例如从"前台客服"转到"退票专员",这是 Swarm 协作的核心信号;
    • run_item_stream_event:清晰标识工具调用的开始与结果,便于展示"正在查询酒店..."等中间状态。
python 复制代码
# 直接消费封装好的业务事件
async for event in result.stream_events():

    # 1. 文本流:SDK 已封装好 ResponseTextDeltaEvent,无需过滤空 chunk
    if event.type == "raw_response_event":
         if isinstance(event.data, ResponseTextDeltaEvent):
             print(event.data.delta, end="")

    # 2. Agent 切换事件:这是 LangGraph 没有的!
    # 直接告诉你:现在系统从 Triage 切到了 Refund
    elif event.type == "agent_updated_stream_event":
         print(f"🔀 [系统]: {event.old_agent.name} → {event.new_agent.name}")

    # 3. 工具调用:清晰的 Item 事件
    elif event.type == "run_item_stream_event" and event.name == "tool_called":
         print(f"🔧 [工具]: {event.item.raw_item.name}")

这种设计让开发者能直接响应业务动作,而无需关心底层 token 或执行图的细节,大幅简化了实时交互的实现。


总结:场景适配,而非优劣之分

场景 推荐框架 理由
智能客服 / Copilot / 动态协作 Agents SDK 原生异步、事件驱动、弱 Schema、MCP 无缝集成,适合高响应、低延迟、频繁跳转的交互场景。
审批流 / 代码审计 / 严格状态控制 LangGraph 强 Schema、可回溯、人工介入友好,适合流程固定、需状态持久化与审计的场景。

二者的关系,正如 FastAPI 之于 Django

一个追求响应速度与开发敏捷 ,一个强调结构严谨与流程可控
没有绝对优劣,只有场景适配。

总结、

本篇我们吃透了Swarm模式入手,理解了Handoff的协作思想。又通过实战代码,验证了Agents SDK在原生流式事件、动态上下文和配置化路由的巨大优势。它不仅仅是Swarm的升级,更是Agent应用上基于LangGraph的革新。

预告:13篇 Streamlit 快速入门

至此,我们的Agent集群在后端已近乎完美。下篇M13,我们将引入Streamlit,利用Agents SDK优秀的流式特性,打造一个能够实时显示打字机效果和系统转接动态的现代化Web应用。

它将是我们打通Agent毕业项目的最后一站。

相关推荐
Biteagle4 小时前
P2SH:比特币的「脚本保险箱」与比特鹰的技术解析
区块链·智能合约
+电报dapp1291 天前
以太坊完成合并后,区块链世界究竟迎来了怎样的改变?
安全·去中心化·区块链·智能合约·零知识证明
小宝哥Code1 天前
区块链(Blockchain)—— 概念、架构与应用
架构·区块链
黄菊华老师1 天前
区块链实战:获取Web3.modules 信息展示
web3·区块链
0x派大星1 天前
深入解析 Uniswap:自动做市商模型的数学推导与智能合约架构
架构·区块链·智能合约·uniswap
墨夶1 天前
交易所安全保卫战:从冷钱包到零知识证明,让黑客连边都摸不着!
java·安全·区块链·零知识证明
+电报dapp1291 天前
波场链DAPP智能合约系统开发:解锁Web3.0时代的价值新范式
大数据·人工智能·web3·去中心化·区块链·智能合约·信任链
子昂Web31 天前
关于账户抽象 ERC-4337 协议
区块链
无欢以承1 天前
什么是区块链?从底层原理到实用场景的最强入门指南(含 Web3、DeFi、NFT 全解)
web3·区块链