ModelRouter
面向多 llms 订阅者,部署在内网,实现一次配置多个服务共同使用的效果。支持指定模型或 auto 模式,按照配置的优先级选择可用模型提供服务。
实现
统一 OpenAI 兼容网关:把多个上游 LLM 源(DeepSeek、OpenAI、Anthropic、Gemini、
Groq、Mistral、Ollama、KimiCode…)通过 Lua 适配器 做协议转换,对内网暴露
一个标准 OpenAI Chat Completions 接口(/v1/chat/completions + /v1/models),
支持单次结算与 SSE 流式。
从 HomeAgent 的多源 LLM 适配层
(internal/agent/api/provider.go + internal/lua/adapters/*)抽离而来并独立演进。
特性
- 多源:一个进程内配置任意多个上游源,按请求的
model自动路由。 - 统一输出:所有源对外都是 OpenAI 格式(含
reasoning_content、tool_calls、usage)。 - 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等签名辅助。 - 鉴权:网关自身用
gateway_keys校验客户端 Bearer key;与上游各自的 key 相互独立。 - 流式:SSE
chat.completion.chunk,含角色首包与[DONE]收尾。
快速开始
cp config.example.yaml config.yaml # 编辑你的源与 key
GOMODCACHE=... GOPROXY=off go build -o llmsproxy ./cmd/llmsproxy
./llmsproxy -config config.yaml
# 无 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_key 用 gateway_keys 之一即可。
配置
见 config.example.yaml。核心字段:
listen: 127.0.0.1:8080 # 网关监听地址(建议绑内网/回环)
gateway_keys: [sk-gw-0001] # 客户端访问网关的 key;留空=不鉴权
default_source: deepseek # model 无法路由时回落的源
adapter_dir: adapters # Lua 适配目录,首启自动写入内置适配器
sources:
- name: deepseek
base_url: https://api.deepseek.com
api_key: sk-...
model: deepseek-v4-flash
adapter: deepseek
# 静态请求头(比适配器默认优先)
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/completions 的 model 解析顺序:
source/model或source:model前缀 → 指定源;- 精确匹配某个源的
model; - 回落到
default_source。
任何 OpenAI 客户端,只要 model 设为某个源的 name/任意名,即可锁定走该源。
Lua 适配器协议
每个适配器是一个返回 table 的 Lua 脚本(internal/lua/adapters/<name>.lua),
加载时可被 adapter_dir 下的同名脚本覆盖。
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/decode、log(level, msg)。
内置适配器
openai deepseek anthropic gemini github groq mistral ollama kimicode。
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 # Lua VM + AdapterCache + 内置适配器 (embed)
internal/provider # Provider(HTTP) + Registry(路由)
internal/gateway # OpenAI 兼容 HTTP 服务 + 鉴权 + SDK/流式
internal/types # 统一格式 & OpenAI wire 类型
测试
go test ./...
覆盖:配置校验、适配器加载/变换、签名钩子、Gateway 鉴权、SDK 结算、SSE 流式、模型路由。