OpenAI Agents SDK:交接、护栏与追踪


文档摘要

OpenAI Agents SDK:交接、护栏与追踪 本节摘要:无法干净委托的 Agent,最后都会把所有东西塞进一个提示里——上下文炸裂、角色串味、无法审计。没有护栏的 Agent,会把 PII(个人身份信息)、违规内容直接发出去,或者无限循环烧钱。OpenAI Agents SDK 是构建在 Responses API 之上的轻量多智能体框架,把这件事产品化成了五个原语:Agent(LLM + 指令 + 工具 + 交接)、Handoff(交接,被建模成名为 的工具)、Guardrail(护栏,在输入/输出/工具调用上触发)、Session(跨轮会话历史)、Tracing(追踪,默认开启的 span)。

OpenAI Agents SDK:交接、护栏与追踪

本节摘要:无法干净委托的 Agent,最后都会把所有东西塞进一个提示里——上下文炸裂、角色串味、无法审计。没有护栏的 Agent,会把 PII(个人身份信息)、违规内容直接发出去,或者无限循环烧钱。OpenAI Agents SDK 是构建在 Responses API 之上的轻量多智能体框架,把这件事产品化成了五个原语:Agent(LLM + 指令 + 工具 + 交接)、Handoff(交接,被建模成名为 transfer_to_<agent> 的工具)、Guardrail(护栏,在输入/输出/工具调用上触发)、Session(跨轮会话历史)、Tracing(追踪,默认开启的 span)。核心洞见是把交接做成工具——模型在工具列表里看到 transfer_to_billing_agent,调用它即触发运行时:拷贝上下文、初始化目标 Agent、用目标 Agent 续跑。这其实就是第 13/28 节的监督者(supervisor)模式,只是被厂商打包成了开箱即用的一等公民。本节吃透五个原语、三种护栏(输入/输出/工具)、并行 vs 阻塞两种模式、默认开启的 span 追踪,并用标准库从零实现一个带交接 + 护栏 + span 追踪的运行时,跑一个分诊 Agent 把请求交接给计费或支持团队。读完本节,你应能识别 SDK 的三种失败模式:交接漂移、护栏绕过、过度追踪。

对应原课程:Phase 14 · Lesson 16 · openai-agents-sdk(原英文 phases/14-agent-engineering/16-openai-agents-sdk/docs/en.md)。前置:第 01 节(Agent 循环)、第 06 节(工具使用)。

学习目标

阅读完本节,你应当能够:

  1. 说出 OpenAI Agents SDK 的五个原语(Agent、Handoff、Guardrail、Session、Tracing)。
  2. 解释 handoff(交接):为什么被建模成工具、模型看到的名字形状、上下文如何转移。
  3. 区分输入护栏、输出护栏、工具护栏,以及 run_in_parallel(并行)与阻塞两种模式。
  4. 解释默认开启的 Tracing:span 覆盖 LLM 生成、工具调用、交接、护栏。
  5. 用标准库实现一个带交接 + 护栏 + span 式追踪的运行时。

一、问题与直觉

两类失败把生产 Agent 拖垮。

无法干净委托的 Agent——它把计费问题、技术支持、退款流程、FAQ 全塞进同一个提示。结果是:上下文窗口被无关指令塞满(第 07 节说的「注意力稀释」),角色定义互相打架,模型在「该用哪个角色的语气」上反复横跳,审计时根本说不清某段输出是谁产出的。

没有护栏的 Agent——它会把用户输入里的 PII 原样回流进日志,会生成违反政策的内容并直接对外发送,会在工具失败后无限重试直到账单爆炸。

OpenAI Agents SDK 把这两件事都固化进原语:Handoff 解决干净委托,Guardrail 解决边界守护,Session 解决跨轮记忆,Tracing 解决可观测性,Agent 把它们组合在一起。

五个原语

  1. Agent(智能体) —— LLM + 指令 + 工具 + 交接。SDK 里的 Agent 类型,拥有自己的工具列表和可交接的目标列表。
  2. Handoff(交接) —— 委托给另一个 Agent。对模型而言,它表现为工具列表里一个名为 transfer_to_<agent_name> 的工具。
  3. Guardrail(护栏) —— 在输入(仅首个 Agent)、输出(仅末个 Agent)或工具调用(每个函数工具)上的校验。
  4. Session(会话) —— 跨轮次的对话历史,自动加载与追加。
  5. Tracing(追踪) —— 内置 span,覆盖 LLM 生成、工具调用、交接、护栏。

交接即工具

模型在工具列表里看到 transfer_to_billing_agent。调用它会触发运行时做三件事:

  1. 拷贝对话上下文(或经 nest_handoff_history beta 选项把它折叠成摘要)。
  2. 用目标 Agent 的指令初始化目标 Agent。
  3. 用目标 Agent 续跑本次运行。

模型不直接「跳转」,它只是像调用任何工具一样调一个特殊名字的工具——运行时识别这个名字并执行交接语义。这让路由决策留给了模型的判断力,而执行语义留给了确定的代码。这正是第 13 节(状态图)与第 28 节(编排)里的监督者模式,只是被厂商产品化了。

三种护栏

  • 输入护栏(Input guardrails) —— 在首个 Agent 的输入上运行。在任何 LLM 调用之前拒绝不安全或越界的请求。
  • 输出护栏(Output guardrails) —— 在末个 Agent 的输出上运行。捕捉 PII 泄漏、违规、畸形响应。
  • 工具护栏(Tool guardrails) —— 按函数工具运行。校验参数、检查权限、审计执行。

运行模式有两种:

  • 并行(Parallel,默认) —— 护栏 LLM 与主 LLM 同时跑。尾延迟更低。但若护栏绊倒,主 LLM 的工作被丢弃(浪费 token)。
  • 阻塞(Blocking,run_in_parallel=False) —— 护栏 LLM 跑。若绊倒,主调用不花 token。

绊倒时抛 InputGuardrailTripwireTriggered / OutputGuardrailTripwireTriggered 异常,由运行时转成结构化的拒绝响应。

追踪与会话

Tracing 默认开启。每一次 LLM 生成、工具调用、交接、护栏都发一个 span,构成一棵 span 树。OPENAI_AGENTS_DISABLE_TRACING=1 可关闭;add_trace_processor(processor) 可把 span 扇出到你自己的后端(与 OpenAI 的并行)。

Session 把对话历史存进后端(SQLite、Redis、自定义)。Runner.run(agent, input, session=session) 自动加载历史并在本轮追加——这是第 07 节「短期记忆」在 SDK 层的落点。

⚠️ 三种失败模式:① 交接漂移(Handoff drift)——Agent A 交给 B,B 又交回 A,无限乒乓;解法是加一个跳数计数器(hop counter),超过 N 次交接即拒绝。② 护栏绕过(Guardrail bypass)——工具护栏只对函数工具触发;内置工具(文件读取、网页抓取)需要单独的策略,别假设它们被覆盖。③ 过度追踪(Over-tracing)——span 里可能含敏感内容;配合 OTel GenAI 内容捕获规则(第 23 节),把敏感内容外部存储、按 ID 引用,而非塞进 span。

二、从零实现

原课程 code/main.py 用标准库实现了 SDK 的形状:

  • AgentFunctionToolHandoff(表现为带交接语义的函数工具)。
  • Runner,带输入/输出/工具护栏、交接派发、跳数计数器。
  • 一个简单的 span 发射器,展示追踪形状。
  • 一个分诊 Agent,按用户查询交接给计费或支持;其中一条输入会触发护栏。

Step 1:Agent 与交接即工具

@dataclass class Agent: name: str instructions: str tools: list = field(default_factory=list) handoffs: list = field(default_factory=list) # 目标 Agent 名 def tool_list(self): # 交接被表示成名为 transfer_to_<name> 的工具 tools = list(self.tools) for target in self.handoffs: tools.append({"name": f"transfer_to_{target}", "description": f"把对话交给 {target} Agent"}) return tools

Step 2:Runner 带护栏与跳数计数器

class Runner: def __init__(self, max_hops=5): self.max_hops = max_hops def run(self, agent, user_input, ctx): self.trace.open("agent", agent.name) for g in agent.input_guardrails: # 输入护栏(仅首个) if g(user_input): raise Tripwire(g.name) hops = 0 while True: decision = llm_decide(agent, user_input, ctx) # 选工具或收尾 if decision.is_handoff: # 交接 hops += 1 if hops > self.max_hops: raise HandoffDrift(hops) agent = REGISTRY[decision.target] # 拷贝上下文 + 切 Agent continue if decision.is_finish: for g in agent.output_guardrails: # 输出护栏(仅末个) if g(decision.output): raise Tripwire(g.name) self.trace.close(); return decision.output self.run_tool(agent, decision.tool_call, ctx) # 含工具护栏

Step 3:span 发射器(追踪形状)

class SpanEmitter: def open(self, kind, name): print(f"[span start] {kind}/{name}") def event(self, kind, **kv): print(f"[span event] {kind} {kv}") def close(self): print(f"[span end]")

运行 python3 code/main.py 会展示:两次成功的交接、一次输入护栏绊倒、一棵映射真实 SDK 发射形状的 span 树。

💡 设计要点:护栏的「绊倒」与执行的「成功」是同等一等公民——绊倒不是一个被吞掉的异常,它产出一条结构化的拒绝响应,让上层能据以行动。这呼应第 06 节:校验失败要变成观察字符串,而不是崩溃。

三、框架对比

框架 何时选 关键取舍
OpenAI Agents SDK OpenAI 优先的产品 handoff/guardrail/tracing 原生化,但绑 Responses API
Claude Agent SDK(第 17 节) Claude 优先的产品 subagent + session store,绑 Anthropic
LangGraph(第 13 节) 想要显式状态与持久续跑 图形状更可控,但交接要自己写
自建 需要精确控制(语音、多厂商、联邦部署) 自己实现这五个原语

跨厂商不变量仍是第 06 节那四样:name + description + JSON Schema 参数 + 关联 ID。SDK 只是把交接也变成了一个符合这套形状的工具。

四、可复用产物

原课程 outputs/skill-agents-sdk-scaffold.md:脚手架一个 Agents SDK 应用——分诊 Agent、交接、输入/输出/工具护栏、会话存储、一个追踪处理器。把 Mock LLM 换成真实 Responses API 调用即可投入生产,五个原语的组合逻辑无需改动。

五、练习

  1. (Easy) 加一个交接跳数计数器:超过 N 次转移即拒绝。追踪其行为。
  2. (Medium) 实现 nest_handoff_history 选项——交接前把历史消息折叠成一段摘要。
  3. (Medium) 写一个阻塞式输出护栏。对比会绊倒与不会绊倒两类提示的延迟。
  4. (Medium)add_trace_processor 接到一个 JSON logger。每个 span 发射出什么形状?
  5. (Hard) 读 SDK 文档,把标准库玩具移植到 openai-agents-python。你把哪些地方建模错了?

本节要点回顾

  1. 五原语:Agent(LLM+指令+工具+交接)、Handoff、Guardrail、Session、Tracing——OpenAI 把多智能体的工程难点固化进了一等公民。
  2. 交接即工具:模型看到 transfer_to_<agent>,调用它触发运行时拷贝上下文、切 Agent、续跑;路由留模型,语义留代码。
  3. 三种护栏:输入(仅首 Agent)、输出(仅末 Agent)、工具(每个函数工具)。
  4. 并行 vs 阻塞:并行默认、尾延迟低但绊倒时浪费 token;阻塞先跑护栏、不浪费 token 但延迟高。
  5. 绊倒抛结构化异常:Input/OutputGuardrailTripwireTriggered,被转成拒绝响应而非崩溃。
  6. Tracing 默认开:每次 LLM/工具/交接/护栏都发 span;OPENAI_AGENTS_DISABLE_TRACING=1 关闭,add_trace_processor 扇出到自有后端。
  7. Session 跨轮记忆:SQLite/Redis/自定义后端,Runner.run 自动加载与追加。
  8. 三大失败模式:交接漂移(加跳数计数器)、护栏绕过(内置工具需单独策略)、过度追踪(敏感内容外部存储按 ID 引用,配合第 23 节)。
  9. 选型:OpenAI 优先用本 SDK,Claude 优先用第 17 节,要显式状态用 LangGraph,要精确控制自建。
  10. 跨厂商不变量:name+description+JSON Schema+关联 ID;交接只是又一个符合这套形状的工具。

下一节,我们转向 Claude Agent SDK——把 subagent(子智能体)、session store(会话存储)与 Claude 原生的工具调用作为一等公民,看 Anthropic 这边如何回答同样的五个工程问题。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U