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密钥, 语义路由, 负载均衡, 零信任