OpenAI Agents SDK 工程笔记:guardrails 输入输出护栏与生产禁区

千笔-AIWritePaper · https://www.aiwritepaper.com
把 Agents SDK 的 guardrails 当成「多写几句系统提示」会踩坑:护栏是可触发 tripwire 的硬检查,不是礼貌提醒。官方 Guardrails 写得很直:input 护栏只跑在链首 Agent;output 护栏只跑在产出终答的 Agent;需要每次函数工具调用都验,应挂 tool guardrails,而不是指望 agent 级 input/output 自动覆盖 handoff 后的专家。input 默认 parallel(与主 Agent 并发),要省 token、防工具副作用必须改 blocking(run_in_parallel=False)。本文按工程笔记写法,钉死选型、可跑片段、边界与生产禁区。示例模型名写作 gpt-4o,以你账号可用快照与官方文档为准。
图:上方 input / output / tool 三类护栏;中部 parallel vs blocking 与 tripwire;下方生产禁区对照。

目标说明
读完你应能独立完成五件事:
- 用一句话说清:input 拦入口、output 拦终答、tool 护栏拦每次函数工具;三者不是同一道门。
- 写出可跑片段:
@input_guardrail/@output_guardrail,触发InputGuardrailTripwireTriggered/OutputGuardrailTripwireTriggered。 - 会选执行模式:成本/副作用敏感用 blocking;只在意延迟才用默认 parallel。
- 知道 workflow 边界:handoff 后链中专家不会自动重跑链首 input;终答前才跑 output。
- 列出生产禁区:把护栏过当业务授权、只挂专家不挂入口、副作用只靠事后 output、用提示词替代 tripwire、把 hosted tool 当已受 function-tool 护栏保护。
规格钉死(对照官方 Guardrails / Guardrails and human review):
- Input:接收与 Agent 相同的初始输入 →
GuardrailFunctionOutput→ 查tripwire_triggered。 - Output:接收终答 → 同上;不支持
run_in_parallel。 - Tool:挂在
FunctionTool;handoff 管道与 hosted tools 不走该护栏管道。 - 并行:input 默认并发;blocking 可保证 trip 时主 Agent 未启动。
- HITL:审批暂停与护栏是正交能力;敏感副作用优先 tool approval + tool 护栏。
适用边界
适合上 guardrails
- 客服/助手:先拦偏题、越权指令,再跑贵模型。
- 终答要脱敏或政策校验:output tripwire 拒绝候选终答。
- 自定义函数工具有写库/发信副作用:tool input/output 护栏每次必跑。
- 需要可观测:异常携带
guardrail_result/ 累计 results,便于审计。
更适合别的门禁(不要硬上 agent 护栏)
- 付款/删库等强副作用:用 human approval + 业务鉴权,而不是只靠 LLM 分类护栏。
- 每次工具调用都要验,却只在 triage 上挂了 input:专家侧仍会漏。
- Hosted tools(WebSearch 等):当前不走 function-tool 护栏管道,需另设策略。
不该指望它单独搞定
- 业务授权:tripwire 未触发 ≠ 用户有权退款。
- 多 Agent 全程覆盖:input 只在链首;output 只在终答 Agent。
- 用 output 撤销已执行工具:工具已跑完;要防执行必须 tool input 护栏或审批。
- 把护栏当内容审核银弹:分类模型也会错;要有人工复核通道。
风险提示
parallel 模式下 tripwire 触发时,主 Agent 可能已耗 token 甚至调过工具。生产若把「贵模型 + 写库 tool」配成 parallel input,缺口在执行模式选择,不在「有没有写护栏函数」。output tripwire 拒绝的是候选终答;会话持久化规则以官方 session 行为为准,勿假设「什么都没写下」。
步骤与机制
1. 三类护栏对照
| 机制 | 何时跑 | 失败时 | 典型用途 |
|---|---|---|---|
| input_guardrails | 链首 Agent 输入 | Input…TripwireTriggered | 偏题/越权/越狱入口 |
| output_guardrails | 终答 Agent 输出后 | Output…TripwireTriggered | 脱敏、政策、格式硬校验 |
| tool 护栏 | 每次函数工具前后 | Tool…Tripwire / reject_content | 参数含密钥、结果泄密 |
| 提示词「请勿…」 | 无硬契约 | 模型口头答应仍越界 | 仅演示(勿当生产门禁) |
| human approval | 工具调用暂停 | 人拒则不执行 | 高危副作用 |
2. 可跑:input 护栏 + tripwire
先 pip install openai-agents,并导出 OPENAI_API_KEY。
import asyncio
from pydantic import BaseModel
from agents import (
Agent, Runner, GuardrailFunctionOutput,
InputGuardrailTripwireTriggered, RunContextWrapper, TResponseInputItem,
)
from agents.decorators import input_guardrail
MODEL = "gpt-4o" # 占位:以账号可用快照为准
class HomeworkOut(BaseModel):
is_math_homework: bool
reasoning: str
guard_agent = Agent(
name="Guardrail check",
instructions="判断用户是否在要求代写数学作业。",
output_type=HomeworkOut,
model=MODEL,
)
@input_guardrail
async def math_guardrail(
ctx: RunContextWrapper[None], agent: Agent, input: str | list[TResponseInputItem]
) -> GuardrailFunctionOutput:
result = await Runner.run(guard_agent, input, context=ctx.context)
out = result.final_output
return GuardrailFunctionOutput(
output_info=out,
tripwire_triggered=bool(out and out.is_math_homework),
)
agent = Agent(
name="Customer support agent",
instructions="你是客服,只回答产品与订单问题。",
model=MODEL,
input_guardrails=[math_guardrail],
)
async def main():
try:
await Runner.run(agent, "帮我解方程:2x+3=11")
print("unexpected: guardrail did not trip")
except InputGuardrailTripwireTriggered:
print("tripwire ok: refuse homework")
asyncio.run(main())
验收:作业类输入应进 except;正常订单问题不应误杀。把「误杀率」记进 smoke,而不是口头说「感觉挺准」。
3. Blocking:防副作用与省 token
需要「护栏未通过则主 Agent 绝不启动」时,创建 InputGuardrail 时设 run_in_parallel=False(或装饰器等价参数,以你安装的 SDK 版本文档为准)。默认 parallel 适合延迟敏感、且工具无副作用的场景。选型口诀:有写库/发信/付款 tool → 先 blocking 或先审批。
4. Output 护栏要点
output 装饰器拿到的是 Agent 的终答(常与 output_type 对齐)。tripwire 为真时抛 OutputGuardrailTripwireTriggered。注意:若某 tool 结果因 tool_use_behavior 直接成为终答,output 护栏仍在事后;不能指望它阻止 tool 已经产生的外部副作用。敏感 tool 必须前置 tool input 护栏或 HITL。
生产禁区
| 禁区 | 为什么危险 | 替代做法 |
|---|---|---|
| 护栏过 = 业务授权 | 分类成功≠有权操作 | 业务鉴权 + 幂等键 |
| 只给专家挂 input | 链首未拦,脏输入已进 | 入口 Agent 必挂 |
| parallel + 副作用 tool | trip 前可能已执行 | blocking 或审批 |
| 只用 output 拦写库 | 库已写完 | tool input 护栏 |
| 提示词代替 tripwire | 无异常、无审计钩子 | 返回 tripwire_triggered |
| 假定 hosted tool 已护栏 | 管道不覆盖 | 另策或避免敏感查询 |
| handoff 后以为 input 重跑 | 官方:仅链首 | 专家侧再挂或 tool 护栏 |
可验证清单
- 能口述 input / output / tool 三者运行位置差异
- 本地跑通一次 input tripwire 与一次「应放行」对照
- 生产配置里对副作用路径使用 blocking 或 HITL
- 多 Agent 图上标出:哪只 Agent 是链首、谁出终答
- tool 护栏覆盖所有自定义写操作工具
- 审计能回答:哪条护栏、何时 trip、当时 input 摘要
踩坑
- 把 guardrails 写在 Runner 参数心里:官方把它挂在 Agent 上,因护栏通常与该 Agent 职责绑定。
- output 与 input 对称地设 parallel:output 不支持该参数。
- tripwire 与 raise 混用不清:函数内未捕获的异常与返回 tripwire 的会话持久化语义不同,按文档区分。
- handoff 后抱怨「护栏没拦」:检查是否只挂在被交接专家上,而脏数据来自链首。
- 用同一贵模型做护栏:失去「快/便宜预检」的成本意义;护栏模型应更便宜或规则化。
与 sessions / handoffs 的协作位
| 层 | 解决什么 | 和护栏的交接面 |
|---|---|---|
| sessions | 跨轮记忆 | output trip 后哪些 item 入史,以官方为准 |
| handoffs | 本轮说话权 | input 不随交接自动重跑 |
| guardrails | 硬门禁 | 不替代鉴权与审批 |
实践顺序:先定入口与终答是谁,再挂 input/output,最后给副作用 tool 加护栏/审批。反过来先堆专家再补门禁,演示能过、生产事故率高。
最小审计字段
建议至少落盘:ts、guardrail_name、kind=input|output|tool、tripwire、agent_name、run_id、input_fingerprint(哈希即可,勿存密钥原文)。验收:五分钟内能回答「谁在何时因何拦住」。
十分钟 smoke 剧本
- 正常客服问:应放行。
- 数学作业问:input trip。
- 打开 blocking,确认 trip 时无主 Agent 工具日志。
- 故意让终答含「应拒字段」,确认 output trip。
- 对写库 tool 注入含
sk-的参数,确认 tool input 拒绝。
五步记入 _w/guardrails-smoke-checklist.md。
总结
guardrails 的工程价值是可触发的硬门禁与清晰的运行位置,不是「又多了一层提示词」。选型先问:拦的是入口、终答还是每次工具?有副作用是否 blocking/审批?生产禁区的共同主题是:护栏通过 ≠ 授权成功 ≠ 副作用安全。把这三层拆开,护栏才能从演示走进可运维。
FAQ:护栏失败后用户侧怎么回?
不要把异常栈直接甩给终端用户。推荐模式:捕获 InputGuardrailTripwireTriggered 后写入审计,再返回固定拒答模板(可含工单号)。输出侧同理:用业务可读的「该回复未通过策略检查」替换原始候选。若使用 RunConfig.output_guardrail_blocked_message,保持占位符不含敏感原文。
FAQ:多轮对话里 input 护栏会每次都跑吗?
对「每次 Runner.run 的链首 Agent」会按该次输入检查。把上一轮 to_input_list() 拼进下一轮时,护栏看到的是你传入的整段输入视图——因此不要假设「第一轮过了,第二轮带毒后缀也会自动安全」。把新用户回合的脏数据追加进来后,仍依赖链首 input。
规则护栏与模型护栏怎么混用?
成本敏感路径优先正则/允许列表(例如检测到 sk-、内网 URL、明确越权动词)。模型护栏留给语义偏题与绕过尝试。混用时注意:规则护栏应短、确定、可单测;模型护栏要固定 output_type,避免自由散文当判定。两者都要能单独 trip,而不是互相「参考意见」。
预发环境必测的误杀与漏杀
至少各留 5 条金标:应放行的难例(含专业术语、投诉语气)与应拦截的攻击/偏题。记录精确率不够时,先调护栏指令与阈值,再考虑换模型。禁止上线后用真实用户当第一次误杀样本却无回滚开关。
发布前配置审一眼
- 链首 Agent 的
input_guardrails非空(若该入口暴露给公网用户)。 - 所有写操作
FunctionTool带 tool input 护栏或 approval。 - 无「parallel input + 无审批写库」组合。
- 文档写明:演示用模型名非生产承诺;tripwire 日志保留周期。
- 与 handoffs 同图评审:标出链首与终答节点。
观测与回放
打开 tracing 后,回放材料应能同时看到:护栏函数名、tripwire 布尔、主 Agent 是否已产生 tool 事件。若 blocking 配置下仍出现主 Agent tool 事件,先怀疑配置未生效或跑了旧进程。把结论写进 _w/guardrails-docs-notes.md。
与官方「护栏 + 人类审批」指南对齐
Guardrails and human review 把自动检查与人工审批并列:前者决定继续/停止,后者在敏感动作上暂停。工程上不要把审批做成「另一个 LLM 护栏」却无人类出口;也不要只有人类审批却对明显越狱入口零自动拦。两条门禁都要出现在 runbook。
输出护栏与 session 持久化(工程提醒)
官方对 output tripwire 与「护栏函数抛异常」的会话写入语义做了区分:tripwire 拒绝候选终答时,可能保留已完成的工具调用回放所需信息,同时排除被拒终答;异常路径则按另一套规则处理。你的应用层不要假设「一 trip 就等于整轮从未发生」。写审计时同时记录:是否 tripwire、是否有 tool 事件、session 是否已落盘。流式跑法下,终端异常仍可能从 stream_events() 冒出——务必排空并统一捕获。
工具护栏最小示例(思路)
对自定义 @tool:入参侧拒绝含密钥模式的 JSON;出参侧发现敏感片段则 reject_content。注意:handoff 调用本身不走 function-tool 护栏;Agent.as_tool() 亦不一定暴露同等选项。把「工具名单」与「护栏覆盖名单」做成同一张表,缺失即发布阻塞。
成本账
每次 input 护栏若再跑一个 Agent,等于双倍入口成本。策略:规则预筛 → 小模型语义护栏 → 主模型。把护栏模型固定为便宜快照,并在账单里单独标签 guardrail,否则你看不见门禁税。
更多推荐

所有评论(0)