OpenAI Agents SDK 高级实战:从MCP工具集成到多Agent协作

2 阅读17分钟

摘要

OpenAI Agents SDK近期大幅增强了对MCP(模型上下文协议)的支持,使AI智能体能够像连接USB设备一样动态加载外部工具。同时,多Agent系统正从实验室走向生产环境:每个Agent只专注一件事,多个Agent协同完成复杂任务。本文用通俗的语言和完整可运行的代码,带你掌握MCP集成和两种多Agent设计模式。

一、MCP集成:让AI Agent的工具即插即用

1.1 什么是MCP

在传统开发中,如果你想让AI Agent去查天气、查订单,通常需要把这些功能写成函数,然后在代码里注册给Agent。每增加一个新工具,就要修改Agent的代码并重新部署。当工具有几十上百个时,这种方式的维护成本非常高。

MCP(Model Context Protocol,模型上下文协议)就是为了解决这个问题而诞生的。它是一个标准化的“工具注册与发现协议”。你可以把它理解为AI界的USB接口:MCP Server是提供工具的设备(比如U盘),Agent是电脑主机。Agent启动时自动连接MCP Server,发现上面所有可用的工具,然后就能直接调用。新工具上线时,只需要更新MCP Server,Agent无需任何改动。

MCP的核心包含三部分:

  • MCP Server:运行在某个地址上的服务,专门提供工具
  • MCP Client:Agent内部用来连接Server的组件
  • 协议:规定了Server如何暴露工具,Client如何发现和调用

1.2 为什么需要MCP

一个真实的企业系统可能面临这些挑战:

  • 工具有几十个,分散在不同部门
  • 业务变化快,工具频繁增删改
  • 多个Agent需要共用同一套工具
  • 不同用户有不同的工具权限

传统做法是把所有工具硬编码进每个Agent,这会导致代码臃肿、耦合严重、难以维护。MCP的做法是将工具抽离成独立服务,Agent只负责连接和使用。这样工具可以独立部署、独立升级,多个Agent可以共享同一个MCP Server,权限控制也可以在Server端统一实现。

1.3 基础集成步骤

在写代码之前,先理清两个角色的关系:

  • MCP Server:工具提供方,运行后暴露一个HTTP地址
  • Agent:工具使用方,通过MCP Client连接Server

下面我们分两步实现一个完整的MCP系统。

步骤一:编写MCP Server(工具提供方)

"""
mcp_server.py
启动一个提供天气、订单、通知等工具的MCP服务
安装依赖:pip install openai-agents fastapi uvicorn
"""
 
import uvicorn
from fastapi import FastAPI
from agents.mcp import MCPServer
 
# 创建FastAPI应用作为服务的容器
app = FastAPI(title="MCP工具服务")
 
# ---------- 定义工具函数 ----------
# 这些是普通的Python函数,MCP会自动将它们包装成标准工具
 
def get_weather(city: str) -> str:
    """获取城市天气(模拟实现)"""
    weather_map = {
        "北京": "晴天,25度,空气质量优",
        "上海": "多云,28度,湿度65%",
        "深圳": "阵雨,30度,体感闷热"
    }
    result = weather_map.get(city, f"{city}天气:晴,22度")
    return f"[天气结果] {city} - {result}"
 
def query_order(order_id: str) -> dict:
    """查询订单状态(模拟实现)"""
    orders = {
        "ORD001": {"status": "已发货", "tracking": "SF123456"},
        "ORD002": {"status": "待支付", "message": "请尽快支付"},
    }
    if order_id in orders:
        return orders[order_id]
    return {"status": "订单不存在", "suggestion": "检查订单号"}
 
def send_notification(user_id: str, message: str) -> str:
    """发送通知(模拟实现)"""
    return f"已向用户{user_id}发送通知:{message[:30]}..."
 
# ---------- 创建MCP Server ----------
mcp_server = MCPServer(
    name="企业工具服务",                    # Server名称
    description="提供天气、订单、通知等工具",  # 描述信息
    tools=[get_weather, query_order, send_notification]  # 注册工具列表
)
 
# 将MCP Server挂载到FastAPI应用的/mcp路径下
app.mount("/mcp", mcp_server)
 
print("MCP Server启动,地址:http://localhost:3333/mcp")
print("已注册工具:get_weather, query_order, send_notification")
 
if __name__ == "__main__":
    # 启动服务,监听3333端口
    uvicorn.run(app, host="0.0.0.0", port=3333)

代码解释

  • MCPServer是OpenAI SDK提供的类,它把你的普通函数包装成符合MCP协议的工具。
  • 使用FastAPI的mount方法将MCP Server挂载到特定路由,这样外部就可以通过HTTP访问。
  • 服务启动后,Agent可以通过http://localhost:3333/mcp访问并获取工具列表。

步骤二:编写Agent端代码(工具使用方)

"""
mcp_agent.py
Agent连接MCP Server,自动发现并使用所有工具
安装依赖:pip install openai-agents python-dotenv
"""
 
import os
import asyncio
from agents import Agent, Runner
from agents.mcp import MCPClient
from dotenv import load_dotenv
 
load_dotenv()  # 加载.env文件中的OPENAI_API_KEY
 
# 检查API Key是否配置
if not os.getenv("OPENAI_API_KEY"):
    print("错误:请设置OPENAI_API_KEY环境变量")
    exit(1)
 
async def main():
    # 创建MCP Client,指向刚才启动的Server地址
    mcp_client = MCPClient(
        server_url="http://localhost:3333/mcp",
        name="我的智能助手客户端"
    )
    
    # 创建Agent,通过mcp_servers参数注入MCP Client
    # Agent启动时会自动连接Server,拉取所有工具并注册
    agent = Agent(
        name="企业智能助手",
        instructions="""
        你是企业助手,可以调用以下工具帮助用户:
        - get_weather: 查询天气
        - query_order: 查询订单状态
        - send_notification: 发送通知
        请根据用户需求选择合适的工具。
        """,
        mcp_servers=[mcp_client]  # 关键:将MCP Server注入Agent
    )
    
    # 测试1:查询天气
    print("用户提问:北京今天天气怎么样?")
    result1 = await Runner.run(agent, "北京今天天气怎么样?")
    print(f"助手回答:{result1.final_output}\n")
    
    # 测试2:查询订单
    print("用户提问:帮我查一下订单ORD001的状态")
    result2 = await Runner.run(agent, "帮我查一下订单ORD001的状态")
    print(f"助手回答:{result2.final_output}\n")
    
    # 测试3:复杂任务(多个工具)
    print("用户提问:查询上海天气,然后给用户user123发个提醒")
    result3 = await Runner.run(agent, "查询上海天气,然后给用户user123发个提醒")
    print(f"助手回答:{result3.final_output}")
 
if __name__ == "__main__":
    asyncio.run(main())

运行方式

  1. 先在一个终端运行 python mcp_server.py
  2. 再在另一个终端运行 python mcp_agent.py

你会看到Agent自动调用了MCP Server上的工具来回答问题。添加新工具只需修改Server代码并重启,Agent端无需任何改动。

1.4 通过SSE集成MCP

前面的例子用的是普通的HTTP请求-响应模式。但有些场景需要服务器主动向Agent推送数据,比如实时股票行情、日志监控等。这时可以用SSE(Server-Sent Events)方式。

SSE是一种基于HTTP的实时通信技术,允许服务器持续向客户端推送数据。它比WebSocket更简单,因为只需要单向传输。

"""
mcp_sse_agent.py
使用SSE协议连接MCP Server,适用于实时数据场景
"""
 
import asyncio
from agents import Agent, Runner
from agents.model_settings import ModelSettings
from agents.mcp import MCPServerSse
 
async def main():
    workspace_id = "demo-workspace"  # 用于多租户隔离的标识
    
    # 使用async with上下文管理器,自动处理连接和清理
    async with MCPServerSse(
        name="SSE实时服务器",
        params={
            "url": "http://localhost:8000/sse",  # SSE服务地址
            "headers": {"X-Workspace": workspace_id},  # 可选的请求头
        },
        cache_tools_list=True,  # 缓存工具列表,提升性能
    ) as server:
        
        agent = Agent(
            name="实时智能助手",
            mcp_servers=[server],
            model_settings=ModelSettings(
                model="gpt-4o",
                tool_choice="auto"  # auto表示模型自己决定是否用工具
            ),
            instructions="使用实时数据工具回答用户问题。"
        )
        
        result = await Runner.run(agent, "东京现在的天气怎么样?")
        print(f"助手回答:{result.final_output}")
 
if __name__ == "__main__":
    asyncio.run(main())

注意:实际使用时需要有一个运行在8000端口的SSE类型的MCP Server。上面的代码展示的是Agent端的写法。

1.5 连接多个MCP Server

一个Agent可以同时连接多个MCP Server,自动聚合所有工具。这在微服务架构中非常有用:每个微服务暴露自己的MCP接口,Agent统一接入。

"""
multi_mcp_agent.py
Agent同时连接多个MCP Server
"""
 
import asyncio
from agents import Agent, Runner
from agents.mcp import MCPClient
 
async def main():
    # 创建三个MCP Client,指向不同的Server
    weather_mcp = MCPClient(
        server_url="http://weather-service:3333/mcp",
        name="天气服务"
    )
    
    order_mcp = MCPClient(
        server_url="http://order-service:3334/mcp",
        name="订单服务"
    )
    
    finance_mcp = MCPClient(
        server_url="http://finance-service:3335/mcp",
        name="财务服务"
    )
    
    # Agent一次性注入所有MCP Server
    # 它会自动合并所有Server上的工具
    super_agent = Agent(
        name="全能企业助手",
        instructions="你可以使用公司所有系统的工具来帮助用户。",
        mcp_servers=[weather_mcp, order_mcp, finance_mcp]
    )
    
    # 测试跨系统查询
    result = await Runner.run(super_agent, "查一下订单ORD001的状态,顺便看看北京的天气")
    print(result.final_output)
 
if __name__ == "__main__":
    asyncio.run(main())

对于生产环境,推荐使用MCPServerManager来统一管理多个Server的连接,它支持并行连接、自动重连等高级特性(需要SDK v0.7.0以上)。

1.6 MCP典型应用场景

场景

需求

MCP方案

企业集成

接入CRM、ERP、OA等多个内部系统

每个系统提供独立MCP Server,Agent统一接入

微服务架构

工具分散在不同微服务中

每个微服务暴露MCP接口,Agent动态发现

第三方API

需要调用天气、支付、地图等服务

用MCP包装第三方API,统一管理

权限控制

不同用户可用工具不同

MCP Server根据用户身份动态返回工具列表

A/B测试

测试不同版本工具的效果

MCP Server根据分组返回不同版本的工具

二、多Agent设计模式:从单兵作战到专家团队

2.1 为什么需要多Agent

单Agent系统把所有任务交给一个模型处理,看起来简单,但实际会遇到很多问题:

  • 指令过于复杂:一个Agent需要理解所有领域的知识
  • 性能瓶颈:处理复杂任务时响应慢
  • 错误传播:一处出错可能影响整个系统
  • 难以维护:代码庞大,修改风险高
  • 工具过多:给一个Agent绑定几十个工具时,模型选错工具的概率大增

多Agent系统的思路是拆分:每个Agent只负责一个特定领域,然后通过协作完成复杂任务。这就像组建一个专家团队,而不是指望一个人什么都会。

2.2 两种核心模式对比

OpenAI Agents SDK支持两种多Agent协作模式:

维度

Manager模式(代理人作为工具)

Handoff模式(交接)

控制权

始终在经理手中

在Agent之间转移

用户体验

感觉始终和同一个人对话

感觉被转接给了更专业的人

输出方式

经理整合后输出

专家直接输出

适用场景

统一品牌、集中控制

深度专业对话、部门转接

上下文

专家看到有限信息

专家看到完整历史

下面分别详细介绍。

2.3 Manager模式:中心化编排

Manager模式中,有一个“经理”Agent负责所有用户交互。它将其他专业Agent通过as_tool()方法包装成工具,然后像调用普通工具一样调用它们。经理收集所有专家的输出,整合后统一回复用户。用户全程只和经理对话,感知不到背后有多个专家在协作。

这种模式的典型流程:

  1. 用户向经理提问
  2. 经理分析需求,决定调用哪个或哪几个专家
  3. 经理调用专家Agent(作为工具)
  4. 专家返回结果给经理
  5. 经理整合结果,回复用户
"""
manager_mode.py
Manager模式完整实现:经理Agent调用多个专家Agent
"""
 
import asyncio
from agents import Agent, Runner
 
# ---------- 定义专家Agent ----------
 
def create_booking_agent():
    """订票专家Agent"""
    return Agent(
        name="订票专家",
        instructions="""
        你是机票酒店预订专家。
        当收到预订请求时,你需要收集:出发地、目的地、日期、人数、舱位/房型偏好。
        返回格式化的预订建议。
        """
    )
 
def create_refund_agent():
    """退款专家Agent"""
    return Agent(
        name="退款专家",
        instructions="""
        你是订单退款专家。
        你需要:确认订单号、查询订单状态、判断是否符合退款条件、告知退款流程。
        严格按照退款政策执行。
        """
    )
 
def create_report_agent():
    """数据分析Agent"""
    return Agent(
        name="数据分析师",
        instructions="""
        你是数据分析专家。
        根据用户需求从数据中提取关键信息,生成结构化报告。
        输出格式可以是表格或列表。
        """
    )
 
# ---------- 创建经理Agent ----------
 
async def main():
    # 实例化专家
    booking_agent = create_booking_agent()
    refund_agent = create_refund_agent()
    report_agent = create_report_agent()
    
    # 创建经理Agent,将专家通过as_tool()方法包装成工具
    manager_agent = Agent(
        name="客服经理",
        instructions="""
        你是企业客服经理,负责与用户的所有直接沟通。
        
        工作流程:
        1. 理解用户的完整需求
        2. 判断需要调用哪些专家(可以是多个)
        3. 调用专家工具获取专业输出
        4. 将专家输出整合成统一、友好的回复给用户
        
        可用的专家工具:
        - 咨询订票专家:处理机票酒店预订
        - 咨询退款专家:处理订单退款售后
        - 咨询数据分析师:处理数据报表
        
        重要:你必须以自己的名义回复用户,不要让用户感知到背后有多个专家。
        """,
        tools=[
            booking_agent.as_tool(
                tool_name="咨询订票专家",
                tool_description="当用户询问机票、酒店预订时调用"
            ),
            refund_agent.as_tool(
                tool_name="咨询退款专家",
                tool_description="当用户询问退款、售后时调用"
            ),
            report_agent.as_tool(
                tool_name="咨询数据分析师",
                tool_description="当用户需要统计数据、生成报表时调用"
            )
        ]
    )
    
    # 测试场景1:单一领域
    print("场景1:用户询问订票")
    result1 = await Runner.run(
        manager_agent,
        "我想下周五从北京飞上海,帮我看看航班"
    )
    print(f"经理回复:{result1.final_output}\n")
    
    # 测试场景2:跨领域(需要多个专家)
    print("场景2:用户同时要求退票和重新预订")
    result2 = await Runner.run(
        manager_agent,
        "我之前订的ORD001机票要退掉,顺便帮我看看去杭州的酒店"
    )
    print(f"经理回复:{result2.final_output}\n")
    
    # 测试场景3:不需要专家的简单问题
    print("场景3:用户问营业时间")
    result3 = await Runner.run(
        manager_agent,
        "你们公司几点营业?"
    )
    print(f"经理回复:{result3.final_output}")
 
if __name__ == "__main__":
    asyncio.run(main())

Manager模式的核心要点

  • 经理Agent统一负责用户交互,用户无感知
  • 专家通过as_tool()包装成工具,经理可以像调用函数一样调用它们
  • 专家返回结果后,经理负责整合和美工
  • 适合需要统一品牌形象、集中安全审计的场景

2.4 Handoff模式:去中心化转接

Handoff模式模拟真实企业的“部门转接”流程。有一个路由Agent(总机)负责初次接待,判断用户问题类型后,将整个对话“交接”给最合适的专家Agent。交接完成后,路由Agent退出,专家Agent完全接管后续对话。

这种模式的关键是handoffs参数。路由Agent通过它声明可以转接的目标Agent列表。当模型决定转接时,SDK会自动处理会话转移,并将完整对话历史传递给目标Agent。

"""
handoff_mode.py
Handoff模式完整实现:总机转接到各专业部门
"""
 
import asyncio
from agents import Agent, Runner, RunConfig
 
# ---------- 定义专业Agent(转接目标) ----------
 
def create_booking_expert():
    """订票专家 - 被转接后接管对话"""
    return Agent(
        name="订票专家",
        instructions="""
        你现在完全接管了这个对话。之前的对话记录已经传递给你。
        
        你的职责:
        1. 确认用户的出行信息(日期、目的地、人数、舱位)
        2. 提供可行的航班/酒店选项
        3. 协助完成预订
        
        注意:接下来的所有问题都由你处理,不再转回总机。
        """
    )
 
def create_refund_expert():
    """退款专家"""
    return Agent(
        name="退款专家",
        instructions="""
        你现在完全接管了这个对话。
        
        你的职责:
        1. 获取订单号并查询订单状态
        2. 判断是否符合退款条件
        3. 告知退款金额和预计到账时间
        4. 引导用户完成退款流程
        """
    )
 
def create_tech_expert():
    """技术支持专家"""
    return Agent(
        name="技术支持",
        instructions="""
        你现在完全接管了这个对话。
        
        你的职责:
        1. 了解用户遇到的技术问题
        2. 提供排障建议
        3. 必要时创建工单
        """
    )
 
# ---------- 创建路由Agent(总机) ----------
 
def create_triage_agent():
    """路由Agent:负责初次接待和分流"""
    booking_agent = create_booking_expert()
    refund_agent = create_refund_expert()
    tech_agent = create_tech_expert()
    
    triage_agent = Agent(
        name="客服总机",
        instructions="""
        你是企业客服总机,负责首次接待用户并分流到正确的部门。
        
        根据用户需求判断应该转接给谁:
        - 订票问题(机票、酒店、火车票) -> 转接给 订票专家
        - 退款问题(订单退款、售后) -> 转接给 退款专家
        - 技术问题(App故障、登录问题) -> 转接给 技术支持
        
        转接后你的任务就完成了,由专家继续回答用户。
        """,
        handoffs=[booking_agent, refund_agent, tech_agent]  # 声明可转接的目标
    )
    
    return triage_agent
 
# ---------- 测试 --------
 
async def main():
    triage_agent = create_triage_agent()
    
    print("场景1:用户询问订票")
    print("用户:我想订一张下周去上海的机票")
    result1 = await Runner.run(triage_agent, "我想订一张下周去上海的机票")
    print(f"最终回复:{result1.final_output}\n")
    
    print("场景2:用户询问退款")
    print("用户:我的订单ORD001要退款")
    result2 = await Runner.run(triage_agent, "我的订单ORD001要退款")
    print(f"最终回复:{result2.final_output}\n")
    
    # 场景3:嵌套转接(专家转接给更细分的专家)
    # 注意:v0.7.0后嵌套Handoff需要通过RunConfig显式开启
    print("场景3:复杂问题需要多级转接")
    result3 = await Runner.run(
        triage_agent,
        "我遇到了复杂的退款和重新预订问题",
        run_config=RunConfig(nest_handoff_history=True)  # 开启嵌套转接历史传递
    )
    print(f"最终回复:{result3.final_output}")
 
if __name__ == "__main__":
    asyncio.run(main())

Handoff模式的核心要点

  • 路由Agent通过handoffs参数声明转接目标
  • 转接时目标Agent获得完整对话历史
  • 转接后源Agent退出,目标Agent完全接管
  • 适合模拟真实部门转接、需要深度专业对话的场景

2.5 两种模式的选择指南

情况

推荐模式

原因

需要统一的品牌形象和话术风格

Manager模式

经理统一输出,用户无感知

需要集中权限控制和操作审计

Manager模式

所有调用经过经理,便于拦截和记录

专家输出需要二次加工或美化

Manager模式

经理可以整合和润色

用户希望像真实企业那样被转接给专家

Handoff模式

更符合自然交互预期

专家需要多轮深入对话(如法律咨询)

Handoff模式

专家获得完整上下文

问题类型清晰,可以明确路由

Handoff模式

路由逻辑简单清晰

实际复杂系统也可以混合使用:总机先Handoff给某个部门的经理Agent,然后该经理内部使用Manager模式调用多个子专家。

三、总结

本文介绍了OpenAI Agents SDK的两个高级特性:MCP集成和多Agent设计模式。

关于MCP集成:

  1. MCP将工具从Agent代码中解耦,变成独立的外部服务。Agent通过MCP Client动态发现和调用工具,实现了工具的热插拔。
  2. 一个Agent可以同时连接多个MCP Server,自动聚合所有工具,非常适合微服务和多系统集成的企业场景。
  3. SSE方式适合需要服务器主动推送实时数据的场景。

关于多Agent设计模式:
4. Manager模式(代理人作为工具)让一个经理Agent负责所有用户交互,将其他专家Agent包装成工具来调用。用户全程只和经理对话,适合统一品牌形象和集中控制的场景。
5. Handoff模式通过handoffs参数实现会话转接,路由Agent将对话完整移交给最合适的专家。适合模拟真实部门转接、需要深度专业对话的场景。
6. 两种模式的本质区别在于控制权的归属:Manager模式下经理始终控制,Handoff模式下控制权会转移。

最后,构建多Agent系统时应遵循职责单一原则,每个Agent只关注一个领域,并通过标准化的接口进行协作。同时要重视测试、监控和版本管理,保证系统的稳定性和可维护性。