OpenAI Agents SDK 实战:多智能体协作与工单分类系统开发指南

发布时间:2026/9/28 16:37:44
OpenAI Agents SDK 实战:多智能体协作与工单分类系统开发指南
1. 从零上手 OpenAI Agents SDK一个资深开发者的踩坑与实战笔记OpenAI Agents SDK 刚出来那阵子我身边不少做 AI 应用的朋友都在讨论。有人觉得它不过是把之前那套 Assistants API 换了个壳也有人觉得它终于把多智能体协作这件事给标准化了。我自己花了两周时间把一个原本跑在 LangChain 上的客服工单分类系统迁移到了 Agents SDK 上中间踩了不少坑也摸出了一些门道。这篇文章就是把这整个过程拆开揉碎讲清楚从核心概念到代码落地再到实际部署时遇到的幺蛾子尽量让看完的人能直接上手干活。先说清楚这个 SDK 到底解决什么问题。简单讲它把构建智能体应用时那些重复性的脏活累活给封装了——比如工具调用循环、多智能体之间的任务交接、对话状态的追踪、安全护栏的插入。以前你要自己写 while 循环判断模型是不是要调工具现在 SDK 帮你处理了。它适合谁呢如果你正在做需要多步骤推理、需要调用外部 API、或者需要多个智能体分工协作的场景比如自动化工单处理、智能客服、数据分析助手那这个 SDK 能省你不少事。但如果你只是想要一个简单的问答机器人那可能直接调 Chat Completions API 更轻快。我这次迁移的系统背景是这样的一个电商客服工单分类器需要根据用户描述判断工单类型退款、物流、商品咨询、投诉然后提取关键信息订单号、商品名称、问题摘要最后路由到对应的处理队列。原来的实现用 LangChain 的 AgentExecutor 加自定义工具跑起来没问题但代码比较臃肿而且多轮对话的状态管理经常出 bug。换成 Agents SDK 之后代码量少了大概四成可维护性好了不少。2. 核心概念拆解Agent、Handoff、Guardrail 到底怎么用2.1 Agent 不是聊天机器人它是一个带指令的执行器很多人第一次看 Agents SDK 的文档会以为 Agent 就是那个你发消息它回消息的东西。其实不是。在 SDK 的语境里Agent 是一个配置对象它包含几个关键属性name名字用于日志和交接、instructions系统指令告诉它该干什么、tools它能调用的工具列表、model用哪个模型。你可以把它理解成一个岗位说明书——这个岗位叫什么、职责是什么、能用哪些工具、由谁来执行。我刚开始犯的一个错误是把所有逻辑都塞进instructions里写了一大段类似“你是一个客服助手你需要判断工单类型如果是退款就提取订单号如果是物流就提取运单号……”的提示词。结果模型经常搞混因为指令太长太杂。后来我改成拆分成多个 Agent每个 Agent 只负责一件事一个专门做分类一个专门做信息提取一个专门做路由。每个 Agent 的instructions保持简短明确准确率立刻上去了。这里有个经验instructions最好控制在三到五句话以内只说你希望它做什么不要试图在里面写完整的业务流程。业务流程应该由代码逻辑或者多 Agent 协作来体现。2.2 Handoff 是交接棒不是函数调用Handoff 是 Agents SDK 里比较有特色的一个机制。它允许一个 Agent 把任务转交给另一个 Agent。比如分类 Agent 判断出这是退款工单就 handoff 给退款处理 Agent。这个机制的好处是每个 Agent 可以有自己的工具集和指令互不干扰。但这里有个坑我踩过Handoff 不是同步调用它不会等目标 Agent 处理完再把结果返回给源 Agent。实际上一旦发生 handoff控制权就完全转移了源 Agent 的后续逻辑不会执行。我一开始以为 handoff 像函数调用一样会返回结果结果写出来的代码逻辑全乱了。正确的做法是把 handoff 看作流程的终点——你交给下一个 Agent你的活就干完了。另外Handoff 的触发方式有两种一种是模型自己决定要不要交接通过工具调用的形式另一种是你在代码里显式调用。我建议对于关键路径上的交接用显式调用更可控。比如分类完成后不要在分类 Agent 的指令里写“如果判断为退款就交接给退款 Agent”而是在代码里拿到分类结果后直接handoff到对应的 Agent。这样逻辑清晰也方便调试。2.3 Guardrail 是安检门不是事后检查Guardrail 这个词翻译成“护栏”其实挺准确的。它是在 Agent 执行过程中插入的检查点用来确保输出符合某些规则。比如你可以设置一个 Guardrail检查 Agent 的输出是否包含敏感信息或者是否偏离了预设的话题范围。我实际用下来的感受是Guardrail 最适合做输入输出的格式校验和内容过滤。比如我要求分类 Agent 的输出必须是 JSON 格式包含category和confidence两个字段。如果模型返回的不是合法 JSONGuardrail 就会拦截并触发重试或者报错。这比在代码里写一堆 try-catch 要优雅得多。但要注意Guardrail 会增加延迟因为每次检查都是一次额外的模型调用或者规则匹配。所以不要设置太多 Guardrail只在你真正在意的地方加。我一般只在两个地方加一是最终输出给用户之前二是关键数据提取之后。3. 环境搭建与第一个可运行的 Agent3.1 安装与 API Key 配置安装很简单一条命令pip install openai-agents如果你用 Poetry 或者 Conda对应调整就行。安装完成后你需要设置 OpenAI 的 API Key。官方推荐的方式是设置环境变量export OPENAI_API_KEYsk-...但我在实际项目里更推荐用.env文件加python-dotenv来管理因为这样方便在不同环境之间切换也避免把 Key 硬编码到代码里。具体做法是在项目根目录建一个.env文件写入OPENAI_API_KEYsk-...然后在代码开头from dotenv import load_dotenv load_dotenv()这样 SDK 会自动读取环境变量里的 Key。注意不要把这个.env文件提交到 Git记得加到.gitignore里。3.2 定义一个最简单的 Agent先来看一个最小可运行的例子。假设我们要做一个天气查询助手它能调用一个获取天气的工具。from agents import Agent, Runner, function_tool function_tool def get_weather(city: str) - str: 获取指定城市的天气信息 # 这里模拟一个天气 API 调用 weather_data { 北京: 晴25°C, 上海: 多云28°C, 广州: 小雨30°C } return weather_data.get(city, f暂时没有{city}的天气数据) weather_agent Agent( name天气助手, instructions你是一个天气查询助手。用户询问天气时调用 get_weather 工具获取信息并简洁地回答。, tools[get_weather], modelgpt-4o-mini ) result Runner.run_sync(weather_agent, 北京今天天气怎么样) print(result.final_output)这段代码跑起来会输出类似“北京今天晴天气温25°C”的结果。几个关键点解释一下function_tool装饰器把一个普通的 Python 函数变成了 Agent 可以调用的工具。SDK 会自动根据函数的类型注解和 docstring 生成工具的 schema告诉模型这个工具叫什么、需要什么参数、是干什么的。所以 docstring 一定要写清楚模型就是靠这个来判断什么时候该调用这个工具的。Runner.run_sync是同步执行入口。如果你在异步环境里比如 FastAPI应该用await Runner.run(...)。run_sync内部其实也是跑了一个事件循环所以在异步函数里调用它会报错。result.final_output是 Agent 最终返回给用户的文本。但result对象里还有很多其他信息比如result.messages包含完整的对话历史result.tool_calls包含所有工具调用记录。调试的时候这些信息很有用。3.3 工具函数的参数设计技巧工具函数的参数设计直接影响到模型能不能正确调用。我总结了几条经验第一参数类型尽量用基础类型。str、int、float、bool这些模型理解起来最准确。如果你需要传复杂结构用 JSON 字符串或者拆成多个简单参数。我试过直接传dict类型模型经常构造出格式不对的参数。第二参数名要有描述性。city比c好order_id比oid好。模型会根据参数名来推断该填什么值。第三docstring 里要说明每个参数的格式要求。比如“订单号格式为纯数字字符串”或者“日期格式为 YYYY-MM-DD”。这样模型在提取参数时会更准确。第四工具函数的返回值尽量是字符串。虽然 SDK 支持返回其他类型但字符串最通用模型也最容易理解。如果你需要返回结构化数据可以返回 JSON 字符串然后在 Agent 的指令里说明如何解析。4. 多 Agent 协作实战工单分类系统完整实现4.1 系统架构设计回到我那个客服工单分类系统的例子。整个流程是这样的用户输入一段描述比如“我上周买的鞋子到现在还没发货订单号是 12345能帮我催一下吗”系统需要做三件事判断工单类型物流问题、提取关键信息订单号 12345、问题摘要“催发货”、路由到对应处理队列。我设计了三个 Agent分类 Agent只负责判断工单类型输出四个类别之一退款、物流、商品咨询、投诉。提取 Agent根据工单类型提取对应的关键信息。物流类提取订单号和运单号退款类提取订单号和退款原因等等。路由 Agent根据分类结果和提取的信息决定把工单发到哪个队列并生成一个结构化的工单对象。这三个 Agent 通过 Handoff 串联起来。分类 Agent 完成后 handoff 给提取 Agent提取 Agent 完成后 handoff 给路由 Agent。每个 Agent 的指令都很短职责单一。4.2 分类 Agent 的实现与调优分类 Agent 的指令是这样的classifier_agent Agent( name工单分类器, instructions你是一个客服工单分类器。根据用户描述判断工单类型。 类型只能是以下四种之一退款、物流、商品咨询、投诉。 只输出类型名称不要输出其他内容。, modelgpt-4o-mini )看起来很简单但实际跑起来发现准确率只有八成左右。主要问题出在边界情况上比如“我买的鞋子不合适想退掉”应该算退款还是商品咨询用户描述里同时提到物流和退款怎么办我的优化方法是给每个类别加一句简短的说明放在指令里instructions你是一个客服工单分类器。根据用户描述判断工单类型。 类型只能是以下四种之一 - 退款用户明确要求退款或退货 - 物流用户询问发货、配送、签收相关的问题 - 商品咨询用户询问商品参数、使用方法、库存等 - 投诉用户表达不满、要求赔偿或升级处理 如果同时涉及多个类型选择用户最核心的诉求。 只输出类型名称不要输出其他内容。加了这几句说明之后准确率提升到了九成五以上。所以不要吝啬在指令里写清楚边界定义这比事后加规则过滤要有效得多。4.3 提取 Agent 的工具设计提取 Agent 需要从用户描述里提取结构化信息。我给它配了一个工具function_tool def extract_order_info(text: str) - str: 从文本中提取订单号。订单号是连续的 5 到 12 位数字。 import re matches re.findall(r\b\d{5,12}\b, text) return matches[0] if matches else 未找到订单号这个工具用正则表达式来提取订单号。为什么不让模型直接提取呢因为模型有时候会把无关的数字也当成订单号比如“我打了 3 次电话”里的 3。用正则先过滤一遍准确率更高。但这里有个细节工具函数的 docstring 里写了“订单号是连续的 5 到 12 位数字”这个信息模型是能看到的。所以模型在调用工具之前会先判断文本里有没有符合这个模式的数字。如果没有它可能就不会调用工具而是直接返回“未找到订单号”。这其实是我们想要的行为。提取 Agent 的指令是这样的extractor_agent Agent( name信息提取器, instructions你是一个信息提取助手。根据工单类型提取关键信息。 如果是物流类提取订单号和问题摘要。 如果是退款类提取订单号和退款原因。 如果是商品咨询类提取商品名称和咨询问题。 如果是投诉类提取投诉对象和投诉诉求。 输出格式为 JSON包含 type 和 extracted 两个字段。, tools[extract_order_info], modelgpt-4o-mini )注意这里我要求输出 JSON。但模型有时候会输出带 markdown 代码块的 JSON比如json {...}。这会导致后续解析失败。解决办法是在指令里明确说“直接输出 JSON不要用代码块包裹”或者在代码里做一层清洗。我两种都用了双保险。4.4 路由 Agent 与最终输出路由 Agent 的职责最简单就是根据前面两个 Agent 的输出生成最终的工单对象。它不需要调用任何工具只需要做格式转换。router_agent Agent( name工单路由器, instructions你是一个工单路由器。根据输入的工单类型和提取的信息 生成一个 JSON 格式的工单对象包含以下字段 - queue: 处理队列名称退款队列、物流队列、咨询队列、投诉队列 - priority: 优先级高、中、低投诉类为高退款类为中其他为低 - summary: 一句话摘要 直接输出 JSON不要用代码块包裹。, modelgpt-4o-mini )整个流程的代码大概长这样async def process_ticket(user_input: str): # 第一步分类 classifier_result await Runner.run(classifier_agent, user_input) category classifier_result.final_output.strip() # 第二步提取信息 extract_input f工单类型{category}\n用户描述{user_input} extractor_result await Runner.run(extractor_agent, extract_input) extracted_info extractor_result.final_output # 第三步路由 router_input f工单类型{category}\n提取信息{extracted_info} router_result await Runner.run(router_agent, router_input) return router_result.final_output这里我没有用 Handoff而是用代码显式串联。原因前面说过显式调用更可控也方便在每一步之间加日志和错误处理。Handoff 更适合那种“交给下一个 Agent 我就不管了”的场景比如一个总控 Agent 把任务分发给不同的专业 Agent。5. 常见问题与排查技巧实录5.1 模型不调用工具怎么办这是最常见的问题。你定义了一个工具但模型就是不用直接凭自己的知识回答。原因通常有三个第一工具的 docstring 写得太模糊。模型不知道这个工具能干什么自然就不会调用。解决办法是把 docstring 写清楚包括工具的功能、适用场景、参数格式。比如不要写“获取数据”要写“根据订单号查询订单状态订单号格式为纯数字字符串”。第二Agent 的 instructions 里没有明确要求使用工具。如果你在指令里说“你可以使用工具来获取信息”模型可能觉得不用也行。改成“你必须使用 get_weather 工具来获取天气信息不要凭自己的知识回答”效果会好很多。第三模型本身的能力问题。gpt-4o-mini在工具调用上不如gpt-4o稳定。如果对准确率要求高建议用gpt-4o。我实测下来同样的工具和指令gpt-4o的工具调用准确率比gpt-4o-mini高大概十五个百分点。5.2 输出格式不稳定怎么处理模型输出 JSON 时经常加一些额外的文字比如“好的这是提取结果{...}”。这会导致json.loads失败。我的处理方法是写一个清洗函数import json import re def clean_json_output(text: str) - dict: # 去掉 markdown 代码块标记 text re.sub(rjson\s*, , text) text re.sub(r\s*, , text) # 找到第一个 { 和最后一个 } start text.find({) end text.rfind(}) if start ! -1 and end ! -1: text text[start:end1] return json.loads(text)这个函数先去掉代码块标记然后截取第一个花括号到最后一个花括号之间的内容最后解析。实测下来能解决九成以上的格式问题。剩下的情况就让它重试一次一般第二次就正常了。5.3 多 Agent 协作时的上下文传递Handoff 的时候上下文是怎么传递的我一开始以为源 Agent 的完整对话历史会自动传给目标 Agent结果发现并不是。目标 Agent 只收到 handoff 时的那条消息之前的对话历史需要你自己传。解决办法是在 handoff 的时候把需要的信息拼接到消息里。比如分类 Agent handoff 给提取 Agent 时消息内容应该是“工单类型物流\n用户描述...”而不是只传用户描述。这样提取 Agent 才能知道当前是什么类型该提取哪些信息。如果你用代码显式串联这个问题就不存在因为你可以完全控制每一步的输入。这也是我推荐显式串联的原因之一。5.4 常见问题速查表问题现象可能原因解决方法模型不调用工具docstring 模糊、指令未要求、模型能力不足完善 docstring、指令中强制要求、换用 gpt-4o输出 JSON 解析失败模型加了额外文字或代码块标记用清洗函数截取花括号内容、指令中要求直接输出 JSONHandoff 后信息丢失上下文未正确传递在 handoff 消息中拼接必要信息、改用显式串联响应速度慢Guardrail 过多、模型太大减少 Guardrail、关键路径用 gpt-4o-mini工具调用参数错误参数类型复杂、参数名不清晰用基础类型、参数名描述性、docstring 说明格式6. 性能优化与成本控制的一些实操心得6.1 模型选择的权衡Agents SDK 支持在 Agent 级别指定模型。这意味着你可以给不同的 Agent 配不同的模型。我的做法是分类和路由这种相对简单的任务用gpt-4o-mini信息提取这种需要理解复杂文本的任务用gpt-4o。这样在保证准确率的前提下成本能降下来不少。具体算一笔账gpt-4o的输入价格是每百万 token 2.5 美元输出是 10 美元。gpt-4o-mini的输入是每百万 token 0.15 美元输出是 0.6 美元。差距大概是十六倍。我的系统里分类和路由的调用量占总量的七成用 mini 模型能省下不少钱。但要注意mini 模型在工具调用上确实弱一些。如果你的工具参数比较复杂或者需要多步推理才能确定调用哪个工具那还是老老实实用gpt-4o。省下的钱可能还不够处理错误重试的成本。6.2 缓存与去重很多工单其实是重复的比如“我的快递怎么还没到”这种问题每天都有几十个。如果每个都走一遍完整的 Agent 流程既慢又贵。我的做法是在入口加一层缓存对用户输入做归一化处理去掉标点、转小写、提取关键词然后查缓存。如果命中直接返回之前的结果。缓存的有效期我设的是 24 小时。因为工单状态可能会变化比如昨天用户问“发货了吗”和今天问同样的问题答案可能不一样。所以缓存时间不能太长。另外对于分类 Agent 这种确定性比较高的任务我用了更激进的缓存策略只要用户输入的关键词组合相同就直接复用分类结果。因为分类结果不随时间变化缓存可以设得很长。6.3 日志与可观测性Agents SDK 自带了一些日志功能但默认比较简略。我建议在关键节点手动加日志记录以下信息每次 Agent 调用的输入输出、工具调用的参数和返回值、Handoff 的发生时间和目标 Agent、整个流程的耗时。这些日志在排查问题时非常有用。比如有一次用户反馈分类结果不对我查日志发现是提取 Agent 在 handoff 时把“商品咨询”误写成了“商品咨询类”导致路由 Agent 匹配不到对应的队列。如果没有详细的日志这种问题很难定位。日志的存储我用的就是简单的文件写入按天分割。对于小规模应用足够了。如果量大了可以考虑用结构化日志加 ELK 或者类似方案。6.4 错误处理与重试策略Agent 调用可能会失败原因包括网络超时、API 限流、模型返回异常等。我的重试策略是这样的对于网络超时和限流自动重试三次每次间隔指数退避1秒、2秒、4秒。对于模型返回异常比如输出格式不对重试一次如果还是不行就降级处理——分类失败就默认归为“商品咨询”提取失败就返回空信息路由失败就发到人工处理队列。降级处理很重要因为客服系统不能因为一个工单处理失败就卡住。宁可处理得粗糙一点也不能让用户等太久。7. 从单机到生产部署时需要注意的几个点7.1 并发控制Agents SDK 的Runner.run是异步的天然支持并发。但 OpenAI 的 API 有速率限制如果你同时发起太多请求会被限流。我的做法是用asyncio.Semaphore控制并发数一般设成 10 到 20 之间。具体设多少要看你的 API 配额和响应时间要求。import asyncio semaphore asyncio.Semaphore(10) async def process_with_limit(user_input: str): async with semaphore: return await process_ticket(user_input)这样即使有一百个请求同时进来也只会同时处理十个其他的排队等待。虽然总耗时变长了但不会触发限流导致大量失败。7.2 超时设置每个 Agent 调用都应该设超时。我设的是 30 秒。超过 30 秒还没返回就认为失败走降级逻辑。这个时间可以根据你的模型和任务复杂度调整。gpt-4o处理复杂任务可能需要 10 到 20 秒所以 30 秒是比较安全的。超时的实现可以用asyncio.wait_fortry: result await asyncio.wait_for( Runner.run(agent, input_text), timeout30.0 ) except asyncio.TimeoutError: # 走降级逻辑 pass7.3 监控与告警生产环境一定要有监控。我主要监控三个指标成功率成功处理的工单数除以总工单数、平均耗时、各 Agent 的调用次数。成功率低于 95% 就告警平均耗时超过 10 秒也告警。监控数据我用的 Prometheus 加 Grafana这是比较标准的方案。如果你不想搞这么复杂至少也要把日志收集起来定期人工检查一下有没有异常。7.4 版本管理与回滚Agent 的指令和工具定义是会变的。每次修改都应该记录版本并且保留回滚的能力。我的做法是把 Agent 的配置指令、模型、工具列表写在一个 YAML 文件里代码从 YAML 加载配置。这样修改配置不需要改代码也方便做版本对比。classifier: name: 工单分类器 model: gpt-4o-mini instructions: | 你是一个客服工单分类器...每次修改 YAML 文件提交到 Git就是一个新版本。如果发现新版本效果不好直接回滚到上一个 commit 就行。8. 一些零散但重要的经验关于工具函数的错误处理。工具函数内部如果抛异常SDK 会捕获并把异常信息返回给模型。模型看到异常信息后可能会尝试重新调用工具或者换一种方式处理。这其实是个不错的设计但前提是异常信息要写清楚。比如不要只写raise ValueError(invalid)要写raise ValueError(订单号格式不正确应该是 5 到 12 位数字)。这样模型才知道怎么修正。关于指令的语言。我试过用中文和英文写指令发现对于gpt-4o系列模型英文指令的遵循度略高一些。但差别不大大概两三个百分点。如果你的用户是中文用户用中文写指令也没问题模型完全能理解。关键是指令本身要清晰明确语言不是主要因素。关于测试。Agent 的行为有一定的随机性所以测试不能只测一次。我的做法是对每个 Agent 准备一组测试用例大概 20 到 30 条每条跑三次统计准确率。如果某个用例三次结果不一致就重点分析原因。这种测试方法比单次测试更能发现潜在问题。关于成本监控。OpenAI 的 API 调用是花钱的一定要设置预算告警。我在 OpenAI 后台设了每月预算上限达到 80% 就发邮件提醒。另外每次 Agent 调用都会返回 token 使用量我把这些数据记录下来定期分析哪些 Agent 消耗的 token 最多看看有没有优化空间。关于模型更新。OpenAI 会不定期更新模型版本比如gpt-4o可能会从gpt-4o-2024-08-06更新到更新的版本。模型更新后行为可能会有变化所以每次更新后都要跑一遍测试用例确认准确率没有下降。如果下降了可以暂时锁定到旧版本等分析清楚原因再升级。关于 Guardrail 的粒度。Guardrail 可以加在 Agent 级别也可以加在工具级别。Agent 级别的 Guardrail 检查的是 Agent 的最终输出工具级别的 Guardrail 检查的是工具函数的返回值。我一般只在 Agent 级别加 Guardrail因为工具级别的检查用代码做更高效。比如工具函数返回后直接在代码里判断格式对不对不对就抛异常不需要额外的模型调用。关于多轮对话。Agents SDK 支持多轮对话但需要你自己管理对话历史。Runner.run每次调用都是独立的不会自动记住上一轮的内容。如果你需要多轮对话要把历史消息拼接到输入里。我的做法是维护一个消息列表每次调用前把历史消息和当前输入一起传给 Agent。但要注意 token 限制历史太长要截断或者做摘要。关于流式输出。Agents SDK 支持流式输出用Runner.run_streamed可以逐 token 获取结果。这对于需要实时显示的场景很有用比如聊天界面。但流式输出会增加代码复杂度因为你要处理各种事件类型文本增量、工具调用开始、工具调用结束等。如果不需要实时显示用普通的run就行。关于错误信息的可读性。Agent 报错时错误信息有时候很晦涩比如“Invalid tool call format”。这种信息对调试帮助不大。我的做法是在代码里捕获异常后把相关的上下文输入、Agent 名称、工具名称一起记录下来方便定位问题。关于工具的幂等性。如果你的工具会修改外部状态比如写数据库、发邮件一定要保证幂等。因为模型可能会重复调用同一个工具或者重试时再次调用。我的做法是在工具函数里加一个请求 ID如果同一个请求 ID 已经处理过就直接返回之前的结果不重复执行。关于 Agent 的命名。Agent 的name属性会出现在日志和 Handoff 记录里所以起名要清晰。不要用agent1、agent2这种要用classifier、extractor、router这种描述性的名字。这样看日志的时候一眼就能知道是哪个 Agent 出了问题。关于指令的迭代。Agent 的指令不是一次就能写好的需要反复迭代。我的做法是先写一个初版跑测试用例看哪些地方出错然后针对性地修改指令。每次只改一个地方改完再跑测试确认有提升再继续。不要一次改太多否则不知道是哪个改动起了作用。关于模型的温度参数。Agents SDK 允许设置temperature。对于分类和提取这种需要确定性的任务我设成 0 或者 0.1。对于需要创造性的任务比如生成回复文案可以设成 0.7 到 0.9。温度越低输出越稳定但也越保守。关于 token 计数。每次 Agent 调用后result对象里会有 token 使用信息。我建议把这些数据记录下来定期分析。如果发现某个 Agent 的 token 消耗异常高可能是指令太长或者工具返回的内容太多。优化指令和工具返回值可以有效降低 token 消耗。关于并发时的上下文隔离。如果你用全局变量来存储对话历史并发时会串数据。一定要用局部变量或者请求级别的上下文。Python 的contextvars模块很适合这种场景。关于测试环境的隔离。测试时不要用生产环境的 API Key也不要用生产环境的数据库。我专门建了一个测试用的 OpenAI 项目用独立的 API Key这样测试产生的费用和调用量不会混到生产数据里。关于文档的维护。Agent 的指令、工具的定义、流程的设计这些都要写文档。不然过两个月你自己都忘了为什么这么设计。我的做法是在代码仓库里建一个docs目录每个 Agent 一个 markdown 文件记录它的职责、指令、工具、测试结果和修改历史。关于社区资源。Agents SDK 还比较新社区资源不多。遇到问题可以先看官方文档和 GitHub 上的 examples 目录。如果找不到答案可以在 GitHub 上提 issue或者在一些 AI 开发者的社区里提问。我遇到过几个问题都是在 GitHub issue 里找到的解决方案。关于版本锁定。openai-agents这个包还在快速迭代不同版本之间 API 可能有变化。建议在requirements.txt或者pyproject.toml里锁定版本号比如openai-agents0.0.7。这样不会因为自动升级导致代码跑不起来。升级版本时先在测试环境验证确认没问题再上生产。关于 API Key 的安全。不要把 API Key 写在代码里也不要在日志里打印 API Key。如果不小心泄露了立即在 OpenAI 后台撤销并重新生成。我见过有人把 Key 提交到公开的 GitHub 仓库结果被人盗用跑了一堆请求账单直接爆了。关于成本优化。除了模型选择还有一些小技巧可以降低成本。比如把多个小请求合并成一个大请求减少 API 调用次数。或者用更短的指令减少输入 token。再或者缓存常见问题的回答避免重复调用。这些技巧单独看省不了多少钱但量大了效果就很明显。关于延迟优化。如果你的应用对延迟敏感可以考虑用更小的模型、减少 Guardrail、并行执行独立的 Agent 调用。比如分类和提取如果互不依赖可以同时发起而不是串行等待。这样总耗时能缩短不少。关于错误重试的退避策略。不要用固定间隔重试因为如果是因为限流导致的失败固定间隔重试很可能再次被限流。用指数退避第一次等 1 秒第二次等 2 秒第三次等 4 秒。这样给 API 足够的恢复时间。关于日志的脱敏。日志里可能会包含用户的敏感信息比如订单号、手机号。记录日志时要做脱敏处理比如把订单号只保留后四位手机号中间四位用星号代替。这既是合规要求也是保护用户隐私。关于 Agent 的可解释性。有时候模型会做出一些出乎意料的决定比如把明显的退款工单分类成了商品咨询。这时候你需要能解释为什么。我的做法是在 Agent 的指令里要求它输出推理过程比如“先判断用户的核心诉求是什么再选择对应的类别”。这样即使分类错了你也能从推理过程里看出是哪里理解偏了。关于工具的粒度。工具不要设计得太粗一个工具干太多事。也不要太细导致模型需要调用很多次才能完成一个任务。我的经验是一个工具对应一个独立的、原子性的操作。比如“查询订单状态”是一个工具“修改订单地址”是另一个工具。不要把这两个合并成一个“订单管理”工具。关于 Handoff 的滥用。Handoff 很强大但不要什么都用 Handoff。如果两个 Agent 之间的交互很频繁或者需要来回传递信息那用代码串联更合适。Handoff 适合那种单向的、一次性的任务交接。关于测试用例的设计。测试用例要覆盖正常情况、边界情况和异常情况。正常情况就是典型的用户输入。边界情况比如空输入、超长输入、包含特殊字符的输入。异常情况比如模型返回格式错误、工具调用失败。每种情况都要有对应的测试用例确保系统在各种情况下都能正常工作。关于性能基准。在优化之前先建立一个性能基准。记录当前的成功率、平均耗时、token 消耗。然后每次优化后对比这些指标确认优化有效。不要凭感觉优化要用数据说话。关于模型的降级策略。如果gpt-4o调用失败比如限流可以降级到gpt-4o-mini。虽然准确率会下降但至少能返回结果。等gpt-4o恢复后再切回来。这种降级策略在高可用系统中很重要。关于 Agent 的复用。如果你有多个场景需要类似的 Agent可以把 Agent 的定义抽出来做成配置不同场景用不同的配置实例化。这样避免重复代码也方便统一维护。比如分类 Agent 的指令模板可以复用只是类别列表不同。关于工具的错误返回。工具函数出错时不要直接抛异常而是返回一个描述错误的字符串。比如“查询失败订单号不存在”。这样模型能看到错误信息并决定是重试还是换一种方式处理。直接抛异常的话SDK 虽然也会把异常信息传给模型但格式可能不如你自己控制的那么清晰。关于指令中的示例。在指令里加一两个输入输出的示例能显著提升模型的遵循度。比如分类 Agent 的指令里可以加“示例输入‘我要退款’输出‘退款’”。但示例不要太多两三个就够了太多会占用 token 且可能让模型过度拟合示例。关于多语言支持。如果你的用户可能用多种语言Agent 的指令里要说明“无论用户用什么语言都用中文输出”。否则模型可能会用用户的语言回复导致后续处理逻辑出错。关于 Agent 的版本管理。每次修改 Agent 的指令或工具都应该记录版本号。我用的格式是agent_name_v1、agent_name_v2。这样在日志里能看到是哪个版本处理的请求方便对比不同版本的效果。关于成本分摊。如果你有多个应用共用同一个 OpenAI 账号建议给每个应用分配独立的 API Key。这样在 OpenAI 后台能看到每个应用的用量和费用方便做成本分摊和预算控制。关于 API 调用的重试次数。不要无限重试设一个上限比如三次。超过三次就放弃走降级逻辑。无限重试会导致请求堆积拖垮整个系统。关于 Agent 的冷启动。第一次调用 Agent 时可能会比较慢因为要建立连接、加载模型等。如果对延迟敏感可以在系统启动时先发一个预热请求让 Agent 提前准备好。关于日志的级别。不是所有日志都需要记录。调试信息用 DEBUG 级别正常流程用 INFO 级别异常情况用 ERROR 级别。生产环境一般只开 INFO 和 ERROR避免日志文件过大。关于 Agent 的监控指标。除了成功率和耗时还可以监控工具调用次数、Handoff 次数、Guardrail 触发次数。这些指标能帮你发现潜在问题。比如 Guardrail 触发次数突然增多可能是模型行为发生了变化。关于测试的自动化。手动跑测试用例太慢建议写成自动化测试脚本。每次修改 Agent 配置后自动跑一遍生成测试报告。这样能快速发现回归问题。关于 Agent 的文档字符串。虽然 Agent 本身没有 docstring但你可以在代码里用注释说明这个 Agent 的用途、输入输出格式、依赖的工具等。这样别人看代码时能快速理解。关于工具的测试。工具函数是普通的 Python 函数可以单独测试。写单元测试确保工具函数在各种输入下都能正确工作。工具函数的 bug 会直接影响 Agent 的表现所以测试要覆盖全面。关于 Agent 的输入预处理。在把用户输入传给 Agent 之前可以先做一些预处理比如去掉多余的空格、截断过长的输入、过滤特殊字符。这样能减少模型的理解负担提升准确率。关于 Agent 的输出后处理。Agent 的输出也可以做后处理比如去掉多余的空格、统一大小写、格式化 JSON。后处理能提升输出的规范性减少下游处理的麻烦。关于 Agent 的并发安全。如果多个请求同时调用同一个 Agent 实例要确保 Agent 的状态不会被污染。Agent 本身应该是无状态的所有状态都通过输入输出传递。如果你在 Agent 里存了状态并发时会出问题。关于 Agent 的配置热更新。如果不想重启服务就能更新 Agent 配置可以把配置放在数据库或者配置中心Agent 每次调用时从那里读取。但这样会增加延迟所以一般只在配置变更不频繁的场景下用。关于 Agent 的 A/B 测试。如果你想对比两个不同指令的效果可以同时部署两个版本的 Agent把流量按比例分配然后对比成功率。这是优化 Agent 的有效方法但需要一定的流量基础才能得出统计显著的结果。关于 Agent 的 fallback。如果所有 Agent 都失败了要有一个最终的 fallback比如返回一个默认的工单对象或者把请求转给人工处理。不要让用户看到错误页面。关于 Agent 的限流。除了控制并发数还可以对单个用户或者单个 IP 做限流防止滥用。比如每个用户每分钟最多发起 10 次请求。这能保护系统不被恶意请求打垮。关于 Agent 的审计日志。对于涉及敏感操作的 Agent比如修改订单、退款要记录完整的审计日志包括谁在什么时候发起了什么操作Agent 做了什么决定最终结果是什么。这在出问题时能追溯责任。关于 Agent 的合规性。如果你的应用涉及个人信息处理要确保 Agent 的行为符合相关规范。比如不要在没有授权的情况下把用户信息传给第三方 API不要在日志里记录敏感信息。关于 Agent 的可访问性。如果你的应用面向公众要考虑可访问性。比如 Agent 的输出要能被屏幕阅读器正确读取不要只用颜色来传达信息。关于 Agent 的国际化。如果你的应用面向多个地区Agent 的指令和输出要考虑地区差异。比如日期格式、货币单位、称呼方式等。关于 Agent 的容错设计。假设任何一个环节都可能出错设计时要考虑每个环节的容错。比如分类错了怎么办提取错了怎么办路由错了怎么办每个环节都要有对应的处理策略。关于 Agent 的渐进式增强。不要一开始就追求完美先让基本流程跑通然后再逐步优化。比如先实现分类和路由提取功能后面再加。这样能快速看到效果也能尽早发现架构上的问题。关于 Agent 的代码组织。把 Agent 的定义、工具函数、流程控制分开放在不同的文件里。比如agents.py放 Agent 定义tools.py放工具函数workflow.py放流程控制。这样代码清晰也方便维护。关于 Agent 的依赖管理。Agent 可能依赖外部服务比如数据库、API。这些依赖要管理好比如设置连接池、处理连接失败、做健康检查。不要让外部依赖的故障导致整个系统不可用。关于 Agent 的测试数据。测试数据要真实最好是从生产环境脱敏后拿过来的。用假数据测试可能发现不了真实场景下的问题。关于 Agent 的性能测试。除了功能测试还要做性能测试。模拟高并发场景看系统能不能扛住。性能测试能发现一些功能测试发现不了的问题比如资源泄漏、死锁等。关于 Agent 的容量规划。根据预期的请求量规划好服务器资源、API 配额、数据库容量。不要等到系统扛不住了才扩容。关于 Agent 的灾备。如果 OpenAI 的 API 挂了怎么办要有备用方案比如切换到其他模型提供商或者降级到基于规则的简单处理。灾备方案要定期演练确保真的能用。关于 Agent 的退出策略。如果决定不再使用某个 Agent要有一个退出策略。比如先停止新请求路由到它等存量请求处理完再下线。不要直接删掉否则正在处理的请求会失败。关于 Agent 的知识更新。模型的知识有截止日期如果你的应用需要最新信息要通过工具调用来获取。比如用搜索工具查最新新闻用数据库工具查最新数据。不要依赖模型自己的知识。关于 Agent 的偏见。模型可能会有偏见比如对某些表述方式反应不同。要定期检查 Agent 的输出看有没有系统性的偏见。如果有通过调整指令或者加 Guardrail 来纠正。关于 Agent 的透明度。用户应该知道他们在和 AI 交互而不是真人。在界面上要明确标注。这既是合规要求也是尊重用户。关于 Agent 的反馈机制。让用户可以反馈 Agent 的回答好不好。这些反馈数据可以用来优化 Agent。比如用户点了“不满意”就把这次对话记录下来分析原因。关于 Agent 的持续改进。Agent 不是上线就完了要持续监控、持续优化。定期回顾日志看有没有新的问题模式然后针对性地改进。关于 Agent 的团队协作。如果有多个人维护 Agent要建立协作规范。比如谁负责修改指令谁负责测试谁负责上线。修改要经过 review避免随意改动导致线上问题。关于 Agent 的文档更新。Agent 改了文档也要同步更新。不然文档和实际不一致会误导后来的人。可以把文档更新作为上线流程的一部分不改文档不让上线。关于 Agent 的培训。如果有新成员加入要有人教他们怎么维护 Agent。可以写一个 onboarding 文档把常见操作和注意事项都写清楚。关于 Agent 的社区分享。你在维护 Agent 过程中积累的经验可以分享给社区。一方面帮助别人另一方面也能从别人的反馈中学到新东西。关于 Agent 的未来。这个领域变化很快新的模型、新的工具、新的方法层出不穷。保持学习保持开放的心态随时准备拥抱变化。我在实际使用 Agents SDK 的过程中最大的体会是它确实能简化多 Agent 应用的开发但也不是银弹。很多问题比如输出格式不稳定、工具调用不准确还是需要你自己在代码层面做处理。SDK 提供的是框架和约定具体的业务逻辑和容错策略还是要你自己设计。另外不要过度设计。我一开始想搞一个很复杂的多 Agent 协作系统后来发现其实两三个 Agent 加代码串联就能解决大部分问题。简单可维护比炫技重要得多。