base_url 怎么填:七家推理平台接入报 404 的四类坑
调不通的时候,人的第一反应几乎总是怀疑 key。重新生成一把、复制粘贴、再试一次,还是不行,于是开始怀疑账号没充值、怀疑模型没权限、怀疑网络被墙。我见过太多人在这条路上耗掉半天,最后发现是 base_url 里少了一段路径。
有个特别省事的分流办法:先看错误码,再决定去查什么。
一、看码分流:401/403 是鉴权,404 是地址
HTTP 状态码在这件事上其实相当诚实,只是很多人没把它当成路标来用。
| 错误码 | 大概率原因 | 该去查什么 |
|---|---|---|
| 401 | 凭据缺失或无效 | key 有没有传、有没有带 Bearer 前缀、有没有复制到空格换行 |
| 403 | 凭据有效但权限不足 | 账号状态、这把 key 有没有开对应权限 |
| 404 | 请求打到了服务端不认识的路径 | base_url 写法、模型 ID 写法 |
| 429 | 触发限流 | 并发与重试策略 |
| 5xx | 服务端问题 | 重试与降级 |
Groq 的官方错误码清单里就把 401 写成「缺失或无效凭据」、403 写成「权限不足」、404 单独列为 Not Found,这几档的语义是分开的。也就是说,当你拿到的是 404,服务端根本没走到验 key 那一步——它连你要访问的那个路径是什么都没认出来。这时候你把 key 换十遍也没用。
404 的成因只有两大类:地址错了,或者模型 ID 错了。模型 ID 那一类我在模型 ID 命名规则里单独展开过,本文只谈地址。
一个快速二分法:把请求体里的模型名换成一个你确定不存在的字符串,比如 this-model-does-not-exist。
- 如果错误信息从「路径不存在」变成了「模型不存在」之类的语义错误,说明地址是通的,问题在模型名上。
- 如果错误信息一模一样,什么都没变,说明请求压根没被路由到聊天补全接口上,问题在地址。
这个动作十秒钟就能做完,比换 key 靠谱得多。
二、七家平台的 base_url 长什么样
下面这张表里的地址逐字取自各家官方文档,不要凭记忆改写,也不要”规整”成看起来更统一的样子。
| 平台 | base_url / endpoint | 接入范式 |
|---|---|---|
| Groq | https://api.groq.com/openai/v1 | OpenAI 兼容 |
| DeepInfra | https://api.deepinfra.com/v1/openai | OpenAI 兼容 |
| Novita AI | https://api.novita.ai/openai | OpenAI 兼容 |
| Together AI | https://api.together.ai/v1 | OpenAI 兼容 |
| Fireworks AI | https://api.fireworks.ai/inference/v1 | OpenAI 兼容(另有 Anthropic SDK 兼容端点) |
| Nebius Token Factory | https://api.tokenfactory.nebius.com/v1/ | OpenAI 兼容 |
| Replicate | https://api.replicate.com/v1/predictions | 自有 predictions API,非 OpenAI 兼容 |
盯着这张表看三十秒,你就会明白为什么这一行是错误率最高的一行:七家里没有两家完全一样。有 /v1 结尾的,有 /openai 结尾的,有两段路径顺序相反的,有多一段的,有带末尾斜杠的,还有一家压根不在同一个体系里。
三、四类坑,对号入座
坑一:路径顺序反了
这是最阴的一类,因为两边用的是同样两个词。
- Groq:
https://api.groq.com/openai/v1 - DeepInfra:
https://api.deepinfra.com/v1/openai
同样是 openai 和 v1 两段,顺序完全相反。人脑对这种”元素相同、次序不同”的字符串识别能力极差,你把 Groq 的地址抄进 DeepInfra 的配置里,再回头逐字校对三遍,很可能三遍都看不出来——因为你的眼睛只在确认”这两个词都在”。
我的习惯是,遇到多段路径的地址,不要用眼睛比对,用 diff 比对。把官方文档里那一行原样贴进编辑器,跟你配置里的那一行放在一起做字符级比较,顺序错乱会立刻显形。
坑二:少一段或者多一段
- Novita AI 是
https://api.novita.ai/openai,没有/v1。 - Fireworks AI 是
https://api.fireworks.ai/inference/v1,比多数平台多一段inference。
这两个方向的错都很好犯,因为大家心里有个”标准形状”:https://api.厂商.com/v1。见过太多这种形状之后,手会自己补全。给 Novita 手滑补一个 /v1,或者抄 Fireworks 时漏掉 inference,都是常见事故。Fireworks 抄漏 inference 的典型表现就是 404,而不是任何看起来跟权限有关的报错——如果这时候你按”权限不足”的方向去查账号,方向就完全跑偏了。
坑三:末尾斜杠
Nebius Token Factory 的官方写法是 https://api.tokenfactory.nebius.com/v1/,带末尾斜杠。
末尾斜杠单独看无关痛痒,麻烦出在拼接上。SDK 拿到 base_url 之后要往后面接 /chat/completions 这样的相对路径,如果它的实现是简单字符串相加,你带了斜杠它也带斜杠,结果就是路径里出现 //。有的服务端会把双斜杠规整掉,有的不会——不会的那种直接给你 404。
反过来也一样:如果 SDK 是先把末尾斜杠剥掉再拼,那带不带都无所谓。问题在于你事先并不知道你用的这个库是哪种实现,而且同一个语言的不同 HTTP 客户端行为还可能不一致。所以末尾斜杠不是”写不写都行”,而是”写成官方那样,然后去看实际发出去的 URL”。
坑四:它根本不是同一种 API
Replicate 的 https://api.replicate.com/v1/predictions 不是 base_url,它是一个具体的接口地址。
Replicate 走的是自己的 predictions API:你提交一个任务,请求体里给 version(模型版本 ID)和 input(输入参数对象),默认是异步的——提交完拿到任务句柄,然后轮询或者用 webhook 回调取结果。想要同步返回,得加 Prefer: wait 请求头,还可以指定等待秒数,比如 wait=5;另有 Cancel-After 头用来设置自动取消超时,格式像 1m30s、2h。除了社区模型的 predictions,官方模型走 https://api.replicate.com/v1/models/{model}/predictions,Deployments 走 https://api.replicate.com/v1/deployments/{deployment}/predictions。
这套东西塞不进 OpenAI 客户端。你把这个地址填进 OpenAI(base_url=...),SDK 会老老实实往后面拼 /chat/completions,于是请求打到 .../v1/predictions/chat/completions——一个谁都不认识的路径,404 是必然结果。
接 Replicate 是换调用范式,不是换一行配置。 这件事在选型阶段就要想清楚,它比比价更靠前:如果你的场景是聊天补全那种一问一答的低延迟交互,异步任务模型会给你带来一整套额外工程(任务状态、回调接收、超时取消);如果是跑图、跑批、长任务,异步反而是它的优势。
四、根源:SDK 会替你拼路径
上面四类坑其实是同一个误解的四种表现——很多人没分清 base_url 和「完整接口地址」的区别。
OpenAI 风格的客户端库里,base_url 是一个前缀,不是终点。你调用 client.chat.completions.create(...),库内部知道这个方法对应的相对路径是 /chat/completions,它会把这段接到你给的前缀后面,凑出真正要发的 URL。同理,列模型对应 /models,嵌入对应 /embeddings。
所以:
base_url = https://api.groq.com/openai/v1
+ 相对路径 = /chat/completions
→ 实际请求 = https://api.groq.com/openai/v1/chat/completions
理解了这一层,“把完整接口地址填进 base_url”为什么必然 404 就一目了然了。假设你在某处文档里看到了完整地址,直接抄进配置:
base_url = https://api.groq.com/openai/v1/chat/completions
+ 相对路径 = /chat/completions
→ 实际请求 = https://api.groq.com/openai/v1/chat/completions/chat/completions
路径重复了一遍。服务端当然不认识。这种错的迷惑性在于:你填进去的那个地址本身是完全正确的,你用 curl 打它能通,所以你会坚信配置没问题,转头去查 key、查网络、查账号。
反方向的错同样存在:有些自建网关或者代理只接受完整地址,你按 SDK 的习惯只填到 /v1 就断了,它不会替你补后半截,结果也是 404。这就是为什么下一节的验证流程要从「实际发出去的 URL」入手,而不是从「我填的对不对」入手。
五、一套通用的验证流程
不管接哪家,这套流程都能用,而且不依赖你对某家平台的记忆。
第一步:用 curl 打你认为的完整接口地址。
把官方文档上的 base_url 逐字复制过来,手动接上 /chat/completions,直接发一个最小请求:
curl -i https://api.groq.com/openai/v1/chat/completions \
-H "Authorization: Bearer $GROQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"llama-3.3-70b-versatile","messages":[{"role":"user","content":"hi"}]}'
加 -i 是为了把状态码和响应头一起打出来。这一步的目的不是让它成功,而是让它给出一个”有语义”的回应。哪怕返回 401,也是好消息——说明路径找对了,服务端走到了鉴权环节。如果返回的还是 404,那就是地址本身有问题,往回查上面四类坑。
第二步:从通了的完整地址倒推 base_url 该填到哪一层。
curl 通了之后,把末尾的 /chat/completions 砍掉,剩下的就是 base_url。这个动作听起来废话,但它把”记忆”换成了”推导”——你不需要记住谁带 /v1、谁的 openai 在前面,你只需要记住”完整地址减去相对路径”。
第三步:在 SDK 里打印实际请求的 URL,做二次确认。
这一步是防止 SDK 的拼接行为跟你的预期不一致(双斜杠、自动补 /v1、代理改写等等)。通用的思路有三条,按侵入性从低到高:
- 开客户端库的调试日志。 多数 HTTP 客户端和 SDK 都有 debug 级别的日志开关,打开后会把请求行完整打出来。具体开关名以你用的那个库的文档为准。
- 挂一个请求钩子。 如果 SDK 允许传入自定义的 HTTP 客户端或者中间件,在发送前把 URL 打到日志里,这是最稳的方式,因为它拿到的就是最终要发出去的那个字符串。
- 把 base_url 临时指向一个你能看到日志的地方。 比如本地起一个只做记录的小服务,让 SDK 打过去,看它到底请求了什么路径。这招在排查网关和代理链路的时候尤其管用。
只要你亲眼看到过一次实际请求的 URL,这个平台的 base_url 你就再也不会填错了。关于 SDK 侧的更多配置细节,可以看用 OpenAI SDK 接第三方模型和base_url 是什么、该填到哪一层。
六、走网关时多一层要验
如果你的调用链上有网关(自建的、开源的、或者厂商提供的),事情会多一层。
网关那一栏里到底该填 base 还是完整地址,取决于网关自己怎么拼。有的网关把上游配置当作前缀,后面按 OpenAI 的路径规则接;有的要求你给出完整的上游接口地址,它只做转发和改写。这两种设计都合理,但填法完全相反,填反了照样 404。
所以加渠道的时候,别在网关的管理页面上凭猜测填完,直接点保存然后祈祷。正确的顺序是:
- 先 SSH 到跑网关的那台机器上,用第五节的第一步 curl 一遍上游地址。这一步还顺带验证了出网、DNS、证书这些问题——很多所谓的”接入失败”其实是那台机器出不了网。
- curl 通了,再回网关配置里填,填完发一个测试请求。
- 如果网关报 404 而机器上 curl 是通的,那基本可以锁定是网关的拼接规则和你的填法对不上,换另一种填法试(填到
/v1还是填到/chat/completions)。
这个”先在网关机器上验一遍”的习惯,能把排查范围从”整条链路”缩小到”网关配置”,效率差好几倍。网关侧的兼容层设计可以参考网关的 OpenAI 兼容层。
七、写进配置的纪律
排查完一次就该沉淀成规矩,不然下次换个平台还得再踩一遍。
地址逐字照抄官方文档。 不做任何”看起来更规范”的改写:不给 Novita 补 /v1,不把 Nebius 的末尾斜杠删掉,不把 DeepInfra 的 /v1/openai 顺手改成 /openai/v1。你觉得不规范的地方,很可能正是人家路由规则的一部分。抄完之后跟原文做一次字符级 diff,别用眼睛校对。
别在代码里手工拼地址。 见过有人写 BASE + "/v1" 这种,理由是”统一一下方便切换”。结果就是七家里有四家被这行代码改坏了。base_url 就应该是一个不可拆的整体字符串,从配置里原样读出来,原样交给 SDK。
把地址、模型 ID、key 绑成一个通道对象一起切。 这三样是强绑定的:Groq 的模型 ID 是扁平名(如 llama-3.3-70b-versatile),DeepInfra 和 Novita 是 组织/模型 两段式(如 deepseek-ai/DeepSeek-V3、deepseek/deepseek-r1),Together 也是两段式(如 MiniMaxAI/MiniMax-M3),Fireworks 是三段式(如 accounts/fireworks/models/deepseek-v3p1),Nebius 是两段式带日期版本后缀(如 deepseek-ai/DeepSeek-R1-0528)。你不可能把 A 家的地址配 B 家的模型名。
所以配置结构应该长这样,而不是三个平铺的环境变量:
{
"channels": {
"groq": {
"base_url": "https://api.groq.com/openai/v1",
"api_key_env": "GROQ_API_KEY",
"model": "llama-3.3-70b-versatile"
},
"fireworks": {
"base_url": "https://api.fireworks.ai/inference/v1",
"api_key_env": "FIREWORKS_API_KEY",
"model": "accounts/fireworks/models/deepseek-v3p1"
}
},
"active": "groq"
}
切换的时候动的是 active 这一个字段,三样东西一起换,物理上杜绝了”改了地址忘了改模型名”这类半吊子状态。多平台迁移的整体做法可以看从 OpenAI 兼容端点迁移。
在配置注释里写上核对日期和文档来源。 各家的接入路径不是永远不变的——比如 Nebius 的文档域名,docs.nebius.com/studio/... 会 307 跳到 docs.tokenfactory.nebius.com,这说明产品线做过更名或者迁移。有个日期戳,半年后接手的人至少知道该不该重新核一遍。
鉴权写法也一并记下。 各家的环境变量名不一样:Groq 用 GROQ_API_KEY,DeepInfra 用 DEEPINFRA_TOKEN,Fireworks 用 FIREWORKS_API_KEY,Nebius 用 NEBIUS_API_KEY,Replicate 用 REPLICATE_API_TOKEN,Together 的文档里写作 $TOGETHER_API_KEY,Novita 的文档里写作 ${API_KEY}。header 形式则基本统一为 Authorization: Bearer <token>。这些也一起进通道对象。
八、遇到 404 的自查清单
按顺序走,一般三分钟内能定位:
- 先确认错误码。 是 404 才走这套流程;401/403 去查凭据,429 去查限流,5xx 去查重试与降级。
- 做模型名二分。 把模型改成一个必然不存在的字符串,看报错语义变不变——变了说明地址对,问题在模型 ID;不变说明问题在地址。
- 拿官方文档的地址做字符级 diff。 重点看这四处:两段路径的顺序、有没有
/v1、有没有多出来的段(如inference)、末尾斜杠。 - 确认你填的是前缀不是完整地址。 检查配置里的值有没有以
/chat/completions结尾;如果有,砍掉。 - 用 curl 打完整地址。 带
-i看状态码。返回 401 是好消息(路径对了),返回 404 说明地址还有问题。 - 打印 SDK 实际发出的 URL。 开 debug 日志、挂请求钩子,或临时指向一个能看日志的本地服务;重点看有没有双斜杠、有没有重复路径段。
- 确认这家是不是 OpenAI 兼容。 Replicate 是自有 predictions API,不能当 base_url 用,得按异步任务的范式改代码。
- 走网关的,先在网关那台机器上 curl 一遍上游。 通了再调网关的填法,别在管理页面上猜。
至于各家的速率限制、价格、免费额度这些数值,本文一概不写——那些以各家官方定价页和控制台为准,且变动频率远高于接入地址。你真正需要固化到配置和文档里的,就是上面这张七行的对照表,加上一套不依赖记忆的验证流程。