Files
ModelRouter/README.md
JianFeeeee 2378bc00ba docs: replace invented memory figures with measured ones, ship the tuning knobs
The README claimed "~15 MB RSS" and, after the log-loading work, "~10 MB idle /
~19 MB with a 29 MB audit log". Those were TEST-INSTANCE numbers: one mock source
and one adapter. The real production config on this host (16 sources, 13
adapters, 59 models) sits at ~37-42 MB, and sat at ~105 MB before this series.
Quoting the single-source figure as the headline was misleading.

Both READMEs now state that memory scales with the number of configured sources
rather than with uptime, give a three-row measurement table (1 source / 1 source
with a 29 MB audit history / the 16-source production instance), and break the
production RSS down per region (Go heap, thread stacks + LuaJIT, mapped binary,
Go reservations, shared libs) so an operator can tell which part their own
deployment will grow.

Two runtime knobs are documented and now shipped by default in the desktop
build's core spawn (cmd/gui/main.js, overridable by exporting either variable):

  * MALLOC_ARENA_MAX=2 — LuaJIT allocates through cgo into glibc malloc, and
    glibc keeps up to 8*nproc per-thread arenas of ~1 MB that are never returned.
    Measured 8-15 arenas (7-12 MB) -> 0.
  * GOGC=50 — halves the Go heap target. Documented explicitly as useless ALONE
    (measured 20.3 -> 21.5 MB, i.e. worse, because the saved heap is eaten by
    more glibc arenas); only the pair cuts settled RSS, by ~19%.

Also corrects the binary size (8-12 MB, ~8 MB after the deploy script's -s -w)
and adds the elastic-pool / on-demand-log / self-healing-cooldown bullets that
README.md already had to README_EN.md.
2026-08-30 09:09:21 +08:00

28 KiB
Raw 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 文件则需要重启进程生效。

核心优势

极致轻量

  • 单二进制:编译后 8~12MB-s -w strip 后约 8MB零运行时依赖仅依赖系统 libc部署即用
  • 低内存占用:与源数量相关,非与运行时长相关——单源 ~10MB、16 源生产实例 ~40MB实测分解与调优
  • 日志按需加载审计日志不常驻内存——默认只加载首屏下滚自动分页CSV 导出流式写出O(1) 内存),离页即释放
  • 零运行时依赖:纯 Go + LuaJIT 静态链接,无需安装 Python/Node/Java 等运行时
  • 启动极快:冷启动 < 200ms热重载配置 < 10ms

强大的多租户调度能力

  • 多密钥多租户支持无限密钥每个密钥独立角色、模型范围、Token 配额、重置周期
  • AUTO 智能调度:基于优先级档位的分级调度,同优先级源自动轮询负载均衡,故障自动毫秒级故障转移
  • 自愈冷却:冷却上限 5 分钟,过半后放行 1 个探测请求,上游/额度恢复即刻回归轮询,无需等满冷却窗口
  • 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 池,安全并发。 池是 弹性 的:Σ max_concurrent 只是上限而非预分配,状态按需创建、空闲时自动回收, 因此空载网关几乎不持有 Lua 状态(启动 0 个)。
    • 扩容步长由适配器最大并发决定:clamp(ceil(max/8), 1, 8),且仅在真实并发争用 (所有现有状态均已占用)时批量预热,顺序流量始终只用 1 个状态。
    • 缩容步长由当前连接数决定:clamp(ceil(冗余/(1+占用)), 1, 冗余)——无连接时一轮收到 常驻下限,忙时每轮只释放 1 个保护热路径。WebUI「适配器」页展示实时池指标。
  • 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 并附上每档失败摘要。

冷却与半冷却探测(自愈调度)

槽位失败后进入指数退避冷却5s → 10s → 20s …,上限 5 分钟)。冷却不是全时段 硬闸:

  • 前半段完全静默:刚失败的槽位不接任何流量,避免对故障上游持续施压。
  • 后半段放行 1 个探测:窗口过半后,同一 (源, 模型) 最多允许 一个 请求作为 探测通过。探测槽数 = clamp(max_concurrent / 10, 1, 2)——max_concurrent: 10 的源恰好放行 1 个探测请求。
  • 探测排在最后:同 tier 内探测候选永远排在健康候选之后,只有没有正常槽可用时 才承接真实流量,因此探测不会抢走可用容量。
  • 探测即真实请求:成功则立即清零失败计数与冷却,槽位当场回到正常轮询—— 上游恢复/额度恢复后不必等满整个冷却窗口。失败则重开一个新窗口,下次探测顺延到 新窗口的中点,不会连续重试。

分类冷却策略:

情况 冷却 说明
传输错误 / 5xx 5s→10s→20s…上限 5min 指数退避
401 / 403 凭据错误 10min 换 key 后由下一次探测自动接回
429 限流 30s 固定 「太快」不等于「坏了」,不进指数阶梯
配额耗尽(insufficient_quota / 余额不足 等) 对齐配额窗口,上限 30min 不进指数阶梯、不涨失败计数,额度恢复即可用

WebUI「优先级」页的槽位健康标签会区分 冷却 / 待探测 / 探测中,悬停可看窗口 起点、探测放行时刻与完全恢复时刻。

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 的具体源。

内存占用(实测与调优)

内存不是一个定数:它随配置的源数量增长(每个源一个 http.Transport 与连接池、 一组健康状态与运行时长无关。本机实测Linux x86_6412 核):

部署形态 启动 RSS 稳态 RSS
1 源 / 1 适配器(最小配置) ~4 MB ~10 MB
1 源 + 29 MB 历史审计日志 ~19 MB ~20 MB
16 源 / 13 适配器 / 59 模型(本机生产) ~28 MB ~3742 MB

历史参考:本项优化前同一生产配置为 ~105 MB。降幅来自三处:审计日志不再全量回放 (约 25 MB、Lua 状态池不再单调增长、以及下面两个运行时开关。

内存构成(生产实例分段测量,/proc/<pid>/smaps

区域 RSS 说明
Go 堆 ~14 MB provider/registry/scheduler 结构 + 连接池缓冲
其他匹名(线程栈 / LuaJIT chunk / runtime ~12 MB 与线程数、已加载适配器数相关
二进制 text+rodata ~8 MB 映射的可执行文件页(只读、可被内核回收)
Go runtime 预留 ~4 MB VSZ 上看到的 2 GB+ 是地址空间预留,不占物理内存
共享库 ~3 MB libc / libluajit / libm

两个推荐的部署开关(只适用于环境变量,无需改代码):

# /etc/systemd/system/llmsproxy.service
Environment=GOGC=50
Environment=MALLOC_ARENA_MAX=2
  • MALLOC_ARENA_MAX=2LuaJIT 的分配走 cgo → glibc mallocglibc 默认允许 8×nproc 个 per-thread arena每个碰到 malloc 的 OS 线程会占用一个(各约 1 MB不归还给系统)。 实测从 815 个 arena约 712 MB降到 0
  • GOGC=50:把 Go 堆增长目标减半。单独使用无效(省下的堆会立即被更多 glibc arena 吃掉,实测 20.3 → 21.5 MB 反而变大),必须与 MALLOC_ARENA_MAX 配合, 两者同时开启才降 ~19%。网关是 I/O 密集型(本机 9 小时仅消耗 1min10s CPU 多出的 GC 周期与它的空闲 CPU 相比可忽略。

桌面版Electron已在 cmd/gui/main.js 里默认为内嵌核心注入这两个开关;手动 export 同名 环境变量可覆盖。如果你用自己的 systemd unit / 容器,建议照上面加上。

可选:GOMEMLIMIT=48MiB 作软上限,实测再省 ~1 MB代价是逐近上限时 GC 转激进, 温和场景不必开。

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(单二进制) 极轻量(二进制 ~8MBRSS 随源数量,单源 ~10MB、16 源 ~40MB内存占用),零依赖单进程,直接跑在 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。