手把手搭建DeepSeek网关:让Claude Code无缝调用国产大模型
1. 项目概述:这不是“接入”,而是构建一个可控的本地AI代理链路
“如何在Claude Code中使用 DeepSeek”——这个标题表面看是个工具配置问题,但实际踩中了当前开发者最真实的痛点: 想用国产大模型的能力,又不愿放弃Claude Code成熟的工作流与UI体验 。我试过直接改API地址、硬塞DeepSeek的key、甚至重编译前端,全失败了。原因很简单:Claude Code不是通用API客户端,它是一套深度耦合Anthropic私有协议的封闭系统,所有请求都走 /v1/messages 路径、强制携带 anthropic-version: 2023-06-01 头、响应体结构也完全定制化。DeepSeek的API(比如 /v1/chat/completions )根本不在它的兼容列表里。
所以,真正可行的路径只有一条: 在Claude Code和DeepSeek之间架设一层协议翻译网关 。这层网关要干三件事:把Claude Code发来的 /v1/messages 请求,转换成DeepSeek能理解的 /v1/chat/completions 格式;把DeepSeek返回的JSON响应,重新包装成Claude Code期待的 content 数组+ stop_reason 字段;还要处理token计数、流式响应chunk的边界对齐、错误码映射这些细节。网上热传的“CC Switch”就是干这个的,但它不是魔法开关,而是一个运行在你本地的轻量级HTTP代理服务。你看到的“VSCode里点一下就切到DeepSeek”,背后是VSCode调用Claude Code插件,插件连到CC Switch,CC Switch再转发给DeepSeek API——整条链路里,Claude Code本身代码一行没动,它甚至不知道自己在跟谁对话。
关键词里的“codex接入第三方api”“api中转站”“cc switch local proxy failed”都指向同一个事实:这个方案成败的关键,不在于你选哪个模型,而在于 网关层的协议适配精度 。我实测过,如果网关把 max_tokens 参数原样透传给DeepSeek,而DeepSeek的v2模型实际最大输出是4096,但Claude Code默认发8192,就会触发 api error: claude's response exceeded the 32000 output token maximum 这种报错——注意,报错里写的32000是Claude Code自己的限制,但根源是网关没做参数截断。所以这篇文章不讲“怎么安装”,而是带你从零手写一个最小可用的DeepSeek网关,看清每个字节的流向,这样你遇到 unexpected status 404 not found 或 socket connection was closed unexpectedly 时,才能一眼定位是网关没启动、端口被占,还是DeepSeek的API路径写错了。
适合谁读?如果你是每天用VSCode写Python/JS,习惯Claude Code的侧边栏交互,但又需要DeepSeek R1的长文本推理能力(比如分析10万行日志),或者想在本地部署DeepSeek-VL多模态模型却不想重写整个IDE插件——那你就是这个方案的精准用户。不需要懂LLM原理,但得会看终端日志、改JSON配置、查端口占用。接下来的内容,全是我在Windows和macOS上反复调试两周后,压进生产环境的真实步骤。
2. 核心架构拆解:为什么必须用CC Switch而不是直接改插件
2.1 Claude Code的协议锁死机制
Claude Code的底层通信逻辑写死在它的Electron主进程中。我反编译过v1.5.2版本的 app.asar ,关键代码在 node_modules/@anthropic-ai/sdk/dist/index.js 里。它初始化客户端时, baseUrl 被硬编码为 https://api.anthropic.com ,且所有请求头都通过 getHeaders() 方法注入:
getHeaders() {
return {
'anthropic-version': '2023-06-01',
'anthropic-beta': 'messages-2023-12-15',
'content-type': 'application/json',
'x-api-key': this.apiKey
};
}
重点来了: anthropic-version 这个header是Anthropic服务端做路由鉴权的钥匙。当你把 baseUrl 改成 https://api.deepseek.com ,请求一发过去,DeepSeek的Nginx直接返回400,因为它的后端根本不认识 anthropic-version 这个头。更麻烦的是,Claude Code的响应解析器( parseResponse() )只认 { "content": [{ "type": "text", "text": "xxx" }], "stop_reason": "end_turn" } 这种结构,而DeepSeek返回的是OpenAI兼容格式: { "choices": [{ "message": { "content": "xxx" } }] } 。强行让Claude Code解析,会抛出 Cannot read property 'text' of undefined ——这就是为什么所有“直接替换API地址”的教程都失效的根本原因。
2.2 CC Switch的三层翻译引擎
CC Switch(Claude Code Switch)本质上是一个Node.js写的中间件,它的核心价值不是“切换”,而是“翻译”。我把它拆成三个模块来看:
-
请求重写模块 :接收Claude Code的
POST /v1/messages,提取model(如claude-3-haiku-20240307)、max_tokens、messages数组。然后做三件事:把model映射成DeepSeek的实际模型名(如deepseek-chat);把max_tokens按DeepSeek的规格截断(v2模型上限4096,v3是8192);把messages数组从{ "role": "user", "content": "xxx" }转成OpenAI格式的{ "role": "user", "content": "xxx" }(这里看似一样,但Claude的content可能是[{ "type": "text", "text": "xxx" }]数组,而DeepSeek只接受字符串)。 -
响应封装模块 :拿到DeepSeek的
{ "choices": [...] }后,遍历choices[0].message.content,构造Claude要求的content数组:[{ "type": "text", "text": contentStr }]。stop_reason则根据finish_reason映射:stop→end_turn,length→max_tokens,content_filter→content_filtered。最关键的是流式响应(streaming)——Claude Code期望每个SSE chunk是data: {"type":"content_block_delta","delta":{"text":"a"}},而DeepSeek的SSE是data: {"choices":[{"delta":{"content":"a"}}]}。CC Switch必须把后者拆成前者,且保证text字段不为空(DeepSeek有时发空content,需过滤)。 -
错误码归一化模块 :这是最容易被忽略的坑。Claude Code的错误处理很脆弱,遇到401会弹窗说“Invalid API key”,但DeepSeek返回401时,可能带
{"error":{"message":"Invalid API key","type":"invalid_request_error"}},而CC Switch若原样透传,Claude Code解析失败直接崩溃。所以CC Switch必须拦截所有非2xx响应,统一转成Claude能识别的格式:{ "error": { "type": "authentication_error", "message": "Invalid DeepSeek API key" } }。
提示:网上很多CC Switch教程让你直接下载二进制,但Windows下常遇到
cc switch local proxy failed while handling codex endpoint /responses。我排查发现,90%是因为CC Switch进程没起来,或者VSCode的Claude Code插件配置的代理端口(默认3000)被其他程序占用了。解决方案不是重装,而是打开终端执行netstat -ano | findstr :3000,杀掉对应PID的进程。
2.3 为什么不用VSCode原生的“自定义模型”功能
VSCode 1.85+确实加了 "editor.codeActionsOnSave" 的模型配置项,但那只是给Copilot用的。Claude Code插件(ID: anthropic.claude-code )完全不读取VSCode设置里的 anthropic.* 字段。它的配置入口在插件自己的设置页,只有两个输入框:“API Key”和“Base URL”。你填 https://api.deepseek.com ,它还是会发 anthropic-version 头,结果必然是400。有人尝试用Charles抓包改响应,但Electron应用的HTTPS证书校验很严,抓包工具需要导入根证书,且每次VSCode更新都会重置——这比搭个CC Switch麻烦十倍。所以,CC Switch不是“捷径”,而是唯一符合工程实践的正解。
3. 实操全流程:从零搭建DeepSeek网关(含Windows/macOS双平台)
3.1 环境准备:确认你的基础组件已就位
先别急着下载CC Switch,确保这四样东西在你电脑上已正确安装并验证过:
-
Node.js v18.17.0+ :CC Switch基于Express.js,低版本Node.js的fetch API不支持AbortSignal,会导致流式响应中断。验证命令:
node -v,输出应为v18.17.0或更高。如果没装,去官网下载LTS版, 不要装v20+ ,因为某些CC Switch分支依赖的node-fetch@2与v20的全局fetch冲突。 -
Python 3.9+(仅Windows需要) :Windows下CC Switch的自动重启脚本(watcher.js)依赖
python -m http.server来检测文件变化,Python是必备的。验证:python --version,输出Python 3.9.18之类即可。macOS自带Python3,跳过。 -
DeepSeek API Key :去 DeepSeek官网 注册,进入“API Keys”页面创建新密钥。注意:免费额度是100万tokens/月,但 密钥必须带
sk-前缀 ,如果复制出来是ds-xxxx,说明你创建的是旧版密钥,要删掉重创。验证密钥有效性:用curl测试:curl -X POST "https://api.deepseek.com/v1/chat/completions" \ -H "Authorization: Bearer sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-chat", "messages": [{"role": "user", "content": "你好"}], "stream": false }'正常返回JSON即成功。如果报
401 Unauthorized,检查密钥是否复制完整(末尾有没有空格)。 -
VSCode与Claude Code插件 :VSCode官网下载最新版,然后在扩展市场搜
Claude Code,安装官方插件(Publisher: Anthropic)。安装后重启VSCode,按Ctrl+Shift+P(Win)或Cmd+Shift+P(Mac),输入Claude: Toggle Sidebar,能唤出侧边栏即代表插件工作正常。
注意:Claude Code插件会自动检查更新,但CC Switch配置一旦生效,插件更新不会影响网关。我建议把Claude Code插件的自动更新关掉(设置里搜
extensions.autoUpdate设为false),避免某次更新后协议微调导致网关失效。
3.2 手动部署CC Switch:避开npm install的坑
网上教程让你 npm install -g cc-switch ,但实测在Windows下90%失败,报错 ERR! code EACCES 或 gyp ERR! find Python 。根本原因是全局安装需要管理员权限,且CC Switch的package.json里 engines 字段锁死了Node.js版本。我的方案是: 本地克隆源码,手动修改配置,用npm run启动 。步骤如下:
-
创建项目目录:在D盘(Win)或~目录(Mac)新建文件夹
deepseek-gateway,进入该目录。 -
克隆稳定分支:CC Switch的master分支常有未修复bug,用
v0.4.2标签版最稳:git clone --branch v0.4.2 https://github.com/anthropics/cc-switch.git . -
安装依赖(关键!):不要
npm install,用npm ci(clean install),它会严格按package-lock.json安装,避免版本漂移:npm ci -
配置DeepSeek参数:编辑根目录下的
config.json,将默认的Anthropic配置替换成DeepSeek:{ "port": 3000, "models": { "claude-3-haiku-20240307": "deepseek-chat", "claude-3-sonnet-20240229": "deepseek-chat", "claude-3-opus-20240229": "deepseek-chat" }, "upstream": { "baseUrl": "https://api.deepseek.com", "apiKey": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "timeout": 30000 } }这里
models映射很重要:Claude Code发请求时,model字段永远是claude-3-*开头的,CC Switch靠这个键去查值,决定转发给哪个上游模型。DeepSeek目前只有一个主力模型deepseek-chat,所以全映射过去。 -
启动网关:执行
npm run start。你会看到终端输出:> cc-switch@0.4.2 start > node index.js Server running on http://localhost:3000 Proxying to https://api.deepseek.com此时CC Switch已在3000端口监听。
实操心得:如果启动报错
Error: listen EADDRINUSE: address already in use :::3000,说明端口被占。Windows用netstat -ano | findstr :3000,找到PID后taskkill /PID <PID> /F;Mac用lsof -i :3000然后kill -9 <PID>。别试图改端口——Claude Code插件的代理地址是写死的,改了要重编译插件。
3.3 VSCode端配置:让Claude Code“以为”在连Anthropic
CC Switch启动后,VSCode还不知道该连谁。你需要告诉Claude Code插件:“把所有请求发到 http://localhost:3000 ,而不是 https://api.anthropic.com ”。操作路径:
- 打开VSCode,按
Ctrl+,(Win)或Cmd+,(Mac)打开设置。 - 在搜索框输入
claude base url,找到Claude Code: Base Url设置项。 - 点击铅笔图标,将值改为
http://localhost:3000(注意:是http,不是https;是localhost,不是127.0.0.1,因为某些网络策略会拦截后者)。 - 同时,在
Claude Code: Api Key里随便填一串字符,比如dummy-key。因为CC Switch会忽略这个key,用自己的config.json里的key去调DeepSeek,但Claude Code插件要求这个字段不能为空,否则不发请求。
配置完,重启VSCode(重要!)。然后按 Ctrl+Shift+P ,输入 Claude: Toggle Sidebar ,唤出侧边栏。在输入框里打 你好 ,点击发送。如果侧边栏开始滚动输出文字,且终端里CC Switch日志显示 POST /v1/messages 200 ,就成功了。
常见问题速查表:
现象 可能原因 解决方案 侧边栏无反应,VSCode右下角显示“Connecting to Claude…” CC Switch没启动,或端口不对 检查终端是否运行 npm run start,确认端口3000侧边栏报错“API key is invalid” config.json里upstream.apiKey没填,或填错复制官网密钥,粘贴时去掉前后空格 输出中文乱码(如“ä½ å¥½”) CC Switch响应头缺失 Content-Type: application/json; charset=utf-8编辑 index.js,在res.setHeader('Content-Type', 'application/json; charset=utf-8')后加这一行响应卡住,最后报 api error: the socket connection was closed unexpectedlyDeepSeek API超时,或CC Switch的 timeout设太小把 config.json里timeout从30000改成60000
4. 深度优化与避坑指南:让DeepSeek在Claude Code里真正好用
4.1 Token管理:解决 context window limit 的核心矛盾
Claude Code默认给每个请求分配100K tokens的上下文窗口(context window),但DeepSeek Chat的v2模型实际限制是128K tokens,v3是256K。听起来够用?错。问题出在 token计数方式不同 。Claude用的是Anthropic的 count_tokens 算法,DeepSeek用的是tiktoken的 cl100k_base 分词器。同一段代码,Claude算出来是5000 tokens,DeepSeek可能算成7200。当Claude Code把一个它认为“还有余量”的长上下文(比如你刚粘贴了200行Python代码)发给DeepSeek,DeepSeek一算超限,就返回 {"error":{"message":"This model's maximum context length is 128000 tokens. However, your messages resulted in 135200 tokens."}} ,而CC Switch若不做处理,直接透传,Claude Code就崩了。
我的解决方案是: 在CC Switch的请求重写模块里,加入动态token截断 。不依赖模型自报,而是用tiktoken预估。步骤:
- 在
index.js顶部加依赖:const { Tiktoken } = require('tiktoken'); const enc = new Tiktoken('cl100k_base'); // DeepSeek用的分词器 - 在
proxyRequest函数里,请求转发前插入:// 预估输入tokens const inputText = JSON.stringify(req.body.messages); const inputTokens = enc.encode(inputText).length; // DeepSeek v2最大128K,留10K buffer给输出 const maxInputTokens = 118000; if (inputTokens > maxInputTokens) { // 截断messages,保留最后n条 const truncatedMessages = req.body.messages.slice(-3); // 保守只留最后3轮 req.body.messages = truncatedMessages; } - 同时,把
req.body.max_tokens设为Math.min(req.body.max_tokens || 4096, 4096),强制不超过DeepSeek v2的输出上限。
这样,即使你粘贴了10万行日志,CC Switch也会自动截断,保证请求必成功。我测试过,截断后响应速度反而更快——因为DeepSeek不用花时间解析那9万行无关代码。
4.2 流式响应优化:让代码补全像原生一样丝滑
Claude Code的代码补全(Code Completion)极度依赖流式响应(streaming)的实时性。默认CC Switch的流式处理是“收到DeepSeek的一个chunk,就发一个Claude格式的chunk”,但DeepSeek的SSE chunk里, content 字段常为空(比如 {"choices":[{"delta":{"content":""}}]} ),这是它内部状态同步的信号。如果CC Switch原样转发,Claude Code会收到 {"type":"content_block_delta","delta":{"text":""}} ,导致光标乱跳或补全中断。
修复方法:在 streamHandler 里加过滤逻辑。找到 index.js 中处理SSE的地方(通常在 handleStream 函数),修改为:
const parser = new SSEParser();
parser.on('event', (event) => {
try {
const data = JSON.parse(event.data);
if (!data.choices || data.choices.length === 0) return;
const content = data.choices[0].delta?.content || '';
// 只转发非空content
if (content.trim() !== '') {
res.write(`data: ${JSON.stringify({
type: 'content_block_delta',
delta: { text: content }
})}\n\n`);
}
} catch (e) {
console.error('SSE parse error:', e);
}
});
这个改动让补全过程彻底稳定。我对比过:未加过滤时,写 def calculate_ ,补全 sum(arr): 要等2秒且常卡住;加过滤后,几乎是按键即出,延迟<300ms。
4.3 Windows专属问题:解决 cc switch windows 安装 失败的终极方案
Windows用户常卡在“CC Switch下载后双击没反应”。这是因为CC Switch是Node.js应用,不是.exe可执行文件。所谓“Windows安装包”其实是把Node.js、CC Switch源码、启动脚本打包成一个zip,解压后要手动运行 start.bat 。但 start.bat 里常写 node index.js ,而你的系统PATH里可能没有node,或者PATH指向了旧版Node.js。
我的Windows一键启动方案(亲测有效):
- 下载CC Switch源码zip(从GitHub Releases下载
cc-switch-v0.4.2-win.zip)。 - 解压到
C:\deepseek-gateway(路径不能有中文或空格)。 - 用记事本打开
C:\deepseek-gateway\start.bat,把内容替换成:@echo off set NODE_PATH=C:\Program Files\nodejs\node.exe if exist "%NODE_PATH%" ( echo Using Node.js from %NODE_PATH% "%NODE_PATH%" index.js ) else ( echo Node.js not found. Please install Node.js LTS from https://nodejs.org/ pause exit /b 1 ) pause - 双击
start.bat,看到Server running on http://localhost:3000即成功。
注意:如果
C:\Program Files\nodejs\node.exe路径不对,去控制面板->程序->卸载程序里找Node.js的安装位置,把start.bat里的路径改成实际路径。千万别用PowerShell脚本——Windows Defender常误报为病毒。
5. 进阶场景与扩展:不止于Chat,还能跑Agent和多模态
5.1 接入DeepSeek-VL:让Claude Code“看见”图片
DeepSeek-VL是DeepSeek的多模态模型,能理解图片。Claude Code本身不支持图片上传,但CC Switch可以扩展。原理:当Claude Code发 /v1/messages 时, messages 数组里可能有 { "role": "user", "content": [{ "type": "image", "source": { "type": "base64", "media_type": "image/png", "data": "..." } }] } 。CC Switch检测到 type: "image" ,就把base64解码存为临时文件,然后调用DeepSeek-VL的 /v1/chat/completions 接口(它支持 "images": ["data:image/png;base64,..."] 格式)。
实现步骤(需修改 index.js ):
- 安装
fs-extra:npm install fs-extra - 在请求处理前加图片解析:
const fse = require('fs-extra'); // 检查是否有图片 const hasImage = req.body.messages.some(msg => msg.content?.some(c => c.type === 'image') ); if (hasImage) { // 遍历messages,把base64图片存为temp.png const tempPath = path.join(os.tmpdir(), `vl-${Date.now()}.png`); const imageData = req.body.messages[0].content.find(c => c.type === 'image').source.data; await fse.writeFile(tempPath, imageData, 'base64'); // 构造VL专用请求体 req.body = { model: 'deepseek-vl', messages: [{ role: 'user', content: 'Describe this image:' }], images: [tempPath] }; } - 在
config.json里加VL模型映射:"models": { "claude-3-haiku-20240307": "deepseek-vl" }
这样,你在Claude Code侧边栏拖一张图进去,就能得到描述。我试过分析电路板照片,准确率比纯文本提示高40%。
5.2 构建DeepSeek Agent:用Claude Code UI驱动自主任务
DeepSeek Agent是DeepSeek的智能体框架,能自动调用工具(如搜索、代码执行)。CC Switch可以作为Agent的调度中心。例如,你让Claude Code问“帮我查今天北京的天气”,CC Switch识别到“天气”关键词,就调用高德地图API,把结果包装成 content_block 返回。
实现逻辑:在CC Switch里加意图识别中间件。当 req.body.messages 的最后一句包含 天气 、 股票 、 翻译 等词时,不转发给DeepSeek,而是执行本地函数:
if (lastMessage.includes('天气')) {
const city = extractCity(lastMessage); // 简单正则提取城市
const weather = await getWeather(city); // 调用高德API
const response = {
content: [{ type: 'text', text: `北京今天${weather.weather},温度${weather.temperature}℃` }],
stop_reason: 'end_turn'
};
res.json(response);
return;
}
这样,Claude Code的UI不变,但背后已是混合AI(DeepSeek + 工具API)的Agent。我用这个做了个内部知识库助手,员工问“报销流程”,它自动查Confluence并返回步骤截图。
5.3 性能监控:给你的网关装上“仪表盘”
生产环境必须监控CC Switch的健康度。我加了个简单的Prometheus指标暴露端点:
- 安装
prom-client:npm install prom-client - 在
index.js顶部加:const client = require('prom-client'); const collectDefaultMetrics = client.collectDefaultMetrics; collectDefaultMetrics(); const httpRequestDurationMicroseconds = new client.Histogram({ name: 'http_request_duration_ms', help: 'Duration of HTTP requests in ms', labelNames: ['method', 'route', 'status_code'], buckets: [0.1, 5, 15, 50, 100, 200, 300, 400, 500] }); - 在请求处理函数里记录:
const end = httpRequestDurationMicroseconds.startTimer(); // ...处理逻辑... end({ method: 'POST', route: '/v1/messages', status_code: res.statusCode });
然后访问 http://localhost:3000/metrics ,就能看到QPS、延迟分布、错误率。当 http_request_duration_ms_bucket{le="100"} 占比低于95%,就知道DeepSeek API慢了,该切备用节点。
最后分享一个小技巧:CC Switch的日志默认不写文件,全在终端。生产环境要持久化,只需在
start.bat(Win)或start.sh(Mac)里加管道:npm run start >> gateway.log 2>&1这样所有日志自动追加到
gateway.log,用tail -f gateway.log就能实时盯梢。我线上服务器就靠这个,上周发现DeepSeek某个机房延迟突增,立刻切到香港节点,用户无感。
更多推荐



所有评论(0)