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

240 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第二章:让 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_KEY``XAI_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.json``modelType` 字段持久化;`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_KEY``OPENAI_BASE_URL`,会返回 warning 而不是直接报错;切到 `gemini``GEMINI_API_KEY` 同理。切到 `grok` 时接受 `GROK_API_KEY``XAI_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` 启动时自动生效:
```bash
# 临时用 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.ts``getAPIProvider()`** 里写死:先看 `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` 里的预设表。这张表内置了四家:
- **DeepSeek** — `https://api.deepseek.com`,注册送 5M tokens30 天),最便宜。模型有 `deepseek-v4-pro`(推荐)、`deepseek-v4-flash`(快)。
- **智谱 GLM** — `https://open.bigmodel.cn/api/paas/v4``GLM-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。
- **小米 MiMo** — `https://api.xiaomimimo.com/v1`1M 上下文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/v4`key 格式也会提示(如 `tp-...``sk-sp-...`)。
填完表单后写入 `~/.claude/settings.json``env` 字段并触发 `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/v1`Ollama
- `OPENAI_ORG_ID``OPENAI_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_KEY``XAI_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_REGION``ANTHROPIC_VERTEX_PROJECT_ID``ANTHROPIC_FOUNDRY_*`CCB 自己不额外定义。
## 模型映射是怎么决定的
CCB 内部统一用 Anthropic 的模型名(`claude-sonnet-4-6``claude-opus-4-6``claude-haiku-4-5-20251001` 等)做调度,落到具体 provider 时再做一次映射。映射函数遵循同一条优先级链:
1. `PROVIDER_MODEL`(如 `GEMINI_MODEL``GROK_MODEL``OPENAI_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-reasoning`sonnet/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 key**`OPENAI_API_KEY``GEMINI_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`,之后所有调用直接复用这个实例。
```ts
// 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 等):
```bash
# 先启动 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/v1``OPENAI_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-reasoner``deepseek-v3``deepseek-v4``deepseek-chat``deepseek-coder``deepseek-r1` 等,凡是模型名包含 `deepseek` 的)会自动启用思维模式。检测逻辑在 `src/services/api/openai/requestBody.ts:21``isOpenAIThinkingEnabled()`
```ts
return modelLower.includes('deepseek') || modelLower.includes('mimo')
```
想强制关掉?设 `OPENAI_ENABLE_THINKING=0`。想给非 DeepSeek 模型强制开?设 `OPENAI_ENABLE_THINKING=1`
启用思维模式后CCB 会在请求体里**同时塞三种格式**,因为不同 endpoint 认不同的字段,互不冲突:
```ts
...(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 返回 400`reasoning_content ... must be passed back`
这套回显策略在 `src/services/providerRegistry/providerCompatMatrix.ts` 里按 provider 分了三档:
- `always-preserve`DeepSeek——总是保留包括空字符串
- `drop-on-non-thinking`permissive 默认)——非思维模型时丢掉
- `strip`Cerebras / Groq / strict-openai——总是丢掉
所以同一份对话历史,发给 DeepSeek 时带 `reasoning_content`,发给 Groq 时被剥得干干净净,互不污染。
## `/effort` 与 `CLAUDE_CODE_EFFORT_LEVEL`:思考强度的四档
`/effort` 控制模型在回答前思考多久。`src/commands/effort/effort.tsx` 接受的参数:`low` / `medium` / `high` / `xhigh` / `max` / `auto``EFFORT_LEVELS``src/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 medium`CCB 会告诉你:"Not applied: CLAUDE_CODE_EFFORT_LEVEL=xxx overrides effort this session"。
落地到 ChatGPT 订阅模式(`OPENAI_AUTH_MODE=chatgpt` + 走 Responses API`src/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-7``opus-4-6``sonnet-4-6``deepseek-v4-pro`,以及 ChatGPT Codex 推理模型。设 `CLAUDE_CODE_ALWAYS_ENABLE_EFFORT=1` 可以强制全开(自担 API 报错风险)。
## 下一步
- 想知道日常发消息、看流式回复、切权限模式怎么操作,看 [第三章:日常对话 —— 交互式 REPL 怎么用](./03-repl.md)
- 想按场景查 slash 命令(`/clear``/compact``/cost` 等),看 [第四章slash 命令速查](./04-slash-commands.md)
- 想接入 MCP server、装插件、写 Skill看 [第五章:扩展 Claude 的能力](./05-extensions.md)