mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-20 00:48:00 +00:00
- Lua adapters per upstream (transform_request/response/stream_chunk, build_headers signing hooks) - AUTO priority routing with per-model kind (chat/image), explicit source/model routing - Per-source concurrency caps with queueing, exponential backoff, AUTO failover - OpenAI-compatible API: chat completions, SSE streaming, image generations, models - Gateway key auth, web UI for adapter/source management, runtime persistence - e2e test running the real binary against mocked upstreams
139 lines
5.4 KiB
Markdown
139 lines
5.4 KiB
Markdown
# llmsproxy
|
||
|
||
统一 OpenAI 兼容网关:把多个上游 LLM 源(DeepSeek、OpenAI、Anthropic、Gemini、
|
||
Groq、Mistral、Ollama、KimiCode…)通过 **Lua 适配器** 做协议转换,对内网暴露
|
||
一个标准 `OpenAI Chat Completions` 接口(`/v1/chat/completions` + `/v1/models`),
|
||
支持单次结算与 SSE 流式。
|
||
|
||
从 [HomeAgent](https://gitcode.com/JianFeeeee/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]` 收尾。
|
||
|
||
## 快速开始
|
||
|
||
```bash
|
||
cp config.example.yaml config.yaml # 编辑你的源与 key
|
||
GOMODCACHE=... GOPROXY=off go build -o llmsproxy ./cmd/llmsproxy
|
||
./llmsproxy -config config.yaml
|
||
```
|
||
|
||
```bash
|
||
# 无 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`](config.example.yaml)。核心字段:
|
||
|
||
```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` 解析顺序:
|
||
|
||
1. `source/model` 或 `source:model` 前缀 → 指定源;
|
||
2. 精确匹配某个源的 `model`;
|
||
3. 回落到 `default_source`。
|
||
|
||
任何 OpenAI 客户端,只要 `model` 设为某个源的 `name/任意名`,即可锁定走该源。
|
||
|
||
## Lua 适配器协议
|
||
|
||
每个适配器是一个返回 table 的 Lua 脚本(`internal/lua/adapters/<name>.lua`),
|
||
加载时可被 `adapter_dir` 下的同名脚本覆盖。
|
||
|
||
```lua
|
||
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 类型
|
||
```
|
||
|
||
## 测试
|
||
|
||
```bash
|
||
go test ./...
|
||
```
|
||
|
||
覆盖:配置校验、适配器加载/变换、签名钩子、Gateway 鉴权、SDK 结算、SSE 流式、模型路由。 |