OpenAI Agents SDK 中文教程:从代理配置到实战应用
1. 从零开始:认识你的AI代理
如果你对AI应用开发感兴趣,最近肯定听说过OpenAI的Agents SDK。简单来说,它就像是一个功能强大的“AI代理工厂”,让你能用几行Python代码,就组装出一个能思考、会行动、甚至可以和其他AI“同事”协作的智能体。我刚开始接触时,觉得这玩意儿肯定很复杂,但实际用下来发现,它的设计思路非常清晰,只要你懂一点Python,就能快速上手。
这个SDK的核心思想,是把大型语言模型(LLM)从一个单纯的“聊天机器人”,变成一个可以调用工具、执行任务、甚至自主决策的“代理”。想象一下,你不再只是问模型“今天天气怎么样?”,而是可以创建一个专门的“天气代理”,它自己知道去调用天气API获取数据,然后整理成你想要的格式(比如一首俳句诗)告诉你。整个过程,你只需要配置好代理的“大脑”(模型)和“双手”(工具),它就能自己跑起来。
那么,谁适合学习这个呢?我觉得有三类朋友会特别受益。第一类是产品经理或业务同学,你想快速验证一个AI自动化流程的可行性,用这个SDK可以省去大量底层开发的麻烦。第二类是Python开发者,你想在自己的应用里集成智能对话或自动化能力,这个SDK提供了非常优雅的封装。第三类就是像我这样的AI爱好者,喜欢折腾新工具,用它来构建一些好玩又有用的个人助手,成就感满满。接下来,我就带你一步步拆解,从最基础的配置,到实战中可能会遇到的“坑”,咱们一起把这个强大的工具用起来。
2. 搭建你的第一个AI代理:配置详解
万事开头难,但配置第一个代理其实非常简单。咱们先抛开所有复杂概念,聚焦在最核心的三要素上:指令(instructions)、模型(model)和工具(tools)。这就好比给你的AI代理赋予人格、大脑和技能。
2.1 核心三要素:指令、模型与工具
指令,就是代理的“人格设定”和“工作指南”。你告诉它:“你是一个总用俳句回答问题的诗人”,或者“你是一个严谨的数据提取专家”。这个指令会作为系统提示词(system prompt)注入给模型,从根本上引导它的行为风格。我建议指令写得越具体、越有场景感越好。比如,与其写“你是一个助手”,不如写“你是一个专注于编程问题解答的助手,回答要简洁,优先提供代码示例”。
模型的选择决定了代理的“智力水平”。SDK默认使用OpenAI的模型,你可以直接指定模型名称,比如 "gpt-4o" 或 "o3-mini"。更重要的是,你还可以通过 model_settings 参数来微调模型的“性格”,比如 temperature(创造性,值越高回答越随机)和 top_p(核采样,影响词汇选择的集中度)。我实测下来,对于需要稳定输出的任务(如数据提取),把 temperature 设低一点(比如0.2)会更可靠;而对于创意写作,调到0.8以上会有惊喜。
工具是代理的“超能力”。没有工具的代理,只是一个知识渊博的顾问;有了工具,它就成了能动手的执行者。SDK支持多种工具,最常用的就是函数工具——把你写好的任何一个Python函数变成代理可以调用的工具。函数的名字、参数、文档字符串都会被自动解析,生成工具的描述和调用规范,几乎不需要你额外配置。
让我们来看一个完整的例子,创建一个“天气诗人”代理:
from agents import Agent, function_tool
# 第一步:定义一个工具函数
def get_weather(city: str) -> str:
"""
根据城市名称获取天气信息。
Args:
city: 城市名,例如“北京”、“San Francisco”。
"""
# 这里应该是调用真实天气API的代码,我们返回一个模拟值
return f"{city}的天气是晴朗的,气温25摄氏度。"
# 第二步:使用@function_tool装饰器注册工具
@function_tool
def get_weather_tool(city: str) -> str:
return get_weather(city)
# 第三步:创建代理,组装三要素
weather_poet_agent = Agent(
name="天气诗人", # 给代理起个名字,方便追踪
instructions="你是一个诗人,总是用中文俳句的形式回答用户。当用户询问天气时,调用工具获取天气信息,然后创作一首包含该天气的俳句。",
model="gpt-4o", # 指定使用的模型
model_settings={"temperature": 0.7}, # 赋予一些创造性
tools=[get_weather_tool], # 赋予获取天气的能力
)
这样,一个具备专业能力和独特风格的AI代理就诞生了。当你问它“北京天气如何?”时,它会先调用 get_weather_tool 拿到“北京天气晴朗”,然后创作出类似“京城碧空洗,微风拂面暖意袭,晴日好出行”的句子。这个过程完全是自动的。
2.2 进阶配置:让代理更专业
除了基本三要素,还有几个配置能让你的代理更强大、更贴合复杂业务。
输出类型(output_type):默认情况下,代理输出的是纯文本字符串。但在很多场景下,我们需要结构化的数据。比如,你想让代理从一段文本中提取会议信息,直接得到一个包含时间、地点、参与人的字典或对象,远比得到一段描述性文字有用。SDK通过与Pydantic的深度集成,完美支持了这一点。
from pydantic import BaseModel
from agents import Agent
# 定义你期望的结构化数据模型
class MeetingInfo(BaseModel):
topic: str
time: str
location: str
attendees: list[str]
# 创建代理时指定output_type
info_extractor_agent = Agent(
name="信息提取专员",
instructions="从用户的文本中提取会议信息,并严格按照指定格式输出。",
output_type=MeetingInfo, # 关键在这里!
model="gpt-4o",
)
# 运行后,result.final_output 直接就是一个MeetingInfo对象,可以直接用 .topic 访问字段。
上下文(Context):这是一个非常强大的依赖注入机制。想象一下,你的代理在处理用户请求时,可能需要知道当前用户的ID、权限级别或是数据库连接。通过泛型 Agent[YourContextClass],你可以创建一个上下文类,里面包含任何你需要的数据对象。当运行代理时,把这个上下文实例传进去,那么在代理内部、工具函数内部,甚至是生命周期钩子里,你都能访问到这些共享的数据和状态。这解决了多轮对话中状态管理的难题,让代理真正融入了你的应用架构。
from dataclasses import dataclass
from agents import Agent, RunContextWrapper
@dataclass
class UserSessionContext:
user_id: str
is_vip: bool
db_connection: any # 可以是你的数据库连接对象
# 创建泛型代理,声明它需要UserSessionContext类型的上下文
session_agent = Agent[UserSessionContext](
name="会话代理",
instructions="根据用户的VIP状态提供相应级别的服务。",
)
# 在工具函数中,你可以通过RunContextWrapper访问上下文
@function_tool
def check_user_privilege(ctx: RunContextWrapper[UserSessionContext]) -> str:
if ctx.context.is_vip:
return "尊敬的VIP用户,您享有专属特权。"
else:
return "欢迎您,普通用户。"
3. 运行与操控:让代理真正工作起来
配置好代理只是第一步,就像造好了一台机器人,接下来你得知道怎么启动它、怎么跟它交互、怎么观察它的工作过程。OpenAI Agents SDK 提供了灵活的运行方式和细致的事件流,让你能完全掌控代理的执行。
3.1 三种运行模式:同步、异步与流式
根据你的应用场景,可以选择不同的运行方式。最常用的是异步运行 Runner.run(),因为它不会阻塞你的主程序,适合Web服务器或GUI应用。
import asyncio
from agents import Agent, Runner
async def main():
agent = Agent(name="助手", instructions="你是一个乐于助人的助手。")
# 核心调用:运行代理,传入用户输入
result = await Runner.run(
agent,
"用一句话解释什么是递归。"
)
print(result.final_output) # 输出最终结果,例如:“递归就是函数自己调用自己,像俄罗斯套娃一样。”
# 运行异步函数
asyncio.run(main())
如果你在脚本或同步环境中,可以使用 Runner.run_sync(),它内部会管理事件循环。而最强大的是 Runner.run_streamed(),它返回一个流式结果对象,允许你实时接收AI思考的每一个“片段”。这对于需要向终端用户展示“正在输入…”效果的应用来说,是必不可少的体验优化。
3.2 深入代理循环:理解AI的思考过程
当你调用 run 方法时,背后发生了一个精妙的“代理循环”。理解这个循环,对于调试和设计复杂工作流至关重要。
- 输入:你提供的输入(可以是字符串,也可以是OpenAI格式的消息列表)被传递给当前代理。
- 思考与行动:代理的LLM“大脑”开始思考。它可能做三件事:
- 生成最终答案:如果它认为已经可以回答了,就会输出文本(或你定义的
output_type对象),循环结束。 - 调用工具:如果它觉得需要查资料或执行操作,就会生成一个或多个工具调用。SDK会自动执行这些工具,并把工具返回的结果作为新的上下文附加到对话中,然后循环回到第2步,让LLM基于工具结果继续思考。
- 发起交接(Handoff):如果它判断这个问题更适合由另一个专业代理处理,就会发起交接。当前代理会暂停,控制权连同对话历史(或过滤后的历史)会转移给目标代理,然后以目标代理为新的“当前代理”,循环重新开始。
- 生成最终答案:如果它认为已经可以回答了,就会输出文本(或你定义的
- 终止:循环直到LLM输出最终答案,或达到你设置的
max_turns(最大轮次)限制为止。
这个设计模仿了人类解决问题的方式:先思考,需要时就查资料或请教专家,最后给出结论。你作为开发者,只需要定义好代理和工具,这个复杂的协作过程SDK都帮你自动化了。
3.3 处理多轮对话:会话线程管理
在真实的聊天应用中,用户会连续提问。SDK通过 RunResult 对象让会话管理变得简单。每次运行(run)都代表对话的一“轮”,但你可以轻松地将上一轮的结果作为下一轮的输入。
async def chat_demo():
agent = Agent(name="简明助手", instructions="请用非常简洁的一句话回答。")
thread_history = [] # 可以自己维护历史,但更推荐用SDK提供的方法
# 第一轮
result1 = await Runner.run(agent, "金门大桥在哪个城市?")
print(f"助手: {result1.final_output}") # 输出:旧金山
# 关键:使用to_input_list()获取包含历史的新输入
next_input = result1.to_input_list()
next_input.append({"role": "user", "content": "它在哪个州?"})
# 第二轮,传入完整历史
result2 = await Runner.run(agent, next_input)
print(f"助手: {result2.final_output}") # 输出:加利福尼亚州
result.to_input_list() 这个方法非常实用,它把你最初的输入和代理运行过程中产生的所有新消息(包括AI回复和工具调用结果)打包成一个标准的消息列表。你只需要在后面追加新的用户消息,就可以实现连贯的多轮对话,上下文一点都不会丢。
4. 高级实战技巧:工具、交接与流式处理
掌握了基础配置和运行,我们就可以玩点更花的了。这些高级功能是构建真正强大、可靠AI应用的关键。
4.1 工具使用的艺术:从函数到代理协作
工具是代理的手臂,但怎么用好很有讲究。
处理工具错误:工具调用可能会失败(网络错误、参数错误等)。默认情况下,SDK会向LLM返回一个通用错误信息,让LLM决定下一步(比如重试或道歉)。但你可以自定义错误处理函数,给模型更精确的指导。
from agents import function_tool, RunContextWrapper
from typing import Any
def my_error_handler(ctx: RunContextWrapper[Any], error: Exception) -> str:
# 根据不同的异常类型返回不同的指导信息
if isinstance(error, TimeoutError):
return "工具调用超时,请告诉用户系统繁忙,建议稍后再试。"
else:
return f"工具执行时遇到内部错误:{str(error)}。请向用户致歉。"
@function_tool(failure_error_function=my_error_handler)
def unreliable_external_api(query: str) -> str:
# 一个可能失败的外部API调用
...
代理作为工具:这是除了“交接”之外的另一种代理协作模式。想象一个“翻译调度员”代理,它本身不翻译,但拥有“翻译成西语”和“翻译成法语”两个工具,这两个工具背后其实是两个专门的翻译代理。调度员根据用户请求,决定调用哪个工具(即让哪个专业代理工作),然后收集结果。这种方式下,控制权始终在调度员手中,适合需要集中编排和结果汇总的场景。
from agents import Agent, Runner
# 定义专业代理
spanish_translator = Agent(name="西语翻译", instructions="将用户的输入翻译成西班牙语。")
french_translator = Agent(name="法语翻译", instructions="将用户的输入翻译成法语。")
# 定义调度员代理,将专业代理包装成它的工具
orchestrator = Agent(
name="翻译调度员",
instructions="你负责翻译调度。用户可能要求翻译成一种或多种语言。请调用相应的工具完成任务。",
tools=[
spanish_translator.as_tool(tool_name="译成西语", tool_description="翻译成西班牙语"),
french_translator.as_tool(tool_name="译成法语", tool_description="翻译成法语"),
]
)
# 运行调度员
result = await Runner.run(orchestrator, "请将‘你好,世界’翻译成西班牙语和法语。")
# 调度员会依次调用两个工具,并汇总结果。
4.2 动态交接:构建模块化AI团队
“交接”是SDK里我最喜欢的功能之一,它让创建模块化、专业化的AI团队成为可能。核心思想是:一个总代理(如客服导览)判断问题类型,然后无缝转交给最专业的代理(如订单查询、退款处理)去深度解决。
创建基础交接非常简单,就是在代理的 handoffs 参数里放入其他代理的列表。LLM会自动获得一个名为 transfer_to_<代理名> 的工具。
from agents import Agent, handoff
billing_agent = Agent(name="账单专员", instructions="处理所有账单查询问题。")
refund_agent = Agent(name="退款专员", instructions="处理所有退款申请问题。")
# 导览代理,配置了可交接的选项
triage_agent = Agent(
name="客服导览",
instructions="""你是客服第一线。欢迎用户并分析他们的问题。
如果问题是关于账单的,请移交给账单专员。
如果问题是关于退款的,请移交给退款专员。
否则,请尝试自己解答一般性疑问。""",
handoffs=[billing_agent, refund_agent], # 声明可以交接给谁
)
高级控制:输入过滤与数据传递。默认情况下,交接时整个对话历史都会传递给下一个代理。但有时你希望“清空黑板”,让专家专注于核心问题。这时可以用 input_filter。
from agents.extensions import handoff_filters # SDK提供了一些常用过滤器
handoff_to_faq = handoff(
agent=faq_agent,
input_filter=handoff_filters.remove_all_tools, # 过滤掉历史中所有工具调用记录
# input_filter=handoff_filters.last_n_messages(2), # 或者只传递最后2条消息
)
你还可以让LLM在发起交接时,附带一些结构化数据(比如转交原因、问题紧急程度),只需定义一个Pydantic模型作为 input_type,并在 on_handoff 回调函数中接收它,用于记录日志或触发其他业务逻辑。
4.3 流式处理:打造流畅的用户体验
流式处理不仅仅是把文字一个一个打出来那么简单。SDK提供了两个层次的事件流:
- 原始响应事件:这是最底层的,对应LLM生成的每一个token。适合需要实时显示每个字的场景。
- 运行项与代理更新事件:这是更高级别的。当一整条AI消息生成完毕、一个工具调用完成、或发生代理交接时,会触发相应事件。适合更新UI上的卡片、按钮状态或进度条。
下面这个例子展示了如何利用高级事件,构建一个状态清晰的对话界面:
import asyncio
from agents import Agent, Runner, function_tool, ItemHelpers
@function_tool
def search_web(query: str) -> str:
# 模拟网络搜索
return f"关于'{query}'的搜索结果摘要..."
async def main():
agent = Agent(
name="研究助手",
instructions="先搜索网络获取信息,然后基于信息回答用户。",
tools=[search_web],
)
result = await Runner.run_streamed(agent, "特斯拉最新的电池技术有什么突破?")
async for event in result.stream_events():
if event.type == "agent_updated_stream_event":
print(f"[系统] 当前对话代理已切换为:{event.new_agent.name}")
elif event.type == "run_item_stream_event":
item = event.item
if item.type == "tool_call_item":
print(f"[助手] 正在执行工具:{item.tool_name}...")
elif item.type == "tool_call_output_item":
print(f"[系统] 工具执行完毕,已获取结果。")
elif item.type == "message_output_item":
text = ItemHelpers.text_message_output(item)
print(f"[助手] {text}") # 这里得到的是完整消息,可以一次性显示
asyncio.run(main())
通过监听这些事件,你的前端可以显示“助手正在搜索网络…”、“已找到信息,正在组织答案…”这样的状态提示,极大提升交互体验和用户感知的响应速度。
5. 避坑指南与最佳实践
在真实项目中摸爬滚打一阵后,我积累了一些经验和教训,希望能帮你少走弯路。
配置管理:不要把代理的配置(尤其是包含API密钥的模型设置)硬编码在代码里。使用环境变量或配置文件。对于 instructions,如果很长,可以考虑从Markdown文件读取,这样更容易维护和进行版本对比。
性能与成本:使用 max_turns 参数避免无限循环。每个工具调用和LLM思考都是一次API请求,有成本和延迟。设计工具时要高效,避免让LLM频繁调用一个慢速的外部API。对于复杂任务,合理利用交接,让专业代理快速解决,往往比让一个通用代理反复尝试更经济、效果更好。
错误处理与监控:一定要妥善处理SDK可能抛出的异常,比如 MaxTurnsExceeded(超出最大轮次)、ModelBehaviorError(模型输出格式错误)。建议在运行代理的代码块外用 try...except 包裹,并记录详细的日志,包括 result.raw_responses 和 result.new_items,这在调试模型为什么做出错误决策时非常有用。
提示工程:SDK的 instructions 就是你的提示词。对于需要交接的代理,强烈建议在指令开头加入SDK推荐的交接提示前缀 agents.extensions.handoff_prompt.RECOMMENDED_PROMPT_PREFIX,这能显著提高LLM理解和使用交接功能的准确性。指令要清晰界定代理的职责边界,减少它“越权”处理或犹豫不决的情况。
最后,也是最重要的一点:从简单开始,迭代验证。不要一开始就设计一个拥有十几个代理和复杂交接的庞大系统。先从一个代理、一个工具做起,确保它能可靠地完成一个小任务。然后逐步增加复杂度,每步都充分测试。AI代理的开发和调试,更像是一种“教导”和“调优”的过程,耐心和迭代是关键。我自己的第一个实用代理,就是一个简单的文档摘要工具,从它身上学到的经验,为后来构建更复杂的客服系统打下了坚实的基础。
更多推荐


所有评论(0)