openziti/llm-gateway
GitHub: openziti/llm-gateway
一个基于 OpenZiti 零信任网络的 OpenAI 兼容代理网关,支持语义路由、负载均衡和跨 NAT 安全连接,解决 LLM 接入中的网络与路由问题。
Stars: 65 | Forks: 3
# llm-gateway
一个兼容 OpenAI 的 API 代理,可将请求路由到 OpenAI、Anthropic 以及任何兼容 OpenAI 的后端(Ollama、vLLM、llama-server、SGLang 等)。可选择通过 [zrok](https://zrok.io) 暴露网关,实现零信任访问。
## 为什么需要另一个 LLM 网关?
大多数 LLM 代理只解决 API 翻译问题。而本网关还解决了网络问题:如何将网关连接到位于 NAT 后面的 GPU 盒子,如何在不打开端口的情况下将其暴露给客户端,如何将请求路由到正确的模型——所有这些都无需额外绑定 VPN、服务网格或路由数据库。
- **基于 zrok(基于 OpenZiti)的零信任网络**——网关及其后端通过 [zrok](https://github.com/openziti/zrok) 基于 [OpenZiti](https://openziti.io) 叠加网络进行通信。无需防火墙规则或端口转发,即可跨 NAT、气隙网络或云边界暴露网关或访问后端。两个方向的工作方式相同。
- **语义路由**——三层级联(关键词启发式、嵌入相似度、LLM 分类器)在客户端省略 `model` 字段时自动选择最佳模型。无需手动维护路由表。
- **多端点负载均衡**——加权轮询、带被动故障转移的健康检查、以及跨推理服务器池的虚拟机休眠检测。适用于 Ollama、llama-server、vLLM、SGLang 或任何暴露 `/v1/chat/completions` 的服务器。专为跨真实硬件分布推理而构建。
- **单个二进制,零基础设施**——一个 Go 二进制文件,一个 YAML 文件。无需数据库、无需消息队列、无需边车。
## 功能
- **兼容 OpenAI 的 API**:可直接替代 OpenAI 客户端库
- **多提供商路由**:根据模型名称自动路由请求
- **语义路由**:可选的三层级联(启发式、嵌入、LLM 分类器),在省略 `model` 时自动选择最佳模型
- **Anthropic 翻译**:透明地将 OpenAI 格式与 Anthropic 的 Messages API 进行互转
- **流式支持**:所有提供商的服务器发送事件(SSE)流式传输
- **多端点负载均衡**:跨多个推理后端的轮询负载分配和自动故障转移
- **OpenTelemetry 指标**:通过 Prometheus 导出请求、延迟、令牌和端点健康指标
- **zrok 集成**:通过 zrok 私有或公共共享暴露网关
- **零信任后端**:通过 zrok 共享连接到任何提供商(无需暴露端口)
## 安装
适用于 Linux、macOS 和 Windows 的预构建二进制文件可在 [Releases](https://github.com/openziti/llm-gateway/releases) 页面获取。
或者使用 Go 安装:
```
go install github.com/openziti/llm-gateway/cmd/llm-gateway@latest
```
或者从源码构建:
```
git clone https://github.com/openziti/llm-gateway.git
cd llm-gateway
go install ./...
```
## 快速开始
1. 创建配置文件:
```
# config.yaml
listen: ":8080"
providers:
open_ai:
api_key: "${OPENAI_API_KEY}"
anthropic:
api_key: "${ANTHROPIC_API_KEY}"
local: # works with any OpenAI-compatible backend
base_url: "http://localhost:11434"
```
2. 运行网关:
```
export OPENAI_API_KEY="sk-..."
export ANTHROPIC_API_KEY="sk-ant-..."
llm-gateway run config.yaml
```
3. 使用任何兼容 OpenAI 的客户端发起请求:
```
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Hello!"}]
}'
```
## 提供商路由
请求根据模型名称前缀进行路由:
| 模型前缀 | 提供商 |
|----------|--------|
| `gpt-*` | OpenAI |
| `o1-*` | OpenAI |
| `o3-*` | OpenAI |
| `claude-*` | Anthropic |
| 其他所有 | 本地/自托管 |
任何不匹配 OpenAI 或 Anthropic 前缀的模型都会被路由到本地提供商。它适用于任何兼容 OpenAI 的后端——Ollama、vLLM、llama-server、SGLang 或类似软件。
### 多端点负载均衡
将请求分布到多个推理后端以实现负载均衡和弹性。当配置了 `endpoints` 时,网关使用带自动故障转移的轮询选择:
```
providers:
local:
endpoints:
- name: gpu-box-1
base_url: "http://10.0.0.1:11434"
weight: 3
- name: gpu-box-2
base_url: "http://10.0.0.2:11434"
- name: remote
zrok_share_token: "abc123"
health_check:
interval_seconds: 30 # default: 30
timeout_seconds: 5 # default: 5
```
每个端点可以使用直接 HTTP 或 zrok 共享。可选的 `weight`(默认值:1)控制端点接收的流量比例——权重为 3 的端点接收的请求大约是权重为 1 的端点的 3 倍。后台协程按配置的间隔 ping 每个端点,并将不健康的端点标记为自动跳过。请求期间的网络错误也会触发即时被动故障转移。所有使用本地提供商的网关功能(聊天完成、嵌入、分类器)都会将请求分布到端点组。
端点不需要运行相同的软件。你可以在同一个池中混合使用 Ollama、vLLM、llama-server 或任何其他兼容 OpenAI 的服务器——负载均衡层不关心 URL 后面是什么。
## API 端点
### POST /v1/chat/completions
兼容 OpenAI 的聊天完成端点。支持流式(`stream: true`)和非流式请求。当启用语义路由时,`model` 字段是可选的。
### GET /v1/models
返回所有已配置提供商的可用模型。当启用语义路由时,包含一个 `auto` 虚拟模型,用于触发自动模型选择。在多端点模式下,返回所有健康端点中已去重的模型合集。
### GET /health
返回 `{"status":"ok"}` 及 HTTP 200。用于存活检查。
### GET /metrics
Prometheus 指标端点(当 `metrics.enabled: true` 时)。暴露请求计数、持续时间直方图、令牌计数器、路由决策、提供商错误、进行中请求仪表以及端点健康状态。
## 配置
### 完整配置示例
```
listen: ":8080"
zrok:
share:
enabled: false
mode: private # public or private
token: "" # use existing persistent share (private only)
providers:
open_ai:
api_key: "${OPENAI_API_KEY}"
base_url: "" # optional: override for Azure or compatible APIs
zrok_share_token: "" # optional: connect via zrok share
anthropic:
api_key: "${ANTHROPIC_API_KEY}"
base_url: "" # optional: override base URL
zrok_share_token: "" # optional: connect via zrok share
local:
base_url: "http://localhost:11434"
zrok_share_token: "" # optional: connect via zrok share
# or use multi-endpoint mode for round-robin + failover:
# endpoints:
# - name: gpu-box-1
# base_url: "http://10.0.0.1:11434"
# - name: gpu-box-2
# base_url: "http://10.0.0.2:11434"
# - name: remote
# zrok_share_token: "abc123"
# health_check:
# interval_seconds: 30
# timeout_seconds: 5
metrics:
enabled: false
```
### 环境变量
API 密钥支持使用 `${VAR}` 语法进行环境变量扩展。
## API 密钥
网关支持用于客户端身份验证的虚拟 API 密钥。生成密钥并将其添加到配置中:
```
llm-gateway genkey
# sk-gw-a1b2c3d4e5f6...
```
```
api_keys:
enabled: true
keys:
- name: alice
key: "sk-gw-a1b2c3d4e5f6..."
allowed_models: ["*"]
```
客户端通过 `Authorization: Bearer ` 标头发送密钥。`/health` 和 `/metrics` 端点保持无需身份验证。密钥可以通过 glob 模式限制访问特定模型。详情请参阅 [docs/api-keys.md](docs/api-keys.md)。
## 语义路由
配置后,网关可以根据请求内容自动选择最佳模型。`model` 字段变为可选——如果省略,请求将通过三层级联:
1. **启发式**——快速的关键词/模式匹配(例如 "translate" -> 快速模型,工具使用 -> 支持工具的模型)
2. **嵌入**——使用 Ollama 或 OpenAI 嵌入计算用户提示与路由示例之间的余弦相似度
3. **LLM 分类器**——当嵌入结果不明确时,请求 LLM 对请求进行分类
每一层都可以独立启用或禁用——例如,可以在不使用嵌入的情况下使用分类器。如果所有层都跳过或没有明确结论,则使用配置的默认路由。当语义路由被禁用或未配置时,行为不变。
### 语义路由配置
```
routing:
allow_explicit_model: true # clients can still specify a model directly
default_route: general # fallback when no layer matches
heuristics:
enabled: true
rules:
- match:
keywords: ["translate", "translation"]
route: fast
- match:
has_tools: true
route: tools
- match:
system_prompt_contains: "you are a code assistant"
route: coding
- match:
max_tokens_lt: 100
message_length_lt: 200
route: fast
semantic:
enabled: true
provider: local # local or openai
model: nomic-embed-text
threshold: 0.82 # confident match
ambiguous_threshold: 0.65 # escalate to classifier
comparison: centroid # centroid, max, or average
classifier:
enabled: true
provider: local
model: llama3
timeout_ms: 5000
confidence_threshold: 0.7
routes:
- name: coding
model: claude-sonnet-4-20250514
description: "code generation, debugging, and technical tasks"
examples:
- "write a python function to sort a list"
- "debug this segfault in my C code"
- name: creative
model: claude-sonnet-4-20250514
description: "creative writing, storytelling, and artistic content"
examples:
- "write a poem about the ocean"
- "tell me a story about a dragon"
- name: fast
model: llama3
description: "simple tasks, translations, and short responses"
examples:
- "translate hello to French"
- "what is 2+2"
- name: tools
model: gpt-4
description: "tasks requiring tool use and function calling"
examples:
- "search the web for recent news"
- "call the weather API for New York"
- name: general
model: llama3
description: "general conversation and miscellaneous tasks"
examples:
- "what is the capital of France"
- "explain quantum computing"
```
### 启发式匹配条件
每个启发式规则都有一个 `match` 块,包含一个或多个条件。当规则中指定多个条件时,所有条件都必须匹配(AND 逻辑)。在 `keywords` 内,任何匹配的关键词都会触发规则(OR 逻辑)。可用的条件:
| 条件 | 描述 |
|------|------|
| `keywords` | 对用户消息内容进行不区分大小写的单词边界匹配 |
| `has_tools` | 匹配请求是否包含/缺少工具定义 |
| `system_prompt_contains` | 对系统消息进行子串匹配 |
| `max_tokens_lt` | 如果 `max_tokens` 低于某个阈值则匹配 |
| `message_length_lt` | 如果消息总字符长度低于某个阈值则匹配 |
| `exclude` | 如果在用户消息中发现某些短语,则抑制关键词匹配 |
### 发送不带模型的请求
启用语义路由后,可以省略 `model` 字段:
```
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"messages": [{"role": "user", "content": "Write a Python function to sort a list"}]
}'
```
### 与聊天客户端(Open WebUI 等)一起使用
像 Open WebUI 这样的聊天客户端需要从模型列表中选择一个模型——它们总是发送 `model` 字段。当启用语义路由时,网关会暴露一个虚拟的 `auto` 模型,用于触发自动路由:
```
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "auto",
"messages": [{"role": "user", "content": "Write a Python function to sort a list"}]
}'
```
将客户端指向 `http://localhost:8080/v1`,然后从模型列表中选择 `auto`。网关会将每个请求通过语义路由级联进行路由。
网关会记录每个路由决策,包括使用的方法、置信度分数、延迟和级联追踪信息。
## 指标
使用 Prometheus 导出器启用 OpenTelemetry 指标:
```
metrics:
enabled: true
```
启用后,网关会在相同的监听地址上通过 `GET /metrics` 提供 Prometheus 指标。可用指标:
| 指标 | 类型 | 描述 |
|------|------|------|
| `llm_gateway.requests` | Counter | 总请求数(按提供商、模型、流式传输) |
| `llm_gateway.request.duration` | Histogram | 请求持续时间(秒)(按提供商、模型) |
| `llm_gateway.tokens.prompt` | Counter | 总提示令牌数(按提供商、模型) |
| `llm_gateway.tokens.completion` | Counter | 总完成令牌数(按提供商、模型) |
| `llm_gateway.routing.decisions` | Counter | 语义路由决策(按方法) |
| `llm_gateway.provider.errors` | Counter | 提供商错误(按 error_type) |
| `llm_gateway.requests.inflight` | Gauge | 当前正在进行的请求数 |
| `llm_gateway.endpoint.healthy` | Gauge | 端点健康状态 |
## 跟踪
启用请求体日志记录以调试路由决策:
```
tracing:
enabled: true
max_content_length: 200 # max characters per message in log output
```
每个聊天完成请求都会记录模型、消息数量、流式标志、工具数量以及每条消息的角色和截断后的内容。详情请参阅 [docs/configuration.md](docs/configuration.md)。
## CLI 参考
```
llm-gateway run
Flags:
--address string listen address (overrides config)
--zrok enable zrok sharing (overrides config)
--zrok-mode string zrok share mode: public, private (overrides config)
```
## zrok 集成
### 暴露网关
启用 zrok 共享以在不打开防火墙端口的情况下暴露网关:
```
zrok:
share:
enabled: true
mode: private
```
或通过 CLI:
```
llm-gateway run config.yaml --zrok --zrok-mode private
```
网关会在启动时记录共享令牌。客户端使用 zrok 访问进行连接。
### 使用持久共享
为了在重启后保持稳定的共享令牌,请使用 zrok CLI 创建持久共享并引用它:
```
zrok:
share:
enabled: true
token: "abc123xyz" # your persistent share token
```
### 通过 zrok 连接后端
任何提供商都可以通过 zrok 共享而不是直接 URL 进行访问:
```
providers:
open_ai:
api_key: "${OPENAI_API_KEY}"
zrok_share_token: "openai-proxy-share-token"
anthropic:
api_key: "${ANTHROPIC_API_KEY}"
zrok_share_token: "anthropic-proxy-share-token"
local:
zrok_share_token: "ollama-share-token"
```
## 示例
### 使用 Python OpenAI 客户端
```
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8080/v1",
api_key="not-needed" # gateway handles auth
)
# 路由到 OpenAI
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}]
)
# 路由到 Anthropic(自动翻译)
response = client.chat.completions.create(
model="claude-sonnet-4-20250514",
messages=[{"role": "user", "content": "Hello!"}]
)
# 路由到本地后端(Ollama, vLLM等)
response = client.chat.completions.create(
model="llama3.2",
messages=[{"role": "user", "content": "Hello!"}]
)
```
### 流式传输
```
stream = client.chat.completions.create(
model="claude-sonnet-4-20250514",
messages=[{"role": "user", "content": "Write a haiku"}],
stream=True
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
```
## 文档
- [入门指南](docs/getting-started.md) —— 逐步设置教程
- [配置](docs/configuration.md) —— 完整配置参考和 CLI 标志
- [提供商](docs/providers.md) —— 提供商详细信息、流式传输和错误处理
- [语义路由](docs/semantic-routing.md) —— 阈值调整、比较模式和缓存
- [API 密钥](docs/api-keys.md) —— 每个密钥的模型和路由限制
- [多端点负载均衡](docs/multi-endpoint.md) —— 加权负载均衡和故障转移
- [指标](docs/metrics.md) —— Prometheus 仪器和示例查询
- [流式传输](docs/streaming.md) —— SSE 流式传输详细信息
- [zrok](docs/zrok.md) —— 用于共享和访问的覆盖网络
## 许可证
Apache 2.0
标签:AI代理, AI风险缓解, Anthropic, API代理, CIS基准, EVTX分析, Go, JSONLines, LLM网关, LLM评估, NAT穿透, Ollama, OpenAI兼容, OpenZiti, Petitpotam, Ruby工具, vLLM, YAML, zrok, 健康检查, 单二进制部署, 多模型路由, 安全库, 开源, 推理基础设施, 故障转移, 无基础设施依赖, 日志审计, 用户代理, 端到端加密, 网络隧道, 自定义请求头, 虚拟API密钥, 语义路由, 负载均衡, 零信任