跳至内容
NewAPI 渠道类型踩坑:Type 5 不是自定义

NewAPI 渠道类型踩坑:Type 5 不是自定义

July 28, 2026

引子:六个渠道一夜之间变成了 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 = 1TypeCustom = 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 字段错误。

渠道列表显示为 MjProxyPlus

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/v1

3. 确认类型常量

grep "Type.*=" /path/to/newapi/constant/channel.go

你的权威参考是这份 Go 源文件,不是任何网文。

4. 检查 abilities 表

NewAPI 维护了一张 abilities 表来管理渠道能力(支持的模型列表)。如果渠道类型不对,这部分数据也可能脏了,需要清理重建。

修复脚本:Python 批量修正

以下是一个完整的修复脚本。它做了四件事:

  1. 更新所有错误渠道的 type 字段
  2. 规范化 base_url
  3. 重��� abilities
  4. 重启 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.pyconstants.py
  • 任何项目:不要用搜索引擎确定 magic number 的含义

教训二:SQLite 直接操作要非常谨慎

NewAPI 使用 SQLite 管理配置,看起来可以直接用 sqlite3 命令操作数据库,比走管理页面快多了。但这也意味着:没有类型校验,没有下拉菜单,没有约束。你输入的每一个整数字段都必须自己确保正确。

如果走管理后台添加渠道,类型字段是个下拉框,你根本不可能选到错误的类型。图快用 SQLite 直连,反而给自己挖了坑。

教训三:Base URL 不要携带路径段

大多数类型在拼接最终请求 URL 时都会自动追加路径段:

类型自动追加举例
Type 1/v1/chat/completionsbase_url 不要带 /v1
Type 43/chat/completionsbase_url 不要带 /v1
Type 45/api/v3/chat/completionsbase_url 不要带 /api/v3
Type 24/v1beta/models/...填 Gemini 根端点

通用的安全做法:base_url 只填写到域名级别的入口点,让框架决定路径拼接。

总结

六个渠道显示为 Midjourney 的故事到这里就结束了,复盘下来核心就一句话:

数字 5 是 MidjourneyPlus,不是自定义。所有渠道类型编号以 constant/channel.go 为准。

如果你正在搭建 NewAPI 网关,请记住:

  1. 找类型映射: grep "Type.*=" constant/channel.go
  2. OpenAI 兼容接口:type = 1base_url 不加 /v1
  3. 原生渠道: 用对应的专用类型(DeepSeek=43,火山=45,Gemini=24)
  4. 通用自定义:type = 8
  5. 数据库操作: 优先走管理后台。必须直连 SQLite 时,先查后改,改完验证
  6. Base URL: 只写域名级入口,不加路径段
  7. 出了问题:channels 表,对照 channel.go,修正后重建 abilities 并重启

这个坑我替大家踩过了。希望这篇文章能让你省下两小时的排查时间。