跳转至

第六章:Guardrails 与降级策略

6.1 Guardrails 解决的是契约校验管不到的问题

第 5 章的契约校验回答"输出格式对不对",Guardrails 回答的是另一件事:输出内容是否安全、是否符合业务允许的边界——即使一段文本完全符合 JSON Schema,它仍然可能包含泄露的隐私信息、越权的操作指令,或者只是单纯地跑题了。

%%{init: {"flowchart": {"nodeSpacing": 12, "rankSpacing": 18, "padding": 8}}}%%
flowchart TB
    IN["用户输入"] --> INGUARD["输入护栏:"]
    INGUARD -->|拦截| REJECT1["拒绝 / 转人工"]
    INGUARD -->|通过| MODEL["模型生成"]
    MODEL --> OUTGUARD["输出护栏:"]
    OUTGUARD -->|拦截| DEGRADE["降级路径"]
    OUTGUARD -->|通过| RESP["返回用户"]
    DEGRADE --> RESP

    style INGUARD fill:#e8f0fe
    style OUTGUARD fill:#e8f0fe
    style DEGRADE fill:#fff3cd

图中各项的完整含义:

  • 输入护栏: Prompt 注入检测 · 越权请求识别
  • 输出护栏: PII 过滤 · 内容安全 · 事实边界

6.2 输入护栏:在模型看到之前拦下风险

检查项 防什么 常见实现
Prompt 注入检测 用户或第三方内容试图改变任务、诱导越权 规则与分类器提供风险信号,不能证明未命中的输入可信
越权请求识别 用户尝试让 Agent 执行超出其权限范围的操作 结合业务权限系统做前置校验,而非指望模型自己拒绝
输入内容安全 违规、有害的输入内容 内容安全 API(如 OpenAI Moderation)前置调用

越权识别不应该完全依赖模型自己判断该不该执行。 模型的拒绝行为可以被对抗性 Prompt 绕过,权限边界应该在业务系统层面用确定性规则强制,模型层的拒绝只是第一道、不是唯一一道防线。

6.3 输出护栏:模型生成之后、返回用户之前

检查项 防什么
PII 检测与脱敏 模型在回答中意外复述了输入上下文里的身份证号、手机号等敏感信息
内容安全审核 生成内容本身违规或有害
事实边界检查 涉及金额、日期等强事实性字段时,与结构化数据源做交叉校验而非只信任模型输出
引用完整性(RAG 场景) 生成内容中的引用是否能在检索到的原文中找到依据,这部分详细展开见 RAG · 生成与评估
def output_guardrail(text: str, context: dict) -> GuardrailResult:
    if moderation_flagged(text):
        return GuardrailResult(action="block", reason="content_safety")
    if contains_pii(text) and not context.get("pii_allowed"):
        return GuardrailResult(action="redact", reason="pii_detected")
    return GuardrailResult(action="allow")

context 必须来自可信的业务授权上下文,不是用户或模型自由填写的字典。上例仅演示决策优先级:阻断优先于脱敏,实际系统仍需聚合所有检查结果并复检脱敏后的文本。护栏超时或不可用也应有显式策略,高风险写操作应停止,低风险场景可返回受限模板并告警。

流式输出一旦发出就无法撤回。逐段审核会增加延迟,也可能漏掉跨段组合的信息;高敏感回答应缓冲完整内容,或使用经过评估的分段放行策略。不要先发送原文、事后才打上「已拦截」标签。

6.4 降级不是"报错",而是一个有梯度的策略阶梯

护栏拦截不一定是服务故障,也不必先返回 500。应按原因选择受限重试、缩小任务、有效缓存或明确拒绝/转人工;下列层级是可选处置,不是必须依次尝试的链路。能返回一段文字,也不代表原任务已经完成。

%%{init: {"flowchart": {"nodeSpacing": 10, "rankSpacing": 12, "padding": 6}}}%%
flowchart LR
    CAUSE["选择安全处理方式"] --> L1["Lv1:有限重试"]
    CAUSE --> L2["Lv2:缩小任务范围"]
    CAUSE --> L3["Lv3:已校验缓存"]
    CAUSE --> L4["Lv4:拒绝或人工复核"]

    style L1 fill:#e6f4ea
    style L4 fill:#fce8e6

图中各项的完整含义:

  • 按拦截原因和风险选处置
  • Lv1: 允许的任务有限重试 政策不变
  • Lv2: 缩小到仍获授权的任务范围
  • Lv3: 返回经权限和时效核对的缓存
  • Lv4: 明确拒绝、模板兜底或转人工
阶梯 触发条件 用户感知
Lv1:换模型重试 仅对允许处理、疑似生成错误的任务,在相同政策下有限重试 延迟和费用增加,也可能再次失败
Lv2:缩小任务范围 仍有可独立完成、获准处理的子任务 明确说明未完成的部分,不把受限回答包装成完整结果
Lv3:返回缓存答案 缓存经当前租户权限、政策版本和有效期验证 标明缓存或信息时效;无法确认适用则不返回
Lv4:模板兜底/转人工 没有安全可用的替代结果,或已确认越权、泄密及禁止执行的风险 明确告知当前无法处理,必要时交给有权人员复核,不必先尝试前三种处置

关键原则:按具体风险选择处置,不从 Lv1 机械地试到 Lv4。 已确认越权、泄密或禁止执行的动作,应停止或进入有权人员的复核流程,不能换模型绕过拦截。涉及支付、医疗或法律的请求还要区分普通信息查询与高影响决定;是否允许提供受限信息由业务政策决定,不能只凭主题词把所有请求当成同一风险。

6.5 护栏本身也需要评测和监控

护栏不是配置一次就一劳永逸的规则集。误报(把正常请求当成风险拦下)和漏报(真实风险没拦住)都需要持续监控:

指标 关注点
护栏拦截率 突然上升可能意味着上游输入分布变化,也可能是护栏规则误伤扩大
正常请求误报率 真实正常请求中被误拦的比例;只复核被拦样本得到的是「拦截中误伤占比」,分母不同
风险请求漏报率 已标注风险请求中被放行的比例;已知红队集只能估计该测试分布,线上还需抽查放行样本

6.6 常见错误

6.6.1 把护栏完全交给模型自我审查

依赖系统 Prompt 里一句"不要泄露隐私信息"来防止 PII 泄露,而不做程序化检测,对抗性输入很容易绕过这种"君子协定"。

6.6.2 拦截之后直接报错,没有降级路径

用户体验是"这次系统崩了",而不是"系统换了个方式回答了我"。降级阶梯的价值就在于把安全拦截对用户体验的冲击降到最低。

6.6.3 所有触发原因都走同一个降级阶梯

应根据具体请求或动作的风险、明确的拦截原因和业务政策选择降级路径,不能仅因涉及资金或医疗就一律采用最严格处理。高影响建议、禁止动作或越权请求仍须遵守相应限制,不得通过“换模型重试”等手段绕过拦截。

6.6.4 护栏规则上线后不再监控误报率

规则集是静态的,但输入分布和攻击手法在持续变化,不做持续监控会导致规则逐渐失效或者误伤持续扩大而不自知。

6.7 本章总结

  1. Guardrails 和契约校验是两回事:前者管内容安全与业务边界,后者管格式是否可被程序消费;
  2. 输入护栏和输出护栏各有分工:前者在模型生成之前拦截风险请求,后者在返回用户之前拦截风险输出;
  3. 权限边界不应完全依赖模型自我拒绝,业务系统层面的确定性规则才是最后兜底的防线;
  4. 降级有多种可选处置,而非自动遍历的重试链,返回模板也不能计作原任务完成;
  5. 处置取决于具体动作和拦截原因,已确认越权或禁止执行的任务不能通过换模型放行;
  6. 护栏本身需要持续评测:监控误报率、漏报率,并用红队测试集验证有效性。

参考资料

本章的参考资料、阅读建议与来源说明见集中参考资料章节。