这两年做AI应用Agent框架我算是折腾了不少。从最早手撸ReAct循环到后来用LangChain搭复杂链路再到各类编排框架轮番换踩坑踩得多了对框架到底该帮开发者解决什么这件事特别敏感。直到我把OpenAI官方的Agents SDK搬进一个实际项目里做多智能体协作才第一次有种官方终于把该做的脏活都接住了的感觉。这篇是OpenAI Agents SDK构建指南的第一篇目标是带你用一个下午的时间搞清楚三件事这个SDK到底好在哪、核心概念怎么理解、以及怎么从零跑通一个带工具和智能体交接的完整应用。适合两类人——一类是已经用LangChain或者CrewAI写过东西、想对比一下官方方案的另一类是刚接触Agent开发、想找个轻量框架直接上手的小白。我可以负责任地说OpenAI Agents SDK是目前我见过的最克制的框架它没有给你一堆抽象概念但恰恰是这种克制让它特别适合直接在生产里落地。1. 为什么偏偏是它Agents SDK背后的设计逻辑1.1 OpenAI的Agent框架进化路线很多人不知道在Agents SDK出现之前OpenAI官方其实先发布过一个叫Swarm的实验项目。Swarm的设计很有意思它把Agent当成一个可以互相交接的实体用handoff函数在多个Agent之间切换执行权。我当时玩Swarm的时候就觉得这个思路很干净——大量Agent应用本质上就是一个路由执行问题判断请求要谁来处理把控制权交过去处理完再交回来。Swarm的问题在于它确实太实验了没有官方工具集成没有可观测性基本就是一套API的手写封装。Agents SDK可以理解成Swarm的正式生产版。它保留了Swarm里最精华的handoff机制同时补齐了普通业务系统真正需要的三块拼图内置工具全家桶、全链路追踪、输入输出护栏。官方在发布时也明确说了这套SDK要把Agent开发从在代码里控制循环转变为让SDK接管循环你只管配置Agent的行为。这个理念我非常认同。还有个容易被忽略的细节Agents SDK默认走的是OpenAI的Responses API这是专门为Agent设计的新接口。如果你用过Chat Completions会发现Responses API的响应结构里多了一块output里面会明确列出每一步的function_call、message、reasoning等条目也就是说模型内部走了几步、调了哪些工具这件事第一次变成了结构化数据而不是靠人在字符串里硬解析。SDK会自动把这些输出转换成Agent的完整执行上下文这对复杂多步任务的稳定性帮助非常大。1.2 轻量优先能少写就少写我第一次打开Agents SDK的文档第一反应是就这——核心概念屈指可数Agent、Runner、Tool、Handoff、Guardrail、Session。没有Chain没有Graph没有Memory模块没有各种花式抽象。我当时还有点担心这会不会表达能力不够用下来才发现恰恰相反。以LangChain为例早期写一个带工具的Agent要理解PromptTemplate、LLMChain、Tool、AgentExecutor、Memory这一堆概念概念之间还有版本差异升级一次破一次。Agents SDK里就一个Agent对象你把instructions、tools、handoffs、model、guardrails装进去再丢给Runner.run_sync剩下的事情SDK全接管了。这个概念模型极其贴合人类直觉一个Agent就是一个角色加它的工具箱和权限范围。这不是功能阉割而是一种明确的设计取舍。Agent框架最该负责的其实是三件事控制循环、工具调度、上下文管理。这三件事SDK都帮你做了而且做得比多数手写方案稳。你要做的反而是最不该被框架替代的事情——设计指令、设计工具边界、设计人机交互的流程。某种程度上Agents SDK像是在逼你把精力花在刀刃上。1.3 和主流框架的横向对比我用了很长一段时间LangChain也短暂试过CrewAI和AutoGen它们各有特点但很难说哪一个是无脑最优解。我整理了一张对比表按我实际使用的体感打分维度OpenAI Agents SDKLangChain / LangGraphCrewAIAutoGen核心抽象数量极少5个左右非常多学习曲线陡中等角色概念清晰较多ConversableAgent体系Agent循环控制SDK内置不可见但稳定可自定义但需要理解图结构内置配置为主内置支持人机混合循环多Agent协作Handoff原生支持需自行设计图节点角色任务分配群聊模式偏研究官方工具集成好WebSearch/FileSearch等弱依赖第三方一般弱可观测性内置Tracing默认开启需要单独接一般一般上手难度低高中中高适合场景生产级业务系统研究/复杂定向流程自动化任务编排多智能体模拟与讨论我现在的结论是如果你做的是面向用户的业务应用比如客服助手、工单处理、内容工作流、数据分析助手Agents SDK是当前省心程度最高的选择。如果你的核心诉求是画一张复杂的DAG图控制每一步逻辑LangGraph那种图编排模型可能更合适但代价是你得自己料理更多细节。2. 核心概念拆解一个下午吃透全貌2.1 Agent唯一的主角Agent是这套SDK里最核心的结构其他所有概念在某种意义上都是它的组成部分。一个典型的Agent配置长这样from agents import Agent agent Agent( name客服助手, instructions你是一个电商客服负责解答用户的售前售后问题。, tools[...], handoffs[...], modelgpt-4o, input_guardrails[...], output_guardrails[...], )有几个细节值得展开说。第一instructions是Agent行为的根。官方文档里专门建议当指令内容比较多时不要硬拼字符串而是放在一个Markdown文件里加载进来比如instructionsMARKDOWN(TEXT2.md)这种写法在Starter App的模板里更常见。我实际用下来发现把系统提示词单独维护成文档比写在一大段Python字符串里好改得多尤其当Agent数量变多时这个习惯能救你命。第二dynamic instructions是一个很实用的隐藏功能。instructions可以传一个函数SDK会在每次执行前调用它根据当前上下文动态生成指令。举个例子一个翻译Agent可以根据会话语言返回不同语气的指令再比如一个客服Agent可以根据用户等级动态调整服务话术。这个机制让同一套Agent代码可以适配多种场景省掉了很多为了换提示词而复制Agent的蠢笨做法。第三一个容易踩坑的点Agent不是越多越好。Handoff确实能解决多Agent分工问题但每多一个Agent路由判断就多一次模型推理延迟和成本是实打实上涨的。我当时做客服系统时设计了四个Agent结果用户一个问题进来要经过两三次交接响应慢了一倍多。后来砍到两个逻辑化简了很多体验反而更好。所以设计初期的原则应该是能用工具解决的不要单独开Agent能两个Agent解决的不要开第三个。2.2 Tool让Agent长出手脚没有工具的Agent只是个聊天机器人有了工具才能做事。SDK里定义工具最简单的方式就是function_tool装饰器from agents import Agent, Runner, function_tool function_tool def get_weather(city: str) - str: 查询指定城市的实时天气返回适合出行的简短建议。 return f{city}今天晴朗气温25摄氏度适合出门。 agent Agent( name天气助手, instructions根据用户提问的城市调用天气查询工具并回答。, tools[get_weather], ) result Runner.run_sync(agent, 上海今天适合出门吗) print(result.final_output)这个函数有四个细节是刚用的人最容易忽略的。函数必须有类型注解。SDK会依据函数的签名自动生成模型的JSON Schema如果参数没有类型注解模型拿到的是残缺描述大概率会乱传参。我第一次写的时候有个参数漏了类型结果模型每次调用都把数字当字符串传函数内部还得做转换排查了半天。文档字符串不是可选项而是模型的工具说明。你有没有发现我给get_weather写的docstring里包含了返回适合出行的简短建议这种话这其实是告诉模型这个工具返回什么语义模型会据此决定如何组织自然语言回复。docstring写得越像功能说明返回值含义触发率和准确性越高。如果你的函数需要更细的参数描述可以在function_tool里传description和params_json_schema做补充覆盖比如给city参数加一个必须是中文城市名的约束。这个在参数形态比较微妙的时候特别有用。第三不要在原函数里做大量Agent侧的决策逻辑。工具应该是一个尽量纯的接口——输入参数返回结构化结果。把业务规则往工具里塞会导致模型输出难以解释排查问题时非常痛苦。2.3 Handoff多Agent协作的官方姿势Handoff是Agents SDK的灵魂级特性也是从Swarm时代延续下来的核心设计。简单说Handoff允许你把另一个Agent声明为当前Agent的接管者当模型判断话题超出自己能力范围时把执行权连同全部上下文一起交过去。from agents import Agent, Runner, handoff billing_agent Agent( name计费助手, instructions你负责处理订单、发票、退款等财务问题回答要专业且简洁。, ) support_agent Agent( name客服主管, instructions你是客服主管。用户问题如果涉及订单、发票或退款必须转交给计费助手处理。, handoffs[billing_agent], ) result Runner.run_sync(support_agent, 我想退款订单号是12345) print(result.final_output)这里要澄清一个常见误解Handoff不是当前Agent调用另一个Agent的工具而是当前Agent主动让出执行权的机制。SDK内部为handoffs列表里的每个Agent生成一个特殊的Handoff工具模型发现自己的指令范围覆盖不了时会发起一次handoff_tool调用然后SDK把对话历史、任务上下文、当前工具调用结果一起转交给目标Agent继续执行。这意味着流水线式的协作变得极其自然一个入口Agent负责意图识别和基础问答业务问题路由给订单Agent技术问题路由给技术Agent未知问题路由给兜底Agent。每个Agent只关心自己的指令和工具集不需要互相感知对方的内部细节。这种一个前台一群专家的结构正好是大多数客服、工单、企业内部系统需要的样子。我在实际项目里用Handoff重构了一个原本用LangChain硬编码if-else逻辑的工单系统代码量砍了差不多60%而且新增业务类型时不需要改路由代码加一个Agent挂到handoffs列表里就行。这种可扩展性非常香。2.4 Guardrail给Agent装上护栏生产环境里Agent最让人不放心的是它可能跑偏——回答超纲、触发危险操作、输出不合规内容。Guardrail就是官方提供的一个闸门机制分InputGuardrail和OutputGuardrail分别拦截输入和输出。from agents import Agent, Runner, InputGuardrail, GuardrailFunctionOutput from pydantic import BaseModel class SafetyOutput(BaseModel): is_safe: bool reasoning: str safety_agent Agent( name输入检查员, instructions判断用户输入是否包含恶意指令或危险操作输出JSON。, output_typeSafetyOutput, ) async def safety_guardrail(ctx, agent, input_data): result await Runner.run(safety_agent, input_data, run_configctx.config) return GuardrailFunctionOutput( output_inforesult.final_output, tripwire_triggerednot result.final_output.is_safe, ) agent Agent( name客服助手, instructions你是一个客服助手。, input_guardrails[InputGuardrail(guardrail_functionsafety_guardrail)], )Guardrail的判断逻辑通常也需要模型参与所以上面的代码里我用了一个独立的safety_agent来做安全性判断。tripwire_triggered为True时SDK会抛出一个InputGuardrailTripwireTriggered异常你的代码可以捕获它并中止后续流程。这样设计的好处是护栏逻辑和主任务逻辑完全解耦护栏坏了不会连累主Agent。我踩过的坑是不要在一个Agent的Guardrail里再引用这个Agent自己否则试试看RecursionError直接教你做人。Guardrail里用的判断模型应该是独立的、尽量轻量的指令要极简只做判断不做事。前面那个safety_agent就非常轻。2.5 Session与Tracing状态与可观测性聊天的多轮上下文在Agents SDK里由Session管理。Runner.run_sync返回的结果对象里带一个session_id多轮对话时把它传回去Agent就能记住之前的对话内容result_1 Runner.run_sync(agent, 记住我的名字是小王) session_id result_1.session_id result_2 Runner.run_sync(agent, 我叫什么名字, session_idsession_id) # result_2.final_output 小王不传session_id的话每次run都是全新会话你的业务系统如果想做用户再次访问时记住之前的对话就必须自己把session_id存下来再回传。这个机制比在Prompt里疯狂塞历史消息优雅得多也方便做会话过期和清理。再看看Tracing。SDK默认会为每次运行生成完整的追踪记录——模型调用、工具调用、Handoff路径、耗时——都会传到OpenAI的Dashboard上。调试多Agent协作时我绝大多数情况都靠这个面板看执行链路一眼就能看出模型是在工具上卡住了还是被Guardrail拦住了还是在Handoff链路上绕圈子。有一点需要注意如果你用的是非OpenAI模型或本地模型Tracing会因为拿不到对应项目信息而在后台反复报错。这时候需要显式关闭from agents import set_tracing_disabled set_tracing_disabled(True)我们后面会在常见问题里再展开讲这个。3. 从零构建一个能跑的多Agent项目3.1 环境准备与安装我用的是Python 3.11实测3.9及以上都可以跑。安装方式很常规pip install openai-agents或者用uvuv add openai-agents装完之后把API密钥配好export OPENAI_API_KEYsk-你的密钥这里有个细节SDK默认走Responses API同样需要OPENAI_API_KEY。如果你是用Azure OpenAI或者第三方兼容接口官方推荐的是在Provider层面做自定义配置但建议第一次上手时直接用官方API跑通再做调整别一上来就在兼容层上折腾会平白增加很多变量。3.2 第一个Agent先能聊天再说老规矩先来一个Hello World级别的Agentfrom agents import Agent, Runner agent Agent( name万能助手, instructions你是一个乐于助人的AI助手。, modelgpt-4o-mini, ) result Runner.run_sync(agent, 用一句话介绍你自己。) print(result.final_output)Runner.run_sync是同步入口适合脚本和快速测试。异步场景用Runner.run(agent, input)流式输出用Runner.run_streamed(agent, input)。三种模式底层逻辑一致只是暴露方式不同。打印result.final_output能拿到最终回答文本这个最常用。此外result.items会返回完整的执行痕迹列表包括每一步的模型消息和工具调用调试时比只看最终文本有用得多。我建议你把result.items打印出来看一眼它能帮你建立一次run内部到底发生了什么的直觉。3.3 给Agent接上工具搜索与自定义函数再来一个实用的搜索Agent。Agents SDK自带了WebSearchTool开箱即用from agents import Agent, Runner, WebSearchTool agent Agent( name研究助手, instructions你是一个信息检索专家用搜索工具回答用户的问题并给出信息来源。, tools[WebSearchTool()], ) result Runner.run_sync(agent, 2025年最值得关注的几个开源AI项目是什么) print(result.final_output)WebSearchTool会自己决定什么时候搜索、搜几次你不需要关心底层的API细节。如果需要文件内检索还有FileSearchTool可以把向量检索能力直接挂给Agent。自定义函数工具的写法在2.2已经演示过。这里补充一个和搜索配合的完整示例——做一个本地知识库网络搜索双通道Agentfunction_tool def query_local_docs(keyword: str) - str: 在内部知识库中检索与关键词相关的文档摘要。 # 这里可以换成真实的向量库查询 return f内部文档《{keyword}操作手册》提到需要先备份配置再重启服务。 agent Agent( name智能客服, instructions优先使用本地知识库工具回答问题本地知识库无法覆盖时再用网络搜索补充。, tools[query_local_docs, WebSearchTool()], )工具列表的顺序和描述会影响模型的选择频率这种本地优先、网络兜底的编排在业务系统里非常常见。你可以通过调整工具描述的措辞让模型知道什么时候应该选哪个工具。3.4 多Agent交接客服工单系统小例子现在把前面学的概念串起来做一个简化版的客服工单系统。需求是用户输入工单系统判断是退款类问题还是技术支持类问题分别交给对应的专家Agent处理。from agents import Agent, Runner, handoff refund_agent Agent( name退款专家, instructions你负责处理退款申请。请引导用户提供订单号并说明退款时效为3-5个工作日。, ) tech_agent Agent( name技术支持, instructions你负责处理产品使用问题。请引导用户描述操作步骤和错误提示。, ) triage_agent Agent( name客服前台, instructions( 你是客服前台。先简单回应客户问题。 如果客户提到退款、发票、订单金额转交给退款专家。 如果客户提到报错、无法使用、功能异常转交给技术支持。 其他问题由你自己回答。 ), handoffs[refund_agent, tech_agent], ) result Runner.run_sync(triage_agent, 我昨天买的东西坏了想申请退款。) print(result.final_output) # 预期会走到 refund_agent输出退款引导话术跑这段代码你会发现result.final_output是最终接收方Agent的回答而中间路由逻辑的痕迹在result.items里可以看到。这就是Handoff的现实形态——买个东西坏了想退款triage_agent判断这是退款请求把工单转给refund_agent由它完成最后的服务。这个看起来简单的模式在生产里能解决一个很头疼的问题业务越来越复杂时不可能把所有指令塞给一个Agent。用Handoff做专业分工每个Agent维护自己的指令和工具整体系统的可维护性会好很多。我在4.1节还会提到一个与Handoff容易混淆的方案——Agent作为工具agents as tools它们的适用场景是有区别的。3.5 模型切换与参数调整Agents SDK默认使用gpt-4o级别模型但你完全可以显式指定。在Agent里加modelgpt-4o-mini可以省成本需要更强推理能力时换modelgpt-4o。除了按名字选还有两种更精细的姿势值得了解。一种是在Agent构造时传入model_settings比如调整温度from agents import ModelSettings agent Agent( name作文助手, instructions你是一个创意写作助手。, modelgpt-4o, model_settingsModelSettings(temperature0.8), )另一种是全局切换API模式。如果你需要走Chat Completions接口而不是Responses APISDK也留了门from agents import set_default_openai_api set_default_openai_api(chat_completions)这个开关对某些兼容性场景有奇效但要记住Responses API专属的一些功能比如部分内置工具在Chat Completions模式下不能完全对齐。我的建议是新项目优先走默认的Responses API只有当你确定要对接一个只支持Chat Completions的网关时才切。4. 常见问题与排查技巧实录4.1 环境兼容性版本和依赖踩过的坑我最早用pip install openai-agents装完一跑就报ImportError: cannot import name Agent from agents。原因几乎都是环境里存在同名agents包或者openai版本太旧。SDK依赖openai1.66.0这个版本才包含Responses API的Python绑定。如果你同时装了旧版openai建议在虚拟环境里重新装一遍或者用uv这类工具保证依赖干净。另外Python 3.8及以下直接不支持别浪费时间升版本就好。SDK本身对系统依赖很少这也是我推荐它的原因之一——坑主要集中在环境层面而环境问题多数可以通过干净的虚拟环境规避。4.2 Agent循环里拿不到工具结果这是SDK文档里明确有章节讲的一个设计限制当Agent作为工具使用时你不能把模型生成的文本当作工具的结果然后直接传回给外层代码。换句话说一个function_tool如果内部调用了另一个Runner.run它的返回值必须是你自己构造的而不是那个内部Runner的final_output字符串。官方文档的原话大意是如果是Agent作为工具模式你在工具内部拿不到模型最终生成的那段文本只能在run的items里拿到结构化输出所以工具函数必须自己构建返回内容。实际业务里如果你确实需要让一个Agent调用另一个Agent后再基于结果继续推理最合适的做法是使用Handoff而不是Agent-as-tool。我一开始试图用Agent-as-tool串起多个流程用着用着就撞上这个限制后来改成Handoff思路逻辑顺了很多。这里选型时的判断标准是需要让出执行权选Handoff需要把另一个Agent封装成纯工具供当前Agent随心调用可以接受输出是结构化数据的选Agent-as-tool。4.3 Guardrail递归不要把Agent放进自己的护栏这个问题我前面提了一句但值得单独拿出来说因为它真的会报错。如果你在safety_agent的input_guardrails里又挂了一个指向它自己或指向外层Agent的Guardrail启动时可能不报错一旦触发就会导致递归调用直到栈溢出。原因很好理解Guardrail要判断输入是否安全为了做这个判断它会调用一个Agent这个Agent又带了GuardrailGuardrail又要调用Agent……死循环。正确的做法是Guardrail里用的判官Agent保持裸奔状态——不给它挂任何Guardrail指令也尽量短只要判断并输出结构化结果这一件事。把它定位成一个独立且纯净的审查组件。如果你觉得这样不够安全可以在外层框架层面再做一遍过滤不要在Agent内部嵌套多层Guardrail。4.4 Tracing报错本地模型怎么关追踪我有一段代码用set_default_openai_api(chat_completions)对接一个本地兼容接口跑起来功能正常但控制台一直被Tracing报错刷屏。原因就是SDK默认把Tracing数据往OpenAI的服务端上报但本地接口和OpenAI官方没有关联的项目信息上报自然失败。解决办法分两种。如果你项目里全是第三方模型/本地模型不用OpenAI Dashboard做追踪直接关掉from agents import set_tracing_disabled set_tracing_disabled(True)如果你还要保留追踪能力则可以通过OpenTelemetry把Tracing导出到你自己的监控系统。SDK支持标准OpenTelemetry协议接入Jaeger或自建监控都不难。这里建议先关掉跑通逻辑再按需接入别让可观测性问题阻塞核心功能的开发。4.5 会话断线多轮对话必须传session_id接SDK做业务系统时最容易忽略的就是session管理。之前做开发时开始我为了省事每次请求都创建一个新会话结果用户第二次提问时Agent完全不记得之前说过什么。你如果遇到昨天还在聊的事情今天Agent像失忆一样先检查是不是没把session_id传回去。正确的做法是在业务层维护一个用户ID - session_id的映射。用户发起新对话时把历史session_id传给Runner.run。还需要注意session_id的有效期问题长期不活跃的会话可能在服务端被清理所以代码里要做好捕获session过期异常后重新开会话的兜底逻辑。4.6 问题速查表我把上面这些坑整理成一个速查表方便你遇到问题时直接对号入座现象常见原因解决方案导入agents失败openai版本过低/环境包冲突升级openai到1.66干净虚拟环境重装Agent不调用工具函数缺类型注解或docstring补全签名和描述确认tools[...]已传入多轮对话丢失记忆未传session_id持久化session_id下次run时回传Guardrail栈溢出Guardrail内嵌自身或相互嵌套判官Agent保持裸奔不挂Guardrail控制台Tracing刷错第三方/本地模型上报失败set_tracing_disabled(True)或接入OTelAgent-as-tool拿不到文本结果框架设计限制改用Handoff交接或让工具自行构造返回响应太慢多Agent链路过长精简Agent数量优先用工具解决我在实际使用中最大的体会是Agents SDK的坑大多数不是框架逻辑有bug而是人还在用过去的思维写代码造成的。它把Agent循环交给了SDK你的核心工作就变成了指令设计、工具设计和协作拓扑设计。方向对了这个框架用起来会非常顺手。这篇先聊到这里我已经把概念和第一个完整项目跑通了。接下来我打算写第二篇深入Handoff和Guardrail在生产里的真实用法包括怎么设计一套多Agent交接的权限控制、怎么在不牺牲响应速度的前提下做输出安全校验以及在复杂业务中怎么基于Tracing的数据做性能调优。有兴趣的可以留意更新。