上周末折腾了两天 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 台。

几个细节:

  1. @function_tool 装饰器会自动提取函数的类型注解和 docstring,告诉 LLM 这个工具干什么、怎么调用。docstring 别偷懒不写。
  2. 工具返回值必须是字符串,不是字符串的话 SDK 会报错。
  3. 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 可能更合适。

代码都测过能跑。有问题评论区聊。

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐