Skip to main content

06 · OpenAI Agents SDK:原语最少,上手最快

仓库openai/openai-agents-python
Star28.7k(TS 版 openai-agents-js 3.7k)
版本0.21.1
语言Python / TypeScript
许可证MIT
层级Framework(0.2x 开始往 Harness 方向长)
一句话官方出品的极简多智能体框架,设计原则是「够用就好,原语越少越好」

OpenAI Agents 编排

图片来源:OpenAI Agents SDK README

它的前身是 2024 年那个实验性的 Swarm。官方对它的设计原则说得很清楚:「enough features to be worth using, but few enough primitives to make it quick to learn」 —— 功能足够值得用,原语少到能很快学会。


一、四个原语,二十分钟能学完

from agents import Agent, Runner

# Agent 只是一份「配置」:叫什么、行为准则是什么、有哪些工具、能交接给谁
agent = Agent(name="Assistant", instructions="You are a helpful assistant")

# Runner 才是执行者:它负责跑那个「调模型 → 调工具 → 再调模型」的循环,
# 直到模型不再调用工具为止。run_sync 是同步版,异步用 await Runner.run(...)
result = Runner.run_sync(agent, "Write a haiku about recursion in programming.")
print(result.final_output) # final_output = 最后一轮模型给出的答案
原语是什么
Agent配了 instructions、tools、guardrails、handoffs 的 LLM
HandoffAgent 把控制权交给另一个 Agent
Guardrail输入 / 输出 / 工具级的校验,可以并行跑
Session跨轮次自动管理对话历史

外加一个 Runner(跑循环)和内置 Tracing(看轨迹)。

LangGraph 对比就很直观:那边要你理解 State、Reducer、Node、Edge、Checkpointer、Interrupt 六个概念才能写第一行有用的代码;这边四个概念就够开工。代价是拓扑表达能力弱得多。


二、Handoff:本专题里独一份的控制权转移

Handoff 的实现很巧妙:它对模型来说就是一个工具。 交接给名为 Refund Agent 的 Agent,模型看到的工具名就是 transfer_to_refund_agent

from agents import Agent, handoff

# 两个专家 Agent
billing_agent = Agent(name="Billing agent")
refund_agent = Agent(name="Refund agent")

# 分流 Agent:它自己不解决问题,只负责判断该转给谁
triage_agent = Agent(
name="Triage agent",
# handoffs 里可以直接放 Agent 对象,也可以用 handoff() 包一层做精细配置
handoffs=[billing_agent, handoff(refund_agent)],
)
# 底层原理:每个 handoff 对模型来说就是一个工具,工具名是 transfer_to_<agent 名>。
# 模型一旦「调用」了这个工具,后续对话就归接手方管了 —— 控制权转移,不会再回来。

模型一旦调用这个「工具」,后续对话就归接手的 Agent 管了 —— 不是「帮我查一下再还给我」,是「我不管了,你接着聊」。

需要精细控制时用 handoff()

from pydantic import BaseModel
from agents import Agent, handoff, RunContextWrapper

# 定义「交接时模型必须填写的表单」
class EscalationData(BaseModel):
reason: str # 升级原因

# 交接发生的瞬间会调这个回调,常用来打日志、预热数据、发通知
async def on_handoff(ctx: RunContextWrapper[None], input_data: EscalationData):
print(f"Escalation agent called with reason: {input_data.reason}")

handoff_obj = handoff(
agent=Agent(name="Escalation agent"), # 转交给谁(固定,不是让模型选)
on_handoff=on_handoff, # 交接回调
# input_type 会变成交接工具的参数 schema,逼模型交代清楚为什么要升级;
# SDK 会本地校验这份 JSON,再把解析好的对象传给 on_handoff
input_type=EscalationData,
)
# 其他常用参数:input_filter(裁剪给下一个 Agent 看的历史)、
# is_enabled(运行时动态开关这个交接)、tool_name_override(改工具名)

其他可调项:tool_name_overridetool_description_overrideinput_filter(裁剪交给下一个 Agent 的历史)、is_enabled(运行时动态开关)。

Handoff vs Subagent,面试常考
Handoff(本框架)SubagentDeepAgents
控制权转移,不回来借出,结果返回后主 Agent 继续
上下文默认接手方看得到历史子 Agent 独立上下文
适合客服分流:账单问题转账单专员深度研究:让子 Agent 查完给我结论

想在这个 SDK 里实现「借出」语义,用 Agent.as_tool() 把 Agent 包成工具(官方叫 agents-as-tools)。


三、Guardrails:三个层次的护栏

这是 OpenAI Agents SDK 相对同类框架做得最完整的部分。

类型跑在哪触发点
Input guardrail链条上第一个 Agent收到用户输入时
Output guardrail产出最终输出的最后一个 Agent生成最终答案后
Tool guardrail每一次 function tool 调用执行前 + 执行后

设计动机很实在:用便宜的小模型先筛一遍,别让贵模型白跑。

from pydantic import BaseModel
from agents import Agent, GuardrailFunctionOutput, Runner
from agents.decorators import input_guardrail

# 护栏的判定结果结构 —— 用结构化输出,避免去解析自然语言
class MathHomeworkOutput(BaseModel):
is_math_homework: bool # 是不是在让我做数学作业
reasoning: str # 判断依据,方便事后排查

# 护栏本身也可以是一个 Agent,但要用便宜的小模型 —— 这是它省钱的关键
guardrail_agent = Agent(
name="Guardrail check",
instructions="Check if the user is asking you to do their math homework.",
output_type=MathHomeworkOutput, # 强制模型按上面的结构返回
)

@input_guardrail # 声明这是一个「输入护栏」
async def math_guardrail(ctx, agent, input) -> GuardrailFunctionOutput:
result = await Runner.run(guardrail_agent, input, context=ctx.context)
return GuardrailFunctionOutput(
output_info=result.final_output, # 留档,出错时能看到判断依据
# tripwire = 「绊线」。设为 True 会立刻抛 InputGuardrailTripwireTriggered
# 异常并中止执行,贵模型就不会被调用
tripwire_triggered=result.final_output.is_math_homework,
)

tripwire_triggered=True 会直接抛 InputGuardrailTripwireTriggered 中止执行。

一个容易踩的性能 / 成本权衡

输入护栏有两种执行模式:

模式行为代价
run_in_parallel=True默认护栏和 Agent 同时跑,延迟最低拦截时贵模型可能已经烧了 token、已经调过工具
run_in_parallel=False护栏先跑完再放行多一次往返延迟,但一个 token 都不浪费

如果你的工具有副作用(下单、发消息、写库),默认的并行模式是有风险的 —— 拦截发生时副作用可能已经产生。这种场景要么关掉并行,要么改用 tool guardrail。


四、Session:对话记忆不用自己管

from agents import Agent, Runner, SQLiteSession

agent = Agent(name="Assistant", instructions="Reply concisely.")
# 第一个参数是会话 ID(一般用用户 ID),第二个是 SQLite 文件路径
session = SQLiteSession("user-123", "conversations.db")

await Runner.run(agent, "我叫小明", session=session)
# 传了同一个 session,第二次调用会自动把上一轮的对话history 拼进去,
# 你不用手动维护 messages 列表 —— 模型能答出「小明」
await Runner.run(agent, "我叫什么?", session=session)

可选后端:内存、SQLite、SQLAlchemy(接任意关系库)、Encrypted Session(加密存储)、OpenAI Conversations(托管在 OpenAI 侧)。

注意这只解决「短期对话记忆」,不是 LangGraph 的 checkpointer:它存的是消息列表,不是完整执行状态,所以做不到「跑到第 8 步崩了从第 8 步恢复」。要长期记忆或跨会话知识,得自己接向量库。


五、Tracing:装完就有,不用配

这是它相对 LangChain 最舒服的地方 —— 不需要注册第三方平台、不需要设环境变量,跑完直接去 OpenAI 平台看轨迹:每一轮模型调用、每一次工具执行、每一次 handoff、每一次 guardrail 判定都在。

代价是默认把轨迹发到 OpenAI。企业环境里这一条要先过合规。可以通过自定义 trace processor 导到自己的后端,但那就回到了「要自己搭」的状态。


六、0.2x 之后:它也在往 Harness 长

2026 年的 SDK 已经不只是「四个原语」了,新增的这些直接对应 01 里 Harness 层的能力:

Sandbox Agent —— 长时任务的工作区

from agents import Runner
from agents.run import RunConfig
from agents.sandbox import Manifest, SandboxAgent, SandboxRunConfig
from agents.sandbox.entries import GitRepo
from agents.sandbox.sandboxes import UnixLocalSandboxClient

agent = SandboxAgent(
name="Workspace Assistant",
instructions="Inspect the sandbox workspace before answering.",
# manifest 描述「沙箱启动时工作区里应该有什么」,
# 这里是把一个 GitHub 仓库的 main 分支签出到 repo/ 目录
default_manifest=Manifest(entries={"repo": GitRepo(repo="openai/openai-agents-python", ref="main")}),
)

result = Runner.run_sync(
agent,
"Inspect the repo README and summarize what this project does.",
# 指定沙箱怎么跑:UnixLocalSandboxClient 是在本机(macOS/Linux)跑,
# Windows 或需要隔离时换 DockerSandboxClient / 托管沙箱
run_config=RunConfig(sandbox=SandboxRunConfig(client=UnixLocalSandboxClient())),
)
# Agent 在这个工作区里可以读文件、跑命令、打补丁,且跨多轮保持工作区状态

配套的 Capabilities 模块已经有 FilesystemShellMemorySkillsCompaction —— 这几个词和 DeepAgents 的能力清单几乎一一对应。这说明 Harness 这一层正在成为所有框架的标配,而不是某一家的特色。

Realtime Agent —— 语音是它的独门优势

from agents.realtime import RealtimeAgent, RealtimeRunner

# RealtimeAgent 和普通 Agent 用法几乎一样,但跑在 WebSocket 长连接上
agent = RealtimeAgent(name="Assistant", instructions="You are a helpful voice assistant. Keep responses short.")
runner = RealtimeRunner(starting_agent=agent)
session = await runner.run() # 建立实时会话

async with session:
await session.send_message("Say hello in one short sentence.")
# 事件流是双向实时的:模型边想边出音频,你可以随时打断它
async for event in session:
if event.type == "audio":
... # event 里是音频数据,转发给前端播放
# 其他常见事件:history_added(有新消息)、agent_end(这轮结束)

基于 gpt-realtime 系列走 WebSocket 的低延迟语音,同时保留工具、handoff、guardrail 全套能力。另有 VoicePipeline(STT → Agent → TTS 的传统三段式)。

要做语音 Agent,这个 SDK 目前是开源框架里最成熟的,别的框架基本都要自己拼。


七、供应商中立性:说清楚边界

官方说「provider-agnostic,支持 100+ LLM」,这话对,但要看清哪些部分对:

能力换模型还能用吗
Agent / Runner / Handoff / Session✅ 完全可以(走 LiteLLM 或 any-llm)
Guardrails✅ 可以
Tracing⚠️ 默认发往 OpenAI 平台
托管工具(WebSearchTool / FileSearchTool / CodeInterpreterTool / ComputerTool / ImageGenerationTool)❌ 仅 OpenAI
Realtime / Voice❌ 依赖 OpenAI realtime 模型
Sandbox Agent⚠️ 本地 / Docker 沙箱可用,但设计围绕 OpenAI 模型能力

结论:拿它当纯编排框架,换模型没问题;一旦用上托管工具、Realtime、官方 tracing,迁移成本立刻上来。这是 01 D9 维度 的典型案例。


八、什么时候用 / 什么时候别用

用它,如果

  • 你的主力模型就是 OpenAI —— 托管工具、Realtime、Tracing 全是白送的
  • 场景是「分流 + 专家」 —— Handoff 就是为客服 / 工单这类场景设计的,写起来最自然
  • 要做语音 Agent —— 目前开源框架里最完整的选择
  • 团队要低学习成本 —— 四个原语,新人半天能读懂全部代码
  • 要输入 / 输出 / 工具三层校验 —— Guardrail 体系比大多数框架完整

别用它,如果

  • 要跑长时任务并断点续跑 —— Session 存的是消息不是执行状态,做不到;用 LangGraph
  • 拓扑复杂(并行分支、回环、多阶段流水线)—— Handoff 表达不了,硬写会很难看
  • 不能把 trace 发到 OpenAI 且不想自建 processor
  • 明确要做多云 / 多模型中立 —— 甜蜜区在 OpenAI 生态内,见上一节
  • 要「借出式」子智能体 —— as_tool() 能凑合,但没有 DeepAgents 的上下文隔离那么彻底

九、一句话对照

框架同一件事的做法
OpenAI Agents SDKAgent(handoffs=[...]),模型自己决定转给谁
LangGraph画一个 supervisor 节点,返回 Command(goto=...)
Google ADKWorkflow(edges=[...]) 或 sub_agents 层级
CrewAI定义 Role + Task,process="sequential"

四种写法背后是同一个「谁来决定下一步」的问题,差别只在你想把这个决定权放在模型手里还是代码手里。