内容
# LLM 网关
**所有 LLM 后端只有一个终点——同时使它们具有代理功能。** LLM 网关是一个与 OpenAI-API 兼容的反向代理:任何 OpenAI SDK 都可以指向它,它会路由到您的自托管和云模型(健康检查、故障转移、稳定别名),然后在**完成中**运行工具——网络搜索、代码沙箱、文档渲染、RAG、每个用户 MCP 连接器——因此普通客户端在没有任何代理代码的情况下获得工具使用。团队准备就绪,具有 OIDC 登录、每个用户令牌和 RBAC,以及内置的聊天 UI,适用于不使用 curl 的人员。作为单个自托管二进制文件(Rust、SQLite)交付——无需 compose 文件,无需向量 DB,无需单独的前端。

## 目录
- [功能介绍](#功能介绍)
- [模型可以调用的工具](#模型可以调用的工具)
- [内置 Web UI](#内置-web-ui)
- [定时操作](#定时操作)
- [对话压缩](#对话压缩)
- [语音对话](#语音对话)
- [集成(每个用户 MCP 连接器)](#集成每个用户-mcp连接器)
- [快速入门(本地开发)](#快速入门本地开发)
- [配置](#配置)
- [聊天附件(S3)](#聊天附件s3)
- [RAG(代码库搜索)](#rag代码库搜索)
- [代理技能](#代理技能)
- [使用网关](#使用网关)
- [HTTP 端点](#http端点)
- [生产部署(容器 + systemd)](#生产部署容器--systemd)
- [Docker Compose](#docker-compose)
- [文档](#文档)
- [贡献](#贡献)
- [许可证](#许可证)
## 功能介绍
- **与 OpenAI 兼容的 API** —— `POST /v1/chat/completions`(流式 + 非流式),`POST /v1/embeddings`,`POST /v1/images/generations` + `POST /v1/images/edits`,`POST /v1/audio/transcriptions`,`POST /v1/audio/speech`(文本转语音,当配置了语音池时),以及 `GET /v1/models`。任何 OpenAI SDK 都可以指向它。
- **多后端路由** —— 命名上游池(`chat` / `transcription` / `embedding` / `image` / `speech` 类型)。每个池根据后端进行轮询(轮询或最少飞行),具有每个后端的健康探测。模型从每个后端的 `/models` 端点动态发现,因此加载后端上的模型使其可路由,无需配置更改。
- **模型别名 + 故障转移** —— 为客户端提供稳定的名称(`alias = ["qwen"]` 在后端),路由到实际加载的模型,因此交换模型无需客户端更改;同一个别名在多个后端是一个负载均衡组。可选的故障转移涵盖未知模型名称(`[fallback].<kind>`)或已知模型的所有后端都已关闭(`fallback_offline`)。参见 [`docs/upstreams.md`](docs/upstreams.md#model-aliases)。
- **OIDC 登录** —— 浏览器登录您的身份提供者;网关然后颁发自己的 `gwk_…` API 令牌。提供者机密仅来自环境。
- **每个用户令牌 + RBAC** —— 令牌在存储时使用 SHA-256 哈希,并且可撤销。角色(从 OIDC 声明映射)控制每个用户可以使用的模型和服务器端工具。
- **使用情况核算、速率限制和配额** —— 每个调用都按用户/令牌/模型计量(请求、令牌和 - 带有每个模型价格 - 支出),在 `/usage` 上显示。在 `/admin/limits` 设置硬速率限制和配额(请求/令牌/成本,超过滚动小时/天/周/月),范围限定为全局、每个角色或每个用户;超出预算的调用者会收到 `429`。自托管池可以标记 `enforce_limits = false`,因此它们的用法仍然记录并在 `/usage` 上显示,但永远不会计入限制或配额。
- **服务器端工具** —— 网关在**完成中**运行工具(网络搜索、URL 获取、文档渲染、代码执行、RAG、网络查找等);客户端只看到一个正常的完成。完整列表参见 [模型可以调用的工具](#模型可以调用的工具)。
- **聊天 UI** —— 一个服务器渲染的、移动友好的聊天界面,位于 `/chat`,具有持久的多对话历史、令牌流式传输、文件附件、语音口述、共享/导出对话以及恢复连接(每次对话都写入 SQLite)。
- **RAG** —— 操作员管理的、索引的代码库,聊天模型可以搜索。
- **代理技能** —— 将 `SKILL.md` 包(或 `.skill` 存档)放入,聊天模型按需加载以遵循您的房屋风格、品牌或领域操作手册——渐进披露,无需微调。在 `/admin/skills` 上传、查看和删除它们,实时生效(无需重启),按角色 RBAC 门控。参见 [代理技能](#代理技能)。
- **定时操作** —— 用户特定的提示,按 cron 时间表运行(每小时/每天/每周/每月,或原始 cron 表达式),每个提示在自己的时区评估。友好型构建器组装 cron 并实时显示下一个运行时间;每次运行都会打开一个聊天,您可以在 UI 中查看历史记录——一个全新的聊天记录,或(可选)继续上一次运行的对话历史记录。参见 [定时操作](#定时操作)。
- **集成(每个用户 MCP 连接器)** —— 管理员策划的 [MCP](https://modelcontextprotocol.io/) 服务器目录(Google Workspace、GitHub、Atlassian、GitLab、...),每个用户使用自己的帐户连接到 `/integrations`。OAuth(支持动态客户端注册)或用户提供的令牌;令牌在存储时加密,并在后台刷新。连接服务器的工具然后可用于模型,范围限定为该用户的权限。参见 [集成](#集成每个用户-mcp连接器)。
## 模型可以调用的工具
这是大多数“OpenAI 兼容代理”项目所没有的部分。网关可以在**服务器端、在完成中**执行工具——模型请求搜索网络、读取您附加的 PDF、渲染品牌 PDF、运行代码沙箱或查询索引代码库——网关运行它、反馈结果,客户端只接收一个普通的完成,带有已完成的答案。通过原始 `/v1/chat/completions` API 和内置聊天 UI,工作原理相同。
每个工具都**按角色 RBAC 门控**,每个用户可以在 `/tools` 页面开启和关闭自己的权限:

| 类别 | 工具 | 模型可以执行的操作 |
|---|---|---|
| **网络和检索** | `search_web`, `fetch_url`, `wikipedia` | 搜索网络(SearXNG 或 Brave),获取任意 URL(文本 → UTF-8,图像 → 可视化,其他二进制 → 元数据),并获取百科全书摘要。 |
| **文档** | `fetch_attachment`, `upload_attachment`, `typst_*` | 读取用户附加的文件——包括**两层 PDF** 读取(提取文本层;如果为空,则为视觉模型栅格化扫描页面)——将文件附加到其自己的回复中,并从操作员定义的 Typst 模板(发票、信件、报告)渲染 **PDF/PNG 文档**。 |
| **文档画布** | `create_document`, `edit_document`, … | 构建一个长文档(报告、规格、文章),跨多个回合编辑,并在实时侧面板中逐节编辑,然后导出为 PDF/DOCX/PPTX——而不是每次回复都重新生成整个文档。参见 [`docs/file-conversions.md`](docs/file-conversions.md)。 |
| **图像** | `generate_image`, `edit_image` | 从文本提示生成图像(图表、模型、营销视觉效果),并在后端支持的情况下编辑现有图像(图像到图像)——在回复中内联渲染。路由到 `image` 类型的上游池(任何 OpenAI `/images/*` 兼容后端:托管提供商或自托管模型)。`edit_image` 仅在后端广告支持时出现,并拒绝不合规的后端。 |
| **代码和沙箱** *(需选择加入)* | `run_in_sandbox`, `generate_document`, `convert_document`, `capture_webpage`, `render_typst`, … | 在隔离的单次使用 VM 中运行 Python/shell(数据处理、格式转换、绘图),将 Markdown 转换为 PDF/DOCX/PPTX,转换办公/PDF/图像格式,截图网页,并渲染 Typst 或 Excalidraw。由 `[sandbox]` 块启用——参见 [`docs/sandbox.md`](docs/sandbox.md) 和 [`docs/file-conversions.md`](docs/file-conversions.md)。 |
| **内存** | `remember`, `recall` | 持久化关于用户的信息(偏好、项目),并在后续对话中回忆。 |
| **网络和运维** | `dns_lookup`, `whois_lookup`, `tls_cert`, `lookup_ip` | DNS-over-HTTPS 记录、RDAP 域名注册、TLS 证书检查(“此证书即将过期?”),以及任意 IP 或主机名的 GeoIP。 |
| **位置** | `get_user_location` | 使用上下文中始终存在的基于 IP 的近似位置,或在任务需要时向浏览器请求精确的 GPS。 |
| **实用工具** | `convert_currency`, `get_current_timestamp`, `company_echo` | 按每日 ECB 汇率转换货币,获取时区感知当前时间,并逐字回声消息(`company_echo` 是工具调用循环的内置烟雾测试)。 |
| **知识库** | `rag_list_collections`, `rag_search` | 搜索操作员索引的代码库/语料库,并获取带有文件路径、行范围和分数的匹配块。 |
| **集成** | `mcp__<server>__*` | 调用任何桥接的 [MCP](https://modelcontextprotocol.io/) 服务器。每个服务器的工具都按命名空间命名,以免冲突。 |
| **技能** | `read_skill` | 加载操作员安装的 [技能](#代理技能)——品牌指南、房屋样式、领域操作手册——然后应用它:提取 `SKILL.md`,然后任何引用的资产(例如内联 SVG)。 |
**工具自行开启**。工具默认关闭以保持模型的工具列表简短——简短的列表更便宜,模型更准确地选择工具。当请求需要模型当前不具备的功能时,它会调用内置的 `enable_tools` 工具来开启相关工具;其真实模式出现在下一回合并保持开启,直到对话结束。因此,模型在需要时准确获取所需工具,不需要操作员为每个对话配置工具列表——所有这些都受用户角色的限制。

## 内置 Web UI
除了 `/chat` 之外,网关还附带一个小的操作员和帐户 UI——无需单独部署仪表盘。管理员页面仅对 `admin` 角色开放。
UI 完全本地化——英语、德语、法语、西班牙语、俄语和中文。从侧边栏的旗帜图标(或登录页面)切换语言;选择存储在 cookie 中,因此立即生效并在会话之间持续。
| | |
|---|---|
|  |  |
| **后端** (`/admin/backends`) — 每个上游池的实时健康状况、飞行中负载和发现的模型。 | **RAG** (`/rag`) — 从 git URL 索引代码库并观察其状态从 *待定* 变为 *就绪*。 |
还有 `/tokens`(创建、旋转和撤销您的 `gwk_…` API 令牌——以及每个令牌的工具使用范围),`/usage`(您自己的请求/令牌使用情况,以及按模型价格计算的支出),`/memory`(查看和编辑助手为您记住的内容),`/scheduled`(按 cron 时间表运行的提示——参见 [定时操作](#定时操作)),`/admin/models`(服务器范围的采样默认值、每个模型的推理预算、每个模型的上下文窗口驱动 [对话压缩](#对话压缩),每个模型的 **价格**(输入/输出每 1M 令牌),在 `/usage` 上将令牌使用转换为支出,以及聊天、语音、图像生成和 RAG 嵌入选择器的每个功能 **默认模型**),`/admin/limits`(速率限制和配额——参见下文),以及 `/admin/users`(注册用户及其解析的角色)。用户页面还可以让管理员 **模拟** 另一个用户进行调试——每次模拟都经过审计并显示持久的横幅。模拟是**可选加入**的:除非您设置 `[gateway].allow_impersonation = true`(默认为 `false`),否则按钮隐藏且 `POST /admin/users/impersonate` 返回 403。
| | |
|---|---|
|  |  |
| **API 令牌** (`/tokens`) — 创建、旋转、撤销和每个令牌的工具范围。 | **使用情况** (`/usage`) — 您自己的请求/令牌量,按后端、源和模型分类。 |

**速率限制和配额**。在 `/admin/limits`,限制调用者每滚动 **小时/天/周/月** 可以使用的 **请求**、**令牌** 或 **支出** 数量——范围限定为 **全局**、**按角色** 或 **按用户**。规则按最具体优先(用户 → 最慷慨的角色 → 全局默认);如果未配置任何规则,则所有用户无限。用户的整个预算在他们的 API 令牌、聊天和定时运行之间共享,如果超出预算,调用者会收到 `429`(或聊天中的温和通知)。自托管池设置 `enforce_limits = false`,因此它们的用法仍在 `/usage` 上记录,但永远不会计入限制或配额。每个用户在 `/usage` 上看到自己的实时限制条。
| | |
|---|---|
|  |  |
| **速率限制编辑器** (`/admin/limits`) — 全局/角色/用户请求、令牌和成本的规则。 | **您的限制** (`/usage`) — 显示的实时进度条显示已用限制与重置时间,以及支出(一旦模型定价)。 |
`/chat` 页面本身的功能不仅仅是流式传输回复:**分叉** 对话、通过公共链接 **分享**、**固定** 收藏夹、导出为 Markdown 或 PDF、**编辑和重试** 回话回合、使用语音按钮 **口述**,以及设置 **每个对话的推理努力**。参见 [`docs/ui.md`](docs/ui.md)。
**移动版**。整个 UI 是响应式的——在手机上,侧边栏折叠成汉堡菜单,聊天、撰写器和管理员页面重新布局为单列,因此网关可以从浏览器在移动设备上完全使用。
| | |
|---|---|
|  |  |
## 预定操作
每个已登录的用户都可以在 `/scheduled` 页面安排提示**自动按计划运行**,例如每日站会摘要、每周仓库总结、每小时健康检查。每个预定操作都只是一个保存的提示加上一个模型、一个计划和一个时区;当它触发时,网关会打开一个聊天会话,由与交互式 `/chat` 页面相同的引擎驱动,因此结果作为一个普通的对话出现,您可以打开并阅读。默认情况下,每个运行都开始一个新的**对话**;打开**重用**后,每个运行继续上一次运行的聊天 — 重放最后几轮的历史记录 — 因此模型会基于上一次的回答继续。计划按用户划分且私有(按用户范围划分,位于正常的会话登录后面 — 不需要管理员角色)。

**计划构建器。** 选择**每小时**、**每日**、**每周**、**每月**或**高级**。友好模式只显示必要的字段(分钟;时间;星期几复选框;一个月中的日期)并且网关会从中组装一个标准的 5 个字段的 cron 表达式 — 非技术用户无需了解 cron。**高级** 使用原始的 `minute hour day-of-month month day-of-week` 表达式,用于表达预设无法表达的内容。无论哪种方式,表达式都在您选择的 **IANA 时区**(例如 `Europe/Berlin`)中评估,并且实时预览 — 通过 `POST /scheduled/preview` 在服务器端计算,因此它不会与调度器实际执行的内容有偏差 — 显示一个纯英文摘要和**下三次运行时间**。每个操作还有一个工具切换器(网络搜索、RAG、附件 — 与聊天中的相同工具集)。
**运行如何触发。** 一个后台工作线程每 30 秒轮询一次,并运行每个到期的操作,首先原子性地声明该操作,以防止慢速运行或重启导致重复触发。如果网关在一个或多个计划时段关闭,错过的事件会**合并成单个补丁运行**,而不是重放成一个积压的日志,而是在启动后第一次轮询时触发。操作可以**暂停**(工作线程跳过它们),恢复、编辑或删除。
## Webhooks
预定操作的事件驱动孪生:不是时钟,而是**传入的 HTTP 调用**触发运行。在 `/webhooks` 页面中,已登录的用户保存一个提示和一个模型,获得一个秘密触发 URL(`/hooks/gwh_…`),并将任何外部服务指向它 — CI 管道、GitHub 或 Discord Webhook、监控警报、表单处理程序或快速 `curl` 命令。当有东西调用 URL 时,网关将调用者发送的请求体中的**任何内容**附加到保存的提示上作为一个清晰的**不受信任**块,然后通过与 `/chat` 相同的引擎运行它,因此结果作为一个普通的对话出现,您可以稍后打开并阅读。
**同步或异步。** 一个 webhook 复选框选择行为:一个**异步** webhook 立即返回 `202 Accepted` 并在后台运行;一个**同步** webhook 使调用者等待并以 JSON 信封(`{"status","session_id","output"}`)返回模型的答案 — 方便需要内联回复的集成。
**新聊天或重用。** 像预定操作一样,webhook 要么打开一个**每次触发的新聊天**(默认),要么**重用**上一次触发的聊天,以便模型将之前的触发视为历史记录(一个运行日志,一个滚动摘要)— 带有重放轮次上限,以便上下文不会无限制增长。
**运行历史记录。** 每次触发 — 以及每次重运行 — 都被记录下来。每个 webhook 都有一个**运行** 页面,列出其最近的运行记录(最多 50 条),每次运行显示触发时间、是否成功以及一个链接到**其生成的聊天** 以查看完整详细信息。从那里,您可以**重运行任何过去的运行**:其确切的有效载荷被重放,并带有您可以调整的提示,进入一个您可以实时观看的新聊天。因此,您可以在不必要求外部服务重新发送任何内容的情况下迭代提示。
**安全性。** URL 中的密钥是凭证 — 只有其哈希值被存储,因此完整的 URL 只显示**一次**在创建时(旋转以生成一个新的;旧 URL 立即停止工作)。工具默认**关闭**:因为 webhook 由匿名外部调用者触发,向模型提供攻击者可控的文本,因此授予它您的工具(网络搜索、RAG、连接器)是一种深思熟虑的、有警告的选项。Webhook 按用户划分且私有,可以在同一页面暂停、编辑、旋转或删除。(速率限制和配额在所有请求表面上单独处理。)
## 会话压缩
聊天会为模型重放整个历史记录,因此长时间聊天的提示会增长,直到它占据模型的上下文窗口。网关会**自动压缩**:当一个回合的测量提示大小(上游自己的 `prompt_tokens`)超过模型上下文窗口的一小部分时,一个后台任务 — 在回合的关键路径之外,例如标题生成 — 将最老的回合总结成一个密集的摘要。下一个回合然后重放 `[请求上下文] + [摘要] + [最近的回合原文]` 而不是完整的历史记录。随着对话的继续增长,它会**重新压缩**:前一个摘要加上老化的新回合会折叠成一个新的摘要,因此上下文在任意长的聊天中保持有界。
UI 中不会丢失任何内容 — 总结的回合仍然保留在记录中,可以滚动到“更早的消息已压缩”分隔符上方;它们只是不会被发送到上游。折叠回合的工具结果会输入到总结器中(它们永远不会像正常历史记录一样重放,但通常是承载上下文的)。
调优位于 `[chat.compaction]`(均为可选):`enabled`(默认 `true`),`trigger_ratio`(触发时达到的窗口分数,默认 `0.7`),`default_context_window`(没有每个模型值的模型的回退窗口,默认 `32768`),`keep_recent_turns`(保留多少个最近的回合原文,默认 `6`),`min_turns_to_compact`(防振荡底线,默认 `4`),以及 `summary_max_tokens`(默认 `1024`)。每个模型的上下文窗口在 `/admin/models` 中设置;空白字段会回退到 `default_context_window`。
## 语音对话
与助手对话并听取其回答。语音模式是一种**管道** — 网关不是 AI,它提供了一个访问 AI 的接口:您的语音被转录(Voxtral,现有的转录池),发送到带有**语音指令**的普通聊天模型,该指令将回复限制在一句或两句口语中,然后通过您配置的**文本转语音池**以语音回复。每次交换都作为一个普通的聊天回合保存在纯文本中,因此您可以随时滚动回去阅读(或继续以文本方式)。
它出现在聊天编辑器中**仅当配置了 `speech` 上游池**并且有语音模型可用时 — 否则切换按钮直接不存在(像转录一样,它会逐渐失效)。与所有其他功能相同的访问层:TTS 也在 `POST /v1/audio/speech` 处暴露给 API 调用者。
**配置它** 通过使用 `kind = "speech"` 添加一个池 — 自托管(Qwen3-TTS、Kokoro、XTTS via openedai-speech、LocalAI)或云(**OpenAI** `api.openai.com/v1`,或任何支持 OpenAI 的 `/v1/audio/speech` 的提供商)。一个可选的 `[upstream_pools.<name>.voices]` 映射为每个口语语言选择一个语音(`de`、`en`、…;`""` 键是默认值)。在非欧盟池上标记 `compliance`(例如 OpenAI);语音模式会将口语文本发送到那里。请参阅 `gateway.example.toml`。
**它是如何工作的:** 点击并保持(按住麦克风)→ 松开 → 转录被提交并带有语音指令 → 随着回复流出,完整的句子被逐一口语化。不可口语的部分(代码、表格)变成一个简短的口语标记,如“代码显示在屏幕上”。它是**半双工**的 — 当助手说话时,麦克风是惰性的(没有回音)。回复的语言遵循您**说**的语言;只有开场问候语使用 UI 语言。始终监听(语音活动)模式和插入模式是计划中的下一个阶段。

- **Rust** (2024 版,工具链锁定到 1.95 via [mise](https://mise.jdx.dev/))— 一个包含 5 个 crate 的工作区:`gateway`、`session-core`、`cli`、`shared` 和 `sandbox-runner`。
- **[rama 0.3.0-rc1](https://ramaproxy.org/)** — HTTP 服务器、路由器、中间件和代理。
- **[plait](https://github.com/devashishdxt/plait)** — 类型检查的自动转义服务器端 HTML(`html! { … }`)。
- **[datastar](https://data-star.dev/)** — 客户端反应性通过 SSE,自托管二进制文件。
- **[daisyUI v5](https://daisyui.com/) + Tailwind v4** — 设计系统,编译为单个 CSS 文件在构建时。
- **sqlx + SQLite** — 持久状态(用户、令牌、会话、聊天历史记录、RAG 集合注册表)。批量 RAG 内容 — 块文本、词法索引、向量 — 存放在 `[rag].data_dir` 下的每个集合存储中,而不是主数据库中。
CSS 包和 `datastar.js` 通过 `include_bytes!` 嵌入二进制文件,因此运行时镜像不需要资产目录。
## 集成(每个用户 MCP 连接器)
每个已登录的用户都可以连接他们**自己的**帐户 — Gmail/Calendar/Drive、GitHub、Atlassian(Jira/Confluence)、GitLab、Slack、Kiwi.com 航班搜索等任何其他 [MCP](https://modelcontextprotocol.io/) 服务器 — 在 `/integrations` 页面,以便模型可以代表他们以**他们**的权限执行操作。这是一个自托管的、每个用户的连接器存储库,与桌面 AI 应用中的连接器类似。

管理员在 `/admin/connectors` 页面管理服务器目录;用户只需点击**连接**。支持四种身份验证模型,按连接器选择:
- **OAuth 2.1 + 动态客户端注册** — 只需配置一个 URL(例如 Atlassian、GitLab.com、自托管 Google Workspace 服务器)。
- **OAuth 2.1 使用手动客户端** — 管理员一次性注册一个 OAuth 应用(例如 GitHub、Slack)。
- **用户提供的令牌** — 每个用户粘贴自己的 API 令牌/PAT(例如自托管 GitLab CE)。
- **无** — 一个公共的、无身份验证的服务器(例如 Kiwi.com 航班搜索);用户仍然单独连接以选择其工具到自己的聊天中。
每个用户的 OAuth 令牌都**静止加密**(AES-256-GCM)并**在后台刷新**,因此连接不会静默过期。每个连接服务器的工具都按命名空间 (`mcp__<server>__*`) 划分,并遵守与内置工具相同的每个工具始终/询问/关闭控制。提供商和部署设置 — 包括自托管 Google Workspace 和 GitLab CE 桥 — 在 [`deploy/README.md`](deploy/README.md) 和 [`docs/connectors.md`](docs/connectors.md) 中。
## 快速开始(本地开发)
您需要 [mise](https://mise.jdx.dev/),它管理 Rust + Node 工具链。
```bash
mise install # Rust 1.95 + Node 24
cp gateway.example.toml gateway.toml
$EDITOR gateway.toml # set at least one [upstream_pools.*] backend (and [oidc] to sign in)
mise run dev # runs the gateway (debug build) on http://localhost:8080
```
如果您正在编辑 UI,请在单独的终端中运行资产监视器(提交的捆绑包意味着这些对于纯后端工作是可选的):
```bash
mise run watch-css # rebuild app.css on change
mise run watch-js # rebuild app.js on change
```
打开 <http://localhost:8080>。登录/铸造令牌需要一个 `[oidc]` 块(见下文)。
**仅限 UI 快捷方式(无 OIDC):** `mise run dev-ui` 启动一个带有模拟后端和预种子会话的真实服务器,并打印一个您可以粘贴到浏览器或 Playwright 中的会话 cookie。
完整的开发者工作流程:[`docs/dev-workflow.md`](docs/dev-workflow.md)。
## 配置
配置是一个单一的 TOML 文件 — `gateway.toml` 在工作目录中,或 `$GATEWAY_CONFIG` 指向的任何位置。 [`gateway.example.toml`](gateway.example.toml) 是完整的带注释的参考;复制并修剪。
**机密永远不会存入文件中。** 配置文件包含**环境变量**的名称(例如 `session_key_env = "GATEWAY_SESSION_KEY"`);网关在启动时从其自己的环境中读取实际值。
最小但完整的配置:
```toml
[bind]
host = "127.0.0.1" # bind loopback; put a TLS-terminating reverse proxy in front
port = 8080
[db]
path = "gateway.sqlite"
[gateway]
public_url = "https://gateway.example.com" # external URL; used to build the OIDC callback
token_ttl_days = 90
session_key_env = "GATEWAY_SESSION_KEY" # names the env var holding a 64-hex (32-byte) key
allow_impersonation = false # opt-in admin impersonation (default false); see below
# At least one upstream pool. `kind` is chat | transcription | embedding | image.
[upstream_pools.local_chat]
kind = "chat"
strategy = "least_inflight" # or "round_robin"
[[upstream_pools.local_chat.backend]]
name = "gpu-01"
base_url = "http://gpu-01.internal:8000/v1"
# api_key_env = "GPU01_KEY" # if the backend itself needs a bearer token
# alias = ["qwen", "fast"] # stable client-facing names → this backend's model
# Needed for sign-in + token minting. Without it, /auth/login and the /login page don't work.
[oidc]
issuer = "https://id.example.com/realms/company"
client_id = "llm-gateway"
client_secret_env = "GATEWAY_OIDC_CLIENT_SECRET"
scopes = ["email", "profile", "groups"]
roles_claim = "groups"
```
配置文件引用的环境变量:
```bash
export GATEWAY_SESSION_KEY=$(openssl rand -hex 32) # 32 random bytes, hex-encoded
export GATEWAY_OIDC_CLIENT_SECRET=… # from your OIDC provider
export GATEWAY_MCP_KEY=$(openssl rand -hex 32) # optional: 32-byte key encrypting per-user MCP connector OAuth tokens at rest
```
`GATEWAY_MCP_KEY` 是可选的:它是用于加密每个用户的 MCP 连接器 OAuth 令牌(以及管理员存储的连接器客户端密钥)的 AES-256-GCM 密钥。如果未设置,网关从 `GATEWAY_SESSION_KEY` 派生一个稳定的密钥;如果**也**未设置(开发),则使用临时密钥并存储的连接器在重启后不会存活(用户只需重新连接)。如果您想将连接器加密与会话-cookie 签名分开,请显式设置它。
可选块,每个都内联记录在 `gateway.example.toml 中`:
- `[rbac]` + `[[roles]]` — 将 OIDC 声明值映射到角色,并按角色门控模型/工具。
- `[chat.s3]` — 在 S3 / MinIO / R2 / Backblaze B2 中存储聊天附件(见下文)。
- `[typst]` — 从模板目录注册文档渲染工具。
- `[sandbox]` — 通过指向 sandbox-runner 服务启用代码执行 + 文档工具(见 [`docs/sandbox.md`](docs/sandbox.md))。
- `[geoip]` — IP→location 用于 `get_user_location` 工具(IP2Location LITE 数据库)。
- `[rag]` — 索引 git 仓库并从聊天中搜索它们(见 [RAG](#rag-codebase-search))。
- `[fallback]` — 为未知或所有关闭的模型名称的服务器范围的后备模型(见 [`docs/upstreams.md`](docs/upstreams.md#fallback-models)。
- `[usage]` — 请求/令牌使用计数器 `/usage` 页面(保留修剪;默认启用)。
- `[feedback]` — UI 中的反馈小widget 文件 GitHub 问题。
### 聊天附件(S3)
聊天编辑器接受任何文件 via paperclip / 拖放 / 剪贴板粘贴。每个文件都上传到`S3`(或任何 S3 兼容的存储器)并内联为用户消息中的围栏文本块(CSV/JSON/源代码/...)或通过 `image_url` 内容部件引用在 OpenAI 请求中(图像)。添加 `[chat.s3]` 块:
```toml
[chat.s3]
endpoint = "https://s3.eu-central-1.amazonaws.com"
region = "eu-central-1"
bucket = "my-gateway-attachments"
access_key_env = "GATEWAY_S3_ACCESS_KEY"
secret_key_env = "GATEWAY_S3_SECRET_KEY"
# key_prefix = "chat-attachments" # optional, this is the default
```
…and 将凭证导出到网关的环境中:
```bash
export GATEWAY_S3_ACCESS_KEY=AKIA…
export GATEWAY_S3_SECRET_KEY=…
```
注意:
- 存储桶可以保持**完全私有**(无需公开读取 ACL,无需凭证预签名能力):网关在服务器端获取每个字节并将其内联发送到上游 LLM — 图像作为请求中的 `data:` URI,其他文件作为文本。所以 `endpoint` 只需要对**网关**可访问,而不是上游 LLM 网络。路径样式请求始终使用,因此不要求 DNS 样式桶域名;相同的形状适用于 MinIO,Backblaze B2 和 R2。
- 能力控制不在网关层面进行 — 将多模态聊天模型连接到池中。差异表现为上游自己的错误在聊天气泡中显示。
- 过去回合的附件被剥离并作为 `[attached: name.ext (omitted)]` 存根替换,因此上下文窗口保持有限。
(代码库搜索)
将网关指向git仓库;它克隆、分割并嵌入它们,并通过`rag_search`工具(以及`raglections`,以便模型可以发现可用的内容)将它们暴露给聊天模型。它用于“从*我们的*代码和文档中回答”而无需将整个仓库塞入上下文窗口。
**要求:** 一个`embedding`类型的上游池(通过它嵌入块和查询),主机路径上的`git`(索引器使用它——容器镜像中包含它),以及一个`[rag]`块。该块是可选的;其主要参数是`data_dir`(第二个参数`clone_concurrency`在`gateway.example.toml`中记录):
```toml
[rag]
data_dirmnt/data/gateway-rag" # 可选;默认为 ./data/rag
```
每个集合都有一个独立的文件夹`<>/<uuid>/`,其中包含其SQLite存储(块文本+词法索引)、`index.usearch`(向量)和git `clone/`。这是重量级、可完全重新生成的状态——将`data_dir`放在一个大的/廉价的磁盘上,与实际备份的小`[db].path`分开。在UI中删除集合会删除其文件夹。
**添加集合。** 作为管理员,打开`/rag`(或`POST /api/v0/rag/collections`)并提供:名称、git URL + 分支/标签、私有仓库的PAT、嵌入模型ID、包含/排除通配符、块大小/重叠(字符;默认为800/100)。后台工作程序克隆并嵌入它;状态从`pending → cloning → indexing → ready`(或`error`,并显示消息)变化。集合可以聚合**多个git源**(多个仓库或分支),每个源独立管理和重新索引。**重新索引**重新拉取源并重建集合通配符** 匹配仓库相对路径,有三种形式(没有完整的通配符引擎):
| 模式 | |---|---|
| `*.rs`, `*.md`扩展名 |
| `src/`, `target/` |前缀(注意尾部斜杠) |
| `vendor`, `node_modules` | 路径中的子字符串 |
| `*` 或 `**` | 所有内容 |
空的包含列表意味着“所有不被排除的内容”。二进制文件、大小超过1 MB的文件和`.git/`始终被跳过;排除优先于包含。
**检索是混合的。** 查询同时在稠密向量索引(usearch,余弦)和词法BM25索引(SQLite FTS5)上运行,并且两个排名通过互惠排名融合。密集召回率捕获释义;词法召回率捕获确切标识符(例如`osd_op_timeout`),而嵌入往往会模糊这些标识符。查询通过指令前缀嵌入(非对称检索);文档则裸露嵌入。
**大小。** 向量索引在磁盘上占主导地位,大约`chunks × embedding_dims × 4 bytes`。对于4096维模型(例如`Qwen3-Embedding-8B`),每个块约为16 KB,因此一个代码库分割成约100k块,需要约1.5 GB。嵌入是索引的慢部分——因此为大型仓库预算时间,并优先使用狭窄的通配符(源+文档)而不是巨大的树上的`*`。
### 代理技能
技能是操作员安装的指令包,聊天模型按需加载—— 房屋风格、品牌指南、领域攻略—— 无需微调或将所有内容塞入系统。技能是`SKILL.md`(YAML前置`name` + `description`,然后是markdown正文)加上可选的`references/`和`assets/`。模型只能看到每个允许的技能的名称+描述(廉价);当请求匹配时,它调用`read_skill`拉取完整正文,然后调用`read_skill(name, path)`获取引用文件(例如内联到HTML的SVG徽标)。一旦在对话中加载,指导在对话的其余部分保持应用/admin/skills页面:上传控件、已加载技能,选中了捆绑包的文件树,以及技能的渲染SKILL.md,旁边是授予的角色。](docs/img/skills.png)
**管理技能。** 将`[skills一个目录,并在其中放置捆绑包:
```
[skills]
"/var/lib/gateway" # 可选;默认为 ./data/skills
```
作为管理员,打开`/admin`以**上传**`.skill`存档(`SKILL.md`捆绑包的zip包),**查看**技能的渲染`SKILL.md` + 文件树,并**删除**技能——所有操作都是需重启:存储重新扫描目录并热交换加载的集合。RBAC控制哪些角色可以使用哪些技能;`read_skill`自动适用于被授予技能的任何角色。两个来源,联合起来:每个角色在配置中的静态`skills`列表(`["*"]`适用于所有,与`tools`完全一样),以及UI中的**每个技能授予编辑器**——点击技能上的**Granted to**,选择允许角色。UI授予存储在DB中,立即生效;配置授予保持权威,在对话框中只读显示。
## 使用网关
**1 — 获取API令牌。** 在`/login`处登录,然后在`/tokens`页面创建`gwk_…`令牌。
**2 — 调用它就像OpenAI API一样:**
```bash
export OPENAI_API_KEY=gwk_…
export OPENAI_BASE_URL=https://gateway.example.com/v1
openai api chat_completions.create -m-id> -g user "Hello"
```
`GET /v`列出网关在所有池中发现模型——从中选择一个`model` ID。
** 或者直接在`/chat`处使用聊天界面**一个模型,附加文件,并使用流式回复聊天保存在服务器端,并在重新连接时恢复。
###点
| 端点 | 认证 | 目的 |
|---|---|---|
| `POST /v1/chat/completions` | Bearer令牌 | 聊天补全(流式+非流式)。 |
| `POST /v1/embeddings` | Bearer令牌 | 嵌入。 |
| `POST /v1/images/generations` | Bearer令牌 | 图像生成(路由到`image`类型的池)。 |
| `POST /v1/images Bearer令牌图像编辑(multipart:`image` + `prompt路由到`image`类型的池。 |
| `v1/audio/transcriptions` | Bearer令牌 | Whisper-style转录(multipart上传)。 |
| `POST /v1/audio/speech` | Bearer令牌 | 文本转语音(OpenAI风格)。仅在配置了`speech`上游池时提供服务。 |
| `GET /v1/models` | Bearer令牌 | 池中发现的所有模型(按ID去重)。 |
| `GET /v1/sandbox/files/{run}/{filename}` | Bearer令牌 | 下载沙盒运行为调用者生成的文件(限定在您的用户)。 |
| `GET /healthz`,`GET /readyz` | 无 | 存活/就绪探测。 |
| `/`,`/login`,`/chat`,`/tokens`,`/tools`,`/memory`,`/scheduled`,`/webhooks`,`/integrations`,`/usage` | 会话cookie | Web UI(`/integrations`是每个用户的MCP连接器存储;`/webhooks`管理您的入站触发器;`/usage`显示您自己的请求/令牌使用情况;管理员获取一个页面“所有用户”切换开关)。 |
| `/hooks/{secret}` | URL中的密钥 | 触发webhook:运行所有者的保存提示,并将请求体作为不受信任的块附加。接受GET和POST;同步webhook返回模型输出作为JSON信封,异步webhook返回`202`。 |
| `/admin/users`,`/rag`,`/admin/models`,`/admin/backends`,`/admin/skills`,`/admin/connectors`,`/admin/limits` | 管理员角色 | 管理UI(用户页面列出注册用户并启动模拟;连接器管理MCP目录——参见[`docs/connectors.md`](docs/connectors.md)进行提供者设置;限制设置每个全局/角色/用户的速率限制和配额)。 |
| `POST /impersonate/stop` | 会话cookie | 结束活动模拟并返回到您自己的帐户。 |
| `/feedback`,`/feedback/extract`,`/feedback/config` | 会话cookie | 反馈小部件:提交GitHub问题,将语音转录为结构化字段,并报告功能是否配置。由配置块`[feedback]`启用。 |
| `/api/v0/*` | 会话cookie | 支持UI的JSON API。 |
`/v1/*点需要`Authorization gwk_…`。Authorization`头在代理处被丢弃,并注入配置的上游密钥();跳跃头在两个方向上都被过滤;上游4xx/5xx被逐字中继。UI页面使用在OIDC登录时生成的签名会话cookie。
## 生产部署(容器+systemd)
CI构建`target/release/gateway`并发布运行时容器镜像(`debian:trixie-slim` + `git` + `ca-certificates`,RAG索引器需要)。在Dockerfile外部构建并复制到容器中。要构建:
```bash
mise run build target/release/gateway(并获取typst CLI)
-t gateway:dev . # Dockerfile将发布复制到镜像中
```
[`deploy/quadlet/`](deploy/quadlet/)提供了一个强化的systemman Quadlet(只读根文件系统,删除,以非特权运行)。其[deploy/quadlet/README.md)是完整的演练;简而言之:
sudo install -m 0750 /etc/gateway
sudo install -m 0644 deploy/quadlet/gateway.container /containers/systemd/
sudo install -m 0644 deploy/quadlet/gateway.volume /etc/containers/systemd/
sudo install -m 0600 deploy/quadateway.example.env /etc/gateway/gateway.env
sudo install -m 0640 gateway.example.toml /etc/gateway/config.toml
sudo $EDITOR /etc/gateway/gateway.env # GATEWAY_SESSION_KEY, GATEWAY_OIDC_CLIENT_SECRET, …
sudo $EDITOR /etc/gateway/config.toml # 上游,[oidc],和DB路径在卷上
sudo systemctl daemon-reload
sudo systemctl enable --now gateway.service
```
操作说明:
- **TLS:** 单元绑定`127.0.0.1:8080` — 使用反向代理(Caddy / Traefik / nginx)在前面终止HTTPS。将 `[gateway].public_url` 设置为外部HTTPS URL,以便OIDC回调正确,并将 `<public_url>/auth/callback` 注册为OIDC客户端的回调URI。
- **状态:** SQLite DB + 会话存储在Podman管理的命名卷中,并在镜像交换时保留。将 `[db].path`(和 `[rag].data_dir`,如果使用)指向该卷。
- **更新:** Quadlet将`Image=`视为真实来源,不会在重启时重新拉取`:latest` — 在生产环境中固定摘要或`<git-sha>`标签。
### Docker Compose
对于运行Docker而不是podman的主机,[`deploy/compose.example.yml`](deploy/compose.example.yml)是等效的堆栈(网关+自托管**Google Workspace** MCP服务器,以及`sandbox`配置文件下的沙盒运行器和出口代理)。
所有与部署相关的文档——两种组件,完整的Google Workspace连接器设置——都在**/README.md`](deploy/README.md)**中。
## 文档
架构、认证、网关API、工具/RBAC、上游和测试在[`docs/`](docs/README.md)中有文档。[`AGENTS.md`](AGENTS.md)兼作人员入职培训。
## 贡献
欢迎贡献——参见[`CONTRIBUTING.md`](CONTRIBUTING.md)了解工作流程、签署要求和贡献者许可协议([`CLA.md`](CLA.md))。
## 许可证
在**GNU Affero General Public License v3.0**(`AGPL-3.0-only`)下许可——参见[`LICENSE`](LICENSE)。
您可以免费使用、研究、修改和重新分发此软件,包括在商业环境中。因为它是AGPL,所以有一项义务特别突出:如果您运行**修改**版本以提供网络服务,则必须向该服务的用户提供完整的对应源代码——包括您的修改——(AGPL §13)。UI带有持久的“源代码”链接;修改后的部署操作员应通过环境变量`GATEWAY_SOURCE_URL`指向自己的源代码。
**商业许可。** 如果AGPL的条款不适合您的使用场景,可以提供单独的商业许可——请联系croit GmbH(<info@croit.io>)。
第三方组件的许可证与二进制文件捆绑或链接到二进制文件;参见[`NOTICE`](NOTICE)。
连接信息
猜你喜欢
暂无推荐服务器