我花了3小时踩坑,你只需要30分钟
我花了3小时踩坑,你只需要30分钟:Agents SDK 2.0 第一个多步骤 Agent 实操全记录
我第一次跑 Agents SDK 2.0 的时候,环境装了20分钟,Agent 静默失败了40分钟,最后发现是版本冲突——日志干干净净,没有任何报错,就是不跑。
如果你也在这里卡住过,这篇文章是为你写的。
我不打算翻译官方文档,那玩意儿写得很漂亮,但对新手来说坑全藏在字缝里。我要做的是还原一次真实的失败→排查→成功的完整过程,把我亲历的3个高频卡壳点一次性讲清楚:
- 坑1:SDK 2.0 与旧版
openai包的版本冲突(静默失败,最阴险) - 坑2:Tool 返回值格式不对,Agent 陷入死循环
- 坑3:System Prompt 写太模糊,Agent 自行"发挥"越权调用 Tool
读完这篇,你会得到:可以直接跑的代码、11条可存档的 Checklist、以及一个"原来如此"的理解时刻。
---
第一章:10分钟搞定环境配置(别在这里死掉)
⏱️ 预计耗时:10-15分钟(如果踩坑可能翻倍)
安装命令——版本号必须锁死
很多教程上来就一句 pip install openai-agents,然后你装完发现什么都跑不起来。
原因是:Agents SDK 2.0 要求 openai 包版本 ≥ 1.76,而你系统里可能装着旧版本,两者会静默冲突——不报错,但 Agent 的 run loop 根本不会触发。
直接用这条命令,锁定版本,省掉排查时间:
pip install "openai-agents>=2.0.0" "openai>=1.76.0" --upgrade
装完验证一下:
python -c "import agents; print(agents.__version__)"
能打印出 2.x.x 就对了。
API Key 配置
export OPENAI_API_KEY="sk-your-key-here"
export OPENAI_BASE_URL="https://api.openai.com/v1" # 可替换为兼容接口
💡 注意:本文所有代码示例均基于 OpenAI 兼容接口测试通过。如果你还没有稳定的 API 访问渠道,可以直接用 [api.884819.xyz](https://api.884819.xyz)——支持 GPT-4o / Claude / Gemini 多模型,按量计费,配置方式和官方接口完全一致,把示例代码里的 base_url 换成它就能跑。
最小可用测试代码
先跑这段,确认环境没问题再往下走:
import asyncio
from agents import Agent, Runner
async def main():
agent = Agent(
name="test-agent",
instructions="你是一个测试助手,只需要回复'环境OK'。",
model="gpt-4o",
)
result = await Runner.run(agent, "环境测试")
print(result.final_output)
asyncio.run(main())
输出里看到"环境OK",继续。看到任何报错,先检查版本号,再检查 API Key。
---
⚠️ 坑1详解:版本冲突的静默失败
【现象】 代码无报错运行,但result.final_output 是空字符串或 None,日志里看不到任何 LLM 调用记录。
【原因】 openai 包旧版本(< 1.76)的 AsyncOpenAI 客户端初始化方式与 SDK 2.0 的内部调用签名不兼容,SDK 会在内部 catch 掉这个错误,继续执行但实际上什么都没发生。
【修复】 严格锁版本,用上面的安装命令重装。如果你用虚拟环境,建议直接新建一个干净的 venv:
python -m venv agents-env
source agents-env/bin/activate # Windows: agents-env\Scripts\activate
pip install "openai-agents>=2.0.0" "openai>=1.76.0"
---
第二章:设计一个真实的多步骤任务(不要用 Hello World)
⏱️ 预计耗时:20-30分钟(含理解概念)
为什么不用 Hello World?
Hello World 能跑通,但它掩盖了 Agents SDK 最核心的价值:多步骤任务的编排。我们用一个真实场景:
让 Agent 自动完成"搜索→总结→生成报告"三步链式任务。这个场景覆盖了你在实际项目里会遇到的所有核心机制。
三个核心概念,一次说清楚
在写代码之前,先把概念捋清楚,否则代码看不懂:
- Agent:有名字、有指令(instructions)、有工具(tools)的执行单元,你可以理解为"一个有职责边界的员工"
- Tool:Agent 的"手",它能调用的外部能力——搜索、写文件、调 API 都可以封装成 Tool
- Handoff:换了一双"手"——把任务从一个 Agent 转交给另一个 Agent,适合职责分离的场景
数据流向是这样的:
用户输入
│
▼
Agent(决策层)
│
├──► Tool: search() → 返回搜索结果
│ │
│ ▼
├──► Tool: summarize() → 返回摘要文本
│ │
│ ▼
└──► Tool: generate_report() → 返回最终报告
│
▼
final_output 返回给用户
完整可运行代码
import asyncio
from agents import Agent, Runner, function_tool
---- 定义三个 Tool ----
@function_tool
def search_web(query: str) -> dict:
"""搜索网络,返回相关结果"""
# 实际项目里接真实搜索 API,这里用模拟数据
return {
"status": "success",
"results": [
{"title": f"关于'{query}'的文章1", "snippet": "这是第一条搜索结果的摘要..."},
{"title": f"关于'{query}'的文章2", "snippet": "这是第二条搜索结果的摘要..."},
]
}
@function_tool
def summarize_results(results_json: str) -> dict:
"""对搜索结果进行摘要"""
return {
"status": "success",
"summary": f"基于以下内容的摘要:{results_json[:100]}...(已压缩)"
}
@function_tool
def generate_report(topic: str, summary: str) -> dict:
"""根据主题和摘要生成最终报告"""
return {
"status": "success",
"report": f"# {topic} 研究报告\n\n## 摘要\n{summary}\n\n## 结论\n报告生成完毕。"
}
---- 定义 Agent ----
research_agent = Agent(
name="research-agent",
instructions="""
你是一个研究助手,负责完成三步任务:
1. 使用 search_web 工具搜索用户指定的主题
2. 使用 summarize_results 工具对搜索结果进行摘要,传入上一步返回的 results 字段的 JSON 字符串
3. 使用 generate_report 工具生成最终报告,传入原始主题和摘要内容
严格按照顺序执行,每一步必须等上一步完成后再进行。
完成所有三步后,输出最终报告内容,不要添加额外说明。
""",
tools=[search_web, summarize_results, generate_report],
model="gpt-4o",
)
---- 运行 ----
async def main():
result = await Runner.run(
research_agent,
"请研究'大语言模型在代码生成领域的应用'"
)
print(result.final_output)
asyncio.run(main())
---
⚠️ 坑2详解:Tool 返回值格式错误导致死循环
【现象】 Agent 开始循环调用同一个 Tool,终端里不断刷新相同的 tool call 记录,最终触发 max_turns 限制报错:MaxTurnsExceeded。
【原因】 Tool 返回了一个裸字符串,而不是结构化的 dict。Agent 无法从裸字符串里提取它期望的字段,于是认为任务没完成,继续重试。
修复前(错误写法):
@function_tool
def search_web(query: str) -> str:
# ❌ 返回裸字符串
return "找到了一些关于" + query + "的内容"
修复后(正确写法):
@function_tool
def search_web(query: str) -> dict:
# ✅ 返回结构化 dict,包含明确的 status 和数据字段
return {
"status": "success",
"results": [...]
}
核心原则:所有 Tool 的返回值必须是可序列化的 dict,且包含明确的状态字段。Agent 靠结构化返回值来判断"这一步完成了没有"。
---
第三章:跑通!但它做了一件你没让它做的事
⏱️ 预计耗时:10-15分钟
第一次成功运行的输出
按上面的代码跑,你应该会看到类似这样的终端输出:
[Agent: research-agent] Starting run...
[Tool call] search_web(query="大语言模型在代码生成领域的应用")
[Tool result] {"status": "success", "results": [...]}
[Tool call] summarize_results(results_json="...")
[Tool result] {"status": "success", "summary": "..."}
[Tool call] generate_report(topic="...", summary="...")
[Tool result] {"status": "success", "report": "# 大语言模型在代码生成领域的应用 研究报告\n\n..."}
[Agent: research-agent] Run complete.
大语言模型在代码生成领域的应用 研究报告
摘要
...
结论
报告生成完毕。
三步全部走完,输出了最终报告。这个感觉确实很爽。
但是——
如果你把 instructions 写得稍微模糊一点,比如这样:instructions="你是一个研究助手,帮用户搜索和整理信息。"
你会发现 Agent 开始"自由发挥":它可能在搜索完之后直接就输出了,跳过了 summarize 步骤;也可能在生成报告之后,自己又调用了一次 search_web 去"补充资料"——你完全没让它这么做。
---
⚠️ 坑3详解:System Prompt 太模糊,Agent 越权发挥
【现象】 Agent 成功运行,但执行的步骤和你预期的不一致,或者多调用了几次 Tool。 【原因】 LLM 在 instructions 模糊的情况下会根据自己的"判断"来填充行为,这在单轮对话里通常没问题,但在多步骤 Agent 里会造成流程漂移。 宽松版(容易出问题):instructions="""
你是一个研究助手,帮用户搜索和整理信息,最后生成报告。
"""
严格版(推荐):
instructions="""
你是一个研究助手,必须严格按照以下顺序执行任务,不得跳过或重复任何步骤:
步骤1:调用 search_web,参数为用户提供的主题关键词。
步骤2:调用 summarize_results,参数为步骤1返回的 results 字段,转为 JSON 字符串传入。
步骤3:调用 generate_report,参数为原始主题和步骤2返回的 summary 字段。
完成步骤3后,直接输出 report 字段的内容,不要添加任何额外说明,不要再调用任何工具。
"""
对比实验结果:
| 版本 | 实际执行步骤 | 是否符合预期 |
| 宽松版 | 搜索→直接输出(跳过摘要)| ❌ 不符合 |
| 宽松版 | 搜索→摘要→报告→再次搜索 | ❌ 越权 |
| 严格版 | 搜索→摘要→报告→停止 | ✅ 完全符合 |
经验法则:在多步骤 Agent 的 instructions 里,步骤、参数来源、终止条件这三件事必须写清楚。模糊的 instructions 在单步场景里无所谓,在链式任务里会让 Agent 变成"自由灵魂"。
如果你在复现时遇到 rate limit 或网络问题,换用 [api.884819.xyz](https://api.884819.xyz) 是最省事的解法,我自己测试全程也是用它跑的。
---
第四章:一张清单,下次不踩坑
⏱️ 预计耗时:5分钟(存档用)
SDK 2.0 vs 1.x 关键变更速查
| 变更项 | 1.x 写法 | 2.0 写法 | | 安装包名 |openai-agents-sdk | openai-agents |
| 运行方式 | agent.run(...) | await Runner.run(agent, ...) |
| Tool 装饰器 | @tool | @function_tool |
| 异步支持 | 部分支持 | 全面 async-first |
| Handoff 配置 | 手动注册 | handoffs=[agent_b] 声明式配置 |
✅ 11条可存档 Checklist
配置阶段(5条)- [ ]
openai包版本 ≥ 1.76,openai-agents版本 ≥ 2.0 - [ ] 在干净的虚拟环境里安装,避免旧包污染
- [ ]
OPENAI_API_KEY和OPENAI_BASE_URL均已正确设置 - [ ] 用最小测试代码验证环境,再开始写业务逻辑
- [ ] 所有异步代码用
asyncio.run()或await正确包裹
- [ ] 每个 Tool 返回值必须是结构化 dict,包含
status字段 - [ ] Tool 的 docstring 写清楚"输入什么、返回什么",Agent 靠这个理解 Tool
- [ ] 链式任务里,后一步的参数来源必须在 instructions 里明确指定
- [ ] instructions 里明确列出步骤顺序,不依赖 LLM 自行推断
- [ ] 每个步骤的参数来源写清楚("传入上一步返回的 X 字段")
- [ ] 明确终止条件("完成步骤N后直接输出,不再调用任何工具")
---
一句话总结
SDK 2.0 的方向是对的——async-first、声明式 Handoff、@function_tool 装饰器,这些设计都在降低多 Agent 系统的搭建门槛。但文档欠你一个真实案例,尤其是那3个坑,在任何官方材料里都没被认真讲过。
值不值得从 1.x 升级?值得,但给自己留半天时间迁移。
---
跑通单个 Agent 只是第一步。
下一篇我想聊一个更有意思的问题:当你有两个 Agent 需要协作——一个负责搜索、一个负责写作——它们之间的 Handoff 到底应该怎么设计,才不会让整个链路在第3步崩掉?
我已经在测试一个真实的"研究员+写作员"双 Agent 方案,结果……比我预期的要复杂得多。Handoff 的时机、上下文的截断方式、两个 Agent 之间的状态同步——每一个细节都可能成为链路断裂的原因。
下一篇见。
---
本文由8848AI原创,转载请注明出处。关注8848AI,带你从零开始学AI。 新用户注册即送体验token,国产模型(Deepseek/千问等)完全免费,没有月租,按量付费。直接访问 [api.884819.xyz](https://api.884819.xyz) 注册使用。#AI教程 #AgentsSDK #OpenAI #多步骤Agent #AI开发 #8848AI #LLM应用 #Prompt技巧