组装防线:串行护栏与控制平面

串行护栏是一种结构,它使单个请求按照预定顺序通过多个检查关卡。控制平面不是直接通过这些关卡的路径,而是一个控制层,负责部署哪些策略和模型以何种顺序执行,并管理其版本。

在之前的实践中,我们分别验证了NeMo Guardrails、基于Nova Lite的安全分类以及Microsoft Presidio。现在,重要的问题不再是“启用了多少工具”,而是“谁来决定顺序,如何合并失败,以及如何追踪策略变更”。本文将通过合成测试用例构建一个串行数据平面,并添加一个小的控制平面契约。

用户请求串行通过多个安全关卡,上层控制平面管理策略和状态

首先,区分数据平面和控制平面

数据平面是实际用户请求、检索文档和模型响应流经的执行路径。控制平面管理策略包、提示版本、模型设置、阈值和部署状态。当控制平面发生故障时,不应立即下载未经验证的新策略,也不应允许所有请求。

类别 职责 不应处理的事项
— — —
数据平面 请求检查、RAG精炼、LLM·工具执行、输出检查 任意策略变更和未经批准的部署
控制平面 策略版本、批准、签名·哈希、部署、回滚 用户请求原文和最终业务权限
应用层 认证、租户、业务权限、最终批准、审计 将访问权限判断委托给LLM
NeMo运行时 Rail·Action·Retrieval·LLM流程编排 用户最终授权的单独判断

这里的控制平面并非指NVIDIA产品的独立必需组件。NeMo官方文档描述了config.yml、Prompt、Colang Flow和Runtime Orchestration。中央策略存储库和审批·部署流程是为运行它们而提出的本文推荐的设计方案。

推荐的串行流程

Browser
  ↓
Application: 인증·Tenant·요청 한도
  ↓
Nova Lite 분류: 명시적 Safety Category
  ↓
NeMo Input Rail: Self-check·Jailbreak·주제 정책
  ↓
Retrieval API: Tenant Filter와 Security Trimming
  ↓
NeMo Retrieval Rail: Presidio로 원문 Chunk 정제
  ↓
Dialog·Execution Rail: 대화 흐름과 Tool 호출 검증
  ↓
Main LLM
  ↓
Output Rail: Safety·PII·형식 검사
  ↓
Application: 최종 업무 권한·감사·응답

NVIDIA官方文档解释说,Input Rail在主LLM之前检查输入,Retrieval Rail在将检索到的文档和块放入Prompt之前进行处理,Execution Rail控制Action和Tool,Output Rail在返回响应之前检查响应。确切的执行顺序可能因所选引擎和配置而异。

串行结构的优点是,在前端进行阻断可以跳过后续昂贵的调用,并且容易解释是哪个关卡做出了决定。缺点是每个阶段的延迟会累加,多个策略可能会重复阻断同一个请求,并且前端的故障可能会影响整体可用性。

实践1:创建通用判定契约

如果每个工具的结果格式不同,集成代码很容易将异常转换为允许。首先,确保所有适配器返回相同的格式。

from dataclasses import dataclass, field
from typing import Literal

Decision = Literal["ALLOW", "BLOCK", "MODIFY", "ERROR"]

@dataclass
class GuardrailResult:
    decision: Decision
    stage: str
    rule_id: str
    policy_version: str
    sanitized_text: str | None = None
    labels: list[str] = field(default_factory=list)

不要在reason中复制用户原文或检测到的个人信息。操作记录中只保留Stage、Rule ID、Decision、Policy Version、Latency和Correlation ID。

实践2:组装串行管道

以下代码是连接具有相同接口的异步适配器(而非实际提供者)的骨架。在每个函数内部插入Nova、NeMo和Presidio调用。

import asyncio

class Blocked(Exception):
    def __init__(self, result: GuardrailResult):
        self.result = result

async def enforce(result: GuardrailResult, text: str) -> str:
    if result.decision in {"BLOCK", "ERROR"}:
        raise Blocked(result)
    if result.decision == "MODIFY":
        if result.sanitized_text is None:
            raise Blocked(GuardrailResult(
                "ERROR", result.stage, "missing_sanitized_text",
                result.policy_version,
            ))
        return result.sanitized_text
    return text

async def run_pipeline(user_text: str, tenant_id: str) -> str:
    text = await enforce(await nova_classify(user_text), user_text)
    text = await enforce(await nemo_input_check(text), text)

    chunks = await authorized_retrieve(
        query=text,
        tenant_id=tenant_id,
    )
    safe_chunks = await presidio_sanitize_chunks(chunks)

    tool_plan = await nemo_dialog_and_execution(text, safe_chunks)
    answer = await call_main_llm(text, safe_chunks, tool_plan)
    answer = await enforce(await nemo_output_check(answer), answer)
    return answer

此示例默认阻断ERROR。对于可用性至关重要的服务,如只读FAQ,可以切换到移除RAG和工具的受限模式,但对于处理敏感信息或具有写入权限的Agent,故障关闭(Fail-closed)是安全的默认值。

实践3:使NeMo配置顺序可见

在NeMo的config.yml中,声明了活动的Rail Flow。官方配置文档说明,Input和Output Flow可以按顺序排列,并且某些环境也支持并行执行。

rails:
  input:
    parallel: false
    flows:
      - self check input
      - check jailbreak
      - mask sensitive data on input

  retrieval:
    flows:
      - check retrieval sensitive data

  output:
    parallel: false
    flows:
      - self check output
      - check output sensitive data

tracing:
  enabled: true
  enable_content_capture: false

修改输入的Rail,其后续检查结果会因顺序而异。NVIDIA官方文档也警告说,并行执行Input Rail Mutation可能因竞争条件导致结果不同,建议在这种情况下使用串行执行。虽然独立的只读分类器可以并行化,但出于性能原因,需要进行回归测试以确保其语义不被改变。

此外,NeMo Tracing的Message Content Capture默认是关闭的。如果在生产环境中开启原文捕获,Prompt、响应和Tool结果可能会在Trace中被复制,因此在没有单独审批、保留期和访问控制的情况下,不应启用此功能。

实践4:最小的控制平面契约

控制平面不是一个允许执行代码接收任意最新文件的下载服务器。它提供经批准的策略包的版本和哈希,数据平面只激活在启动时经过验证的包。

bundle_id: llm-guardrails-prod
version: 2026.09.03-1
status: approved
minimum_runtime: "0.18"
default_failure_mode: fail_closed

stages:
  - id: nova-input-safety
    policy: policies/nova-input-v3.txt
    timeout_ms: 1200
  - id: nemo-input
    policy: configs/nemo/config.yml
    timeout_ms: 1800
  - id: presidio-retrieval
    policy: policies/pii-entities-v5.yml
    timeout_ms: 500

artifacts:
  manifest_sha256: "배포 과정에서 계산한 값"

清单中不应包含实际的Secret、API Key或个人信息样本。策略包应通过Git的Pull Request或审批工作流创建为不可变工件,数据平面应验证允许的签名者、哈希和运行时版本。

实践5:创建部署和回滚状态

策略部署应像代码部署一样对待。

draft → reviewed → approved → canary → active
                     ↘ rejected
active → rollback_previous

在金丝雀发布中,使用部分合成测试流量比较正常允许率、攻击阻断率、超时、P95延迟和分阶段成本。如果未通过标准,则回滚到之前的策略包。不要假设“最新策略是安全的”,而是留下与之前版本可比较的证据。

变更 必需验证
— —
安全提示 正常·攻击分类回归,输出格式
Presidio识别器 误报·漏报,语言变体
Rail顺序 变异前后结果,后续调用发生情况
模型替换 策略合规性,延迟,成本,超时
工具Schema 允许函数·参数·结果验证
故障模式 故障注入时阻断或受限模式

需要观察的最小信号

每个请求应将以下值作为结构化日志和跟踪属性记录:

  • request_id,已验证tenant_id的非识别内部标识符
  • policy_bundle,policy_version,runtime_version
  • 分阶段决策,rule_id,latency_ms
  • 执行的LLM·Retriever·Tool的类型和成功状态
  • 最终blocked_by和是否处于受限模式

Prompt和响应原文、PII、Credential应从默认遥测中排除。度量标签中也不应包含用户输入或唯一标识符。可追溯性并非来自存储大量原文,而是通过相同的Request ID连接策略版本和执行路径。

串行、并行或混合型选择

方式 优点 风险 适用情况
— — — —
完全串行 顺序和阻断原因明确 累积延迟,前端瓶颈 变异,敏感RAG,工具Agent
完全并行 可缩短独立检查延迟 竞争条件,重复成本,集成复杂性 只读独立分类器
混合型 保留语义顺序并进行部分优化 设计·观测复杂性 运营规模扩大的服务

推荐的起点是串行。在测量结果积累之后,只对不修改彼此输入的独立检查进行并行化。如果并行调用中的一个首先允许了请求,不应立即返回主响应;最终的组合策略必须等待所有必需的判定。

实践完成清单

  • 区分了数据平面和控制平面的职责。
  • 认证、租户和业务权限留给了应用层和下游。
  • 所有护栏适配器都返回通用判定契约。
  • 文档化了Rail的执行顺序和短路规则。
  • 定义了错误、超时和未知情况的默认行为。
  • 在策略包中记录了版本、哈希、审批状态和运行时条件。
  • 准备了金丝雀发布和回滚到旧版本的路径。
  • 将Prompt、PII和Credential从默认遥测中排除。
  • 每次策略、模型、顺序变更都执行合成回归测试。
  • 通过跟踪确认阻断后没有RAG、Tool或主LLM调用。

总结

串行护栏并非简单地将多个安全产品串联起来。它旨在明确每个阶段的输入和输出、修改权限、失败策略和最终决策者。应用层负责认证、授权和业务控制,NeMo协调LLM处理流程,而Nova和Presidio等检查则在既定边界内提供专业判定。

控制平面在此执行路径上管理策略版本、审批、部署和回滚。首先,通过简单的串行路径确保准确性,只有在有测量依据时才对独立检查进行并行化。下一篇中,我们将使用Promptfoo重复执行已知攻击和正常请求,以验证策略变更是否会破坏现有防线。

官方资料

※ 控制平面包和审批·部署状态并非官方产品要求,而是本文推荐的运营设计方案。NeMo API和配置可能因版本而异,请务必通过固定的发布文档和合成测试用例进行确认。


Comments

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注