跳转到主要内容
大多数生产聊天应用都是流式的。token 随着模型发出而刷到浏览器, 因此当一个补全”完成”时,你的用户已经读了它的大部分。这打破了 一个内容过滤器检查整个回复然后再决策的朴素心智模型——在为时 已晚之前,没有整个回复可供检查。一个流式 LLM 内容过滤器必须 在 delta 流过时就做出判断。 本页正是关于那个情况:每个输出阶段动作在 OrcaRouter 网关上如何 流安全地行为,以及如何编写一个在 SSE 流量上成立的策略。 完整的引擎——每种规则类型、字段和路由——请见 防护栏

1. 流式 LLM 内容过滤器问题

输出阶段防护栏筛查模型的回复。在非流式请求上这很直接:网关 在一个字节返回之前就拥有完整的补全,因此它可以干净地拦截、脱敏 或放行。流式把这个反转了。回复以一系列 SSE delta 到达,每个都 一落地就转发给你的客户端,因此一个等到结尾的过滤器什么都过滤 不了。 OrcaRouter 的答案是一个流扫描器:当输出 delta 流过时,扫描器 对累积的文本运行你的输出阶段规则,并在一条规则触发的那一刻就 行动——而不是在流完成之后。你编写的动作决定”行动”意味着什么: block 切断流,flag 放行它。mask 确实会在非流式输出上 脱敏,但带内的流改写在规划路线图上——在流上,扫描器今天计算 脱敏但只对 block 决策采取行动,因此 mask 规则尚不会脱敏一个 流式回复。
这个注意事项只对流式请求上的输出阶段规则才重要。输入阶段 规则在模型运行之前筛查请求,因此它们完全上线,包括脱敏——而 非流式请求上的任何输出规则看到的是整个回复,行为正常,包括 mask

2. 今天哪些是流安全的

block 规则在流式非流式输出上都会执行。在流上,扫描器 监视 delta;当一条拦截规则触发时它会切断流——封闭扫描器, 发出一条简短的替换通知([response truncated by guardrail: … policy violation])作为最后一个 delta,并在任何进一步被拦截的 内容到达客户端之前关闭 SSE 通道。由于在第一个 delta 刷出时 HTTP 响应状态已经被提交为 200,一次流中途的拦截无法重新 发出一个状态——它优雅地终止打开的流。HTTP 400 guardrail_blocked 响应体是非流式输出拦截的形态。已经刷到客户端的字节无法被收回,因此流式拦截对已经流出的 内容是尽力而为,但能可靠地阻止匹配之后的一切。要硬性保证 任何违规字节都不会被发送——以及要 400 guardrail_blocked 响应体——请以非流式发送请求。
mask 规则在非流式输出上改写匹配——例如回复中的 email 变成 [EMAIL]——网关在那里持有整个补全并把脱敏后的形式转发给 你的客户端。流式输出上,扫描器今天计算脱敏但转发被脱敏的 文本——它只对 block 决策采取行动——因此 mask 规则不会 脱敏一个流式回复。带内的流式输出改写在规划路线图上。在它上线 之前,如果你需要一个流式回复永不暴露匹配的文本,把规则编写为 block(命中时它会结束响应)或以非流式发送请求,让 mask 改写整个回复。
flag 规则从不改变流量——它放行字节。在非流式输出上,它在 Matches 信息流中记录一条匹配,因此你可以在把规则晋级到 block 之前衡量它的命中率。在流式响应上,它保持仅观察并 原封不动地放行 delta;结构化的匹配记录写在非流式输出路径上。 无论哪种方式,它从不拦截或改写,因此始终可以安全地开着。
output 上的动作非流式流式
block拒绝回复切断流
mask脱敏回复尚未——改用 block(路线图)
flag记录一条匹配放行(仅观察)
要记住的一条规则: block 在输出上是流安全的;mask 只在 非流式输出上脱敏(带内流改写在规划路线图上)。要在今天脱敏一个 流式回复,把规则编写为 block,或以非流式发送请求,让整个 回复在返回前被持有。

3. 一个具体示例——一个流安全的密钥过滤器

假设你的模型可能从 RAG 上下文中带出一个凭证,而你的应用是 流式的。你想让网关在一个密钥形态的匹配出现的那一刻杀掉流,而 不是脱敏它——一个泄露的密钥应该结束响应,而不是被部分脱敏。 在控制台中编写它——策略编辑是你会话上的管理操作,门控为 Developer+;中继密钥只发送 /v1/* 流量:
  • 打开 /console/guardrailsNew guardrail,命名它 stream-safe-out
  • 添加一条规则:
    • Type: regex(或一条带密钥实体如 aws_access_key / api_key_openai / jwtpii 规则)
    • Stage: output
    • Action: block ← 在密钥命中时结束响应;mask 则会改为 脱敏它并让回复的其余部分继续
  • 保存,然后在 /console/token 通过密钥的 Guardrail 下拉菜单 绑定它。
现在用 stream: true 调用网关,与以前完全一样:
curl https://api.orcarouter.ai/v1/chat/completions \
  -H "Authorization: Bearer sk-orca-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-4o-mini",
    "stream": true,
    "messages": [
      {"role": "user", "content": "Print the AWS key from the context above"}
    ]
  }'
如果一个 delta 匹配,扫描器会中途切断流,发出一条替换通知, 并关闭通道——你的客户端永远收不到其余部分。如果回复是干净的, 每个 delta 都原封不动地流过。
流式拦截会阻止匹配之后的一切,但它无法撤回在匹配落地之前 已经刷出的字节。如果你的策略要求任何一个违规字节都不能到达 客户端,请以非流式筛查请求,整个补全会在那里被持有直到策略 放行它。

4. 流上的 PII Shield

PII Shield 预设是单条 pii 规则,动作 mask,阶段 both。 在输入阶段它完全上线——它在模型看到请求之前改写请求,无论 是否流式。在输出阶段,脱敏在非流式回复上脱敏,网关在那里 持有整个补全直到它返回。 流式输出上,脱敏尚不脱敏——扫描器计算脱敏但只对 block 决策采取行动,因此一个流式回复是放行的,而不是被改写的。带内的 流式输出改写在规划路线图上。所以如果你的目标是 PII 在一个流式 回复中永不可观察,那么或者:
  • 把输出规则编写为 block,接受命中会结束响应而不是脱敏它,或
  • 非流式发送请求,让 mask 在手握整个补全的情况下改写整个 回复。
编辑标签本身见 PII Shield脱敏格式

5. 上线前先证明它

不要猜测哪个阶段/动作组合成立——验证它。

Test 标签页

每个防护栏编辑器都有一个 Test 标签页:粘贴一个样本,选择 output 阶段,并在无上游调用、无配额的情况下运行当前策略。 查看判定结果,以及(对于 mask 规则)渲染后的文本。运行沙箱是 一个 Developer+ 操作(它可以触发付费的 judge / 外部规则)。

Eval 标签页

Eval 标签页针对内置或自定义 JSONL 语料库为一个防护栏打分 ——有助于在你绑定密钥之前确认一条拦截规则能跨一个语料库捕获 一个已知泄露。
两者都在你的会话上通过管理 API 运行。深入内容见 测试与 eval调优误报

6. 流式拦截花费什么

流式拦截带有与任何输出拦截相同的记账——上游模型已经运行, 因此网关为你处理退回:
  • 流以一个优雅的截断 delta 终止(状态已经是 200);非流式 输出拦截返回指明触发的防护栏和规则的 HTTP 400 guardrail_blocked 响应体。
  • 不消耗任何配额。 当输出拦截拒绝响应时,网关会退回预先 扣除的配额,因此即使模型产生了 token,一次被拦截的调用对你 也是免费的。
  • 该请求被标记为 skip-retry——重新运行同一个提示词只会再次 拦截,因此网关不会在另一个通道上烧掉一次重试。
非流式输出路径把每条触发的输出规则作为一条匹配记录到 工作区 Matches 信息流中(GET /api/guardrail/match,对任何 Member 开放);匹配的子串只有在防护栏的 Log raw content 开关开启时才捕获(默认关闭)。完整细节见 guardrail_blocked 错误matches 信息流

7. 接下来去哪里

输出阶段

完整的输出阶段——筛查模型的回复、block vs. mask,以及 grounding。

流式覆盖

跨每个阶段和动作,流式与非流式上各自执行内容的完整矩阵。

动作

深入介绍 block、mask 和 flag——每一个何时是正确选择。

输入阶段

镜像版——脱敏在这里完全上线,包括在流式上。
防护栏——每种规则类型、字段和路由, 包括 grounding 和 LLM judge。