openai-agents-python-sdk 源码解析 | 第二篇:环境搭建与第一个文本 Agent

openai-agents-python-sdk 源码解析 | 第二篇:环境搭建与第一个文本 Agent 本篇导读上一篇我们先建立了 OpenAI Agents Python SDK 的项目地图Agent是声明对象Runner是执行入口Tools、Handoffs、Guardrails、Sessions 和 Tracing 是围绕模型调用组织起来的运行时能力。这一篇开始进入实际运行。目标很明确在当前源码仓库中跑通第一个文本 Agent并理解这几件事本地开发环境应该怎么准备。OPENAI_API_KEY在哪里生效。最小Agent Runner示例长什么样。Runner.run和Runner.run_sync有什么区别。RunResult.final_output、new_items、raw_responses和usage应该怎么看。本文仍然以源码阅读为主但会从一个可运行示例切入。先跑起来再回头看源码理解效率会高很多。当前仓库的开发环境这个项目不是单文件脚本而是一个标准 Python SDK 仓库。核心配置在pyproject.toml常用命令在Makefile。从pyproject.toml可以看到几个关键信息包名是openai-agents。源码包路径是src/agents。Python 版本要求是3.10。开发依赖包含pytest、ruff、mypy、pyright、mkdocs、coverage等。项目使用uv管理 workspace 和依赖。如果你是在当前仓库中开发或阅读源码建议从仓库根目录执行makesync这个命令会执行uvsync--all-extras --all-packages--groupdev它会安装全部 extras、workspace 包和开发依赖。后续运行示例、测试、类型检查都建议使用uv run这样可以保证使用的是当前项目环境。如果你是在一个外部业务项目里使用 SDK可以安装 PyPI 包pipinstall--timeout60--retries3openai-agents不过本系列是源码解析后续默认都在当前仓库中观察实现。配置 OPENAI_API_KEY官方 quickstart 明确要求设置OPENAI_API_KEY。在 macOS 或 Linux 终端中可以这样设置exportOPENAI_API_KEYsk-...这个环境变量只对当前终端会话生效。关闭终端后需要重新设置。在运行示例前可以先确认变量是否存在python-cimport os; print(bool(os.getenv(OPENAI_API_KEY)))输出True表示当前终端已经能读取到 API Key。注意不要把真实 API Key 写入示例代码、博客正文、测试文件或提交记录。示例中保留sk-...占位即可。运行官方最小示例当前仓库里最直接的入门示例是uv run python examples/basic/hello_world.py这个文件内容很短核心逻辑可以概括成importasynciofromagentsimportAgent,Runnerasyncdefmain():agentAgent(nameAssistant,instructionsYou only respond in haikus.,)resultawaitRunner.run(agent,Tell me about recursion in programming.)print(result.final_output)if__name____main__:asyncio.run(main())这就是 SDK 的最小使用闭环创建一个Agent。调用Runner.run。读取result.final_output。如果这段示例能成功运行说明本地依赖、API Key 和基础模型调用链路已经打通。第一个文本 Agent同步版本README 中展示的是Runner.run_sync写法。对于普通命令行脚本这种方式最容易理解fromagentsimportAgent,Runner agentAgent(nameSummaryAgent,instructions你是技术文章摘要助手用三句话概括输入内容。,)resultRunner.run_sync(agent,Agent runtime 负责协调模型、工具和状态。)print(result.final_output)这段代码适合简单脚本。本地快速验证。没有现成 asyncio event loop 的同步环境。但它不适合在已经运行事件循环的环境里直接调用比如某些 Web 框架 handler、Jupyter notebook 或异步任务内部。当前源码中run_sync会检查是否已经存在 running loop如果存在会抛出运行时错误提示不能在已有事件循环中调用同步入口。这不是 SDK 限制异步能力而是为了避免同步 API 和已有事件循环互相干扰。第一个文本 Agent异步版本官方examples/basic/hello_world.py使用的是异步版本importasynciofromagentsimportAgent,Runnerasyncdefmain()-None:agentAgent(nameSummaryAgent,instructions你是技术文章摘要助手。)resultawaitRunner.run(agent,请解释 Agent 和 Runner 的区别。)print(result.final_output)if__name____main__:asyncio.run(main())异步版本适合Web 服务。后台任务系统。需要并发运行多个 Agent 的场景。已经处在 asyncio event loop 中的代码。后续要接入 streaming、Realtime、MCP 等异步能力的场景。本系列后续文章会优先使用异步写法因为 SDK 内部运行、工具调用、MCP、Realtime 和 streaming 都天然更贴近异步模型。Agent 的最小配置现在回到Agent本身。最小示例里只传了两个字段agentAgent(nameSummaryAgent,instructions你是技术文章摘要助手。,)这两个字段分别承担不同职责name给 Agent 一个可识别名称Tracing、handoff 和调试输出中都会用到。instructions告诉模型这个 Agent 的职责和回答方式类似系统指令。instructions不只是普通 prompt。源码中它可以是字符串也可以是一个函数。函数版本会接收运行上下文和当前 Agent用于动态生成 instructions。例如examples/basic/dynamic_system_prompt.py就展示了动态 instructions 的写法。后续第三篇会专门解析Agent的字段设计这里先记住一个原则Agent用来声明“这个智能体应该如何工作”不是用来保存一次运行的临时状态。Runner.run 做了什么Runner.run是异步入口。源码中的 docstring 把运行循环总结为四步使用当前输入调用 Agent。如果模型已经产生最终输出结束循环。如果发生 handoff切换到新的 Agent 后继续循环。如果发生工具调用执行工具把结果交回模型后继续循环。可以用下面的流程理解最终输出工具调用Handoff输入文本Runner.runAgent 配置模型调用结果类型RunResult.final_output执行 Tool切换 Agent第二篇的示例没有工具也没有 handoff所以链路会比较短输入进入Runner.run模型返回文本SDK 把结果包装成RunResult。这也是入门时先跑普通文本 Agent 的原因。它能让我们先看清最短路径再逐步加入工具、handoff、guardrail 和 session。RunResult 应该看什么Runner.run和Runner.run_sync返回的都是RunResult。它不是单纯的字符串而是一次运行的结构化结果。常用字段包括字段含义final_output最后一个 Agent 的最终输出入门阶段最常用new_items本次运行中新产生的消息、工具调用、工具输出等 itemraw_responses底层模型返回的原始响应列表last_agent本次运行最后实际执行的 Agentinput_guardrail_results输入 guardrail 的执行结果output_guardrail_results输出 guardrail 的执行结果context_wrapper.usage本次运行累计的 token 和 request 使用量可以写一个很小的调试函数观察结果defprint_run_summary(result)-None:print(Final output:,result.final_output)print(Last agent:,result.last_agent.name)print(New items:,len(result.new_items))print(Raw responses:,len(result.raw_responses))print(Total tokens:,result.context_wrapper.usage.total_tokens)这里最需要关注的是new_items。当后续引入工具调用和 handoff 后它会记录更多运行过程 item。理解new_items是理解多轮 Agent loop 的基础。usage 从哪里来examples/basic/usage_tracking.py展示了 usage 的读取方式resultawaitRunner.run(agent,Whats the weather in Tokyo?)print(result.context_wrapper.usage.total_tokens)usage挂在context_wrapper上而不是直接挂在final_output上。这符合 SDK 的运行模型token 使用量属于整次 run 的上下文不属于某一段最终文本。当一次 run 中发生多次模型请求时usage会累计请求数、输入 token、输出 token 和总 token。排查成本、性能和循环次数时这个字段很有用。第二轮对话怎么继续quickstart 中提到三种延续对话的方式目标推荐方式完全手动控制历史且保持 provider-agnosticresult.to_input_list()让 SDK 自动加载和保存历史session...使用 OpenAI server-managed continuationprevious_response_id或conversation_id入门阶段可以先理解to_input_list()firstawaitRunner.run(agent,请记住我在学习 Agents SDK。)secondawaitRunner.run(agent,first.to_input_list()[{role:user,content:我在学什么}])print(second.final_output)这段代码表达的是把第一次运行产生的新上下文转换成下一次模型输入然后追加新的用户消息。真实业务中更常用的是 session。因为手动拼接历史容易出错也不利于持久化、重试和上下文裁剪。Session 会在后续单独成篇。常见问题1. 没有设置 API Key现象通常是认证失败或客户端初始化失败。先检查当前终端python-cimport os; print(os.getenv(OPENAI_API_KEY))如果输出为空说明当前终端没有读取到环境变量。2. 在 Jupyter 中调用 run_syncJupyter 通常已经运行了事件循环不适合直接调用Runner.run_sync。应改用resultawaitRunner.run(agent,你的问题)print(result.final_output)3. 示例运行很慢先区分是依赖环境问题还是模型请求问题如果uv run python -c import agents; print(agents.__version__)很慢优先检查环境和依赖。如果导入很快但 Agent run 慢通常是网络、模型响应或工具执行耗时。如果后续加入工具调用需要观察usage、new_items和 tracing。4. 不知道模型实际被调用几次入门阶段先看print(result.context_wrapper.usage.requests)print(len(result.raw_responses))如果后续加入工具一次用户请求可能对应多次模型调用因为工具结果会被送回模型继续生成最终回答。最小调试脚本可以把下面脚本保存为临时文件运行用来观察第一次 run 的核心结果importasynciofromagentsimportAgent,Runnerasyncdefmain()-None:agentAgent(nameSummaryAgent,instructions用两句话解释技术概念。)resultawaitRunner.run(agent,什么是 Agent runtime)print(输出:,result.final_output)print(Agent:,result.last_agent.name)print(Items:,len(result.new_items))print(Tokens:,result.context_wrapper.usage.total_tokens)asyncio.run(main())这段脚本只观察四个值最终输出、最后执行的 Agent、新增 item 数量和 token 使用量。后续引入工具后可以继续用这个脚本结构扩展调试字段。源码入口本篇建议重点看四个文件examples/basic/hello_world.py最小异步示例。docs/quickstart.md官方入门流程。src/agents/run.pyRunner.run和run_sync的实现入口。src/agents/result.pyRunResult字段和 continuation helper。阅读时可以带着三个问题Runner.run的输入参数如何映射到内部AgentRunner.run。run_sync为什么不能在已有 event loop 中调用。RunResult为什么要保留new_items和raw_responses而不是只返回字符串。本篇小结这一篇完成了第一个文本 Agent 的最小闭环当前源码仓库推荐使用make sync和uv run。运行真实模型前必须设置OPENAI_API_KEY。Agent(name, instructions)是最小智能体声明。Runner.run是异步主入口Runner.run_sync适合同步脚本。RunResult.final_output是最终答案但排障时还要看new_items、raw_responses、last_agent和usage。下一篇会深入Agent对象本身重点解析instructions、prompt、handoffs、model、guardrails、output_type和tool_use_behavior等字段的设计。实践任务建议按顺序完成执行make sync准备开发依赖。设置OPENAI_API_KEY。运行uv run python examples/basic/hello_world.py。改写一个自己的SummaryAgent分别用Runner.run和Runner.run_sync运行。打印result.final_output、result.last_agent.name、len(result.new_items)、len(result.raw_responses)和result.context_wrapper.usage.total_tokens。打开src/agents/run.py阅读Runner.run的 docstring。打开src/agents/result.py确认RunResult为什么能支持to_input_list()。