模型接入分层对照:想换个供应商,各家要你改哪一层

2026-08-17

先把问题问具体,不然对照没法做。

假设你手上这套 agent 已经跑了一段时间,现在要求变了:模型请求得走公司自建的那个 OpenAI 兼容网关,不能再直连公网端点;网关后面挂的是哪家模型,由运维那边定,你不管。这件事落到你身上,就是三个很实在的问题——改哪个文件、改完要不要重启、改完之后模型的”会不会思考”这类能力谁来声明

这四个项目对这三个问题的答案分得很开。下面按各自公开的配置结构走一遍。

DeepSeek Harness:取决于你加载的是哪个 adapter 插件

先说限定:DeepSeek Harness 仓库的 README 在开头就写了 “Developer preview”,并且用加粗全大写写着会有破坏兼容性的变更(THERE WILL BE COMPATIBILITY-BREAKING CHANGES),当前版本是 0.1.0-rc.5。下面提到的字段名和默认值随时可能变,这一节的所有结论都带这个前提。

docs/cookbook/adding-an-llm-adapter.md 给的是”写一个新 adapter”的路子:继承 LlmAdapter、实现 stream()、导出 name / inject / Config / apply,然后在 apply 里调 ctx.llm.registerAdapter(['my-provider'], new MyAdapter(…))。文档明写注册是 all-or-nothing、重复 route 会抛错,options.provider 选 adapter、options.model 是该供应商自己的模型 id。

这条路是代码层。但仓库里同时摆着两个参考实现,走的层完全不一样。

packages/llm/llm-deepseek/src/index.ts 里有一行常量:

/** The single provider route this plugin owns. */
const PROVIDER = 'deepseek-official'

注释就写着 “single”。这个插件的 Config 有 9 个字段(apiKeyEnvbaseURLthinkingreasoningEffortmaxTokensdefaultContextWindowmodelsstreamIdleTimeoutMsretryPolicy),你可以把 baseURL 指到自建网关——源码里 baseURL 的回退顺序是 config.baseURL → 环境里的 DEEPSEEK_BASE_URLPUBLIC_BASE_URL。但 route 名字是写死的,你没法用这个插件多挂一条别家的线。

另一个实现 packages/llm/llm-pi-ai 才是配置层。它的 README 第一段就说得很直白:一个插件实例持有一个以 route 为键的 provider profile 字典,pi-ai 没有内置的 route 可以整条直接声明出来,所以”一个 OpenAI 兼容网关、一台自建服务器、或者一个比已装 catalog 更新的供应商,是配置而不是代码改动”(README 自述)。配置写在 cordis.ymlproviders 下面,长这样:

acme-gateway:
  displayName: Acme Gateway
  apiKeyEnv: ACME_GATEWAY_API_KEY
  api: openai-completions
  baseURL: https://gateway.acme.example/v1

api 能填什么,去 packages/llm/llm-pi-ai/src/provider.ts 里看那张 PROTOCOLS 表,一共三个键:openai-completionsopenai-responsesanthropic-messages。这个范围是被刻意收窄的——同文件的注释和 README 都说明了理由:Bedrock 要用 AWS 凭证做 SigV4 签名、Vertex 要 project 和 location、Azure 要 api-version、Codex 走 OAuth,这些”不是一个 key + 一个 endpoint + 几个 header 能完整描述”的协议,如果放进来就等于交给你一条没法认证的 route(README 自述)。

值得单独记一笔的是生效时机llm-deepseekapply() 里,绝大多数事实是每次请求现解析的(resolveAdapterOptions 被包在一个惰性 options() 里),所以换个 base URL 或换个 key 不用重启;唯一一个”注册时捕获”的事实是 retryPolicy,源码注释写明它变了就调 registration.replace([PROVIDER]) 原地换,而不是先 dispose 再注册——因为后者会在两步之间露出一个空 route 集合,观察者会看见这个 provider 消失又回来。这种”哪些字段热生效、哪些要重新注册”的分界,在配置文档里是查不到的,只能读 apply()

Codex:改 TOML,字段全是传输层

Codex 的 provider 定义在 codex-rs/model-provider-info/src/lib.rs。文件头的模块注释写明只有两个来源:编译进二进制的内置默认值,和用户写在 ~/.codex/config.tomlmodel_providers 键下面的条目,后者在运行时扩展或覆盖前者。

ModelProviderInfo 结构体我数了一下,18 个 pub 字段:namebase_urlenv_keyenv_key_instructionsexperimental_bearer_tokenauthawswire_apiquery_paramshttp_headersenv_http_headersrequest_max_retriesstream_max_retriesstream_idle_timeout_mswebsocket_connect_timeout_msrequires_openai_authsupports_websocketssupports_standalone_web_search

这 18 个字段的性质很一致:全是传输层的东西——端点、认证、请求头、重试、超时。里面没有”这个模型会不会思考""上下文多大”这类模型能力字段。能力声明在 Codex 的哪一层,本篇的核对范围里没找到对应说明,不比。

有一个字段值得盯一眼:wire_apiWireApi 枚举现在只剩一个变体 Responses。同文件里有个常量把话说死了:

const CHAT_WIRE_API_REMOVED_ERROR: &str = "`wire_api = \"chat\"` is no longer supported.…";

反序列化时遇到 "chat" 直接报这个错,遇到别的值报 unknown variant。也就是说,你那个自建网关如果只讲 Chat Completions,这条 TOML 路走不通——这跟前面 DeepSeek Harness 那张三键 PROTOCOLS 表把 openai-completions 放在第一个的取舍,正好是反过来的两个方向。

内置 provider 一共 5 条:openaiamazon-bedrockamazon-bedrock-runtime,加上按 Ollama 与 LM Studio 端口生成的两条 oss provider。built_in_model_providers() 上方的源码注释自述了为什么就这几条:他们不想去当”哪些第三方 provider 该被打包进 Codex CLI”的裁判,鼓励用户自己往 model_providers 里加。另一条注释说明内置 provider 一般不可覆盖,例外是内置的 Bedrock 那两条允许你改端点、认证、header 和 AWS 设置。

默认值可以顺手抄下来备查(都是源码里的常量,不是”你用起来会怎样”的保证):DEFAULT_STREAM_IDLE_TIMEOUT_MS = 300_000DEFAULT_STREAM_MAX_RETRIES = 5DEFAULT_REQUEST_MAX_RETRIES = 4,后两者用户可配但硬上限都是 100。顺带一提,DeepSeek Harness 的 packages/llm/llm-deepseek/src/adapter.tsDEFAULT_STREAM_IDLE_TIMEOUT_MS 也是 300_000;两处数值相同,就说到这里。

Pi:三层可选,改完连重启都不用

Pi 的文档把接入切成了三层,packages/coding-agent/docs/providers.mdmodels.md 分别管前两层。

第一层是内置 catalog + 凭证。providers.md 里那张环境变量表列了一长串供应商与其 auth.json 键名,文档写明 ~/.pi/agent/auth.json0600 权限创建,且 auth 文件里的凭证优先于环境变量。整体的凭证解析顺序文档给了四步:CLI --api-keyauth.json → 环境变量 → models.json 里的自定义 provider key。

第二层是 ~/.pi/agent/models.json,也就是你那个自建网关该落的地方。provider 级字段有 baseUrlapiapiKeyoauthheadersauthHeadermodelsmodelOverridesapi 可选四种:openai-completionsopenai-responsesanthropic-messagesgoogle-generative-ai——比前面 DeepSeek Harness 那张表多一个 google-generative-ai

模型级字段里,contextWindow 不填默认 128000maxTokens 不填默认 16384。能力声明是显式的:reasoning 布尔,加一个 thinkingLevelMap 三态映射——键是 pi 自己的 thinking 级别(off / minimal / low / medium / high / xhigh / max),值省略时,文档写明 high 及以下的标准档走供应商的默认映射、而 xhighmax 这两个扩展档按不支持处理;填字符串表示支持并把这个值发给供应商;填 null 表示不支持,该档会被隐藏、跳过或钳掉。另外还有 compat.supportsDeveloperRolecompat.supportsReasoningEffort 两个开关,文档说这常见于 Ollama、vLLM、SGLang 这类 OpenAI 兼容服务端不认 developer role 的场合。

第三层才是代码:custom-provider.md 里的 pi.registerProvider(),走扩展,文档说它是留给需要自定义认证、过滤、刷新或流式实现的场合。

生效时机上 Pi 给了一句很省事的话:models.json “每次你打开 /model 时重新加载,会话中直接改,不用重启”(文档原文大意)。另外文档明写 models.json 里的 shell command 形式凭证("!command")是在请求时解析的,pi 刻意不给它加 TTL、陈旧复用或恢复逻辑,理由文档自述是不同命令需要不同的缓存与失败策略,pi 推断不出来——要缓存请你自己包一层脚本。

Claude Code:能改端点,但不给你换模型

Claude Code 是闭源产品,这里只用官方文档能查到的说法,不推断实现。

它的模型配置文档一上来就把边界划了:model 设置只接受模型别名模型名,模型名在不同 provider 上是不同形态——Anthropic API 用完整模型名、Amazon Bedrock 用 inference profile ARN、Microsoft Foundry 用 deployment name、Google Cloud’s Agent Platform 用 version name。文档里还专门加了一个提示框:ANTHROPIC_BASE_URL 改的是请求发到哪里,不是哪个模型来回答。

网关那一页说得更死:任何暴露受支持 API 格式的网关都能用,但 Anthropic 官方文档明写不背书、不维护、不审计第三方网关产品,也不支持通过任何网关把 Claude Code 路由到非 Claude 模型。同一页还写了运营侧的代价:Claude Code 每个版本都在加能力,网关如果不转发这些,对应功能就会坏,所以网关得跟着 Claude Code 一起更新。

所以对本篇开头那个需求,Claude Code 侧能改的层就是环境变量和 settings 文件这一层,具体有这么几个口子(都在官方文档里写明):

  • ANTHROPIC_CUSTOM_MODEL_OPTION/model 列表里加一条自定义条目,文档写明这个 id 不做校验,你的端点接受什么字符串就能填什么;配套还有 _NAME_DESCRIPTION
  • 网关部署下设 CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1,让 Claude Code 从网关的 /v1/models 端点填充选择器
  • modelOverrides 把单个 Anthropic 模型 id 映射到供应商侧的字符串
  • ANTHROPIC_DEFAULT_OPUS_MODEL 这一族变量各自带 _SUPPORTED_CAPABILITIES 后缀,用来手动声明能力。文档给的理由是:Claude Code 靠模型 id 匹配已知模式来启用 effort、extended thinking 这类功能,而 Bedrock ARN 或自定义 deployment name 常常匹配不上,于是这些功能就没开。文档给的能力值一共六个——effortxhigh_effortmax_effortthinkingadaptive_thinkinginterleaved_thinking,你要哪些就逗号列哪些;文档还写明这个变量一旦设了,没列出来的能力会被关掉,不设才走按 id 匹配的内置检测

生效时机上,文档写明 Claude Code 在启动时读环境变量,所以要么先 export 再起 claude,要么重启已有会话。

一张表,和它什么时候会咬到你

换供应商改哪一层协议/格式的口子模型能力谁声明改完何时生效
DeepSeek Harnessllm-pi-aicordis.ymlllm-deepseek route 写死,只能改 baseURLPROTOCOLS 三键route / model 两级的 reasoningEffortscompat多数字段按请求解析;retryPolicy 触发 replace 重注册
Codex~/.codex/config.tomlmodel_providerswire_api 只剩 responses该结构体里没有,本篇没核到,不比本篇没核到,不比
Pimodels.json;需要自定义认证/流式则写扩展api 四选一reasoning + thinkingLevelMap 三态 + compat打开 /model 时重载,不用重启
Claude Code环境变量 + settings 文件官方文档:不支持经网关路由到非 Claude 模型_SUPPORTED_CAPABILITIES 逗号列表启动时读环境变量,需重启会话

这张表真正会咬到你的地方不是”谁更灵活”,而是你的需求落在哪一格

如果你的网关只讲 Chat Completions,Codex 那条 TOML 路径当场就断在 wire_api 的枚举上;换成 DeepSeek Harness 的 llm-pi-ai 或 Pi 的 models.jsonopenai-completions 都是一等公民。

如果你要接的是 Bedrock、Vertex、Azure 这类”认证本身有仪式”的端点,方向反过来:Codex 内置了 Bedrock 那两条并允许你改 AWS 设置,而 DeepSeek Harness 的 llm-pi-ai README 明说这几个协议不放进可配置表;Pi 的 providers.md 则把 Bedrock、Vertex、Azure 各自的环境变量单独列了一节。

如果你要接的干脆不是 Claude 模型,Claude Code 的官方文档已经把这条路写成”不支持”了,剩下三个才是可谈的对象。

如果你要频繁试端点,Pi 的”打开 /model 就重载”和 DeepSeek Harness 的”按请求解析”,在各自文档与源码注释里都被写成”不用重启”这一类;Claude Code 的文档则明确要求先 export 再启动,或者重启已有会话。

几个我们没有依据、因此不比的维度

  • 四者的实际连通成功率、延迟、错误恢复表现:我们没有安装、没有运行过其中任何一个,一个字都不写。
  • Codex 侧的模型能力声明放在哪一层:ModelProviderInfo 里没有,本篇的核对范围里也没找到别处的对应说明。
  • 四者配置文件的优先级合并规则:只有 Pi 的凭证解析顺序和 Claude Code 的 settings 优先级在本篇引用到的文档里写明,另外两个我们没有沿着这条路径核完,不比。
  • 谁的设计”更好”:这四个项目对”供应商”这个概念的边界划得根本不在同一个位置,本文只陈述各自写明的机制。

本文依据 DeepSeek Harness 官方仓库(github.com/deepseek-ai/deepseek-harness)的 README、docs/ 下的 架构与子系统文档、以及 packages/ 下的源码整理,核对日 2026-08-17,对应仓库快照 47f9438(版本 0.1.0-rc.5)。 本文内容为仓库源码与文档口径,我们没有安装、也没有运行过这个项目, 因此不涉及界面外观、操作手感与运行速度的任何描述。 该仓库 README 自述处于开发者预览阶段并明确说明未来会有破坏兼容性的变更, 文中出现的命令、配置与默认值随时可能变动,请以仓库最新内容为准。

文中涉及的 Claude Code 内容依据其官方文档(code.claude.com/docs)整理,该产品闭源,本文不推断其实现; Codex 依据 github.com/openai/codex 快照 c6058cc、Pi 依据 github.com/earendil-works/pi 快照 027a5847 整理。 本文只对照各方公开写明的机制,不对三者做优劣排名。

安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。

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