Files
claude-code/docs/outline-output/user/02-providers.md
2026-06-15 16:51:29 +08:00

17 KiB
Raw Blame History

第二章:让 Claude 听你的 —— 配置 Provider 与模型

把 CCB 接到你自己想用的那家 API 上:怎么选、怎么切、为什么没生效。

一张表看懂 7 个 Provider

CCB 不绑定 Anthropic 官方账号,内置了 7 条 API 通道。src/commands/provider.ts 里硬编码的有效值就这 7 个,对应 /provider <name> 能接的参数:

Provider modelType 适合谁
Anthropic 官方 anthropic(默认,内部叫 firstParty 有 Anthropic API key 或 Claude 订阅的人
OpenAI 兼容 openai DeepSeek、Ollama、vLLM、智谱、通义、Moonshot、Cerebras、Groq 等任何 OpenAI Chat Completions 协议端点
Gemini gemini Google Gemini 系列
Grok grok xAI Grok 系列(GROK_API_KEYXAI_API_KEY 都行)
Bedrock bedrock AWS 用户,走 CLAUDE_CODE_USE_BEDROCK=1,依赖 AWS_REGION
Vertex vertex Google Cloud 用户,走 CLAUDE_CODE_USE_VERTEX=1,需要 ANTHROPIC_VERTEX_PROJECT_ID
Foundry foundry Azure AI Foundry 用户,走 CLAUDE_CODE_USE_FOUNDRY=1,需要 ANTHROPIC_FOUNDRY_* 系列

注意一个区别:anthropic / openai / gemini / grok 这四个会落到 ~/.claude/settings.jsonmodelType 字段持久化;bedrock / vertex / foundry 三个云厂商只设环境变量,不写 settings.json——源码注释明确写了 "cloud providers controlled solely by env vars"。

想知道当前生效的是哪个:

/provider
Current API provider: openai

三种切换方式:/provider/login、环境变量

同一个目标有三条路,按你的场景选。

/provider <name> 最直接——一行命令立刻切换,写入 settings.json。比如刚配完 DeepSeek 的环境变量,想切过去:

/provider openai
API provider set to openai.

它还会顺手做体检:切到 openai 时如果缺 OPENAI_API_KEYOPENAI_BASE_URL,会返回 warning 而不是直接报错;切到 geminiGEMINI_API_KEY 同理。切到 grok 时接受 GROK_API_KEYXAI_API_KEY 任一存在即可。

/login 是引导式表单——会弹出一个交互界面(ConsoleOAuthFlow 组件),让你按栏目填字段、选预设。对第一次配的人最友好,特别是接国产大模型(见下一节)。它除了填表单,还会触发一连串副作用:重置 cost state、刷新 GrowthBook feature flags、清掉 trusted device token 再重新 enroll、把 authVersion 自增让其他 hook 重新拉数据。所以 /login 不只是"写个 key"那么简单。

环境变量是 CI/自动化场景的玩法——所有 provider 都有对应的 CLAUDE_CODE_USE_* 开关,写到 shell 配置或 .envrc 里,ccb 启动时自动生效:

# 临时用 DeepSeek 跑一个会话,不污染全局配置
CLAUDE_CODE_USE_OPENAI=1 OPENAI_API_KEY=sk-xxx \
  OPENAI_BASE_URL=https://api.deepseek.com/v1 \
  OPENAI_MODEL=deepseek-chat ccb

三条路里优先级在 src/utils/model/providers.tsgetAPIProvider() 里写死:先看 settings.modelType/provider 写进去的),再看 CLAUDE_CODE_USE_BEDROCK/VERTEX/FOUNDRY,再看 CLAUDE_CODE_USE_OPENAI/GEMINI/GROK,最后 fallback 到 firstParty。这个顺序解释了下一个排错点。

中国 LLM 引导式登录DeepSeek、智谱、通义、小米 MiMo

/login 走 "China LLM" 栏目时会用上 src/utils/chinaLlmProviders.ts 里的预设表。这张表内置了四家:

  • DeepSeekhttps://api.deepseek.com,注册送 5M tokens30 天),最便宜。模型有 deepseek-v4-pro(推荐)、deepseek-v4-flash(快)。
  • 智谱 GLMhttps://open.bigmodel.cn/api/paas/v4GLM-4.7-Flash 永久免费,有 Coding PlanLite ¥72/mo、Pro ¥216/mo、Max ¥576/mo
  • 通义千问https://dashscope.aliyuncs.com/compatible-mode/v1,开通后 90 天免费 tierCoding Plan ¥200/mo。
  • 小米 MiMohttps://api.xiaomimimo.com/v11M 上下文Token Plan 四档Lite ¥39/mo 起)。

预设表的好处是:你不用记 base URL、不用查 key 怎么申请、不用猜模型 ID。表单里每个 provider 都带 apiKeyPage 字段,直接给你跳转申请 key 的链接;每个模型还标了输入/输出每百万 token 的价格、上下文窗口、推荐 tag。选 Coding Plan 模式时,resolveChinaProviderBaseURL() 会自动把 base URL 切到对应 coding endpoint比如智谱切到 https://open.bigmodel.cn/api/coding/paas/v4key 格式也会提示(如 tp-...sk-sp-...)。

填完表单后写入 ~/.claude/settings.jsonenv 字段并触发 applyConfigEnvironmentVariables(),不用重启 ccb

用 ChatGPT 订阅当后端:设备码流程与凭证存储

如果你有 ChatGPT 订阅,可以让 CCB 直接走 ChatGPT 账号体系,而不是去 OpenAI 平台申请 API key。这套实现在 src/services/api/openai/chatgptAuth.ts

启用方式是设置 OPENAI_AUTH_MODE=chatgpt(同时把 provider 切到 openai。CCB 会启动 OAuth 设备码流程:调 https://auth.openai.com/api/accounts/deviceauth/usercode 拿一个 user_code 和验证 URL你在浏览器里打开 https://auth.openai.com/codex/device 输入这个 code 完成登录CCB 这边轮询 /api/accounts/deviceauth/token(最多 15 分钟,每 5 秒一次)拿回 authorization code再换成 id_token / access_token / refresh_token 三件套。

凭证默认存到 ~/.claude/openai-chatgpt-auth.json(文件权限 0600)。值得注意的兼容点如果那个文件不存在CCB 会 fallback 读 ~/.codex/auth.json(即 Codex CLI 的凭证文件,路径由 CODEX_HOME 环境变量控制,默认 ~/.codex)。源码里有句日志:[OpenAI] Using ChatGPT auth from Codex auth.json。这意味着你在 Codex CLI 登过的账号CCB 可以无缝接用。

刷新偏差窗口是 REFRESH_SKEW_MS = 5 * 60 * 1000,即 5 分钟。getValidChatGPTAuth() 每次被调用时检查 access_token 的 JWT exp 字段,如果距离过期不到 5 分钟就主动 refresh避免请求途中 token 失效。

每个 Provider 需要哪些环境变量

下面这张清单是从源码逐个挖出来的,配的时候照着对一遍就不会漏。

OpenAI 兼容src/services/api/openai/client.ts

  • OPENAI_API_KEY — 必填
  • OPENAI_BASE_URL — 强烈推荐,比如 http://localhost:11434/v1Ollama
  • OPENAI_ORG_IDOPENAI_PROJECT_ID — 可选
  • OPENAI_AUTH_MODE=chatgpt — 走 ChatGPT 订阅模式时设
  • OPENAI_MODEL — 指定模型 ID可选不设 CCB 自己选档位)

Gemini 兼容packages/@ant/model-provider/src/providers/gemini/modelMapping.ts

  • GEMINI_API_KEY — 必填,没有就 resolveGeminiModel() 会直接 throw
  • GEMINI_MODEL — 直接指定模型(最高优先级)
  • GEMINI_DEFAULT_SONNET_MODEL / GEMINI_DEFAULT_OPUS_MODEL / GEMINI_DEFAULT_HAIKU_MODEL — 按 anthropic 模型族映射
  • ANTHROPIC_DEFAULT_SONNET_MODEL 等 — 向后兼容(已废弃但仍读)

Grok 兼容src/services/api/grok/client.ts + modelMapping.ts

  • GROK_API_KEYXAI_API_KEY — 任一即可,前者优先
  • GROK_BASE_URL — 可选,默认 https://api.x.ai/v1
  • GROK_MODEL — 直接指定(最高优先级)
  • GROK_DEFAULT_OPUS_MODEL 等 — 按 family 映射
  • GROK_MODEL_MAP — JSON 字符串,一次性传完整映射表

Bedrock / Vertex / Foundry:依赖各家 SDK 的标准环境变量(AWS_REGIONANTHROPIC_VERTEX_PROJECT_IDANTHROPIC_FOUNDRY_*CCB 自己不额外定义。

模型映射是怎么决定的

CCB 内部统一用 Anthropic 的模型名(claude-sonnet-4-6claude-opus-4-6claude-haiku-4-5-20251001 等)做调度,落到具体 provider 时再做一次映射。映射函数遵循同一条优先级链:

  1. PROVIDER_MODEL(如 GEMINI_MODELGROK_MODELOPENAI_MODEL)——直接写死,最高优先级
  2. PROVIDER_DEFAULT_{FAMILY}_MODEL——按 sonnet / opus / haiku 三个 family 分别覆盖
  3. ANTHROPIC_DEFAULT_{FAMILY}_MODEL——向后兼容的共享环境变量
  4. 内置默认表Grok 在 modelMapping.ts 里有硬编码表,比如 opus family 默认映射到 grok-4.20-reasoning

举两个具体例子。Gemini 路径下如果你只设了 GEMINI_DEFAULT_SONNET_MODEL=gemini-2.5-flash,那么 CCB 调用 sonnet 时会用 flash调用 opus 时会因为找不到映射抛错:Gemini provider requires GEMINI_MODEL or GEMINI_DEFAULT_OPUS_MODEL (or ANTHROPIC_DEFAULT_OPUS_MODEL for backward compatibility) to be configured.

Grok 路径下,没设任何 GROK_* 时走默认表opus family → grok-4.20-reasoningsonnet/haiku family → grok-3-mini-fast。模型名带 [1m] 后缀1M 上下文标记)会在映射前被 replace(/\[1m\]$/, '') 剥掉。

为什么切了 Provider 没生效

这是 issue 区最高频的困惑之一,根因几乎都在 getAPIProvider() 的优先级上。

settings.modelType 优先于环境变量。如果你之前用过 /provider openai,那 ~/.claude/settings.json 里就写死了 "modelType": "openai"。后来你想换回 Anthropic 官方,只在 shell 里 unset CLAUDE_CODE_USE_OPENAI——没用,因为 settings 的优先级更高。正确做法是用 /provider unset,它会清掉 modelType 字段并删除所有 CLAUDE_CODE_USE_* 环境变量:

/provider unset
API provider cleared (will use environment variables).

注意 /provider unset 只清 Provider不清 API keyOPENAI_API_KEYGEMINI_API_KEY 这些是独立保留的,你想彻底换 provider 还得自己清 key。

isFirstPartyAnthropicBaseUrl() 有个 TODO 陷阱src/utils/model/providers.ts:43)。这个函数判断当前是不是走 Anthropic 官方 endpoint逻辑是看 ANTHROPIC_BASE_URL 有没有设、设的是不是 api.anthropic.com。但 TODO 注释明确写了:"这里会有问题, 只配置了 openai 协议的用户, 按理说会为 true 导致问题"。意思是:如果你只设了 OPENAI_BASE_URL(指向 DeepSeek但没设 ANTHROPIC_BASE_URL,这个函数会返回 true(因为 ANTHROPIC_BASE_URL 未设默认 firstParty让下游某些 firstParty 专属的行为(比如特定 betas 头)泄漏到 OpenAI 兼容路径上。如果遇到奇怪的请求被拒,先检查这个。

我改了 API key 但没生效

另一个高频坑,根因是模块级 client cache。

getOpenAIClient()src/services/api/openai/client.ts:39)和 getGrokClient()src/services/api/grok/client.ts)都是单例缓存:第一次调用时读 process.env.OPENAI_API_KEY / OPENAI_BASE_URL 构造一个 OpenAI SDK 实例,存到模块级变量 cachedClient,之后所有调用直接复用这个实例。

// client.ts 的核心逻辑
let cachedClient: OpenAI | null = null
export function getOpenAIClient(): OpenAI {
  if (cachedClient) return cachedClient
  // ... 读 env、new OpenAI(...)
  cachedClient = client
  return client
}

这意味着:你在 REPL 里改了 process.env.OPENAI_API_KEY(或通过 /login 重写了 settings.json 的 env但当前会话的 client 实例还是用旧 key 构造的——下一次请求还是旧 key。两种解法

  1. 重启 ccb——最简单粗暴,所有模块级 cache 自然清空
  2. 调用 clearOpenAIClientCache() / clearGrokClientCache()——程序化清缓存,但你没法在 REPL 里直接调,需要走 /login 这类会触发完整副作用的路径

/login 命令的 onDone 回调里调了 context.onChangeAPIKey(),这个 hook 会负责让下游感知 key 变了。所以改 key 的正确姿势是走 /login,而不是手改 settings.json 后期望立刻生效

本地模型与自托管端点

CCB 的 OpenAI 兼容层对本地模型特别友好,因为 Ollama、vLLM、LM Studio 这些工具都暴露 OpenAI Chat Completions 协议。

Ollama(本地跑 Llama、Qwen 等):

# 先启动 ollama 并 pull 一个模型
ollama serve &
ollama pull qwen2.5-coder:32b

# 让 CCB 用它
export CLAUDE_CODE_USE_OPENAI=1
export OPENAI_API_KEY=ollama              # ollama 不校验 key随便填
export OPENAI_BASE_URL=http://localhost:11434/v1
export OPENAI_MODEL=qwen2.5-coder:32b
ccb

vLLM(自托管推理引擎):把 OPENAI_BASE_URL 指向你的 vLLM server默认 http://localhost:8000/v1OPENAI_MODEL 填你启动 vLLM 时 --model 传的名字。

DeepSeek 自托管:跟官方 API 一样走 OpenAI 兼容,区别只是 base URL。注意思维模式的请求体格式跟官方 API 略有不同(见下一节)。

本地模型的两个实用环境变量:

  • OPENAI_MAX_TOKENS —— 显卡显存不够时强制限制 max output tokens比如 RTX 3060 12GB 跑 65536-token 模型会 OOM调小这个
  • API_TIMEOUT_MS —— 默认 60000010 分钟),本地模型推理慢的话可以调大

DeepSeek 思维模式:三格式注入与空字符串回显

DeepSeek 系列模型(deepseek-reasonerdeepseek-v3deepseek-v4deepseek-chatdeepseek-coderdeepseek-r1 等,凡是模型名包含 deepseek 的)会自动启用思维模式。检测逻辑在 src/services/api/openai/requestBody.ts:21isOpenAIThinkingEnabled()

return modelLower.includes('deepseek') || modelLower.includes('mimo')

想强制关掉?设 OPENAI_ENABLE_THINKING=0。想给非 DeepSeek 模型强制开?设 OPENAI_ENABLE_THINKING=1

启用思维模式后CCB 会在请求体里同时塞三种格式,因为不同 endpoint 认不同的字段,互不冲突:

...(enableThinking && {
  thinking: { type: 'enabled' },                              // 官方 DeepSeek API
  enable_thinking: true,                                       // 自托管 DeepSeek-V3.2
  chat_template_kwargs: { thinking: true, enable_thinking: true }, // 自托管 + MiMo
}),

这里有个反直觉但关键的细节:必须把 reasoning_content: ''(空字符串)原样回显回去。DeepSeek v4 在思维模式下,如果模型直接回答(不思考),会在 assistant message 里返回 reasoning_content: ""(空字符串而非缺失)。下一次请求必须把这个空字符串原样传回去,否则 DeepSeek 返回 400reasoning_content ... must be passed back

这套回显策略在 src/services/providerRegistry/providerCompatMatrix.ts 里按 provider 分了三档:

  • always-preserveDeepSeek——总是保留包括空字符串
  • drop-on-non-thinkingpermissive 默认)——非思维模型时丢掉
  • stripCerebras / Groq / strict-openai——总是丢掉

所以同一份对话历史,发给 DeepSeek 时带 reasoning_content,发给 Groq 时被剥得干干净净,互不污染。

/effortCLAUDE_CODE_EFFORT_LEVEL:思考强度的四档

/effort 控制模型在回答前思考多久。src/commands/effort/effort.tsx 接受的参数:low / medium / high / xhigh / max / autoEFFORT_LEVELSsrc/utils/effort.ts 里写死是 ['low', 'medium', 'high', 'xhigh', 'max'](注意 max 对外部用户是 session-only不持久化只有 USER_TYPE === 'ant' 才能 persist

/effort low       # Quick, straightforward implementation with minimal overhead
/effort medium    # Balanced approach with standard implementation and testing
/effort high      # Comprehensive implementation with extensive testing
/effort xhigh     # Extended reasoning beyond high, short of max
/effort max       # Maximum capability with deepest reasoning
/effort auto      # 跟随模型默认

CLAUDE_CODE_EFFORT_LEVEL 环境变量覆盖一切,优先级最高。它还接受 unset / auto 两个特殊值表示"别发 effort 参数"。设了环境变量再跑 /effort mediumCCB 会告诉你:"Not applied: CLAUDE_CODE_EFFORT_LEVEL=xxx overrides effort this session"。

落地到 ChatGPT 订阅模式(OPENAI_AUTH_MODE=chatgpt + 走 Responses APIsrc/services/api/openai/responsesAdapter.ts 把 effort 映射成 reasoning.effort 参数。Responses API 只认四档('low' | 'medium' | 'high' | 'xhigh'),所以 /effort max 在 ChatGPT 模式下会被 resolveAppliedEffort() 降级为 xhigh(源码注释:"Keep /effort max usable as a familiar alias in ChatGPT subscription mode")。

不是所有模型都支持 effort。modelSupportsEffort()src/utils/effort.ts:34 里维护白名单,目前包含 opus-4-7opus-4-6sonnet-4-6deepseek-v4-pro,以及 ChatGPT Codex 推理模型。设 CLAUDE_CODE_ALWAYS_ENABLE_EFFORT=1 可以强制全开(自担 API 报错风险)。

下一步