400 不是你提示词里的 bug。它是
一个策略在尽职。你的工作是找出哪个策略,然后决定是修复调用还是放宽规则。
1. 我的 llm 请求为什么被拦下了?—— 从错误码开始
托管网关上的每一次安全拦截都返回 HTTP 400,在 OpenAI 形态的错误体里 带一个机器可读的code。那个错误码是这条路上的第一个岔口——它告诉你要
调试哪个控制平面、要打开哪个信息流。
firewall_approval_pending
一次工具调用被挂起等待人工审批——而非被拒绝。去解决它,
别去调试它。参见 §4。
三个错误码都是 skip-retry:重新运行那个完全相同的调用会路由到同一个
策略并再次被拦。重试是被浪费的延迟——改去修复输入或规则。完整的错误码
表在 错误码。
2. guardrail_blocked —— 在 Matches 中找到规则
guardrail_blocked 意味着附加到你密钥(或你工作区默认值)的一个内容策略
对请求输入或模型输出运行了一次 block 动作。拦截消息点名防护栏和规则,
而该请求不消耗你任何配额——输入拦截在计量之前触发;输出拦截退回预先
扣除的配额。
追踪它:
打开 Matches 信息流
在控制台里,前往 Guardrails 页面上的 Matches 标签
(
GET /api/guardrail/match,Member)。每一条触发的规则都落在这里——
它的 RuleType、Action、Stage,以及一个 Detail 字符串,例如
pii: email, phone 或 matched 3 keyword(s)。过滤到那次拦截
按
action = block 和你请求的时间过滤。匹配的那一行告诉你规则类型
(pii、regex、keyword、max_chars、llm_judge、grounding、
external)以及它是在 input 还是 output 阶段触发的。一个具体例子
你发出一条包含某客户 SSN 的支持回复。你的pii-shield 防护栏有一个
entity_actions 覆盖,对 ssn 执行拦截:
400 guardrail_blocked。Matches 信息流显示
RuleType: pii、Action: block、Stage: input、Detail: pii: ssn。
修复是一个产品决策,而非代码改动:把这个覆盖放宽到 mask(模型永远
看不到 SSN,调用照常通过),或者保留拦截并在上游剥离 SSN。完整的规则
类型和 PII 实体参考,参见 防护栏。
3. firewall_blocked —— 在 Events 中找到判定
firewall_blocked 意味着一条 防火墙 策略
拒绝了一次工具调用。在 inbound 执行面上它浮现为 400;通过 MCP 网关
它浮现为一个工具错误(firewall deny: <reason>),这样模型就能
反应而不是崩溃。错误的 metadata 携带原因码、风险因子和得分。
在 Events 信息流中追踪它(GET /api/workspace/firewall/events,
Developer+)——每一次评估背后的原始记录。每个事件携带一个判定以及
它被看到的执行面:
| 判定 | 它对你这次拦截意味着什么 |
|---|---|
deny | 一条规则(或 default_verdict)拦下了这次调用。这就是你的 firewall_blocked。 |
audit | 放行但记录——如果策略处于影子模式,还包括一个 [shadow] 的 “would deny”。 |
cap_cost | 该运行累积的花费越过了某条规则的分(cents)上限;解析为一次拒绝。 |
把 Events 过滤到那次拒绝
按
verdict=deny 过滤,再按 tool、run_id 或 session_id 过滤
以隔离那次确切的调用。事件点名匹配到的规则以及执行面——inbound、
response、mcp 或 egress。在匹配到的规则上读原因
原因字符串(例如
destructive shell command、
egress host not allowed)告诉你规则是匹配在工具名、一个
args_match 子句,还是一个 egress 目的地上。
判定词汇表 和
glob 与 JSONPath 参考 解码
这种匹配。4. firewall_approval_pending —— 它是被挂起,而非被拒绝
firewall_approval_pending 是唯一一个你不应当当作拦截来对待的 400。
一个 pending_approval 判定为人工挂起了这次工具调用;错误体携带一个
审批 id。这次调用没有失败——它在等待。
- 一名审查者解决它——从控制台(Developer+)或经由你自己的
HMAC webhook 回调(
POST /api/v1/firewall/approvals/:id/callback)。 - 你的智能体用错误里的那个 id 轮询
GET /api/v1/firewall/approvals/:id(网关令牌)。 - 一旦获批,携带一次性的
X-OrcaRouter-Firewall-Approval头重新提交 原始调用,网关便放行它那一次。
5. 不是安全拦截?先排除密钥
并非每个400 都是一次防护栏或防火墙判定。在你一头扎进信息流之前,
先排除密钥约束——这些在任何策略运行之前就拒绝,并且不携带上面
那些安全错误码:
在上游调用之前模型就被拒绝
在上游调用之前模型就被拒绝
密钥的
model_limits 允许列表不包含所请求的模型。对列表之外模型的
请求会被提前拒绝。把该模型加到密钥上,或调用一个被允许的模型。在认证时因 IP 被拒绝
在认证时因 IP 被拒绝
密钥有一个
allow_ips 允许列表,而请求来自列表之外的地址。把调用方
的 IP / CIDR 加进去,或从一个被允许的网络发起调用。消费上限已达到
消费上限已达到
密钥的
credit_limit_usd 上限已耗尽(0 表示无限制)。提高上限,
或轮换到一个还有余量的密钥。在 /api/v1/firewall/* 路由上得到 403
在 /api/v1/firewall/* 路由上得到 403
网关 hook(
evaluate、MCP 派发)需要一个带
is_firewall_gateway=true 的密钥。一个普通中继密钥会得到 403。
为那些路由铸造一个 firewall-gateway-scoped 密钥。6. 两分钟分诊流程
拦在提示词或响应文本上
错误码是
guardrail_blocked → 打开 Matches,过滤
action=block,读 Stage + Detail。修复内容或规则;在防护栏
Test 标签里证明它。拦在一次工具调用上
错误码是
firewall_blocked → 打开 Events,过滤
verdict=deny,读执行面 + 原因。修复调用或规则;在防火墙
Test 标签里证明它。调用被挂起
错误码是
firewall_approval_pending → 轮询审批 id 并携带审批头
重新提交。没什么要调试的。以上皆非
没有安全错误码 → 检查密钥:
model_limits、allow_ips、
credit_limit_usd,或缺失网关范围导致的 403。7. 相关参考
错误码
完整的错误码表——网关能返回的每一种拦截、挂起和拒绝。
判定词汇表
每个防火墙判定意味着什么,以及它何时解析为一次拒绝。
Glob 与 JSONPath
解码匹配到你这次调用的
tool_name_glob 与 args_match。防护栏 vs 防火墙
哪个平面触发了——文本筛查还是动作治理。
