Claude Code 的 LLM 网关协议长什么样:gateway 这一层替你挡了什么

2026-08-18

一、先把问题问准:网关到底该动什么

自建网关接上去以后,真正难查的一类故障不是「连不上」,而是「大部分能用,某个功能突然报 400」。官方文档把这类问题的常见成因写在了明处:客户端发给网关的格式,和网关背后那个上游接受的格式对不上。再往下拆,落到具体动作上就是一句话——网关对某个请求头或 body 字段做了它自以为无害的处理。

Claude Code 官方文档为此单独开了一页 code.claude.com/docs/en/llm-gateway-protocol,标题是 Gateway protocol reference,明写是给「配置网关产品去对接 Claude Code 的运维方」看的。这一页把网关对每个 header、每个 body 字段的处置只归成两个动词:Forward unchanged(逐字节传给上游,不许碰)和 Consume(网关可以读它来做路由、归属、链路追踪,不必转发)。文档补了一句兜底——凡是没被标成 forward unchanged 的,随你消费或忽略。这份契约是一张「不许动」的清单,不是「必须处理」的清单。

(如果你跑的是 Anthropic 自带的 Claude apps gateway,文档写明它会在 GET /protocol 上提供这份契约的机器可读版本。本文只沿着文字版走。)

二、第一步:客户端说的是哪种格式,由变量决定

网关收到什么,取决于客户端被配置成说哪种 API 格式。文档列了三种,各自对应不同的选择变量和端点:

格式由什么选中端点必须原样转发
Anthropic MessagesANTHROPIC_BASE_URL/v1/messages/v1/messages/count_tokens(可选)anthropic-betaanthropic-version 请求头
Amazon Bedrock InvokeModelANTHROPIC_BEDROCK_BASE_URLCLAUDE_CODE_USE_BEDROCK=1/model/{model}/invoke/model/{model}/invoke-with-response-stream/model/{model}/count-tokens(可选)anthropic_betaanthropic_version 请求 body 字段
Google Cloud’s Agent Platform rawPredictANTHROPIC_VERTEX_BASE_URLCLAUDE_CODE_USE_VERTEX=1:rawPredict:streamRawPredictcount-tokens:rawPredict(可选)anthropic-betaanthropic-version 请求头,以及 anthropic_version body 字段

这张表里最容易看漏的是第二列和第四列的错位:同一个能力,在 Anthropic 格式里走请求头,在 Bedrock 格式里走 body 字段。网关如果只写了一套「转发 anthropic-* 请求头」的规则,换到 Bedrock 格式那条路上就什么都没转发。文档另外提到 Microsoft Foundry 和 Claude Platform on AWS 实现的也是 Anthropic Messages 格式,分别通过 ANTHROPIC_FOUNDRY_BASE_URLANTHROPIC_AWS_BASE_URL 路由;挡在 Claude Platform on AWS 前面的网关还必须额外转发 anthropic-workspace-id 请求头。

匹配路径而不是完整 URL,文档特意点了这一句:推理请求发往 /v1/messages?beta=true。网关还会看到一些「尽力而为」的启动流量,拒掉不影响功能——Anthropic 格式的网关收 HEAD /api/hello 连接预热探测,Bedrock 格式的网关收 GET /inference-profiles?type=SYSTEM_DEFINED。反过来,fast mode 的可用性检查和 WebFetch 域名安全检查永远不会出现在网关日志里,文档写明它们直接调 api.anthropic.com 而不跟随 ANTHROPIC_BASE_URL;在禁止直连出网的网络里,fast mode 可能报连接错误而经网关的推理一切正常,别拿这个现象怀疑网关。

三、第二步:请求头里哪些不许动

文档给出的请求头表里,必须 forward unchanged 的只有 anthropic-versionanthropic-beta,以及上游是 Claude Platform on AWS 时的 anthropic-workspace-id。其余的网关都可以自己消费。

值得单独说的是 anthropic-beta:它是逗号分隔的能力值列表,文档要求逐字转发,不要对单个值做白名单,理由是这个集合会随 Claude Code 版本变化。还有一条隐藏后果:当开发者用 claude.ai 登录认证(只设了 ANTHROPIC_BASE_URL 而没设网关凭据时可能出现),这个头里还带着上游要求的 OAuth 能力值,剥掉它这些请求会直接 401

三个 x-claude-code-* 头是给归属统计用的:x-claude-code-session-id 标识一次会话,不解析请求体就能把一次会话的请求聚起来;x-claude-code-agent-id 只出现在会话内 spawn 出来的 subagent 发的请求上;x-claude-code-parent-agent-id 只出现在嵌套 agent 上。文档给了一条硬提醒:subagent ID 每次 spawn 都新生成,agent team 的成员 agent 跨重连复用一个基于名字的稳定 ID,但两种情况下它标识的都是一个 agent,不是人也不是设备,别当用户标识用。开发者设了 ANTHROPIC_CUSTOM_HEADERS 的话,那些头也会一并出现。

这一节的落脚点是文档里那一小节的标题:Forward as open lists——把请求头和 body 字段当成开放列表。新能力正是以新的 anthropic-beta 值、新的 body 字段、偶尔新的 anthropic-*x-claude-code-* 头的形式到达的,按今天观察到的清单钉死转发规则,就会在引入下一个能力的那次发版上把它剥掉。

四、第三步:system 数组的第一个块,碰不得

这是整页里最反直觉、也最容易被网关中间件误伤的地方。Claude Code 会在 system prompt 前面加一小段 attribution block,含客户端版本和一个由对话派生的指纹;api.anthropic.com 这个端点会在处理前把它剥掉——但剥离是位置性的,只有当它作为 system 数组的第一个块原样到达时才生效。于是文档列了三条要求,每一条都对应一种常见的网关行为:

  • 原样转发 system 数组,保持这个块在第一位。 在前面再插一个 system 块、重排数组、或者把数组拼成单个字符串,都会让剥离失效,这个块就进了 prompt,也进了 prompt 缓存的 key
  • 让它单独占一个数组条目。 端点会把「以 attribution 头开头的合并块」整体当成 attribution 丢掉——包括被合并进去的其余 system prompt
  • 如果网关非要重塑 system 内容,就设 CLAUDE_CODE_ATTRIBUTION_HEADER=0 让客户端根本不发这个块。文档明说:Anthropic 和各云厂商的 Claude 端点会读这个块做归属,所以要去掉就在客户端去掉,别在网关里剥或者挪

文档还专门澄清了这个变量的性质:它是为网关与第三方缓存兼容存在的,不是隐私控制——直连时整个请求本来就发给 Anthropic API。它有个例外:请求发往 api.anthropic.com 且当前凭据不是 Anthropic profile 或 federation 凭据时,auto mode 的分类器请求即便设了 0 也仍带这个块(文档说这类请求跳过了 system prompt 的其余部分,块是请求体里唯一标识 Claude Code 流量的东西),该例外在 v2.1.229 之前不存在。

另一条对做缓存的网关很实际:从 v2.1.181 起,请求走自定义 base URL 时这个块在一次对话的生命周期内稳定,按完整请求体做 key 的网关侧 prompt 缓存因此能工作;更早的版本里它含每请求变化的 token。

五、第四步:header 与 body 是成对的,剥一半最惨

文档把这条讲得很直白:凡是会加 body 字段的能力,都配一个 beta 头,这一对是一起走的

  • 网关剥了头、放了 body,或者把 Anthropic 格式的 body 转给了 schema 不同的上游 → 硬 400
  • 只有当两半同时缺席,这个能力才会安静地关掉

还有一句是给做内容审计的团队看的:为内容检查而改写或脱敏请求体的网关,破坏配对的方式和剥离一模一样——文档的原则是「inspect without modifying」,要看可以,别改。

特性表里列了六项,症状各不相同,挑三行说:context_management 字段配对上下文管理的 beta 头,破坏后典型报错是 400Extra inputs are not permitted,文档说这在「网关收 Anthropic 格式请求却转给 Amazon Bedrock」的场景里很常见;output_config 这个 body 字段同时承载 effort、结构化输出格式和 task budget 设置,每一项各配自己的 beta 头;extended context 和 interleaved thinking 则只有 beta 头没有 body 字段,头被剥掉的表现是「悄无声息地不可用」,上游根本没收到能力请求,不报错——这种最难查。

adaptive reasoning 是个特例:它没有 beta 头,Claude Code 直接发 thinking: {"type": "adaptive"},且会把它不认识的模型名(比如网关起的别名)当成当前模型来发这个字段,上游模型构建不接受时就是 400

还要注意一个默认值:fine-grained tool streaming 属于「仅直连启用」的那批默认项,只要请求走自定义 base URL 就默认关闭,网关要收到它得由开发者设 CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING=1(这是文档写明的默认值,随版本可能变动)。要整体关掉预发布能力则是 CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1,它对所有 provider 生效,但不影响按模型选择的 adaptive reasoning,也不会压掉订阅认证需要的 OAuth 能力。

六、回程:流必须是流,错误体不能包

响应侧文档给了三条硬要求,每条都能单独把体验打穿。

第一,推理响应必须流式。 网关如果缓冲完整响应再转发,客户端会卡住。

第二,keep-alive ping 也要转发。ANTHROPIC_BASE_URLANTHROPIC_AWS_BASE_URL 的连接上,Claude Code 会数网关转发的每一个字节,包括 SSE 的 ping 事件和注释行,静默超过默认时长就中止流(文档写明的默认是 300 秒,随版本可能变动)。问题在于:长时间思考的停顿期里,上游的 ping 是唯一的流量,网关把它剥了或缓冲了,思考停顿就会被判成静默。上游本身不发 ping 的(文档举了 Amazon Bedrock 的二进制 event-stream),转译时要自己在静默间隙发 ping

一个值得记的不对称:走 ANTHROPIC_BEDROCK_BASE_URLANTHROPIC_VERTEX_BASE_URLANTHROPIC_FOUNDRY_BASE_URL 的网关不被这个字节级看门狗包住,即使它们转的是 Anthropic Messages 格式,那边改由一个空闲超时来中止静默的流;ANTHROPIC_BEDROCK_BASE_URL 这条路上可以用 CLAUDE_ENABLE_BYTE_WATCHDOG_BEDROCK 把字节看门狗加回来。

第三,错误响应体原样转发。 Claude Code 在某些上游拒绝之后会自动重试,并把被拒的能力在本次对话剩余部分关掉(thinking 字段、thinking 签名、对话中途的 system 消息这三类能这样恢复;上下文管理和工具 schema 字段的拒绝不重试,400 直接抛给开发者)。而这套重试逻辑是按上游错误的措辞匹配的——网关把上游错误包进自己的 envelope,哪怕状态码保留了,恢复路径也断了。

七、启动时那一次 /v1/models

最后是模型发现。当 ANTHROPIC_BASE_URL 指向一个暴露 Anthropic Messages 格式的网关时,Claude Code 可以在启动时查网关的 /v1/models,把返回的模型加进 /model 选择器。它默认关闭,要开得设 CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1。文档自述了默认关闭的理由:避免共用一个 API key 的网关把这个 key 能访问的全部模型暴露给每个用户。另有三种情况发现不会跑:设了任何 CLAUDE_CODE_USE_* provider 变量时(哪怕 ANTHROPIC_BASE_URL 也设了)、ANTHROPIC_BASE_URL 未设或指向 api.anthropic.com 时、非必要流量被 CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC 或组织策略关掉时。请求本身文档写得很具体:

GET /v1/models?limit=1000

带 3 秒超时,任何重定向都算失败——文档给的理由是防止凭据泄漏到重定向目标。所以哪怕 httphttps 的跳转也会让发现静默失败,端点要直接服务在配置的 base URL 上。凭据这里也和推理请求不一样:发现请求只发一个凭据头,设了 ANTHROPIC_AUTH_TOKEN 就用它做 bearer token,否则把解析出的 API key(含 apiKeyHelper 的返回值)放进 x-api-key;而推理请求会把 helper 值同时放进两个头。网关要给 /v1/models 做鉴权,就必须接受 x-api-key,否则用 helper 的部署会挂在这里。响应侧 Claude Code 只读 data 数组里每项的 id 和可选的 display_name

{
  "data": [
    { "id": "claude-sonnet-4-6", "display_name": "Claude Sonnet 4.6" },
    { "id": "claude-opus-4-8" }
  ]
}

(上面是官方文档里的示例响应,其中的模型 ID 只是文档当时的示例值,平台上有哪些模型随时在变,别当清单用。)

过滤规则是:id 字符串里任意位置claudeanthropic(大小写不敏感)就保留,其余忽略。所以 vertex_ai/claude-sonnet-4-6 这种带 provider 前缀的 ID 能过。文档写明 v2.1.223 之前是按前缀匹配的,那时带前缀的 ID 会被藏起来。网关用了不匹配这个规则的别名时,文档说开发者可以用模型配置变量手动加。

Windows 侧:这一页里唯一点名操作系统差异的是发现结果的缓存路径——Linux/macOS 是 ~/.claude/cache/gateway-models.json,Windows 是 %USERPROFILE%\.claude\cache\gateway-models.json;设了 CLAUDE_CONFIG_DIR 缓存就改放在那个目录下。缓存每次启动刷新,请求失败或网关没实现 /v1/models 时,选择器回落到上次启动的缓存列表或内置列表。除此之外这一页没写其它 Windows 与 Linux/macOS 的行为差异。

八、这份契约实际上在替你挡什么

网关这一层挡下的东西是错位的:它替组织挡住了 provider 凭据(code.claude.com/docs/en/gateways 那页把凭据分成两种——每个开发者自己持有的 developer credential,和网关持有、所有转发流量共用的 provider credential),也挡住了「换 provider 要动开发者机器」(文档把这句限定在 Claude apps gateway 或其它暴露单一 Anthropic 格式端点的网关上);但它同时把 Claude Code 的能力演进挡在了你自己的转发规则外面——文档说得很清楚,独立维护的网关需要随每次发版更新转发规则。

再补一条容易踩的边界:用网关凭据连接时用量计到组织的 provider 账户,claude.ai 订阅不被使用也不被计费;但只设 ANTHROPIC_BASE_URL 而不设任何网关凭据是例外,这时请求仍走网关,已保存的 claude.ai 登录仍是当前凭据。

本文引用的变量若需组合使用(例如 ANTHROPIC_BEDROCK_BASE_URLCLAUDE_CODE_USE_BEDROCK=1 同时设置),以上为按官方文档中的字段语义组合的示例,未经实测,以官方文档与 API 的实际响应为准。


本文依据 Claude Code 官方文档(code.claude.com/docs)于 2026-08-17 的公开内容整理。 该产品闭源,本文只复述官方文档写明的机制,不推断其内部实现我们没有对文中涉及的功能做过实测,因此不涉及界面外观、操作手感与运行速度的任何描述。 该产品迭代频繁,文中涉及的命令、配置项与默认值随版本变动,请以官方文档最新内容为准。 本文不涉及价格、额度与限流的具体数值,相关信息请以官方定价与用量说明页为准。 安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。