把多个上游模型服务聚合成本地一份 OpenAI Chat Completions 契约的 Windows 桌面网关。
自带面板(内嵌 WebView2 桌面壳 + 系统托盘),负责渠道管理、用量计量与状态查看。 上游的协议差异全部在适配层里消化,对外只暴露一种协议。
┌────────────────────────────┐
OpenAI 客户端 ────▶│ /v1/chat/completions │
(任意 SDK) │ /v1/models ← 唯一对外契约│
└─────────────┬──────────────┘
│ 按 model 前缀路由
┌─────────────▼──────────────┐
│ src/internal/provider │
│ 协议适配 · Key 池 · 保真转发 │
└───┬────────────────────┬───┘
┌──────────────┘ └──────────────┐
▼ ▼
┌──────────────┐ ┌────────────────────┐
│ 内嵌型渠道 │ │ 托管型 provider │
│ 本进程内转发 │ │ 独立子进程 + 动态端口│
│ 自定义/预设端点│ │ 探活 · 断线重拉 · 杀树│
└──────────────┘ └────────────────────┘
# 1) 构建(Windows / Go 1.25+ / 需 WebView2 运行时)
# 脚本会先把 web/ 同步到 src/internal/assets/ 再编译,保证外置与内嵌一致
python tools/release/build.py
# 2) 运行(桌面壳 + 托盘)
./ModelMux.exe也可以直接调 Go(此时 src/internal/assets/ 用的是上次同步的副本):
go build -C src -trimpath -ldflags="-s -w -H windowsgui" -o ../ModelMux.exe .启动后托盘图标右键 →「打开面板」,或在浏览器里访问启动日志打印的地址(默认 http://127.0.0.1:1234/)。
在面板「渠道」里添加你的上游,然后就能用 access_key 调 /v1 了:
curl http://127.0.0.1:1234/v1/chat/completions \
-H "Authorization: Bearer <面板里复制的 access_key>" \
-H "Content-Type: application/json" \
-d '{"model":"<你的模型名>","messages":[{"role":"user","content":"hi"}]}'根目录只有 exe 本体与按职责分开的文件夹——一个文件夹只放一类东西:
modelmux/
├─ ModelMux.exe 构建产物(go build 生成,唯一散落在根的文件)
├─ README.md ← 你正在读的
├─ LICENSE
├─ .gitignore
│
├─ web/ 面板前端资源(唯一真源:HTML / JS / CSS)
├─ config/ 用户配置(modelmux.json,首次启动自动生成)
├─ data/ 运行期数据(logs/ usage/ cache/ instances/ ports.json)
│
├─ src/ Go 源码
│ ├─ main.go 启动顺序:配置 → 日志 → 单实例 → 端口 → 编排 → 服务 → 窗口/托盘
│ ├─ go.mod / go.sum
│ ├─ app.ico / appres.syso / winres/ PE 资源:图标、DPI 清单、版本信息
│ └─ internal/
│ ├─ config/ 配置读写与归一化(未知协议明确报错,不猜默认值)
│ ├─ logging/ 结构化日志:内存环 + 滚动文件
│ ├─ orchestrator/ 托管型 provider 的子进程生命周期与端口分配(杀树、先等真退出)
│ ├─ provider/ 内嵌型渠道:路由、协议适配、Key 池、连接测试
│ │ ├─ workbuddy/ 内置原生实现(MIT 上游照搬内嵌)
│ │ ├─ trae/ 内置原生实现(MIT 上游照搬内嵌)
│ │ └─ zcode/ 内置原生实现(按契约**独立重写**,无上游代码)
│ ├─ native/ 进程内原生装配层:按 kind 把上面的实现装进本进程
│ ├─ metrics/ 用量计量与定价
│ ├─ claim/ 限时套餐的定时领取
│ ├─ desktop/ WebView2 壳、托盘、单实例、有序退出
│ ├─ assets/ 前端资源的**构建镜像**(由 web/ 同步而来,不要手改)
│ └─ web/ 内置 HTTP 服务:对外 /v1 出口 + 对内 /api 控制面
│
├─ test/ 端到端验证脚本(假上游 + CDP 驱动真实界面)
│ └─ shot_tools/ 界面截图(给人看的,不含断言)
├─ tools/ 开发与发布期脚本
│ ├─ release/ 构建、全量快照、公开副本导出
│ ├─ icon/ 图标生成与比对
│ └─ win/ 桌面集成:快捷方式、窗口截图
└─ docs/ 设计与改造记录
go:embed 不允许 ..,而面板资源要放进 internal/assets 这个包才能被内嵌——
所以「根目录的 web/」和「src/internal/assets/」在物理上必须是两处。约定:
web/是唯一真源,改前端只改这里。src/internal/assets/是构建镜像,由tools/release/build.py在编译前用 sha256 逐文件同步,不手工改。- 运行时优先读 exe 同级的
web/(便于热改调试),读不到才回落到 exe 内嵌副本。 也就是说:删掉web/应用照常工作,只是前端不能外置调整。
这三条是硬约束,改动时不能破:
-
对外契约只有一份——OpenAI Chat Completions。任何上游的协议差异都在
src/internal/provider的适配层吸收,不对外暴露第二套格式。 -
工作方式分两类,都不引入第三方许可负担。
- 照搬 MIT 上游源码(
workbuddy/trae):须保留其版权声明,许可原文集中放在src/THIRD-PARTY-LICENSES/;照搬来的那部分不改变本项目自身的 MIT。 - 独立重写(
zcode):上游dengyie/zcode2api是 AGPL-3.0,不能照搬, 因此按实测契约从零重写成 Go(src/internal/provider/zcode/)。 本仓库里没有任何上游代码,所以也不需要它的许可原文。
两种方式都不是上游 AGPL 代码,因此都可内嵌且不改变 ModelMux 的 MIT。 AGPL 项目(
new-api)仍只允许进程级调用:它是独立子进程,不构成衍生作品。 - 照搬 MIT 上游源码(
-
不随包分发第三方二进制。托管型 provider 由用户自行获取,本项目不分发。
配套的工程约定:
| 约定 | 原因 |
|---|---|
| 未实现的协议明确报错并附原因,不回落近似协议「试一下」 | 静默回落会把上游的报错变成用户看不懂的谜题 |
| 上游错误原样透传(状态码 + 错误体) | 客户端要靠状态码决定重试策略 |
| 转发保真优先:格式一致时一个字节都不碰 | 改 JSON 用 map[string]json.RawMessage,map[string]any 经 float64 往返会破坏大整数与高精度小数 |
| 区分「字段缺失」与「空值」 | Go 里 nil slice = 字段缺失,空数组 = 显式清空 |
| 会导致路由歧义的配置禁止 + 报错,不猜默认值 | 猜错会把请求静默送到错误的上游 |
| 类型 | 配置键 | 运行形态 |
|---|---|---|
| 内嵌型 | embedded_providers |
跑在网关进程内。自定义 OpenAI 兼容端点,或选用内置预设(见下一节)。支持多 Key 轮询、权重、模型前缀。 |
| 托管型 | managed_providers |
由 ModelMux 托管:进程内原生(内置实现,不起外部进程)或独立子进程(动态分配端口、注入环境变量、探活、按树杀干净)。由 mode 字段决定。 |
托管型渠道的管理面板由网关反向代理到面板里,Authorization 由服务端注入——
面板侧不接触上游密钥。
预设只是预填了一组字段的模板:选中后每一项都能改,保存后就是一个普通渠道, 不参与路由 / 额度 / 日志里的任何特殊分支。
托管型渠道有两种运行方式,由渠道的 mode 决定,模板会替你选好:
- 进程内原生(
mode=native):ModelMux 自己装配内置实现,在本进程内起一个只绑127.0.0.1的服务。不需要任何外部可执行文件,面板与控制台都照常使用。 - 独立子进程(
mode=process,缺省):编排器动态分配端口、注入环境变量、探活、退出时按树杀干净。 升级前的配置没有mode字段,一律按子进程解释,行为与旧版完全一致。
| 预设 | 项目 | 运行方式 | 说明 | 许可 |
|---|---|---|---|---|
workbuddy |
WorkBuddy 2 API | 进程内原生 | 把 CodeBuddy 账号变成 OpenAI 兼容接口的多账号网关:OAuth 登录、账号池三因子加权轮转、分级熔断与冷却、会话粘性。已内置,不需要 wb2api.exe;把「数据目录」指到原 wb2api 目录即可沿用已登录的账号。 |
MIT |
zcode |
zcode2api | 进程内原生 | ZCode 账号运营 + 双协议网关一体机:账号池轮询、额度监控、限时套餐领取、设备码登录,同时对外提供 Anthropic Messages 与 OpenAI Chat Completions。已内置(按实测契约独立重写,见下方说明),不需要 Python 环境;把「数据目录」指到原 zcode2api 的 data/ 即可沿用已登录的账号。 |
AGPL-3.0(上游)/ 本项目为独立实现 |
new-api |
new-api | 独立子进程 | 多渠道聚合与分发底座。启动命令 new-api.exe,端口环境变量 PORT。 |
AGPL-3.0 |
trae |
trae2api-web | 进程内原生 | 把 Trae IDE 的模型能力暴露成本地 OpenAI 兼容端点:账号池调度、冷却状态机、每日签到与设备码/回调登录闭环。已内置,不需要 node server.js;登录回调需要一个固定端口(默认 18080,见数据目录下的 config.json)。 |
MIT |
custom-managed |
自定义进程 | 独立子进程 | 任何能用环境变量指定端口、且暴露 OpenAI 兼容端点的可执行程序。 | — |
workbuddy与trae都是照搬上游 MIT 源码内嵌的实现(上游文件头与版权声明逐字保留), 许可原文见src/THIRD-PARTY-LICENSES/。zcode是独立重写:上游dengyie/zcode2api(AGPL-3.0)只作为契约来源 (在其上采样出 HTTP / 落盘 / 出站三类契约),本项目按契约从零实现, 仓库里没有任何上游代码,因此src/THIRD-PARTY-LICENSES/里没有 zcode 目录。 表中其余项目都是独立程序,由使用者自行获取与部署:ModelMux 不包含也不分发它们的二进制, AGPL 上游(new-api)仅以独立进程方式调用、不构成衍生作品。
| 预设 | 上游 | 协议 | 默认接入点 | 获取 Key |
|---|---|---|---|---|
openrouter |
OpenRouter | OpenAI | https://openrouter.ai/api/v1 |
keys |
zhipu |
智谱 BigModel | OpenAI | https://open.bigmodel.cn/api/paas/v4 |
apikeys |
deepseek |
DeepSeek 官方 | OpenAI | https://api.deepseek.com/v1 |
api_keys |
siliconflow |
SiliconFlow 硅基流动 | OpenAI | https://api.siliconflow.cn/v1 |
ak |
moonshot |
Moonshot / Kimi | OpenAI | https://api.moonshot.cn/v1 |
api-keys |
anthropic |
Anthropic(Claude) | Anthropic Messages | https://api.anthropic.com |
keys |
openai |
OpenAI 官方 | OpenAI | https://api.openai.com/v1 |
api-keys |
ollama |
本地 Ollama | OpenAI | http://127.0.0.1:11434/v1 |
免 Key |
vllm |
本地 vLLM / LM Studio | OpenAI | http://127.0.0.1:8000/v1 |
免 Key |
custom |
任意 OpenAI 兼容端点 | OpenAI | 自填 | — |
上表里只有 anthropic 一家协议不同:上游说 Anthropic Messages,由适配层转成 OpenAI 格式对外。
openai 的部分新模型只在新版 Responses 协议下可用,需要时可在渠道里改选协议。
托管型渠道有三种来源方式,与许可无关、只取决于上游的获取成本:
- MIT 上游照搬源码内嵌(如
workbuddy、trae):逐字保留上游文件头与版权声明,许可原文集中放在src/THIRD-PARTY-LICENSES/,照搬部分不改变本项目自身的 MIT。 - 自己独立重写(如
zcode):按实测契约从零实现,仓库里不含任何上游代码,可内嵌。 - AGPL 上游仅进程级调用(当前的
new-api):ModelMux 只把已存在于本机的程序 作为子进程拉起,不包含也不分发其二进制,也不代其上游服务授予任何权利, 不构成衍生作品;各项目自身的免责声明同样适用。
- 渠道增删改查、启停、连通性测试、从上游拉取模型列表
- 对外
/v1出口带access_key鉴权(首次启动自动生成,面板可查看 / 复制 / 重新生成) - 用量与请求计量(
/api/metrics)、请求日志(含来源 IP 与 User-Agent) - 限时套餐定时领取(默认关闭;开启后按时间窗口自动领取,也可手动触发)
- 面板端口运行中热切换(
/api/settings/port) - 关窗行为可选:最小化到托盘继续运行 / 直接退出应用(选完即存,无需重启)
- 托盘驻留、单实例(重复启动会唤出已有窗口)、按显示器 DPI 换算窗口尺寸
- 无界面模式:
-headless,只跑内置服务不建窗口不建托盘
要求:Windows 10/11、Go 1.25+、WebView2 运行时(Windows 11 自带,Windows 10 需单独安装)。
推荐用发布脚本(它会先同步 web/ → src/internal/assets/ 再编译,产物落在仓库根):
python tools/release/build.py # 同步 + 编译
python tools/release/build.py --check # 只校验两份前端是否一致,不编译
python tools/release/build.py --no-build # 只同步,不编译直接调 Go 也可以,但要自己保证 src/internal/assets/ 是最新的:
go build -C src -trimpath -ldflags="-s -w -H windowsgui" -o ../ModelMux.exe .-H windowsgui 必须有,否则会多出一个黑色控制台窗口。
图标资源 src/appres.syso 已随仓库提供,go build 会自动拾取(同目录的 *.syso 会被自动收集)。
需要重建时:
cd src && go run github.com/akavel/rsrc@v0.10.2 -ico app.ico -o appres.sysoModelMux.exe # 桌面壳 + 托盘
ModelMux.exe -headless # 只跑服务(等价于 MODELMUX_HEADLESS=1)数据目录默认跟 exe 同级,也就是「绿色 / 便携」形态:把整个文件夹拷到哪,配置和数据就跟到哪。
modelmux/
├─ ModelMux.exe
├─ config/
│ └─ modelmux.json 配置(access_key 首次启动自动生成)
├─ data/
│ ├─ ports.json 上次用过的端口,重启优先复用
│ ├─ logs/ 结构化日志
│ ├─ usage/ 用量计量累积
│ ├─ cache/ WebView2 用户数据目录
│ └─ instances/ 托管型 provider 的实例数据
└─ web/ (可选)外置前端,见上文「前端资源为什么有两份」
查找顺序(第一个成立者胜出):
- 环境变量
MODELMUX_HOME(显式指定,最高优先级) - exe 所在目录——但必须实测可写(会在该目录建临时文件再删掉验证),
避免 exe 放在
Program Files、只读介质或受控文件夹访问拦截时静默失败 %LOCALAPPDATA%\ModelMux(回落,Windows 上的常规选择)- 系统临时目录下的
ModelMux(最后的兜底)
config/ 与 data/ 缺失时会在启动时自动创建,不需要手工准备。
面板监听在 127.0.0.1,端口默认动态分配;可在面板「设置」里固定为指定端口(如 1234)。
实际地址以启动日志为准。
| 路径 | 说明 |
|---|---|
/ |
面板(优先读 exe 同级 web/,读不到用 exe 内嵌副本) |
/v1/chat/completions、/v1/models |
OpenAI 兼容出口,需 access_key |
/api/status、/api/healthz、/api/logs |
状态与日志 |
/api/channels* |
渠道管理(增删改查 / 启停 / 测试 / 拉模型) |
/api/channels/{name}/upstream/{path...} |
托管型渠道的管理面板反代 |
/api/metrics |
用量与请求计量 |
/api/claim* |
限时套餐领取 |
/api/settings/port、/api/settings/close |
端口与关窗行为 |
/api/quit |
有序退出 |
test/ 下是真实运行的验证脚本(需要 Python 3):用假上游覆盖协议分支,用 CDP 驱动真实界面点击。
python test/verify.py # 全链路:托管型 provider 全生命周期、端口热切换
python test/verify_upstream_ui.py # 上游控制台逐视图
python test/verify_side_console.py # 侧栏渠道卡与常驻控制台入口
python test/verify_layout_ui.py # 侧栏 / 概览拆分 / 渠道嵌套编辑
python test/verify_pick_ui.py # 拉取模型全选与结果弹窗
python test/verify_theme.py # 首帧主题(防亮暗闪烁)
python test/verify_claim.py # 限时套餐定时领取链路
python test/verify_zcode_accounts.py # zcode 账号面板(托管型子进程 + 假网关)
python test/verify_zcode_native.py # zcode 内置原生(ModelMux 自己装配,无外部进程)
python test/gui_check.py # 托盘与窗口生命周期
python test/check_sources.py # 源码级不变式(内联 style、通知 API 是否被重新引入)
python test/verify_silent_minimize.py # 最小化 / 隐藏不得产生系统通知
python test/verify_proxy_real.py # 真实 wb2api 账号管理代理(用你的真实配置)跑之前先看清楚:
| 脚本 | 前置条件 |
|---|---|
verify.py / verify_claim.py / verify_pick_ui.py / gui_check.py / verify_zcode_native.py |
使用隔离的 MODELMUX_HOME,不会动你的真实配置 |
verify_layout_ui.py / verify_side_console.py / verify_upstream_ui.py / verify_theme.py |
需要本机已有一个实例在跑(默认 http://127.0.0.1:1234/) |
gui_check.py |
需要独占:机器上不能有其它 ModelMux 实例,否则会被单实例逻辑唤出并退出 |
verify_workbuddy.py |
|
verify_proxy_real.py |
|
verify_silent_minimize.py |
需要本机已有一个实例在跑(按 exe 名找 PID,并会最小化该窗口) |
check_sources.py |
不需要实例,纯源码检查 |
test/shot_tools/ 下是截图工具(shots*.py、panel_shot.py、shot_settings.py),
只产出给人看的 PNG、不含任何断言,所以不在上面的验证清单里。
python tools/release/build.py # 构建:同步 web/ → src/internal/assets/,再编出根目录的 ModelMux.exe
python tools/release/backup_project.py # 全量快照(源码 + 配置 + 编译产物),默认输出到仓库同级的 _backups/
python tools/release/export_for_github.py # 导出可公开的干净副本(白名单式)export_for_github.py 是白名单式的:只列进清单的文件才会出去(src/ web/ docs/ tools/ test/
与 README.md LICENSE .gitignore),因此新增文件时不会不小心把含真实密钥的 config/
或运行期 data/ 带进公开仓库。含真实账号 token 的 _ref/ 与历史快照已移出仓库,
.gitignore 里的规则保留作兜底。目标目录已存在时默认中止,确认要同步进已有仓库时加
GITHUB_EXPORT_INTO=1——该模式下只覆盖同名文件、不动 .git,但会清掉「源里已删除、
目标里还留着」的文件(否则从白名单撤下的文件会永久留在公开仓库里),要保留它们设
GITHUB_EXPORT_NO_PRUNE=1。
三个脚本的路径都自动推导,也可用 MODELMUX_SRC / BACKUP_DIR / GITHUB_EXPORT_DIR / GO 覆盖。
Go 依赖:
| 模块 | 许可 |
|---|---|
github.com/jchv/go-webview2 |
MIT |
github.com/jchv/go-winloader |
ISC |
golang.org/x/sys |
BSD-3-Clause |
托管型 provider 与面板里出现的上游服务分两类:MIT 上游照搬源码内嵌(workbuddy、trae,
版权声明逐字保留),其余是独立程序、由使用者自行获取,本项目不包含也不分发其二进制。
各项目的链接、许可与说明见已并入的 Provider;
照搬部分的许可原文见 src/THIRD-PARTY-LICENSES/,
其余项目的许可与使用合规性由各自项目及使用者自行负责。
MIT,见 LICENSE。