这两年做 AI 编程,我感触最深的一件事:大多数人的瓶颈不是模型不够强,而是账号和工具太散。今天想清空说透一套组合——cc-switch 与 sdcb/chats 构成的最小闭环 AI 编程基础设施。cc-switch 管身份切换,sdcb/chats 管统一入口,两者加起来,基本就能解决日常开发里 80% 的“切来切去”和“上下文割裂”问题。这篇文章会从为什么需要基础设施、到一步步部署、再到我踩过的坑,一次性讲清楚,适合重度使用 AI 编程助手的人,也适合想给团队搭共享网关的同学。
1. 整体思路与方案拆解:为什么是这两个工具
1.1 先看清楚:AI 编程基础设施到底缺什么
现在做 AI 辅助开发,很多人桌上摆着一堆工具:Claude Code、Codex、Cursor、ChatGPT Web、还有各种本地模型。单个工具都很好用,但合在一起就乱了——每个工具都有自己的登录态、自己的会话存储、自己的 API Key,用起来像是在维护五六个小系统。
我自己之前的状态是:项目 A 用账号 1 跑 Claude,项目 B 用账号 2 跑 Claude,偶尔还要切到 Codex 处理一下别的任务。切账号要么去翻文件改配置,要么把一整段.env复制来复制去。更麻烦的是,切完之后历史对话经常对不上,上次聊到哪了完全靠记忆。这其实就是基础设施缺失的典型症状。
把“AI 编程助手”当成一个基础设施来看,至少需要三层:第一层是账号与密钥管理,解决“我该用谁的身份调用哪个模型”;第二层是访问入口统一,解决“不管后端接的是哪个厂商,我前端都从同一个地方进”;第三层是会话与上下文治理,解决“换模型换账号之后,上下文不能断”。cc-switch 和 sdcb/chats 刚好分别承担了前两层,第三层需要我们自己在工程上做一点设计。
1.2 两个工具的定位完全不同
cc-switch 是一个账号切换工具,它的核心工作对象是本地 CLI 工具的配置文件。你可以把多个 AI 服务商账号、API 地址、密钥都登记进去,用一个命令切换当前生效的那一套配置。Claude Code 启动时读什么配置、Codex 用哪个 token,都由 cc-switch 统一改写。它就像大楼里的门禁卡管理员,每个人进出哪一层,由它来发卡和登记。
sdcb/chats 则是一个自托管的聊天服务,提供 Web 界面,并且暴露 OpenAI 兼容的 API 接口。它可以同时接入 OpenAI、Anthropic、Azure、Ollama 等多家后端,前端只需要学会一种对话协议。它更像是前台总机,所有外部来电统一接进来,再根据你的需求转接到不同部门。
这两个工具不是竞争关系,而是互补关系。cc-switch 管的是“本地 CLI 这一侧怎么连出去”,sdcb/chats 管的是“服务端把多个上游模型封装成统一接口”。组合起来的效果是:你在终端里用的 Claude Code,和你在浏览器里用的 Web 聊天,背后都可以走同一个模型出口,而账号切换只在配置层发生一次。
1.3 为什么不用自己写脚本或买轮子
其实账号切换用 shell 脚本也能写,无非就是改改环境变量、替换一下配置文件。但脚本会非常脆弱,因为每个工具的配置格式不一样:Claude Code 认~/.claude/settings.json,Codex 认~/.codex/config.toml,Cursor 又是另一套逻辑。一旦某个工具更新了配置格式,脚本就要跟着改。cc-switch 这类工具存在的价值就是把这层差异封装起来,社区在维护,更新及时。
sdcb/chats 这种统一的网关也是一样。自建一个 OpenAI 兼容网关,听起来很简单,但要处理不同厂商的鉴权方式、错误码、模型名映射、流式响应格式差异,工程量不小。直接用成熟开源项目,省掉的是跟上游协议死磕的时间。
选择这套组合还有一个原因:数据自持。所有会话记录和配置都在自己的机器或内网服务器上,不依赖某个 SaaS 平台的账号体系。这对于处理一些敏感项目代码来说,心理负担小很多。
2. cc-switch:账号切换的关键拼图
2.1 安装与初始化配置
cc-switch 的安装很直接,从 GitHub Releases 下载对应平台的可执行文件,放到~/.local/bin或者任意一个在 PATH 里的目录就行。比如在 Linux 上,我习惯这样操作:
mkdir -p ~/.local/bin cp cc-switch-linux-amd64 ~/.local/bin/cc-switch chmod +x ~/.local/bin/cc-switch cc-switch --version安装完之后,建议先初始化一下配置目录。不同版本可能提供init子命令,如果没有,也可以直接手动创建配置目录。它一般会把配置放在~/.cc-switch下面,结构大概是每个账号一个 JSON 片段,或者一个大配置文件。这跟很多 CLI 工具的习惯一致。
初始化之后,用cc-switch list看一下当前状态,如果提示没有配置文件,就新建一个。我这边用到的命令命名可能和最新版有差异,但核心逻辑基本不变——无非是增删改查几个 profile,再加一个use来做切换。
2.2 添加供应商与账号配置
cc-switch 的核心概念是 profile,也就是一套完整的连接配置。一个 profile 至少包含三部分:名称、provider 类型、连接参数(API Key、Base URL)。provider 类型通常有claude-code、codex、cursor或者通用的openai-compatible。
我建议直接用配置文件来维护,比一条条命令敲更快,也方便备份。下面是一个典型的配置片段:
{ "profiles": [ { "name": "sonnet-home", "provider": "claude-code", "apiKey": "sk-ant-xxxxx", "baseUrl": "https://api.anthropic.com" }, { "name": "gpt-work", "provider": "codex", "apiKey": "sk-xxxxx", "baseUrl": "https://api.openai.com/v1" } ] }如果你更喜欢用命令,一般是cc-switch add <name> --provider <type> --api-key <key> --base-url <url>这种形式。添加完用cc-switch list确认。
这里有一个从我实际使用中得来的建议:profile 的命名不要乱来,把用途和模型写进去,比如claude-sonnet-personal、gpt-4o-work,否则 profile 一多,切起来就凭感觉了。
2.3 切换账号与集成到 Claude Code、Codex
切换动作本身很简单:cc-switch use sonnet-home。执行完后,它会在后台更新 Claude Code 的~/.claude/settings.json,把ANTHROPIC_BASE_URL和ANTHROPIC_AUTH_TOKEN替换成当前 profile 的值。Codex 那边则是对应更新它的配置。
验证是否切换成功,我一般会做两件事:
cc-switch current cat ~/.claude/settings.json看到当前 profile 名和配置文件里的 Base URL、Token 都对得上,才继续干活。这里要提醒一下:如果你在 shell 的.bashrc或.zshrc里手动 export 过ANTHROPIC_AUTH_TOKEN这类环境变量,它的优先级可能会覆盖文件配置,导致 cc-switch 切了但实际没生效。后面问题排查部分我会专门讲。
对于 Cursor,cc-switch 通常不能直接登录或切换 Cursor 账号,因为 Cursor 的账号体系是独立的。但是 Cursor 支持自定义 OpenAI 兼容接口,所以我们可以把 Cursor 指向 sdcb/chats 的网关地址,用网关来统一控制模型来源,这一点在后面的实操章节展开。
2.4 切换账号后上下文不能加载的解决办法
这是很多人问的一个问题:用 cc-switch 切换账号之后,之前对话的上下文不能加载,有没有办法?我自己最早也被坑过一次,后来搞清楚了原因。
首先要看你说的是哪一类上下文。如果是 Claude Code 本地记录的会话历史,它默认存放在~/.claude/projects下,按项目目录划分。cc-switch 切换账号时只改了 API 凭据,并不会去动这个目录,所以理论上旧会话文件都还在。但是 Claude Code 在加载会话时,可能会根据当前账号的配置对会话做权限校验,或者你在切换账号后进的是一个新目录,自然就看不到旧记录了。
对策是给不同 profile 设置独立的配置目录。Claude Code 提供了一个环境变量CLAUDE_CONFIG_DIR,可以指定配置和会话存储的位置。在 shell 里写一个切换函数,调用 cc-switch 之后同时导出这个变量:
function useai() { cc-switch use "$1" export CLAUDE_CONFIG_DIR="$HOME/.claude-$1" mkdir -p "$CLAUDE_CONFIG_DIR" }这样每次切换账号,本地会话也跟着切换,相当于每个账号都有自己独立的记忆空间,互不干扰。如果你已经有一堆旧会话在~/.claude/projects里,可以先手动搬到对应目录中,再继续使用。
如果是 sdcb/chats 里的 Web 会话,情况类似:chats 的会话通常绑定到创建时的 provider 和模型,如果 provider 的 API Key 或 Base URL 变了,旧会话不会自动失效,但继续对话时用的已经是新身份。最好的做法是在 chats 里为不同场景建独立会话,别把长期依赖放在某一个会话里;重要上下文写成项目文件(比如CLAUDE.md),这样换账号、换模型都不慌。
3. sdcb/chats:把模型接入做成统一入口
3.1 Chats 到底能做什么
sdcb/chats 是一个基于 .NET 的自托管聊天服务。它最大的价值不是“又一个 ChatGPT 界面”,而是把多个上游模型厂商的差异藏起来,对外提供一个稳定的接口。简单说,你在 Web 界面里可以切换 GPT、Claude、本地 Ollama 模型,而这些模型对话能力的暴露方式是一致的。
对我来说,它最关键的能力是 OpenAI 兼容 API。很多开发工具(各种 IDE 插件、自动化脚本、内部工具)只认 OpenAI 格式的接口,而如果上游是 Anthropic 或本地模型,协议就对不上。chats 把这层转换做掉了,我只需要把工具的 Base URL 指向 chats,模型名填一个我自己好记的名字,剩下的适配都在服务端完成。
3.2 Docker 部署与基础配置
部署 chats 例子用的是 Docker Compose,这样好维护、好备份。先在服务器上建目录:
mkdir -p ~/ai-infra/{config,data} cd ~/ai-infra写一个docker-compose.yml:
version: '3.8' services: chats: image: sdcb/chats:latest container_name: chats ports: - "5000:8080" environment: - ASPNETCORE_URLS=http://+:8080 volumes: - ./data:/data - ./config/appsettings.json:/app/appsettings.json restart: unless-stopped注意我特意把配置文件和持久化数据目录都挂载了出来。这是因为 chats 的模型配置和会话记录都值得长期保存,容器可以随时重建,但数据不能丢。
第一次启动前,需要准备config/appsettings.json。最小化的配置长这样:
{ "Providers": { "openai": { "Type": "OpenAI", "ApiKey": "sk-xxxx", "BaseUrl": "https://api.openai.com/v1" }, "claude": { "Type": "Anthropic", "ApiKey": "sk-ant-xxxx", "BaseUrl": "https://api.anthropic.com" } } }启动之后,浏览器访问http://localhost:5000,应该能看到聊天界面。如果看不到,先看容器日志:
docker logs chats -f排查配置加载是否正确。
3.3 模型路由与 OpenAI 兼容适配
chats 前端界面里可以选择模型,这个模型列表来自你配置的 Providers。但我实际使用中发现,服务端能识别的模型名和我们平时在命令行里用的名字可能不完全一样。比如某个上游模型版本号很长,我嫌麻烦,就会在 chats 配置里做一层模型名映射,把它改成一个短名字。
更常见的适配场景是这样的:你有一个工具只支持 OpenAI 格式,但你想让它用 Claude。那就在 chats 里把 Claude 模型暴露成 OpenAI 兼容接口,同时在客户端设置模型名为“gpt-4”这种占位符,然后在 chats 内部把这个名字映射到真正的 Claude 模型。配置方式大致是:
{ "Providers": { "claude": { "Type": "Anthropic", "ApiKey": "sk-ant-xxxx", "BaseUrl": "https://api.anthropic.com", "ModelMappings": { "gpt-4": "claude-sonnet-4-20250514" } } } }这样所有低层客户端都不用改业务代码,只要发一个model: gpt-4的请求,实际上跑的是 Claude。这个模式特别适合团队里已经有大量“为 OpenAI 接口写死”的脚本和工具的场景。
4. 端到端实操:从零搭建这套基础设施
4.1 环境规划与目录设计
我建议把所有内容集中在一个目录里,方便后续备份。整体结构如下:
~/ai-infra/ config/ appsettings.json # chats 配置 cc-switch/ # cc-switch 配置备份 data/ # chats 持久化数据 scripts/ backup.sh # 备份脚本 useai.sh # cc-switch 切换脚本端口方面,chats 暴露 5000 就够用。出于安全考虑,如果这台机器有公网 IP,不要直接把 5000 端口暴露出去,建议放在内网,或者用反向代理加一层 Basic Auth。因为 chats 是自带 Web 界面的服务,不加认证暴露公网等于让别人白嫖你的模型额度。
4.2 部署 sdcb/chats 实例
先按上一步的结构创建目录,写好config/appsettings.json,然后启动:
cd ~/ai-infra docker compose up -d等容器起来之后,先验证最基本的功能:
curl http://localhost:5000/v1/models如果配置正确,会返回模型列表的 JSON。这一步能同时验证两件事:服务是否正常监听,Provider 是否成功挂载。如果返回空列表,多半是 appsettings.json 里 Provider 没配对,仔细检查 JSON 格式和 ApiKey 字段名。
接着可以测试一次完整的对话请求:
curl -X POST http://localhost:5000/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your-chats-token" \ -d '{ "model": "gpt-4", "messages": [{"role": "user", "content": "你好,请做一个简单介绍"}] }'注意这里的 Authorization Token 不是上游 API Key,而是 chats 对客户端的认证标识,要么在配置里设置,要么它默认允许本地访问。具体情况看项目文档。
4.3 用 cc-switch 接入 CLI 编程工具
现在到了关键一步:让终端里的 Claude Code 也走这套基础设施。有两种接法:一种是 Claude Code 直接连官方 Anthropic 接口,只是把账号交给 cc-switch 管;另一种是 Claude Code 也走 chats 的网关。第一种在上一章已经讲过,第二种取决于 chats 是否提供 Anthropic 原生兼容端点。如果提供了,就在 cc-switch 里加一个 profile:
cc-switch add claude-via-chats \ --provider claude-code \ --api-key local-dev-key \ --base-url http://localhost:5000/anthropic cc-switch use claude-via-chats如果 chats 只提供 OpenAI 兼容端点,而 Claude Code 只认 Anthropic 协议,就不必强行让 CLI 走 chats。我现在的做法是:终端工具走 cc-switch 直连上游,Web 对话和各种 IDE 插件走 chats。二者共享同一套 Provider 配置,但入口分开,逻辑上也很清晰。
不管哪种接法,验证方式都一样:在任意一个临时项目目录里跑一句简单指令:
claude "用 python 写出快速排序,并附带测试用例"然后去 chats 的日志或者 Web 界面看是否产生了对应的请求记录。如果 Claude Code 报连接错误,用cc-switch current确认 profile 确实切过去了,再检查 Base URL 是否可达。
4.4 给 Cursor / IDE 接上同一个网关
Cursor 默认有自己的一套登录和模型逻辑,但它也支持自定义 API。进入设置里的 Models 配置,选择 OpenAI API Key 方式,填上:
- Base URL:
http://localhost:5000/v1 - API Key:chats 配置的客户端 token
- 模型名:chats 中已存在的模型名或映射后的占位名
填完之后,Cursor 里的对话请求就会打到 chats 网关,再由 chats 决定转给哪个上游模型。这样一来,团队内部可以统一更换上游供应商,而不需要每个成员改自己的 Cursor 设置。对个人开发者来说,也避免了在多个 IDE 里反复填 API Key 的麻烦。
不过我还要提醒一点:这种方式只影响 Cursor 的模型 API 调用,不会影响 Cursor 本身的账号登录和同步功能。如果你想管理的只是“用哪个模型跑 AI 对话”,那没问题。
5. 常见问题与排查技巧实录
5.1 切换后上下文不能加载的完整排查路径
这个问题值得单独列一节。当你发现切换账号后旧的上下文加载不出来,先按这个顺序排查:
第一步,确认切换真的生效了。执行cc-switch current,再看一眼~/.claude/settings.json里的 Token 和 Base URL 是不是目标 profile 的。
第二步,确认本地会话目录没有被动过。Claude Code 的会话放在~/.claude/projects下,如果你设置了独立的CLAUDE_CONFIG_DIR,就去看那个目录。旧会话文件一般都在,只是没有被加载。
第三步,确认 chats 的 Web 会话是否绑定 provider。如果绑定,切到新 provider 之后旧会话仍然在,但继续对话会使用新身份,所以建议在关键节点复制上下文到新会话,或者用导入方式延续。
根据我自己的经验,最稳妥的方案是:把每个账号的配置目录固定下来,不要轻易迁移;关键上下文写进项目里的CLAUDE.md;每天结束时备份一次会话目录。这样即使哪天真丢了上下文,也能从备份中找回。
5.2 环境变量不生效或配置被覆盖
这是 cc-switch 使用中非常常见的一种“假切换”。现象是:cc-switch 显示已经切到 profile A,但运行 Claude Code 时用的还是 profile B 的 Token。原因大概率是你之前的 shell 配置里硬编码了环境变量。
排查方法:
env | grep -iE "anthropic|openai|api_key|token"如果看到类似ANTHROPIC_AUTH_TOKEN=xxx的输出,说明它压过了配置文件里的值。解决办法是打开.bashrc或.zshrc,删掉这些硬编码,只保留 cc-switch 的管理入口。如果你希望某些项目单独用某个 Key,不要用全局变量,而是用项目里的.env,并且只在当前终端里 source。
还有一种情况:cc-switch 改完配置文件,但你的终端还缓存着旧的环境变量或命令路径。执行一下hash -r重置命令哈希,再新开一个终端窗口,基本就能解决。
5.3 sdcb/chats 请求超时、模型不存在、401
用表格把常见症状和原因列一下,方便对应:
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 请求返回 401 | chats 客户端 token 错误,或上游 ApiKey 配置有误 | 检查 Authorization 头和 appsettings.json |
| 模型不存在 | 客户端请求的模型名不在 chats 模型列表中 | 配置 ModelMappings,或改用模型列表中的名字 |
| 请求超时 | 上游服务响应慢,或 chats 到上游网络不通 | 调大超时时间,先 curl 上游接口确认连通性 |
| 流式响应乱码 | 协议不兼容,比如用了 OpenAI 客户端访问 Anthropic 原生 | 统一走 chats 的 OpenAI 兼容端点 |
遇到这些问题时,先不要怀疑 chats 本身,用上一步的 curl 命令直接测一下本地端点。如果本地返回正常,那就是客户端配置问题;如果本地也异常,再去看容器日志,多半能直接看到上游返回的错误信息。
5.4 多账号限额与成本控制
用 cc-switch 在多个账号之间切换,本质上是把多个 API 额度的使用入口合到一起。这在个人开发场景下确实方便,但我必须提醒:要遵守各平台的服务条款,不要做明令禁止的共享和滥用。我的原则是不同用途对应不同账号,比如个人项目用一个、公司项目用一个,互不混用。
成本控制上,chats 的模型列表让我可以直观看到每个 Provider 的调用情况。如果发现某个模型调用量异常高,可以去 logs 里按 model 字段统计。脚本层面也可以做一点限制,比如在 chats 前面加一层简单的令牌桶,或者通过反向代理限制单个 IP 的请求频率。这些手段在小团队里已经够用。
6. 进阶:把基础设施延伸到团队与多模型协同
6.1 多模型 A/B 对比与优先路由
当 chats 接入了多个 Provider,你可以像做实验一样对比不同模型在同一个编程任务上的表现。比如同样让 Claude 和 GPT 写一个异步爬虫,分别记录它们的代码风格、报错次数、修复效果。这种对比在真实使用场景中比跑 benchmark 更实用。
我的做法是在 chats 里为每个模型开一个会话,或者在本地写一个脚本,循环切换 cc-switch 的 profile 来跑同一个 prompt,然后输出结果。刚开始会觉得切换很麻烦,但一旦配置好,整套流程非常快。
6.2 本地离线模型兜底
私密代码和离线场景是我接入本地模型的主要原因。Ollama 支持 OpenAI 兼容接口,所以只需要在 chats 的 Provider 里加一个:
{ "Providers": { "ollama": { "Type": "Ollama", "BaseUrl": "http://localhost:11434/v1" } } }这样 Web 聊天界面里就多了一个本地模型可选。当外网不稳定或者敏感项目不允许出网时,切换到本地模型,体验虽有差距,但至少不中断工作流。对于个人基础设施来说,这算是一个很实用的降级方案。
6.3 团队共享与权限隔离
如果你们是一个小团队,可以共用同一个 chats 实例,把上游 API Key 统一托管在服务端,成员不需要知道真正的密钥。每个人在自己电脑上用 cc-switch 连接同一个 chats 网关,或者直接在 Cursor 里配置统一的 Base URL。
权限隔离取决于 chats 是否支持用户体系。如果不支持,最简单的做法是在前面加一层反向代理,用 Basic Auth 限制访问;再进一步可以写一个简单的中间件,按请求中的自定义 Header 区分成员。对于大多数内部工具场景,做到“能访问但不暴露明文密钥”已经足够。
6.4 配置版本化与灾备
cc-switch 的配置目录和 chats 的config/appsettings.json都应该纳入版本管理。我自己是把~/ai-infra/config做成了一个 git 仓库,任何 profile 改动都有记录,出问题可以git checkout回滚。
数据备份也不能落下。chats 的会话记录都写在./data目录里,我用一个 cron 脚本每天打包:
#!/bin/bash tar -czf ~/backups/chats-$(date +%F).tar.gz ~/ai-infra/data备份这件事很枯燥,但经历过一次容器被误删之后,你就会明白这几秒的压缩操作能省多少事。
最后分享一点我实际用下来的体会:cc-switch 和 sdcb/chats 都不是那种需要花很多时间研究的复杂工具,真正的价值在于把它们放进同一个工作流之后,你的日常开发就变得非常顺滑——切换账号不再需要翻配置文件,换模型不再需要改客户端。给每个 profile 命名时加上用途和模型后缀,这个习惯让我在维护十几个配置时依然不混乱。基础设施的意义不在于多复杂,而在于它存在之后,你根本感觉不到它的存在。