我花了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_KEYOPENAI_BASE_URL 均已正确设置
  • [ ] 用最小测试代码验证环境,再开始写业务逻辑
  • [ ] 所有异步代码用 asyncio.run()await 正确包裹
Task 设计(3条)
  • [ ] 每个 Tool 返回值必须是结构化 dict,包含 status 字段
  • [ ] Tool 的 docstring 写清楚"输入什么、返回什么",Agent 靠这个理解 Tool
  • [ ] 链式任务里,后一步的参数来源必须在 instructions 里明确指定
Prompt 规范(3条)
  • [ ] 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技巧