串行护栏是一种结构,它使单个请求按照预定顺序通过多个检查关卡。控制平面不是直接通过这些关卡的路径,而是一个控制层,负责部署哪些策略和模型以何种顺序执行,并管理其版本。
在之前的实践中,我们分别验证了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重复执行已知攻击和正常请求,以验证策略变更是否会破坏现有防线。
官方资料
- NVIDIA NeMo Guardrails Architecture Overview
- NVIDIA NeMo Guardrails Rail Types
- NeMo Guardrails Configuration
- NeMo Guardrails Sequence Diagrams
- NeMo Guardrails Message Checking API
- NeMo Guardrails Content Capture
※ 控制平面包和审批·部署状态并非官方产品要求,而是本文推荐的运营设计方案。NeMo API和配置可能因版本而异,请务必通过固定的发布文档和合成测试用例进行确认。
发表回复