Files
ModelRouter/README.md
JianFeeeee 94cbcb6771 docs: source templates, split image AUTO chain, headless/desktop installers
- AUTO chain section: chat and image generation are now two independent
  chains (auto / auto_image) toggled in the Priority page; legacy image
  discovery fallback documented
- new Source templates section: multi-key balancing via reusable templates
  (Templates manager, From template / As template in the add-source dialog)
- Desktop GUI section: two installer kinds — Headless (server, plain binary)
  and Desktop (Electron GUI); release artifact naming ModelRouter-Headless-* /
  ModelRouter-Desktop-*; stale 1.0.0 version pins replaced with <ver>
2026-08-27 13:02:48 +08:00

23 KiB
Raw Permalink Blame History

ModelRouter

English: README_EN.md

AI-assisted: 本项目使用 AI 辅助编程(代码与文档由 AI 协作完成,经人工复核)。

面向多 llms 订阅者,部署在内网,实现一次配置多个服务共同使用的效果。支持指定模型或 auto 模式,按照配置的优先级选择可用模型提供服务。

轻量且增源无需重编译:网关本体是单 Go 二进制(约 10MB零运行时依赖。新增/切换上游 只需在 config.yaml(或 WebUI加一个 sources 条目或挂一个 .lua 适配器——不改 Go、 不重编译。源与适配器的改动经 WebUI 提交时即时生效(热更新);直接编辑 config.yamladapter_dir 下的 .lua 文件则需要重启进程生效。

核心优势

极致轻量

  • 单二进制:编译后约 10MB零运行时依赖仅依赖系统 libc部署即用
  • 极低内存占用:空闲状态仅 ~15MB RSS满载并发 100+ 请求时峰值 < 100MB
  • 零运行时依赖:纯 Go + LuaJIT 静态链接,无需安装 Python/Node/Java 等运行时
  • 启动极快:冷启动 < 200ms热重载配置 < 10ms

强大的多租户调度能力

  • 多密钥多租户支持无限密钥每个密钥独立角色、模型范围、Token 配额、重置周期
  • AUTO 智能调度:基于优先级档位的分级调度,同优先级源自动轮询负载均衡,故障自动毫秒级故障转移
  • Token 配额管理:精确到模型级别的 Token 配额控制,支持小时/周/月/自定义小时周期自动重置
  • 同优先级源负载均衡:同一优先级档位的多个源,请求自动 Round-Robin 均匀分发,故障毫秒级故障转移
  • Source-Model 前缀路由:支持 source-model/source:model/source/model 精确指定上游源

生产级可靠性

  • 热加载配置WebUI 修改源/密钥/适配器/AUTO链即时生效无需重启
  • 密钥加密落盘AES-256-GCM 加密存储 api_key、网关密钥、请求头master.key 0600 权限保护
  • 实时源探测GET {base}/models 定期探测,毫秒级感知上游状态,不污染调度退避状态
  • 全链路审计HTTP 访问日志、登录/配置变更审计、请求记录 CSV 导出、密钥用量统计 CSV 导出

灵活的协议适配

  • LuaJIT VM:每个适配器独立 VM + worker 池安全并发Lua 脚本热加载无需重启
  • Lua 适配器协议transform_request / transform_response / transform_stream_chunk 双向转换
  • 动态请求头钩子build_headers(meta) 支持 HMAC 签名、动态 Header 注入
  • 多模态透传image_url 等多模态内容在多源间无损透传Anthropic/Gemini/Ollama 自动转换

实现

统一 OpenAI 兼容网关:把多个上游 LLM 源DeepSeek、Qijiar、OpenAI、Anthropic、Gemini、 Groq、Mistral、Ollama、KimiCode…通过 Lua 适配器 做协议转换,对内网暴露 一个标准 OpenAI Chat Completions 接口(/v1/chat/completions + /v1/models 支持单次结算、SSE 流式、AUTO 模型路由多模态透传。

HomeAgent 的多源 LLM 适配层 internal/agent/api/provider.go + internal/lua/adapters/*)抽离而来并独立演进。

特性

  • 多源:一个进程内配置任意多个上游源,按请求的 model 自动路由。
  • AUTO 模式default_model: AUTO 时按优先级页保存的 AUTO 链档位逐档调度并发请求。
  • 统一输出:所有源对外都是 OpenAI 格式(含 reasoning_contenttool_callsusage)。
  • 生图POST /v1/images/generationskind: image 的模型独立路由。
  • 多模态content 数组(image_url在多源间无损透传Anthropic/Gemini/Ollama 自动转换。
  • LuaJIT VM:基于 golua 绑定的 LuaJIT每个适配器独立 VM + worker 池,安全并发。
  • disable_thinking:请求 disable_thinking:true(或上游对应字段)动态开关推理。
  • Lua 适配协议:每个源挂一个 .lua 适配器,完成 transform_request / transform_response / transform_stream_chunk 双向转换,协议差异全在 Lua 层。
  • 签名 / 请求头钩子:适配器可定义 build_headers(meta),在 Go 发 HTTP 前 动态注入/签名请求头——用于云端 API 校验调用方 app如 KimiCode 只放行特定 agent。提供 hmac_sha256_hex / sha256_hex / base64_encode 等签名辅助。
  • WebUI:内置管理界面,可在线查看/新增/编辑上游源与模型、配置 AUTO 优先级链、 管理密钥模型范围,写入运行时文件持久化。
  • 密钥加密存储:运行时文件中的上游 api_key、自定义请求头值、网关 key 均以 AES-256-GCM 加密落盘(master.key 独立 0600LLMS_PROXY_MASTER_KEY)。
  • 实时源探测:状态页探测各源可达性(GET {base}/models,失败回退最小请求), 不污染正常调度的退避状态,错误信息在 UI 可悬停查看。
  • 鉴权:网关自身用 gateway_keys 校验客户端 Bearer key与上游各自的 key 相互独立。
  • 流式SSE chat.completion.chunk,含角色首包与 [DONE] 收尾。
  • 全链路审计HTTP 访问日志、登录/配置变更审计、请求记录 CSV 导出、密钥用量统计 CSV 导出
  • AES-256-GCM 加密存储运行时文件敏感字段加密落盘master.key 0600 权限,支持环境变量注入主密钥

快速开始

GOMODCACHE=... GOPROXY=off go build -tags luajit -o llmsproxy ./cmd/llmsproxy
./llmsproxy -config config.yaml    # 首次运行自动生成默认配置并打印随机 admin key

依赖 goluaLuaJIT 绑定)。必须-tags luajit 构建,否则默认走内置超集 gopher-lua 路径(行为略有差异)。

配置不入库:仓库不携带任何 config 文件(配置文件含密钥)。二进制首次运行时 会在 -config 指定的位置生成默认配置:随机 admin key打印在启动日志中、 仅绑定 127.0.0.1:8080。首次登录后请在 WebUI「密钥」页更换管理员密钥。

# 无 key -> 401
curl http://127.0.0.1:8080/v1/models

# 单次($KEY 换成首次启动日志打印的 admin key
curl -H "Authorization: Bearer $KEY" \
     -d '{"model":"AUTO","messages":[{"role":"user","content":"hi"}]}' \
     http://127.0.0.1:8080/v1/chat/completions

# 流式
curl -N -H "Authorization: Bearer $KEY" \
     -d '{"model":"AUTO","stream":true,"messages":[{"role":"user","content":"hi"}]}' \
     http://127.0.0.1:8080/v1/chat/completions

任何 OpenAI SDK 把 base_url 指到网关地址、api_key 用任意一个已创建并授权的网关 key 即可。

配置

首次运行在 -config 路径自动生成默认配置(默认 ./config.yaml),核心字段:

listen: 127.0.0.1:8080        # 网关监听地址(建议绑内网/回环)
gateway_keys: [sk-gw-<随机>]  # 初始 admin 密钥(每次安装随机生成,仅首启写入运行时存储用)
default_model: AUTO           # model 无法路由时自动按优先级链选源
adapter_dir: adapters         # Lua 适配目录;目录不存在时首启创建并 seed 内置,存在则只读
runtime_file: runtime.json    # WebUI 编辑的源/密钥/AUTO 链持久化到此文件

sources:
  - name: deepseek
    base_url: https://api.deepseek.com
    api_key: sk-...            # 也可用 api_key_env: SOME_ENV 引用环境变量(不落盘明文)
    adapter: deepseek
    max_concurrent: 8
    models:
      - id: deepseek-v4-flash
        priority: 100          # YAML 源首次启动会 seed 进 AUTO 链的初值
        kind: chat
      - id: deepseek-v4-pro
        priority: 60
        kind: chat
    # 静态请求头(比适配器默认优先)
    headers: { X-Tenant: prod }
    # 传给 Lua build_headers 的透传元数据
    meta: { app_id: x, app_secret: y }
    temperature: 0.7
    max_tokens: 4096
    timeout: 120s              # 请求超时,默认 120s

config.yamlruntime.jsonmaster.keyadapters/ 均不提交版本库(见 .gitignore),请随备份带走——master.key 丢失后运行时密文无法解密。

运行文件生成在 -config 所在目录:adapter_dir / runtime_file 自动指向 配置文件旁边的 adapters/runtime.json,从任意工作目录启动都可用。

运行时文件(runtime_file)中的敏感字段自动加密:

  • 加密算法 AES-256-GCM格式 enc:v1:<base64>
  • 主密钥来源:环境变量 LLMS_PROXY_MASTER_KEY64 位 hex否则读取 runtime_file 同目录的 master.key;都不存在时首次启动自动生成 master.key0600
  • 升级时旧明文文件自动兼容:首次运行正常读取,任何 UI 保存操作触发全文件加密迁移。
  • 注意:master.key 丢失后密文无法解密,请随配置一起备份;切勿提交到版本库。

网关密钥(多密钥)

网关鉴权采用「多密钥 + 角色 + 模型范围」架构,密钥持久化在 runtime_filekeys 字段(加密存储):

  • gateway_keys 配置只是初始 admin 密钥种子:首次启动迁移为运行时 admin key之后不再参与鉴权管理。
  • 重要:首次启动后请在 WebUI「密钥」页更换管理员密钥——初始密钥明文写在 config.yaml 里(每次安装随机生成),继续使用存在被盗风险;用新密钥登录后删除初始密钥。
  • WebUI 密钥页可创建/删除密钥;每个密钥可指定 admin(管理全部)或 user(仅看自己的 key角色并配置模型范围(模型 + 源 + token 配额 + 重置周期)。
  • 客户端用任意一个已授权的密钥明文作为 BearerAuthorization: Bearer <key>)。
  • 删除密钥即从运行时存储移除,立即失效。

模型路由

/v1/chat/completionsmodel 解析顺序:

  1. source/modelsource:model 前缀 → 指定源;
  2. 精确匹配某个源的 model
  3. 配置 default_model: AUTO 时 → 走优先级页保存的 AUTO 链(见下);
  4. 否则回落(返回错误)。

任何 OpenAI 客户端,只要 model 设为某个源的 name/任意名,即可锁定走该源; 设为 AUTO(或网关配了 default_model: AUTO)即自动按优先级链选源。

AUTO 链(优先级页)

AUTO 调度由优先级页保存的规则决定,源配置里的 models[].priority 数字不再 参与调度、也不再显示。聊天与生图是两套独立的链,在优先级页面通过「聊天 / 生图」 按钮切换编辑:

  • 聊天链runtime_fileauto 字段):服务 POST /v1/chat/completionsmodel: AUTO 请求;只接受 kind: chat 模型,配置在链里的生图槽位会被忽略。
  • 生图链runtime_fileauto_image 字段):服务 POST /v1/images/generationsmodel: AUTO 请求;只接受 kind: image 模型。未配置生图链时AUTO 生图回落 到「所有含生图模型的源、按注册顺序」的旧行为,兼容旧配置。

每条规则 = 一个「槽位」:{ model, source, tier, token_quota, period, hours }

  • tier 表示优先级档位:同一档的模型并排、共享该优先级;档位从上到下递减。
  • 同一模型可配置多个槽位(如 A 源低配 → B 源低配 → A 源高配 → B 源高配),按档位顺延。
  • token_quota > 0 时该槽位在重置周期内用满即顺延到下一槽位;period 支持 hour / week / month / nhour(配合 hours),空 = 不限。
  • 同 tier 内按「偏好分」排序,失败槽位冷却后自动跳过;冷却 / 配额耗尽 / hard 错误会顺延到下一 tier全部失败时返回 503 并附上每档失败摘要。

Source与适配器Adapter的关系

  • 是到某个上游的连接描述:namebase_urlapi_key、模型列表与优先级。
  • 适配器 是协议转换逻辑Lua 脚本):把统一 OpenAI 格式请求转成上游原生格式, 再把上游响应/流式分块转回统一格式。
  • 一个适配器可被多个源复用(如 openai.lua 同时服务多个 OpenAI 兼容站点); 同一个源也可以切换不同适配器(改 adapter 字段即可)。
  • 源决定“连谁、暴露哪些模型”,适配器决定“怎么对话”——二者在 sources[] 条目中 通过 adapter 字段关联。

加载流程(internal/coreCore 负责装配):

  1. 启动时 lua.NewVM(adapter_dir) 加载全部适配器:内置适配器(编译期 embed+ adapter_dir 下的同名覆盖文件;内置文件写在代码内,覆盖文件要求更高优先级。
  2. config.Load 读取 config.yamlconfig.NewStore(runtime_file) 读 WebUI 改动的 运行时源,二者按名称合并成完整源列表。
  3. rebuildRegistry 为每个源创建 provider.Provider(持有目标适配器),并按源的 并发上限配置适配器 worker 池大小。
  4. 请求进来时 Registrymodel 路由到 ProviderProvider 调适配器 transform_request → HTTP 发送 → transform_response / transform_stream_chunk

WebUI 上的"新增/编辑源"、"上传 Lua 适配器"、"改 AUTO 优先级链"与"密钥模型范围" 都即时生效(写入运行时文件或 adapter_dir 后重新装配,无需重启);直接编辑 config.yaml / adapter_dir 下的文件则需要重启进程才会重新加载。

在 WebUI 删除源/适配器时运行时WebUI 创建)的源与 .lua 文件会真正移除; config.yaml 中定义的基源无法改写配置文件,采用删除标记隐藏(重启后仍隐藏), 在 UI 里重新添加同名源即可恢复。

源模板(多 key 负载均衡)

多个上游 key 共享同一套配置URL / 适配器 / 模型列表 / 并发 / rpm逐一复制 整段 source 太冗余。源模板保存除 nameapi_key 以外的全部源字段, 从模板一键创建多个仅 key 不同的源,天然支持多 key 负载均衡(每个展开源独立调度、 独立冷却、独立健康状态)。

  • WebUI「源」页右上角「模板管理」按钮:列出全部模板,可编辑 / 删除 / 新建。
  • 添加源弹窗右上角两个按钮:
    • 从模板创建 → 选模板 → 表单自动填充name + key 仍需你填);
    • 存为模板 → 把当前表单非 key/name 字段存为新模板。
  • 模板持久化在 runtime_filesource_templates 字段,与运行时源一样热管理, 改完即时生效。模板本身不含密钥,可以安全共享 / 纳管。

模板不会自己变成源——它只是「配方」。真正承担流量的是从模板创建出来的、带 key 的具体源。

disable_thinking

请求体带 "disable_thinking": true网关透传给各适配器DeepSeek 适配器将其 映射为 extra_body.thinking.type = "disabled" 关闭推理,其余源按各自协议处理。

WebUI

内置管理界面(GET /),登录后可在浏览器完成:

  • 状态页:源在线状态(实时探测 + 悬停看错误)、模型/源/key 用量统计、请求记录, 支持按时间范围导出 CSV点击模型可生成 pin 到该模型的连接配置。
  • 对话页:流式/非流式调试。
  • 密钥页:创建/编辑网关 key为每个 key 配模型范围(模型 + 源 + token 配额 + 周期), 管理员管理全部 key用户只看到自己的 key。
  • 优先级页:拖拽积木配置 AUTO 链档位。
  • 源页在线增删改上游源API key 等敏感字段加密落盘)。
  • 适配器页:上传 / 删除 Lua 适配器脚本。

改动写入 runtime_file(重启仍生效)。

Lua 适配器协议

完整 API 见 Lua 适配器 API 文档English)。

每个适配器是一个返回 table 的 Lua 脚本(internal/lua/adapters/<name>.lua 加载时可被 adapter_dir 下的同名脚本覆盖——改 Lua 脚本无需重编译 重启即生效;通过 WebUI 上传的适配器与在线编辑的源配置则即时生效。

return {
  name = "mysrc",
  version = "1.0.0",
  endpoint = "/chat/completions",        -- 上游路径(可被 source.endpoint 覆盖)
  headers = { ["X-Static"] = "v" },      -- 静态默认请求头build_headers 缺省时使用)

  -- 请求转换:把 OpenAI 格式 req 转成上游原生格式,返回字符串
  transform_request = function(raw_json) ... end,

  -- 响应转换:把上游原生响应转成统一格式字符串
  --   { content, reasoning_content, finish_reason, token_usage{...}, tool_calls[{...}] }
  transform_response = function(raw_json) ... end,

  -- 流式分块转换:把上游 SSE data 转成 { content, done, ... },返回 "" 则跳过
  transform_stream_chunk = function(raw_chunk) ... end,

  -- [可选] 动态请求头/签名钩子
  -- meta = { url, method, body, api_key, timestamp, source={ name, meta={...} } }
  build_headers = function(meta) return { ["X-App-Sign"] = sign } end,
}

内置信号辅助:hmac_sha256_hex(key, data)sha256_hex(data)base64_encode(s)tohex(s)json.encode/decodelog(level, msg)

内置适配器

openai deepseek anthropic gemini github groq mistral ollama kimicode

anthropic/gemini/ollama 适配器内置多模态转换(image_url → 各自上游格式);若 源启用 disable_thinkingdeepseek 适配器会把 extra_body.thinking.type 置为 disabled

kimicode 是展示 build_headers 的样例:云端校验调用方 app需要按 meta.app_secret 对时间戳+URL+请求体哈希做 HMAC 签名并附 X-App-Sign 等头。 配好 sources[].meta.{app_id, app_secret, app_agent} 即可。

桌面 GUIElectron

可选独立产物,面向内网桌面端用户:内嵌 ModelRouter 核心Clash Verge 形态), 一键启停、系统托盘常驻、开机自启、静默启动,内容区一比一内嵌完整 WebUI (状态/对话/密钥/优先级/源/适配器 六个页面,免登录)。服务器用户继续使用纯 Go 二进制。

两种安装包Headless 与 Desktop

发布到 Release 的安装包分为两类,面向不同用户:

  • Headless服务器版:纯核心二进制(无 Electron配置驱动适合服务器、 容器、systemd 等无人值守场景。Linux 为 .tar.gz(二进制 + config.example.yaml
    • adapters/ + READMEWindows 为 .zip
  • Desktop桌面版Electron GUI 安装包,内嵌核心,适合个人桌面日常使用。

选择 GUI 还是纯后端

场景 推荐 理由
服务器 / 内网网关 / 无人值守常驻 Headless(单二进制) 极轻量(~10MB、~15MB RSS零依赖单进程直接跑在 systemd/任意容器里,远程管理
个人桌面日常使用 / 多设备内网共享 DesktopGUI 免登录内嵌 WebUI、系统托盘一键启停、开机自启、静默后台适合不懂命令行的使用者
Windows 桌面 Desktop 纯后端在 Windows 上需自行注册服务GUI 提供原生托盘/自启体验
CI 一键出三平台安装包 Desktop 打包脚本 make gui-* 系列出 deb/AppImage/NSIS可进发行流水线

两者完全同源GUI 内嵌的就是纯后端的同一份 llmsproxy 核心LuaJIT 版), 配置/适配器格式完全一致,可随时互换。

构建与运行

cd cmd/gui
npm install
make gui          # 或 npm run dev —— 开发运行(需先 make build 生成 bin/llmsproxy
make gui-deb      # 本机构建 deb分享给其它 Linux 用户)
make gui-dist     # 构建 deb + AppImagelinux
make gui-win      # 构建 Windows nsis 安装包(本机 mingw + wine
make gui-win-docker  # 构建 Windows nsis 安装包(全 docker 自足,无需本机 mingw
make gui-dist-dir # 仅解包产物(调试用,不产安装包)

产物在 cmd/build/gui-dist/ModelRouter-<ver>.AppImagemodelrouter-gui_<ver>_amd64.debModelRouter Setup <ver>.exewin。rpm 需系统 rpmbuild。 发布时会把产物重命名为 ModelRouter-Desktop-*GUIModelRouter-Headless-*(纯后端) 两类上传到 GitCode Release。

Windows 打包docker 固化方案,推荐)

make gui-win-docker 一步完成:交叉编译核心 + NSIS 打包,宿主机无需安装 mingw

  • 构建镜像:cmd/gui/docker/win-builder/Dockerfilegolang:1.25 + mingw-w64 + 预编译 LuaJIT windows 版 lua51.dll 与 import lib。LuaJIT 源码取自 gitcode 镜像国内可达github 兜底Go 依赖走 https://goproxy.cn
  • 核心编译:cmd/gui/scripts/win-core-docker.sh —— 在容器内产出 cmd/gui/bin/{llmsproxy.exe,lua51.dll}
  • 一键发布:cmd/gui/scripts/dist-win-docker.sh —— 核心编译后调用 electron-builder宿主需有 wine 用于 NSIS
  • 首次运行自动构建 modelrouter/win-builder 镜像;已存在则直接复用。
# 全 Windows 发布(核心重编 + NSIS
make gui-win-docker

特性

  • 内嵌核心:自动拉起捆绑的 llmsproxyLuaJIT 版),配置/密钥/adapter 存于 用户数据目录 <userData>/profile/,首次运行自动生成随机 admin key 写入 keys (非 seed并自注入WebUI 免登录、不会提示"更换初始密钥"。端口可在设置中更改(默认 8787
  • 系统托盘:内核状态点、开机自启/静默启动开关、启停/重启内核、退出。
  • 静默启动--silent 参数或设置项,启动仅驻托盘不弹窗;首次运行始终显示窗口。
  • 开机自启Windows/macOS 用 setLoginItemSettingsLinux 写 ~/.config/autostart/modelrouter-gui.desktop,静默开启时自动附加 --silent

目录

cmd/llmsproxy            # 入口(纯 Go 服务端)
cmd/gui                  # Electron 桌面 GUI内嵌核心 + 托盘 + 打包)
internal/config          # YAML 配置加载/校验
internal/lua             # LuaJIT VM + worker 池 + AdapterCache + 内置适配器 (embed)
internal/provider        # Provider(HTTP) + Registry(路由)
internal/gateway         # OpenAI 兼容 HTTP 服务 + 鉴权 + SDK/流式 + WebUI
internal/types           # 统一格式 & OpenAI wire 类型

测试

go test -tags luajit ./...

覆盖:配置校验、适配器加载/变换、签名钩子、Gateway 鉴权、SDK 结算、SSE 流式、 模型路由、多模态透传与 disable_thinking。