使用 Cloudflare Workers 搭建轻量级 LLM API 网关
在这个 LLM 服务商们每天都在进行军备竞赛的时代,对我们这种面向 LLM 编程的程序员来说,最常见的一个痛点莫过于管理一大堆 API Endpoint 和 Secret Key 了,再加上:
- 不同的服务商 Host 了相同或不同的模型。
- 在不同的服务商里都撒了币。
- 不同服务商的 SLA 可能相去甚远。
- 要在不同的软件里重复配置这些相同的内容。
这一系列问题的解决方案自然是一个统一的 LLM API Gateway,原理上看也并不复杂,只消维护 Model 和 API Provider 之间的映射规则,然后按需转发即可。
{"model": "claude"}
⬇️
LLM API Gateway
⬇️
{"model": "claude-3-7-sonnet-latest"}
⬇️ ⬇️
Anthropic AWS Bedrock
实际上市面上也早已有成熟的开源方案,例如 songquanpeng/one-api。但对于我来说,部署这么一套管理系统显得有些太重了,作为个人使用,似乎不太需要额外的租户管理和账单系统,加之如果想要进行远端部署,产生额外的服务器、域名等维护成本也有些令人抗拒,所以“造轮子”似乎又成了最终的选择。
比起年轻气盛时动不动就想“万丈高楼平地起”地从零开始造轮子,成年人造轮子的哲学则是“应拼尽拼”,能用现成的预制零件快速拼出来的轮子也是好轮子——于是开始整理造这么一个私人 LLM API Gateway 的基本要求:
- 易于维护,部署成本要尽可能的低,最好能用现成的 SaaS。
- 开箱即用,使用和配置方式简单,即暴露一个统一的 API 接口,可以自由配置模型的转发映射。
- 安全,有基本的鉴权以防止滥用。
- 通用,适配主流的 LLM API Provider 格式。
首先,作为一个几乎无状态的 API Gateway,最核心的逻辑就是转发 HTTP 请求,所以立马出现在选品单上的就是 Cloudflare Workers,其提供的 Severless 应用部署非常适合写这种 Proxy,再加上配套的 Cloudflare Workers KV,转发配置需要持久化存储的需求也被满足了。于是我基于官方的 Rust SDK worker-rs 实现了 one-united,仅需简单的配置,就可以把一个轻量级的 LLM API Gateway 部署到 Cloudflare Workers 上。
部署方式
因为官方提供了非常齐全的配套,整个部署过程需要做的准备仅需提前安装上较新版本的 Rust 和 npm 即可。
首先把项目拉到本地,然后开始编辑我们的 wrangler.toml
。
git clone https://github.com/one-united/one-united.git
cp wrangler.example.toml wrangler.toml
其实这里要做的就是创建一个 KV namespace,一条命令就能搞定:
npx wrangler kv:namespace create config
运行成功后把输出中提供的 kv_namespaces
部分粘贴到 wrangler.toml
文件中即可,格式类似于:
[[kv_namespaces]]
binding = "config"
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
然后就是用最后一条命令完成最终部署:
npx wrangler deploy
然后你的服务就跑在 https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev
上可供访问了!
配置文件
刚部署好的 one-united 自然是没有配置任何模型转发的,项目里提供了一个规则模板,其格式还是比较直观的,我以此为例展示一个我自己的使用场景,来看看具体如何配置自己的模型转发。
我最常用 LLM 的一个场景就是翻译,例如 Bob 的 AI 翻译服务。
可以看到我定义了一个名为 translator
的模型,其转发逻辑的配置如下:
rules:
- model: translator
providers:
- identifier: oh-my-gpt
models:
- gpt-4o-mini
- identifier: openrouter
models:
- openai/gpt-4o-mini
- identifier: dailyio
models:
- gpt-4o-mini
- meta-llama/Llama-3.3-70B-Instruct-Turbo
不难发现对于翻译服务,我使用的都是主流模型中 Token 价格较低的模型,如此一来在使用较为频繁的翻译场景下,可以在保证质量的前提下尽可能节省 Token。providers
的配置字段都比较直接,在此不表,按需添加和配置自己的提供商即可。
目前 one-united 还没有图形化界面,所以更新配置需要用 curl 直接把 config.json
扔给接口:
curl -X POST https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/config \
-H "Content-Type: application/yaml" \
--data-binary @config.yaml
一些提升体验的功能
在简单转发的基础上,我也根据平时使用的一些经验和习惯加了一些必要的功能和优化。
设置 API KEY
虽然说整个流程属于私人部署,但也不免存在接口泄漏的可能,避免被他人滥用导致 Token 额度被迅速消耗完,可以给自己的 Gateway 也设置上 API Key。这里有两个操作办法, 一个是直接通过命令行设置 ONE_API_KEY
。
npx wrangler secret put ONE_API_KEY
也可以到 Cloudflare 的 Workers 后台界面添加:
此后所有的 curl 请求都可以带上 -H "Authorization: Bearer $ONE_API_KEY"
进行鉴权了,同样,在类似 Chat Bot 的 API 配置中,也需要填上 $ONE_API_KEY
方可正常调用。
负载均衡
当同一个模型名配置了多个不同的 (Provider, Model)
映射时,为了保证尽可能好的延迟表现,每次会通过负载均衡机制在不同映射间进行切换,并记录每次请求的耗时,最终尽可能地选择转发至延迟较低的提供商。
目前这个策略还比较简陋,我还在考虑是否要添加诸如基于权重或者 Token 成本的负载均衡策略。
常用的接口
one-united 一共提供了以下几个接口:
WorkerRouter::new()
.get_async("/config", get_config)
.post_async("/config", save_config)
.get_async("/stats", get_stats)
.get_async("/v1/models", get_models)
.post_async("/v1/chat/completions", route_chat_completions)
.run(req, env)
.await
其中 /v1/models
和 /v1/chat/completions
都是 OpenAI 兼容的常用接口,后者不用说,就是最常用的 LLM 使用接入口。前者则是 List Models 接口,对于一些提供了自动获取模型信息功能的软件来说,可以方便的通过这个接口一键添加所有当前可用的模型信息:
前面一个小节提到过 one-united 存在负载均衡机制,通过 /stats
这个接口可以看到当前所请求 Workers 实例内的延迟统计信息,方便判断不同服务商的延迟表现如何:
❯ curl -s -H "Authorization: Bearer $ONE_API_KEY" https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/stats | jq
{
"created_at": "2025-03-06T04:22:13.862Z",
"hash": "362c5ee09afe8b5c82f132161496c00072ce850e3a39d204315bf823e8311de8",
"latency": [
{
"identifier": "cf-openrouter",
"model": "anthropic/claude-3.7-sonnet",
"ms": 1473
}
],
"lifetime": "3m 42s 685ms"
}
OpenRouter 统计适配
如果你搭配 OpenRouter 使用的话,可以看到在 OpenRouter 的 Activity 界面上是可以识别到 one-united 转发来的请求标识的,方便掌握具体的用量。
这个也是根据 OpenRouter 官方的文档加的请求识别头来实现的:
// "HTTP-Referer" and "X-Title" will be used by service like OpenRouter to identify the request.
headers.set("HTTP-Referer", "https://github.com/JmPotato/one-united")?;
headers.set("X-Title", "one-united")?;
Bypass Rule
如果你想 Bypass 掉规则,直接请求对应 Provider 的某个模型,可以使用 model@@provider_identifier
这个语法,例如直接请求配置中来自 OpenRouter 的 GPT-4o mini:openai/gpt-4o-mini@@openrouter
,请求就会直接发给 OpenRouter,而不会经过负载均衡中转,指哪打哪。