1. 流式 LLM 内容过滤器问题
输出阶段防护栏筛查模型的回复。在非流式请求上这很直接:网关 在一个字节返回之前就拥有完整的补全,因此它可以干净地拦截、脱敏 或放行。流式把这个反转了。回复以一系列 SSE delta 到达,每个都 一落地就转发给你的客户端,因此一个等到结尾的过滤器什么都过滤 不了。 OrcaRouter 的答案是一个流扫描器:当输出 delta 流过时,扫描器 对累积的文本运行你的输出阶段规则,并在一条规则触发的那一刻就 行动——而不是在流完成之后。你编写的动作决定”行动”意味着什么:block 切断流,flag 放行它。mask 确实会在非流式输出上
脱敏,但带内的流改写在规划路线图上——在流上,扫描器今天计算
脱敏但只对 block 决策采取行动,因此 mask 规则尚不会脱敏一个
流式回复。
这个注意事项只对流式请求上的输出阶段规则才重要。输入阶段
规则在模型运行之前筛查请求,因此它们完全上线,包括脱敏——而
非流式请求上的任何输出规则看到的是整个回复,行为正常,包括
mask。2. 今天哪些是流安全的
block——流安全(中途切断流)
block——流安全(中途切断流)
block 规则在流式和非流式输出上都会执行。在流上,扫描器
监视 delta;当一条拦截规则触发时它会切断流——封闭扫描器,
发出一条简短的替换通知([response truncated by guardrail: … policy violation])作为最后一个 delta,并在任何进一步被拦截的
内容到达客户端之前关闭 SSE 通道。由于在第一个 delta 刷出时
HTTP 响应状态已经被提交为 200,一次流中途的拦截无法重新
发出一个状态——它优雅地终止打开的流。HTTP 400
guardrail_blocked 响应体是非流式输出拦截的形态。已经刷到客户端的字节无法被收回,因此流式拦截对已经流出的
内容是尽力而为,但能可靠地阻止匹配之后的一切。要硬性保证
任何违规字节都不会被发送——以及要 400 guardrail_blocked
响应体——请以非流式发送请求。mask——仅非流式输出(带内流改写在规划路线图上)
mask——仅非流式输出(带内流改写在规划路线图上)
mask 规则在非流式输出上改写匹配——例如回复中的 email
变成 [EMAIL]——网关在那里持有整个补全并把脱敏后的形式转发给
你的客户端。在流式输出上,扫描器今天计算脱敏但不转发被脱敏的
文本——它只对 block 决策采取行动——因此 mask 规则不会
脱敏一个流式回复。带内的流式输出改写在规划路线图上。在它上线
之前,如果你需要一个流式回复永不暴露匹配的文本,把规则编写为
block(命中时它会结束响应)或以非流式发送请求,让 mask
改写整个回复。flag——仅观察,从不改变流量
flag——仅观察,从不改变流量
flag 规则从不改变流量——它放行字节。在非流式输出上,它在
Matches 信息流中记录一条匹配,因此你可以在把规则晋级到
block 之前衡量它的命中率。在流式响应上,它保持仅观察并
原封不动地放行 delta;结构化的匹配记录写在非流式输出路径上。
无论哪种方式,它从不拦截或改写,因此始终可以安全地开着。output 上的动作 | 非流式 | 流式 |
|---|---|---|
block | 拒绝回复 | 切断流 |
mask | 脱敏回复 | 尚未——改用 block(路线图) |
flag | 记录一条匹配 | 放行(仅观察) |
3. 一个具体示例——一个流安全的密钥过滤器
假设你的模型可能从 RAG 上下文中带出一个凭证,而你的应用是 流式的。你想让网关在一个密钥形态的匹配出现的那一刻杀掉流,而 不是脱敏它——一个泄露的密钥应该结束响应,而不是被部分脱敏。 在控制台中编写它——策略编辑是你会话上的管理操作,门控为 Developer+;中继密钥只发送/v1/* 流量:
- 打开
/console/guardrails,New guardrail,命名它stream-safe-out。 - 添加一条规则:
- Type:
regex(或一条带密钥实体如aws_access_key/api_key_openai/jwt的pii规则) - Stage:
output - Action:
block← 在密钥命中时结束响应;mask则会改为 脱敏它并让回复的其余部分继续
- Type:
- 保存,然后在
/console/token通过密钥的 Guardrail 下拉菜单 绑定它。
stream: true 调用网关,与以前完全一样:
4. 流上的 PII Shield
PII Shield 预设是单条pii 规则,动作 mask,阶段 both。
在输入阶段它完全上线——它在模型看到请求之前改写请求,无论
是否流式。在输出阶段,脱敏在非流式回复上脱敏,网关在那里
持有整个补全直到它返回。
在流式输出上,脱敏尚不脱敏——扫描器计算脱敏但只对 block
决策采取行动,因此一个流式回复是放行的,而不是被改写的。带内的
流式输出改写在规划路线图上。所以如果你的目标是 PII 在一个流式
回复中永不可观察,那么或者:
- 把输出规则编写为 block,接受命中会结束响应而不是脱敏它,或
- 以非流式发送请求,让 mask 在手握整个补全的情况下改写整个 回复。
5. 上线前先证明它
不要猜测哪个阶段/动作组合成立——验证它。Test 标签页
每个防护栏编辑器都有一个 Test 标签页:粘贴一个样本,选择
output 阶段,并在无上游调用、无配额的情况下运行当前策略。
查看判定结果,以及(对于 mask 规则)渲染后的文本。运行沙箱是
一个 Developer+ 操作(它可以触发付费的 judge / 外部规则)。Eval 标签页
Eval 标签页针对内置或自定义 JSONL 语料库为一个防护栏打分
——有助于在你绑定密钥之前确认一条拦截规则能跨一个语料库捕获
一个已知泄露。
6. 流式拦截花费什么
流式拦截带有与任何输出拦截相同的记账——上游模型已经运行, 因此网关为你处理退回:- 流以一个优雅的截断 delta 终止(状态已经是 200);非流式
输出拦截返回指明触发的防护栏和规则的 HTTP 400
guardrail_blocked响应体。 - 不消耗任何配额。 当输出拦截拒绝响应时,网关会退回预先 扣除的配额,因此即使模型产生了 token,一次被拦截的调用对你 也是免费的。
- 该请求被标记为 skip-retry——重新运行同一个提示词只会再次 拦截,因此网关不会在另一个通道上烧掉一次重试。
GET /api/guardrail/match,对任何
Member 开放);匹配的子串只有在防护栏的 Log raw content
开关开启时才捕获(默认关闭)。完整细节见
guardrail_blocked 错误
和 matches 信息流。
7. 接下来去哪里
输出阶段
完整的输出阶段——筛查模型的回复、block vs. mask,以及 grounding。
流式覆盖
跨每个阶段和动作,流式与非流式上各自执行内容的完整矩阵。
动作
深入介绍 block、mask 和 flag——每一个何时是正确选择。
输入阶段
镜像版——脱敏在这里完全上线,包括在流式上。
