Files
ModelRouter/README.md

9.8 KiB
Raw Blame History

ModelRouter

English: README_EN.md

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

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

实现

统一 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 时按各源模型的 priority 自动选最高可用源。
  • 统一输出:所有源对外都是 OpenAI 格式(含 reasoning_contenttool_callsusage)。
  • 多模态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:内置管理界面,可在线查看/新增/编辑上游源与模型,写入运行时文件持久化。
  • 鉴权:网关自身用 gateway_keys 校验客户端 Bearer key与上游各自的 key 相互独立。
  • 流式SSE chat.completion.chunk,含角色首包与 [DONE] 收尾。

快速开始

cp config.example.yaml config.yaml   # 编辑你的源与 key
GOMODCACHE=... GOPROXY=off go build -tags luajit -o llmsproxy ./cmd/llmsproxy
./llmsproxy -config config.yaml

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

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

# 单次
curl -H "Authorization: Bearer sk-gw-local-0001" \
     -d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"hi"}]}' \
     http://127.0.0.1:8080/v1/chat/completions

# 流式
curl -N -H "Authorization: Bearer sk-gw-local-0001" \
     -d '{"model":"deepseek-v4-flash","stream":true,"messages":[{"role":"user","content":"hi"}]}' \
     http://127.0.0.1:8080/v1/chat/completions

任何 OpenAI SDK 把 base_url 指到网关地址、api_keygateway_keys 之一即可。

配置

config.example.yaml。核心字段:

listen: 127.0.0.1:8080        # 网关监听地址(建议绑内网/回环)
gateway_keys: [sk-gw-0001]    # 客户端访问网关的 key留空=不鉴权
default_model: AUTO           # model 无法路由时自动按 priority 选源
adapter_dir: adapters         # Lua 适配目录,首启自动写入内置适配器
runtime_file: runtime.json    # WebUI 编辑的源持久化到此文件

sources:
  - name: deepseek
    base_url: https://api.deepseek.com
    api_key: sk-...
    adapter: deepseek
    max_concurrent: 8
    models:
      - id: deepseek-v4-flash
        priority: 100          # 越大越优先被 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

模型路由

/v1/chat/completionsmodel 解析顺序:

  1. source/modelsource:model 前缀 → 指定源;
  2. 精确匹配某个源的 model
  3. 配置 default_model: AUTO 时 → 按各源模型的 priority(数字大优先)选最高可用源;
  4. 否则回落到 default_source

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

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 适配器"都即时生效(写入运行时文件或 adapter_dir 后重新装配,无需重启);直接编辑 config.yaml / adapter_dir 下的文件则需要重启进程才会重新加载。

disable_thinking

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

WebUI

内置管理界面(GET /),登录后可在浏览器查看/新增/编辑上游源与模型, 改动写入 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} 即可。

目录

cmd/llmsproxy            # 入口
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。