最近后台问得最多的一个问题,就是Claude Code、Codex、PI这些终端agent,到底怎么接第三方API或者本地模型。很多人照着网上的教程把API key填好,一跑就报unexpected status 401 unauthorized: incorrect api key provided,后面还跟着一串sk-svcac...开头的key前缀;还有人明明已经把LM Studio的本地模型加载好了,却发现Claude Code根本不认这个模型名,报400 this model's maximum context length is 1048576 tokens。
这篇文章把我实测过、并且还在用的接入方式完整写出来:第三方API怎么接、本地模型怎么接、cc switch这类切换工具怎么用不会冲突,以及那堆看着吓人的报错到底在说什么。如果你不想被官方配额和订阅费绑死,想用DeepSeek、智谱这类商业API,或者干脆用自己电脑上的Qwen小模型,这篇文章应该能帮你少走很多弯路。
1. agent默认绑定官方模型的逻辑,以及打破绑定前的三个关键认知
先说一个很多人没搞明白的基础问题:Claude Code、Codex、PI这些agent,为什么默认只能连官方模型?
答案其实很朴素——官方下载的包里,默认的接口地址、鉴权方式、请求协议都是写死的。Claude Code启动后默认去找Anthropic的https://api.anthropic.com/v1/messages,Codex CLI默认去找OpenAI的接口,PI也默认指向它自己的官方端点。你看到的“接入第三方API”的各种教程,本质上都没什么魔法,就是让agent不再走那个写死的默认地址,而是把请求发到你指定的新地址上。
1.1 用一张表搞清楚agent和API之间的映射关系
在动手之前,先把每个agent的“默认端点”和“可覆盖变量”理清楚。
| agent | 默认端点 | 常用覆盖变量 | 默认请求协议 |
|---|---|---|---|
| Claude Code | https://api.anthropic.com | ANTHROPIC_BASE_URL、ANTHROPIC_AUTH_TOKEN、ANTHROPIC_MODEL | Anthropic Messages API |
| Codex CLI | https://api.openai.com/v1 | OPENAI_BASE_URL、OPENAI_API_KEY | OpenAI Responses / Chat Completions |
| PI | 官方端点 | 配置文件中的provider定义 | 多协议,常见/responses、/v1/chat/completions |
记住这张表就够了。后面所有的配置,都是在给这些变量赋值,让agent的请求改道。
1.2 三个必须理解的认知
认知一:协议不同,请求路径和请求体格式完全不同。
Claude Code走的是Anthropic Messages协议,核心路径是/v1/messages,请求体里是system、messages、max_tokens、tools这些字段;Codex和PI很多默认走OpenAI协议,核心路径是/v1/responses或/v1/chat/completions,请求体结构又是另一套。
这一个差异直接决定你能不能接成功。很多第三方API服务商和本地模型工具,对外暴露的是OpenAI兼容接口,只认/v1/chat/completions;而Claude Code只会发/v1/messages。两边鸡同鸭讲,自然就报各种看不懂的错。
认知二:鉴权头不同。
Anthropic协议通常认x-api-key或Authorization: Bearer ...作为密钥头;OpenAI兼容接口只认Authorization: Bearer ...。第三方服务商的门槛处往往只校验其中一种。你的key没问题,但是请求头没被网关识别,照样给你弹一个401 unauthorized: incorrect api key provided。
认知三:模型名是协议的一部分,不是“文件名”。
你在LM Studio里看到的是qwen1.5-0.5b-chat这个本地模型文件,但它对外暴露的API模型ID可能是qwen1.5-0.5b-chat,也可能是lmstudio-community/qwen1.5-0.5b-chat这种带命名空间的完整ID。填错一个字符,agent照样跑不起来。
1.3 先问自己一句:你接的是“兼容端点”还是“转换层”
这是我最建议你在动手前先做的一个判断。
如果你的第三方服务商直接提供了Anthropic兼容的base URL,比如DeepSeek的https://api.deepseek.com/anthropic这种,那Claude Code这边就很简单,设置环境变量直接指向它就行,不需要额外装任何东西。
如果对方只提供OpenAI兼容接口,而你想接的是Claude Code,那你就必须加一层“协议转换层”,把Anthropic格式翻译成OpenAI格式。本地模型接入Claude Code时,基本都属于这种情况。
Codex和PI则反过来,它们本身就是OpenAI协议,接本地模型时不需要复杂转换,把base URL指到LM Studio或Ollama就行。
2. 第三方API接入:一次配置成功的最小流程与常见401的真相
2.1 Claude Code接第三方API的最小流程
我以DeepSeek为例,因为它的Anthropic兼容端点做得比较省心,而且很多人都在用。
在终端里设置三个环境变量,然后直接启动Claude Code:
export ANTHROPIC_BASE_URL="https://api.deepseek.com/anthropic" export ANTHROPIC_AUTH_TOKEN="sk-你的DeepSeek密钥" export ANTHROPIC_MODEL="deepseek-chat" claude如果一切正常,你会直接进入对话界面。但我建议你同时检查一下~/.claude/settings.json,因为某些版本的Claude Code会优先读项目配置里的模型设置,光设环境变量不够。
{ "env": { "ANTHROPIC_MODEL": "deepseek-chat", "ANTHROPIC_SMALL_FAST_MODEL": "deepseek-chat" } }这里有个细节容易忽略:Claude Code内部会同时用到“主模型”和“轻量模型”,前者做复杂推理,后者做标题生成、命令汇总这类零碎任务。如果你只设了主模型,没设轻量模型,它在某个环节可能还会偷偷去请求默认的Anthropic模型,然后被网关拒绝。所以干脆两个都设成同一个模型最省事。
智谱、讯飞星火这类国内商业API,思路完全一样。去它们文档里找“以Anthropic协议接入”或“OpenAI兼容接入”的base URL,填进ANTHROPIC_BASE_URL即可。
2.2 Codex接第三方API的最小流程
Codex CLI的配置方式和Claude Code不太一样,它推荐用配置文件,而不是纯环境变量。
在~/.codex/config.toml里加上这样一个provider定义:
[model_providers.deepseek] name = "DeepSeek" base_url = "https://api.deepseek.com" env_key = "DEEPSEEK_API_KEY" wire_api = "chat"然后在同一个文件的模型配置区:
[model] provider = "deepseek" model = "deepseek-chat"最后导出密钥:
export DEEPSEEK_API_KEY="sk-你的DeepSeek密钥" codex注意,wire_api = "chat"这个字段在部分Codex版本里叫wire_api,在另一些版本里可能叫protocol,或者干脆自动判断。你安装的是什么版本,就以那个版本codex --help或官方示例里的字段为准。
2.3 401 unauthorized的真相:别急着怀疑key
我见过的401 incorrect api key provided,真正是key打错的情况不到一半。
先说报错本身。sk-svcac****这种带前缀的key段,是网关在返回错误时顺手带出来的key前缀,帮你回忆用的是哪一把key。看到这个报错,第一个动作不是去重新复制key,而是先用curl把问题切成两段来验证。
第一步,验证这个key在OpenAI兼容端点上是否有效:
curl -s https://api.deepseek.com/v1/models \ -H "Authorization: Bearer sk-你的key" \ | head -20第二步,验证这个key在Anthropic兼容端点上是否有效:
curl -s https://api.deepseek.com/anthropic/v1/messages \ -H "x-api-key: sk-你的key" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "model": "deepseek-chat", "max_tokens": 32, "messages": [{"role": "user", "content": "hi"}] }'如果第一条返回正常的模型列表,第二条却返回401,说明这个服务商根本不认Anthropic协议,或者你填的base URL不对,跟key本身没关系。如果两条都通,那问题就回到agent这一侧:环境变量没生效、配置文件里写死了别的provider、或者你用sudo运行时把环境变量弄丢了。
我遇到的最隐蔽一种情况是:终端里确实export了变量,但claude这个命令是通过别名或包装脚本启动的,脚本内部重新设置了环境,把外部的ANTHROPIC_BASE_URL给覆盖了。所以验证环境变量最简单粗暴的方式,就是启动前先看一眼:
env | grep ANTHROPIC确认ANTHROPIC_BASE_URL是你想要的地址,再启动agent。
3. 本地模型接入(LM Studio / Ollama)的端到端配置:从下载模型到curl验证
本地模型接入,是我觉得最值得写的一段。因为这里面的坑,十个有九个不是因为模型不行,而是因为没有搞清楚“协议转换”这个问题。
3.1 本地模型暴露出来的API长什么样
LM Studio启动本地服务器后,默认监听127.0.0.1:1234,对外提供的是OpenAI兼容API,路径是/v1/chat/completions。Ollama默认监听127.0.0.1:11434,同样提供OpenAI兼容/v1接口。
所以,不管你是用Codex还是PI,只要它们走OpenAI协议,接本地模型就是一件非常顺的事。
先说一个必须养成的习惯:不要猜模型ID,直接查。
curl -s http://127.0.0.1:1234/v1/models返回的JSON里id字段就是你要填的模型名。比如:
{ "data": [ { "id": "qwen1.5-0.5b-chat", "object": "model", "owned_by": "lmstudio" } ] }用返回的id去配置,别用文件名猜。
3.2 Codex接本地模型:最省心的一条路
Codex本身就是OpenAI协议,接LM Studio只需要设置两个环境变量:
export OPENAI_BASE_URL="http://127.0.0.1:1234/v1" export OPENAI_API_KEY="lm-studio" codex密钥随便填一个非空字符串就行,因为本地服务不校验。这个lm-studio只是为了让Codex的鉴权流程不至于报空key错误。
启动之前,先手动验证一下本地服务是通的:
curl -s http://127.0.0.1:1234/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "qwen1.5-0.5b-chat", "messages": [{"role": "user", "content": "你好,请回复OK"}], "max_tokens": 32 }'如果你能拿到一段正常的choices文本,Codex这边基本一次就能通。
3.3 Claude Code接本地模型:必须加一层协议转换
Claude Code只能发Anthropic协议,LM Studio只认OpenAI协议,这个矛盾怎么解决?加一个转换层。
市场上已经有现成的开源转换工具,比如claude-code-router。原理很简单:它在你本地起一个服务,监听比如127.0.0.1:8080,对外假装自己是Anthropic端点,收到/v1/messages请求后,把请求体改写成OpenAI格式,再转发给127.0.0.1:1234,最后把OpenAI的响应改回Anthropic格式。
配置思路大致是:
export ANTHROPIC_BASE_URL="http://127.0.0.1:8080" export ANTHROPIC_AUTH_TOKEN="local-token" export ANTHROPIC_MODEL="qwen1.5-0.5b-chat" claude转换工具那边,把上游指向http://127.0.0.1:1234/v1。
如果你不想依赖现成工具,也可以自己写一个很轻的转换服务。核心逻辑就四步:
- 接收
POST /v1/messages,解析system、messages、tools、max_tokens; - 把这些字段映射成OpenAI Chat Completions的
messages结构; - 转发给本地模型的
/v1/chat/completions; - 把返回的
choices[0].message.content包装成Anthropic的content块格式。
自己写一遍的最大好处,是你会彻底理解为什么malformed stream这类报错会出现——因为转换层处理流式响应时,要把OpenAI的SSE流格式转换成Anthropic的SSE流格式,稍微差一个字段,客户端就解析失败。
3.4 上下文长度报错:为什么小模型也会报1048576 tokens
很多人碰到过这样一个报错:
400 this model's maximum context length is 1048576 tokens. however...第一反应是“我的输入太长了吧”。但注意,1048576 token是服务端模型元数据里声明的最大上下文长度,不是说你真的能用满。
本地模型更明显。你用LM Studio加载qwen1.5-0.5b-chat这种小模型时,默认上下文可能只有几千token,但元数据里写的最大长度可以很大。当agent一次性把大量文件内容、工具结果塞进请求时,就会撞上限,触发400。
我自己的处理方式分三层:
- 第一层,在LM Studio加载模型时,明确把Context Length设成合理值,比如8192,别让它用“无限”这种自动模式;
- 第二层,在转换层或agent配置里限制
max_tokens,别让输出端无限制申请; - 第三层,改掉“把整个文件
cat给agent”的坏习惯,先用rg、grep把相关内容捞出来再喂进去:
rg -n "TODO|FIXME" src/ | head -50这一个习惯能帮你省掉八成跟上下文长度有关的报错。
4. 使用cc switch等切换工具时的配置隔离与冲突规避
4.1 cc switch这类工具到底解决了什么问题
当你既有Claude Code官方账号,又买了DeepSeek的API,还想偶尔切到本地模型的时候,就会面临一个很现实的问题:每次切换都要去改环境变量、重启终端、改配置文件,太痛苦了。
cc switch这类切换工具的核心功能,就是把这些配置组合收纳到一个图形界面里,点一下就能切。它在本地起一个转发服务,收到agent请求后,根据你当前选中的方案,把请求转给对应的上游端点。
这里有一个大家经常担心的问题:cc switch会不会和官方账号冲突?
我的实测结论是:不会。它不改写你~/.claude目录下的登录凭据,只是在你启动agent时,从环境变量层面把请求地址切走。你切回“官方账号”方案后,请求又会走Anthropic官方端点,登录态还是原来那个,不需要重新扫码。
4.2 冲突的真正来源
说几个我实际遇到过的冲突点,都是绕过“官方账号冲突”这个伪命题之后才真正冒出来的:
端口占用。
cc switch的本地转发服务要监听一个端口,如果端口被其他程序占用了,它就会启动失败,或者转发链路断裂。判断方法很简单:
netstat -ano | grep 端口号有别的进程占用,就换一个端口,或者把那个进程关掉。
环境变量残留。
这是最阴的一种。你之前手动export ANTHROPIC_BASE_URL指向过某个第三方API,然后这个变量还在当前终端里。cc switch虽然设置了它自己的base URL,但它的启动方式是通过包装命令拉起agent,包装命令里可能又引入了额外环境变量,结果两个变量互相打架。
env | grep -E "ANTHROPIC|OPENAI"启动前看一遍,有残留就清掉:
unset ANTHROPIC_BASE_URL unset ANTHROPIC_AUTH_TOKEN同端口的多个agent。
cc switch在处理Codex的/responses端点时,如果同时开了多个终端窗口,或者你手动启动了一个Codex实例占用了同样的本地端口,就会报类似local proxy failed while handling codex endpoint /responses的错误。它不是Codex挂了,是转发层拿不到端口。
4.3 我推荐的配置隔离方式
如果你不太想依赖切换工具,我建议你用“项目级.env + 启动函数”的方式做隔离,这套方法我用了大半年,几乎没再踩过环境变量污染的坑。
每个项目目录下放一个.env文件,比如:
# 项目A,走DeepSeek ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic ANTHROPIC_AUTH_TOKEN=sk-xxx ANTHROPIC_MODEL=deepseek-chat然后在你自己的shell配置文件里写一个函数:
function cc-deepseek() { set -a source ./.env set +a claude "$@" }这样只有当前项目启动Claude Code时才加载这套环境变量,关掉终端就自动干净,其他项目不受影响。
如果你用的是cc switch这类工具,最重要的习惯是:切换完后通过env | grep确认当前生效的base URL,再开始干活。很多莫名其妙的报错,其实都是“你以为切过去了,实际没有”。
5. 踩坑实录:从401、400到“stream was malformed”的完整排查思路
前面讲了很多配置方法,但这部分才是真正值钱的地方。我把几个高频报错从头到尾过一遍,给你一套可以复用的排查思路。
5.1 拿到报错后,先分类再动手
不要一上来就改配置。先判断这属于哪一类:
| 报错特征 | 大概率问题 | 排查起点 |
|---|---|---|
401 unauthorized: incorrect api key provided | key、鉴权头、base URL三者之一不匹配 | 用curl直接打base URL验证key |
404 not found | 请求路径不对,发到了不存在的端点 | 看真实请求URL,确认/v1/messages还是/v1/chat/completions |
400 model not found | 模型ID和服务商实际支持的名字对不上 | 调/v1/models查有效模型ID |
400 maximum context length | 上下文窗口超限 | 调整模型加载时的context length |
response stream was malformed | 流式响应格式不对,转换层/网关改坏了SSE | 先关闭流式输出测试 |
local proxy failed while handling... | 本地转发服务端口或配置问题 | 检查端口占用、确认切换工具版本 |
5.2 案例一:key明明没问题,却一直401
现象:用同一个key,在DeepSeek官网测试工具里一切正常,但Claude Code就是报401 incorrect api key provided。
我的排查顺序:
- 先跑
env | grep ANTHROPIC,看环境变量是不是真的在; - 用curl分别测OpenAI兼容端点和Anthropic兼容端点(就是前面2.3节那两个命令);
- 如果Anthropic端点通,问题就在agent侧,把
settings.json里所有env块都看一遍,重点看有没有别的地方覆盖了ANTHROPIC_AUTH_TOKEN; - 如果Anthropic端点不通,直接找服务商的技术支持,问“你的Anthropic兼容端点是不是真的能用”。
有一次我查到最后,发现是服务商的“Anthropic兼容端点”版本比较老,不认tools字段,Claude Code一带上工具定义就401。这种问题你配置改一万遍都没用,要么将就着用OpenAI兼容端点加转换层,要么换供应商。
5.3 案例二:cc switch处理codex endpoint /responses失败
现象:用cc switch切换到第三方API后,跑Codex任务,弹出一段英文报错,提到local proxy failed while handling codex endpoint /responses。
这段报错直译是“本地转发层在处理Codex的/responses端点时失败了”。结合前面的分类,问题基本锁定在转发层,不是你的key错了,也不是模型名错了。
排查思路:
- 确认cc switch的本地转发服务进程还活着;
- 确认端口没被占用;
- 看cc switch自己的日志,定位是解析
/responses请求体时出错,还是转发给上游时网络失败; - 如果某个切换配置是“官方Codex账号”,不要走转发层,直接用Codex原生的config.toml做直连。
Codex官方CLI本身就支持config.toml里写多个provider,我个人现在更倾向于直接用原生配置,cc switch这类工具只用来切Claude Code的环境变量,两边分开反而更少出问题。
5.4 案例三:response stream was malformed and no response was produced
现象:pi agent或Codex接到第三方API返回后,报“响应流畸形,没有产生响应”。
这个报错的本质是:客户端在解析SSE流时,中途遇到了不符合协议的分片,直接判定整个响应无效。
经验上,九成原因是转换层对流式响应处理不完整。比如,OpenAI兼容接口返回的流式事件字段和Anthropic客户端期望的字段对不上,转换层又没有做字段映射,客户端读到一半就崩了。
我的处理顺序:
- 先把流式关闭,如果转换层支持
stream: false,或者agent有--no-stream之类的参数,先用非流式跑通一遍; - 非流式正常,说明模型本身没问题,问题在流式转换;
- 再看是不是本地小模型输出不规范。部分量化后的小模型,生成的SSE事件里会混入异常字符,网关照单全收,客户端自然解析失败;
- 最后考虑换模型。很多时候不是配置问题,就是那个模型和当前client不兼容。
5.5 案例四:400 maximum context length is 1048576 tokens
这个报错我在3.4节聊过,这里补充一个定位技巧。
报错信息中的1048576是模型元数据里声明的上限。你要先判断是“请求里带的内容真的超过了”,还是“元数据虚高让你误以为没超”。
最简单的验证方式:拿一个很短的消息手动请求一次,比如:
curl -s http://127.0.0.1:1234/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{"model":"qwen1.5-0.5b-chat","messages":[{"role":"user","content":"hi"}]}'如果短消息正常,长消息报400,说明是上下文吃紧了;如果连短消息都报maximum context length,说明模型加载时的上下文配置有问题,或者转换层往里塞了元数据。检查LM Studio模型加载界面的Context Length,以及代码里是否误传了max_tokens超大值。
6. 我的一些个人经验与推荐组合
6.1 这几套组合我实测最舒服
| 使用场景 | 推荐组合 | 理由 |
|---|---|---|
| 日常代码补全、重构、写测试 | Claude Code + DeepSeek/智谱的Anthropic兼容API | 上下文大、推理质量高、不占本地资源 |
| 批量小任务、离线环境、隐私敏感代码 | Codex + LM Studio本地Qwen模型 | 免费、本地运行、数据不出电脑 |
| 团队多环境快速切换 | cc switch + 各项目独立.env | 配置隔离清晰,切换快 |
| 纯本地且必须用Claude Code | Claude Code + claude-code-router + LM Studio | 协议转换层解决格式差异 |
6.2 不要一上来就追求“全本地”
我自己在本地模型上花了很多时间,最后得出的结论是:对绝大多数人来说,混合使用才是最优解。
日常开发里,Claude Code这类agent真正值钱的时刻,是处理大段上下文、跨文件重构、复杂调试的时候。这些场景对模型理解能力要求很高,本地小模型确实吃力。反过来,那些“把这几个日志文件里报错信息提取一下”“把这段话术翻译成英文”之类的琐碎任务,本地小模型又完全够用,还不用花API费用。
所以我现在的流水线是:大活走第三方商业API,杂活切本地模型,两边共存,靠配置文件做隔离。
6.3 怎么验证当前是不是真的走通了
最后分享一个我每次配置完都会做的验证流程,很短,但能省掉大量排查时间:
- 启动agent前,检查环境变量:
env | grep -E "ANTHROPIC|OPENAI" - 启动agent时加调试参数,Claude Code用
--debug --verbose,Codex看官方日志开关; - 看请求日志里实际请求的host和模型名。如果host还是
api.anthropic.com,说明你的环境变量没被读到;如果host已经指向第三方服务商,但模型名还是默认的claude-...,说明模型配置没生效; - 如果走了cc switch之类的转发层,看它的日志窗口,那里能看到完整的请求转发链路。
我自己踩完这一圈,最深的体会是:99%的接入失败不是key的问题,而是路径、协议、环境变量这三样东西没对上。把“默认请求路径是什么”“对方服务认什么协议”“当前环境变量到底生效没有”这三件事查清楚,剩下的就是水到渠成的事。