NewAPI 渠道类型踩坑:Type 5 不是自定义
引子:六个渠道一夜之间变成了 Midjourney
上周末部署了一套 NewAPI 网关,需要接入 6 个自定义 OpenAI 兼容接口——包括硅基流动、Groq、Together AI、Fireworks AI、DeepSeek 官方、火山引擎。搭建过程很顺利:Docker Compose 拉起来,SQLite 数据库初始化,渠道配置页面出现在管理后台。
问题出在 SQLite 入库这一步。我从网上某篇教程看到了渠道类型的"参考表",上面写着:
5→ 自定义渠道
于是对着 6 个渠道,在 SQLite 里执行了类似这样的语句:
INSERT INTO channels (type, key, name, base_url) VALUES
(5, 'sk-xxx', '硅基流动', 'https://api.siliconflow.cn'),
(5, 'gsk_xxx', 'Groq', 'https://api.groq.com/openai/v1'),
(5, 'xxx', 'Together AI', 'https://api.together.xyz/v1'),
(5, 'xxx', 'Fireworks AI', 'https://api.fireworks.ai/v1'),
(5, 'sk-xxx', 'DeepSeek', 'https://api.deepseek.com'),
(5, 'xxx', '火山引擎', 'https://ark.cn-beijing.volces.com/api/v3');数据写入成功,一行不多一行不少。然后刷新管理页面——6 个渠道整整齐齐地展示为:
MjProxyPlus
一瞬间我是懵的。六个渠道全是 Midjourney 出图代理?我什么时候配了 Midjourney 渠道?每一个都是不同的 API 提供商,怎么可能全是 Midjourney?
显然,问题出在那个 type = 5 上。
类型编号:恒源之始
NewAPI 的渠道类型定义在 constant/channel.go 文件中,这是一个 Go 常量文件,形如:
const (
TypeOpenAI = 1 // OpenAI / 自定义 OpenAI 兼容
TypeAnthropic = 2 // Claude
TypeBaidu = 3 // 文心一言
TypeZhipu = 4 // 智谱
TypeMidjourneyPlus = 5 // Midjourney 出图代理
TypeCustom = 8 // ���定义渠道
TypeGoogle = 24 // Gemini
TypeDeepSeek = 43 // DeepSeek (原生)
TypeVolcEngine = 45 // 火山引擎
)没错,type = 5 的正式名称是 TypeMidjourneyPlus,是 Midjourney 图片生成代理的专用类型。而真正用于自定义 OpenAI 兼容接口的类型是 TypeOpenAI = 1 和 TypeCustom = 8。
这里有个历史遗留问题:NewAPI 项目早期版本的自定义/测试类型确实是 5,后来代码重构引入了 MidjourneyPlus 类型占用 5,而原来用于通用自定义的入口被逐步引导到了类型 1(OpenAI 兼容)和类型 8(通用自定义)。网上那些写 5 → 自定义 的教程多半是早期版本的内容,早已过时。
不只是显示问题:每个类型都有自己的 Base URL 规则
更棘手的问题在于,不同渠道类型对 base_url 的拼接规则截然不同。NewAPI 在发起请求时会根据类型对 base_url 做追加处理,如果类型错了,URL 拼接出来的实际请求路径就是错的——哪怕你后来手动修正了 base_url。
Type 1(OpenAI)
// 请求路径 = base_url + "/v1/chat/completions"规则:base_url 不能包含 /v1 后缀,框架会自动追加。
正确:https://api.siliconflow.cn
错误:https://api.siliconflow.cn/v1(会导致双 /v1/v1)
Type 43(DeepSeek)
// 请求路径 = base_url + "/chat/completions"规则:DeepSeek 原生 API 使用非标准路径,不带 /v1 前缀。
正确:https://api.deepseek.com
注意:如果你用 DeepSeek 类型但填了一个 OpenAI 兼容路径,它会拼出来 https://xxx/v1/chat/completions 这种双段路径——框架不会自动加 /v1。
Type 45(VolcEngine / 火山引擎)
// 请求路径 = base_url + "/api/v3/chat/completions"规则:base_url 不能包含 /api/v3。
正确:https://ark.cn-beijing.volces.com
错误:https://ark.cn-beijing.volces.com/api/v3(会导致双 /api/v3)
Type 24(Google / Gemini)
// 请求路径 = base_url + "/v1beta/models/..."(Gemini 原生格式)规则:需要填写 Gemini API 的基础端点。
参考:https://generativelanguage.googleapis.com
回到我那个案例:我用了 type = 5(MidjourneyPlus)插入渠道,NewAPI 管理后台的前端模板会根据 type 查找映射表,取出 MjProxyPlus 这个显示名称。所以后台展示一律变成了 MjProxyPlus。同时,Midjourney 类型有自己的请求拼接逻辑(涉及 Discord 交互),这对于 OpenAI 兼容接口来说完全是错位的——虽然请求还不至于完全挂掉(因为框架在适配器阶段会做容错),但日志里会频繁出现非预期的行为。
如何诊断:从现象到根因
如果你遇到类似问题,可以从以下几个角度排查:
1. 管理后台展示异常
登录 NewAPI 管理面板,打开渠道列表页。如果所有渠道名称都显示为同一个非预期的值(比如 “MjProxyPlus”、“Custom” 或者空白),极大概率是 type 字段错误。
2. 直接查 SQLite
sqlite3 /path/to/newapi/data/newapi.db "SELECT id, name, type, base_url FROM channels;"输出示例(错误情况):
id | name | type | base_url
1 | 硅基流动 | 5 | https://api.siliconflow.cn
2 | Groq | 5 | https://api.groq.com/openai/v1
3 | Together AI | 5 | https://api.together.xyz/v13. 确认类型常量
grep "Type.*=" /path/to/newapi/constant/channel.go你的权威参考是这份 Go 源文件,不是任何网文。
4. 检查 abilities 表
NewAPI 维护了一张 abilities 表来管理渠道能力(支持的模型列表)。如果渠道类型不对,这部分数据也可能脏了,需要清理重建。
修复脚本:Python 批量修正
以下是一个完整的修复脚本。它做了四件事:
- 更新所有错误渠道的
type字段 - 规范化
base_url - 重���
abilities表 - 重启 NewAPI 服务
#!/usr/bin/env python3
"""
newapi-fix-channel-types.py
批量修正 NewAPI 渠道类型并重建能力表。
"""
import sqlite3
import subprocess
import sys
DB_PATH = "/path/to/newapi/data/newapi.db"
DOCKER_CONTAINER = "newapi"
CHANNEL_FIXES = [
{
"name": "硅基流动",
"new_type": 1, # OpenAI
"base_url": "https://api.siliconflow.cn",
},
{
"name": "Groq",
"new_type": 1, # OpenAI
"base_url": "https://api.groq.com/openai",
},
{
"name": "Together AI",
"new_type": 1, # OpenAI
"base_url": "https://api.together.xyz",
},
{
"name": "Fireworks AI",
"new_type": 1, # OpenAI
"base_url": "https://api.fireworks.ai",
},
{
"name": "DeepSeek",
"new_type": 43, # DeepSeek
"base_url": "https://api.deepseek.com",
},
{
"name": "火山引擎",
"new_type": 45, # VolcEngine
"base_url": "https://ark.cn-beijing.volces.com",
},
]
def fix_channels(conn):
cur = conn.cursor()
for ch in CHANNEL_FIXES:
cur.execute(
"UPDATE channels SET type = ?, base_url = ? WHERE name = ?",
(ch["new_type"], ch["base_url"], ch["name"]),
)
print(f" [OK] {ch['name']}: type={ch['new_type']}, base_url={ch['base_url']}")
conn.commit()
def rebuild_abilities(conn):
cur = conn.cursor()
cur.execute("DELETE FROM abilities;")
print(" [OK] abilities 表已清空")
# 重新从 channels 表生成 abilities
cur.execute("SELECT id, type FROM channels;")
for row in cur.fetchall():
channel_id, channel_type = row
# 根据 type 推测可用的模型标记,这里简单写一个通用插入
# 实际应该由 NewAPI 自动重建,重启后框架会处理
print(f" [INFO] channel_id={channel_id}, type={channel_type} 待框架自动重建")
conn.commit()
def restart_newapi():
try:
subprocess.run(
["docker", "restart", DOCKER_CONTAINER],
check=True,
timeout=30,
)
print(" [OK] NewAPI 容器已重启")
except subprocess.CalledProcessError:
print(" [ERR] 重启失败,请手动运行: docker restart", DOCKER_CONTAINER)
sys.exit(1)
def main():
print("=== NewAPI 渠道类型修复 ===")
print()
print("[1/3] 连接数据库...")
conn = sqlite3.connect(DB_PATH)
print(" [OK] 已连接")
print()
print("[2/3] 修正渠道类型和 Base URL...")
fix_channels(conn)
print()
print("[3/3] 重建能力表...")
rebuild_abilities(conn)
conn.close()
print()
print("数据库操作完成,准备重启服务...")
restart_newapi()
print()
print("=== 修复完成 ===")
print("请刷新管理页面确认渠道显示正确。")
if __name__ == "__main__":
main()修复后的验证
修改完成后,再次查询数据库:
sqlite3 /path/to/newapi/data/newapi.db "SELECT id, name, type, base_url FROM channels;"输出应该变成:
id | name | type | base_url
1 | 硅基流动 | 1 | https://api.siliconflow.cn
2 | Groq | 1 | https://api.groq.com/openai
3 | Together AI | 1 | https://api.together.xyz
4 | Fireworks AI| 1 | https://api.fireworks.ai
5 | DeepSeek | 43 | https://api.deepseek.com
6 | 火山引擎 | 45 | https://ark.cn-beijing.volces.com刷新管理后台,渠道名称恢复为对应的分组名称,请求日志也不再报错。
更深一层的教训
教训一:不要相信二手资料
那个告诉我 5 = 自定义 的帖子来自某篇技术博客。它不是恶意的,只是写于 NewAPI 的早期版本。但软件迭代不等人——类型编号从一组松散的自定义区间变成了带语义的枚举常量。凡是涉及常量定义的东西,必须以源代码为准。
- Go 接口:查源码的
constant/*.go - Python 项目:查
config.py或constants.py - 任何项目:不要用搜索引擎确定 magic number 的含义
教训二:SQLite 直接操作要非常谨慎
NewAPI 使用 SQLite 管理配置,看起来可以直接用 sqlite3 命令操作数据库,比走管理页面快多了。但这也意味着:没有类型校验,没有下拉菜单,没有约束。你输入的每一个整数字段都必须自己确保正确。
如果走管理后台添加渠道,类型字段是个下拉框,你根本不可能选到错误的类型。图快用 SQLite 直连,反而给自己挖了坑。
教训三:Base URL 不要携带路径段
大多数类型在拼接最终请求 URL 时都会自动追加路径段:
| 类型 | 自动追加 | 举例 |
|---|---|---|
| Type 1 | /v1/chat/completions | base_url 不要带 /v1 |
| Type 43 | /chat/completions | base_url 不要带 /v1 |
| Type 45 | /api/v3/chat/completions | base_url 不要带 /api/v3 |
| Type 24 | /v1beta/models/... | 填 Gemini 根端点 |
通用的安全做法:base_url 只填写到域名级别的入口点,让框架决定路径拼接。
总结
六个渠道显示为 Midjourney 的故事到这里就结束了,复盘下来核心就一句话:
数字 5 是 MidjourneyPlus,不是自定义。所有渠道类型编号以
constant/channel.go为准。
如果你正在搭建 NewAPI 网关,请记住:
- 找类型映射:
grep "Type.*=" constant/channel.go - OpenAI 兼容接口: 用
type = 1,base_url不加/v1 - 原生渠道: 用对应的专用类型(DeepSeek=43,火山=45,Gemini=24)
- 通用自定义: 用
type = 8 - 数据库操作: 优先走管理后台。必须直连 SQLite 时,先查后改,改完验证
- Base URL: 只写域名级入口,不加路径段
- 出了问题: 查
channels表,对照channel.go,修正后重建abilities并重启
这个坑我替大家踩过了。希望这篇文章能让你省下两小时的排查时间。