OpenAI Agents SDK 实战:用 Python 写一个多 Agent 协作系统
这个 SDK 的优点是 API 薄,上手快,三个概念够用。比 LangGraph 的学习曲线平很多,代码也好读。限制在于对非 OpenAI 模型支持不太好。Responses API 是 OpenAI 独有的,切到 Chat Completions API 后有些功能(比如 Guardrails)用不了。如果你的项目需要混合多家模型,LangGraph 或 CrewAI 可能更合适。代码都测过能跑
上周末折腾了两天 OpenAI Agents SDK,把单 Agent、工具调用、多 Agent 交接(Handoff)和 Agent 当工具这几种模式全跑了一遍。记录一下过程,附完整代码,pip install 就能跑。
为什么选这个 SDK
市面上 Agent 框架不少,LangGraph、CrewAI、AutoGen 都有人用。我之前用 LangGraph 写过一个客服机器人,链路一长调试起来头疼,光搞明白 state graph 的数据流就花了大半天。
OpenAI Agents SDK(原来叫 Swarm)走了另一条路:API 很薄,核心概念就三个,Agent、Tool、Handoff。代码量少,出了问题容易定位。缺点也有,绑 OpenAI 的模型比较深,换别的 LLM 要切 Chat Completions API,不是开箱即用。
适合原型验证快的场景,Agent 数量在 5 个以内、不需要复杂的状态管理。
环境准备
Python 3.11+,用 uv 或 pip 都行:
# uv(推荐)
uv init agent-demo && cd agent-demo
uv add openai-agents
# 或者 pip
pip install openai-agents
设置 API Key:
export OPENAI_API_KEY="sk-你的key"
注意:SDK 默认走 Responses API,这是 OpenAI 独有的。如果你想接 DeepSeek 或 Qwen 等其他模型,需要在代码开头加一行切换:
from agents import set_default_openai_api
set_default_openai_api("chat_completions")
模式一:单 Agent + 工具调用
最基础的用法。定义一个 Agent,给它挂一个工具函数,让它自己决定什么时候调用。
import asyncio
from agents import Agent, Runner, function_tool
@function_tool
def search_database(query: str) -> str:
"""在产品数据库中搜索,返回匹配的产品信息"""
# 模拟数据库查询
products = {
"笔记本": "MacBook Pro M4, 价格 14999, 库存 23 台",
"耳机": "AirPods Pro 3, 价格 1899, 库存 156 台",
"键盘": "HHKB Professional, 价格 2499, 库存 8 台",
}
for key, value in products.items():
if key in query:
return value
return "没找到相关产品"
agent = Agent(
name="产品助手",
instructions="你是产品查询助手。用户问产品信息时,调用 search_database 工具查询。回答要简洁,直接给结果。",
tools=[search_database],
)
async def main():
result = await Runner.run(agent, "HHKB 键盘多少钱?还有货吗?")
print(result.final_output)
asyncio.run(main())
运行结果:
HHKB Professional 键盘售价 2499 元,目前库存 8 台。
几个细节:
@function_tool装饰器会自动提取函数的类型注解和 docstring,告诉 LLM 这个工具干什么、怎么调用。docstring 别偷懒不写。- 工具返回值必须是字符串,不是字符串的话 SDK 会报错。
- Runner.run 是异步的,内部走了一个循环:调 LLM → 判断要不要用工具 → 用工具 → 把结果喂回 LLM → 再判断……直到 LLM 给出最终答案。
模式二:Agent 交接(Handoff)
Handoff 是这个 SDK 比较有意思的设计。一个 Agent 处理不了的问题,直接把对话"交接"给另一个 Agent,后者拿到上下文继续干。
场景:客服系统。前台 Agent 判断问题类型,技术问题交给技术 Agent,退款问题交给售后 Agent。
import asyncio
from agents import Agent, Runner
# 技术支持 Agent
tech_agent = Agent(
name="技术支持",
instructions="""你是技术支持工程师。只处理技术问题:安装报错、配置问题、Bug 排查。
回答时给出具体的命令或步骤,不要说"建议联系客服"这种废话。""",
)
# 售后 Agent
refund_agent = Agent(
name="售后处理",
instructions="""你是售后专员。处理退款、换货、物流问题。
退款需要订单号,没提供就先问。金额超过 500 元需要标注"需主管审批"。""",
)
# 前台 Agent,负责分流
router_agent = Agent(
name="前台接待",
instructions="""你是前台接待。判断用户问题类型:
- 技术问题(安装、报错、配置)→ 交给技术支持
- 售后问题(退款、换货、物流)→ 交给售后处理
不要自己回答专业问题,判断完直接交接。""",
handoffs=[tech_agent, refund_agent],
)
async def main():
# 测试技术问题
result = await Runner.run(router_agent, "pip install 的时候报了个 SSL 证书错误,怎么办?")
print("技术问题:", result.final_output)
print("最终处理 Agent:", result.last_agent.name)
print()
# 测试售后问题
result = await Runner.run(router_agent, "我要退货,订单号 20260401-8823,买了个 2999 的显示器")
print("售后问题:", result.final_output)
print("最终处理 Agent:", result.last_agent.name)
asyncio.run(main())
运行结果(大意):
技术问题: 试试这几步:
1. pip install --trusted-host pypi.org --trusted-host files.pythonhosted.org 包名
2. 如果还不行,检查代理设置:echo $https_proxy
3. 更新证书:pip install --upgrade certifi
最终处理 Agent: 技术支持
售后问题: 订单 20260401-8823,显示器 2999 元。
已登记退货申请。因金额超过 500 元,需主管审批,预计 1-2 个工作日内处理。
最终处理 Agent: 售后处理
踩坑记录:
- handoffs 参数接收的是 Agent 列表,不是字符串。前台 Agent 拿到这些 Agent 的 name 和 instructions 摘要,自己判断该交给谁。
- 交接后上下文会传递。用户之前说的话,后面的 Agent 都能看到,不用重复传参。
- result.last_agent 可以看最终是哪个 Agent 处理的,调试时有用。
- 交接只能往一个方向走。售后 Agent 想转回前台的话,需要在它的 handoffs 里也加上 router_agent,但这样容易死循环。实际项目里注意这个坑。
模式三:Agent 当工具用
Handoff 是"交出控制权",而 Agent 当工具用是"借你干个活,结果还给我"。主 Agent 保持控制权,子 Agent 像函数一样被调用。
场景:写一篇技术文章,先让"调研 Agent"搜集资料,再让"写作 Agent"成文,最后主 Agent 汇总。
先看一个用 handoffs 的版本,说明为什么它不够用:
import asyncio
from agents import Agent, Runner
research_agent = Agent(
name="调研员",
instructions="你负责技术调研。给你一个技术主题,你列出 3-5 个知识点,每个点用一两句话说清楚。不要写长文。",
)
writer_agent = Agent(
name="写手",
instructions="你负责写技术段落。给你知识点列表,你把它们写成连贯的技术文章段落。语言简洁,带代码示例。",
)
editor_agent = Agent(
name="主编",
instructions="""你是技术主编。工作流程:
1. 把用户的主题交给调研员,拿到知识点
2. 把知识点交给写手,拿到文章段落
3. 你做最终审校,检查技术准确性,调整格式后输出
调用子 Agent 时直接传入明确的指令。""",
handoffs=[research_agent, writer_agent],
)
async def main():
result = await Runner.run(editor_agent, "写一段关于 Python asyncio 事件循环的技术说明")
print(result.final_output)
asyncio.run(main())
问题在于:用 handoffs 实现的话,控制权会转移,主编没法拿回来做审校。更好的做法是用 Runner.run 嵌套调用,让主 Agent 在工具函数里调子 Agent:
from agents import function_tool
@function_tool
async def do_research(topic: str) -> str:
"""调用调研员 Agent 搜集技术资料"""
result = await Runner.run(research_agent, f"调研这个技术主题:{topic}")
return result.final_output
@function_tool
async def do_writing(notes: str) -> str:
"""调用写手 Agent 根据调研笔记撰写文章段落"""
result = await Runner.run(writer_agent, f"根据以下知识点写技术段落:\n{notes}")
return result.final_output
editor_agent = Agent(
name="主编",
instructions="""你是技术主编。工作流程:
1. 用 do_research 工具调研主题
2. 用 do_writing 工具生成文章
3. 你做最终审校后输出""",
tools=[do_research, do_writing],
)
这样主编始终掌握控制权,调研和写作当工具调用,结果返回后主编可以继续编辑。
实测踩坑:
- 异步工具函数要加 async,不加的话 Runner 内部会报 coroutine 相关的错。
- 子 Agent 调用会消耗额外的 token。每次子 Agent 启动都是一次新的 LLM 请求。主 Agent 加两个子 Agent,一次完整流程大概消耗 3000-5000 tokens(看回复长度)。我跑了 10 次测试,平均每次花 0.03 美元左右。
- 子 Agent 之间没有共享上下文。调研 Agent 不知道写手 Agent 写了什么,写手也不知道调研的过程。所有信息靠主 Agent 中转。这是刻意的设计,保持各个子 Agent 独立。
实际项目中该选哪种模式
跑完这几种模式,我的判断标准是这样的:
工具调用(模式一)适合 Agent 只有一个但需要访问外部数据的场景,比如查数据库、调 API。最简单,90% 的场景够用。
Handoff(模式二)适合多个 Agent 各管一摊、处理完就结束的场景。客服分流是最典型的例子。
Agent 当工具(模式三)适合需要一个主 Agent 编排流程,子 Agent 干活后把结果交回来的场景。内容生产、数据分析报告这类任务比较合适。
写在最后
这个 SDK 的优点是 API 薄,上手快,三个概念够用。比 LangGraph 的学习曲线平很多,代码也好读。
限制在于对非 OpenAI 模型支持不太好。Responses API 是 OpenAI 独有的,切到 Chat Completions API 后有些功能(比如 Guardrails)用不了。如果你的项目需要混合多家模型,LangGraph 或 CrewAI 可能更合适。
代码都测过能跑。有问题评论区聊。
更多推荐


所有评论(0)