400。三个安全错误码覆盖了你会遇到的情形:一个被筛查的
提示词或响应、一次被拒绝的工具调用,以及一次被挂起等待人工审批的工具调用。
本页是这些错误码的参考——每一个的使用场景、确切的 HTTP 状态、它给你带来
的代价,以及那条最重要的规则:重试逻辑必须对它们做特殊处理。 三者都被
标记为 skip-retry;盲目地重新运行同一个调用只会再次触发同一个控制。
这些是*执行(enforcement)*错误码——网关决定不转发你的调用。它们有别于
上游提供商错误(一个模型 429、一次上下文溢出),也有别于认证失败。要了解
某个具体请求为何被拦下,参见
为什么这次被拦下了?。
1. llm 安全错误码一览
每一次安全拦截都返回 HTTP 400,带有一个 OpenAI 形态的错误体 (error.code 是下面那个类型化字符串)。在原生 Claude(/v1/messages)
路由上,同一个错误码以 Claude 错误形态传递,因此 SDK 路由在各协议间
是确定的。
| 错误码 | 拦下什么 | 配额代价 |
|---|---|---|
guardrail_blocked | 一个命中 block 规则的提示词或响应 | 无 |
firewall_blocked | 一次被拒绝的工具调用 / 声明 | 不消耗模型 token |
firewall_approval_pending | 一次被挂起等待人工审查者的工具调用 | 不消耗模型 token |
2. guardrail_blocked —— 一个被筛查的提示词或响应
当一条带 block 动作的 防护栏 规则触发时返回——
一个被拒绝列表收录的关键词、一次正则命中、一个你选择拦截而非脱敏的 PII 或
密钥实体、一个 llm_judge 判定,或一次失败的 grounding 检查。
HTTP 400。 消息点名触发的防护栏和规则。
配额影响:无
配额影响:无
一个被拦截的请求不消耗配额。输入阶段拦截在计量之前触发,因此
永远不会有计费。输出阶段拦截在模型响应之后运行,因此网关会在返回
错误之前退回预先扣除的配额。无论哪种方式,你都不为一次被拦截的
调用付费。
为什么它是 skip-retry
为什么它是 skip-retry
判定是内容的属性,而非通道的属性。重新运行同一个提示词——即便对照
一个不同的模型——也会产生同一个拦截。修复输入(或策略),而不是重试。
一个 mask 反而是什么样子
一个 mask 反而是什么样子
mask 规则不返回这个错误码。一个被脱敏的匹配(例如
jane@acme.com → [EMAIL])会就地脱敏,调用照常进行——你得到一个
200,只是去掉了那段敏感内容。只有 block 动作会浮现 guardrail_blocked。
(flag 完全不改变流量。)3. firewall_blocked —— 一次被拒绝的工具调用
当 防火墙 为一次工具调用解析出一个 deny 判定时
返回——一条破坏性的 shell 命令、一次 SSRF 形态的 fetch、一个在拒绝列表上的
egress 目的地,或一个处于 block 模式的 技能。
这次拒绝如何浮现,取决于
执行面:
inbound / response / egress
HTTP 400,
error.code = firewall_blocked。错误体携带结构化的
error.metadata(reason_code、风险 factors、risk_score),因此
你能解释这次拦截,而不只是看到它。mcp 执行面
作为一个工具错误(
firewall deny: <reason>)返回,而非一次传输
失败——这样模型能看到这次拒绝并换一个工具、询问用户,或停止,而不是
让整个运行崩溃。4. firewall_approval_pending —— 被挂起等待人工
在一次工具调用命中 pending_approval 判定的那一刻返回。一个人工介入
(human-in-the-loop)门控不能是一次阻塞式的内联等待,因此网关会立即返回
一个**已挂起(held)**响应,而不是长轮询。
HTTP 400。 该错误携带审批 id,让你的智能体知道要解决哪一次挂起。
这是唯一一个你应当通过解决并重新提交来应对的错误码——而不是把它当作
一个终结性失败:
等待一个决策
一名审查者从控制台(Developer+)解决它,或者你的审批系统收到一个
HMAC 签名的 webhook 回调。你的智能体轮询
GET /api/v1/firewall/approvals/:id 获取状态。审批路由(
/api/v1/firewall/approvals/*)运行在一个
firewall-gateway-scoped 密钥上,而非你的控制台会话。完整的环路参见
人工审批(HITL),回调签名参见
Webhook 负载。5. 为什么三者都跳过重试
标准 SDK 重试逻辑假设一个400 在第二次尝试时可能成功。这些错误码打破了
这一假设——拦截是确定性的,因此一次盲目重试会浪费一次往返,并(对挂起的
调用而言)静默地把一次审批重新入队。
'skip-retry' 在实践中意味着什么
'skip-retry' 在实践中意味着什么
OrcaRouter 自己的内部重试/回退机制绝不会针对另一个通道重新尝试一个
返回了这些错误码之一的调用。在你的客户端里照做:遇到一个安全错误码时,
停下并按判定行事,不要循环。
每个错误码的正确反应
每个错误码的正确反应
guardrail_blocked→ 修复输入或放宽策略;把拒绝浮现给用户。不要重试。firewall_blocked→ 该动作不被允许;让智能体选择一个不同的工具或 请求帮助。不要重试。firewall_approval_pending→ 解决这次挂起,然后携带审批头重新提交 一次(§4)。一次不带审批头的重试会再次挂起。
6. 配额与计费小结
一次安全拦截绝不会为被拦截的那一份工作向你计费。| 错误码 | 何时触发 | 计费结果 |
|---|---|---|
guardrail_blocked(输入) | 在模型调用之前 | 从不计量 |
guardrail_blocked(输出) | 在模型响应之后 | 退回预先扣除的配额 |
firewall_blocked(inbound) | 在模型调用之前 | 不消耗模型 token |
firewall_approval_pending | 在派发之前 | 不消耗模型 token |
7. 相关参考
为什么这次被拦下了?
把一次拦截追踪到产生它的确切规则、执行面和原因。
判定词汇表
每一个防火墙判定——allow、audit、deny、sanitize、pending_approval、
cap_cost——以及各自浮现什么。
Webhook 与错误负载
完整的错误信封、
error.metadata 字段,以及审批回调签名。执行模式
Shadow、observe 与 enforce——一个判定何时真正改变流量。
