最近在做一个多模型接入的小项目发现每次对接不同的 AI 服务商都要写一堆适配代码密钥管理也散落各处临时想统一走一个入口却找不到趁手的工具。花了一周时间我直接自己搓了一个轻量级 AI 聚合网关并且利用 Cloudflare 的免费额度完成了云上部署整个过程没有买服务器、没有备案成本几乎为零。这篇文章会把整个思路和实现过程完整记录下来从核心概念、环境准备、网关代码实现到一键部署、自动发布、常见排错一次讲清楚。不管是想自己搭一个 AI 网关还是想白嫖云资源做个人项目这篇文章都值得收藏。1. 背景与核心概念1.1 什么是 AI 聚合网关先来说说 AI 聚合网关到底是干什么的。正常情况下一个应用如果同时对接多个 AI 模型提供方比如 OpenAI、Anthropic、Google Gemini以及国内的一些大模型服务你需要分别处理各家不同的接口地址、认证方式、请求格式、限流策略。一旦模型数量变多代码会变得非常臃肿维护成本也很高。AI 聚合网关做的事情就是把这些差异都屏蔽掉对外提供一个统一、稳定的 API 入口。客户端只需要按照一套标准格式发起请求网关负责将请求转发到正确的模型服务商并把响应结果返回给客户端。用通俗的话来说网关就像是一个前台中转站。你不用再记住每个公司的门牌号和接待方式只需要跟前台说一句“我要找谁”后面的事情由前台来处理。1.2 AI 聚合网关的核心功能一个完整的 AI 聚合网关通常包含以下能力功能模块说明统一 API 入口对外提供标准接口客户端无需关心每个上游服务的差异模型路由根据请求中的模型名称自动转发到对应的服务商密钥管理多个上游服务的 API Key 统一存放在网关侧不暴露给客户端参数转换将统一格式的请求转换为各服务商要求的格式错误处理与重试上游故障时返回友好提示可选自动重试限流与权限校验控制调用频率防止接口被滥用日志与统计记录请求量、延迟、失败率便于观察和排错这些能力如果自己从零去写工作量不小。但如果利用 Cloudflare Workers 这样的边缘计算服务配合第三方开源模型网关的骨架可以在很短时间内搭出来。1.3 为什么选择 Cloudflare 作为部署平台Cloudflare 不是一家传统的云服务器厂商它的核心优势在于全球边缘网络。你写的一段代码可以部署到离用户最近的节点上用户访问时延迟更低。对于个人开发者来说Cloudflare 最有吸引力的其实是免费额度Workers 免费计划每天有 10 万次请求额度个人项目完全够用。免费提供 HTTPS 证书可以绑定自定义域名。提供 Workers KV 存储可以存放配置数据。配合 GitHub 可以进行自动化部署。换句话说你不需要购买任何云服务器也不需要为流量付费只要代码量不大、请求量在免费额度内这个网关可以长期“零成本”运行。2. 整体架构与方案设计2.1 免费云上部署的整体架构在动手之前先把整体架构想清楚后面写代码时思路会顺畅很多。本文实现的 AI 聚合网关采用如下架构客户端发送标准 OpenAI 兼容格式的请求到 Cloudflare Workers 网关地址。Workers 中的网关程序解析请求提取模型名称和消息内容。网关根据模型名称匹配上游服务配置替换 API Key转换请求格式。通过fetch转发到真实的 AI 服务商接口。获取上游响应转换回统一格式返回给客户端。整个链路中只有网关这一个入口是暴露在外的所有上游服务的密钥都保存在 Cloudflare Workers 的环境变量或 KV 存储中。2.2 Cloudflare 免费额度说明Cloudflare 的免费计划对个人项目非常友好。以 Workers 为例每天 10 万次请求。每天 30 分钟 CPU 时间。支持 Workers KV每天上限 10 万次读操作。免费绑定自定义域名并启用 CDN 加速。自带基础防护能力。需要注意的是免费额度是每日计算的如果某一天请求量突然暴增可能会触发用量限制这时请求会返回错误。对于个人学习项目和小流量应用来说这个额度已经非常宽裕了。2.3 项目目录结构在写代码之前我先列出项目的完整目录结构让你有一个整体感知ai-gateway/ ├── .github/ │ └── workflows/ │ └── deploy.yml # GitHub Actions 自动部署配置 ├── src/ │ ├── gateway.js # Worker 主入口路由分发 │ ├── config.js # 上游服务配置 │ ├── providers/ │ │ ├── openai.js # OpenAI 适配器 │ │ ├── anthropic.js # Anthropic 适配器 │ │ └── gemini.js # 自定义模型适配器 │ └── utils/ │ ├── response.js # 统一响应处理 │ └── crypto.js # 签名校验工具 ├── dashboard/ │ ├── index.html # 简单管理面板 │ └── app.js # 面板前端逻辑 ├── .dev.vars # 本地开发环境变量 └── wrangler.toml # Cloudflare Workers 配置这个目录结构并不是死的你可以根据自己的实际需要增删。核心是src目录下的网关逻辑其他都可以灵活调整。3. 环境准备与账号配置3.1 本地开发环境开始之前先确保本地环境满足以下要求。版本需要根据你的实际项目情况调整本文示例以常见环境为例重点演示配置思路。Node.js 18 或以上版本。npm 或 pnpm 包管理器。Git 命令行工具。一个 GitHub 账号用来存储代码和触发自动部署。一个 Cloudflare 账号用来部署 Workers。如果你还没有安装 Node.js可以去官网下载 LTS 版本安装完成后在终端验证一下node -v npm -v3.2 Cloudflare 账号准备登录 Cloudflare 控制台后不需要做太复杂的配置。你需要拿到两个关键信息Account ID在控制台首页右侧可以找到。API Token在右上角头像 - My Profile - API Tokens 中创建。创建 API Token 时选择Edit Cloudflare Workers模板权限范围只需要包含 Workers 即可。生成的 Token 只会显示一次一定要保存好。3.3 安装 Wrangler CLIWrangler 是 Cloudflare 官方提供的命令行工具用来开发、调试、部署 Workers。安装方式很简单npm install -g wrangler安装完成后验证一下版本wrangler --version然后登录 Cloudflare 账号wrangler login执行后浏览器会打开授权页面点击允许即可。登录成功后在终端会看到对应的提示。如果不想全局安装也可以作为项目依赖安装这样团队协作时版本更统一npm install -D wrangler4. 核心代码AI 聚合网关 Worker 实线4.1 创建 Worker 项目使用 Wrangler 初始化一个项目mkdir ai-gateway cd ai-gateway wrangler init在初始化过程中Wrangler 会询问是否创建基础代码和配置文件按需选择即可。最终项目里会生成一个wrangler.toml文件和src/目录。接下来我们需要安装路由处理相关的依赖。为了保证网关代码足够轻量我只引入一个用于 URL 匹配的库npm install itty-routeritty-router是一个轻量级路由库非常适合 Cloudflare Workers 环境体积小、语法简单。4.2 配置 wrangler.tomlwrangler.toml是 Cloudflare Workers 的核心配置文件。我的参考配置如下name ai-gateway main src/gateway.js compatibility_date 2024-09-01 workers_dev true [vars] GATEWAY_TOKEN your-gateway-token # 以 OpenAI 为例其他服务商的密钥也可以放在这里 OPENAI_API_KEY sk-your-openai-key # 如果用到 KV 存储开下面这行 # [[kv_namespaces]] # binding GATEWAY_KV # id your-kv-namespace-id配置说明nameWorker 服务名称会作为默认子域名的一部分。main入口文件路径。compatibility_dateCloudflare 运行时兼容性日期。vars环境变量可以在代码中直接读取。有一点要特别提醒你的 API Key 如果写在这个配置文件里那么上传到 GitHub 时一定要确保仓库是私有的。更安全的做法是使用.dev.vars存放本地密钥生产环境的密钥通过 Cloudflare 控制台或 GitHub Actions Secrets 注入。4.3 编写网关主入口网关主入口是整篇文章的核心它负责接收所有请求并转发到对应的上游 AI 服务。先来看一个简化版的主入口代码// 文件路径src/gateway.js import { Router } from itty-router; import { handleChatCompletion } from ./routes/chat; import { handleModels } from ./routes/models; import { authMiddleware } from ./middleware/auth; const router Router(); // 所有请求都要经过鉴权中间件 router.all(*, authMiddleware); // 获取模型列表 router.get(/v1/models, handleModels); // 对话补全接口 router.post(/v1/chat/completions, handleChatCompletion); // 健康检查 router.get(/health, () new Response(OK, { status: 200 })); export default { async fetch(request, env, ctx) { try { return await router.handle(request, env, ctx); } catch (err) { return new Response( JSON.stringify({ error: { message: err.message || Internal Server Error, type: internal_error } }), { status: 500, headers: { Content-Type: application/json } } ); } } };这段代码的职责非常清晰使用itty-router注册路由规则。对所有请求先执行鉴权中间件。对不同的路径分发到对应的处理函数。全局捕获异常统一返回 JSON 格式错误。4.4 实现鉴权中间件既然是网关就不能让所有拿到地址的人随便调用。我给网关加了一个简单的 Token 鉴权机制。// 文件路径src/middleware/auth.js export async function authMiddleware(request, env) { // 健康检查不需要鉴权 if (new URL(request.url).pathname /health) { return; } const authHeader request.headers.get(Authorization) || ; const token authHeader.replace(Bearer , ); if (!token || token ! env.GATEWAY_TOKEN) { return new Response( JSON.stringify({ error: { message: Unauthorized, type: auth_error } }), { status: 401, headers: { Content-Type: application/json } } ); } }这个中间件的逻辑很简单请求头里必须携带Authorization: Bearer token并且 token 要和环境变量GATEWAY_TOKEN一致否则直接返回 401。在实际项目中这里可以升级为 JWT 校验、API Key 轮换、按用户维度限流等能力本文先保持最简实现。4.5 实现对话补全路由对话补全接口是网关的核心。下面是一个能实际工作的简化版本逻辑是根据请求里的模型名称把请求转发给 OpenAI 的 Chat Completions 接口。// 文件路径src/routes/chat.js const OPENAI_CHAT_URL https://api.openai.com/v1/chat/completions; export async function handleChatCompletion(request, env) { const body await request.json(); const model body.model; if (!model) { return new Response( JSON.stringify({ error: { message: Missing model parameter, type: invalid_request_error } }), { status: 400, headers: { Content-Type: application/json } } ); } // 这里可以做模型路由根据 model 名称转发到不同服务商 // 为了演示这里统一走 OpenAI const upstreamResponse await fetch(OPENAI_CHAT_URL, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${env.OPENAI_API_KEY} }, body: JSON.stringify(body) }); const data await upstreamResponse.json(); return new Response(JSON.stringify(data), { status: upstreamResponse.status, headers: { Content-Type: application/json, Access-Control-Allow-Origin: * } }); }在上面的例子里网关做的事情其实就是“透传”。客户端发什么网关就原样转发给 OpenAI。注意这只是一个演示版本实际项目中还需要处理以下问题不同服务商的接口格式不同需要做参数转换。请求失败时需要返回更明确的错误信息。响应需要统一格式方便客户端处理。4.6 实现 OpenAI 兼容接口为了让上游服务格式不统一的问题得到解决我在网关内部设计了一个“适配器”概念。每个上游服务商实现一个chat方法负责将统一请求体转换为该服务商要求的格式。以 OpenAI 适配器为例// 文件路径src/providers/openai.js export async function chat(messages, options, env) { const url options.baseUrl || https://api.openai.com/v1/chat/completions; const apiKey env.OPENAI_API_KEY; const response await fetch(url, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${apiKey} }, body: JSON.stringify({ model: options.model || gpt-4o-mini, messages, temperature: options.temperature ?? 0.7, max_tokens: options.maxTokens || 1000 }) }); if (!response.ok) { const errorText await response.text(); throw new Error(OpenAI upstream error: ${response.status} ${errorText}); } return await response.json(); }再看一个自定义模型的适配器示例假设上游接口是兼容 OpenAI 格式的但接口地址和密钥不同// 文件路径src/providers/custom.js export async function chat(messages, options, env) { const url env.CUSTOM_BASE_URL || https://custom-ai.example.com/v1/chat/completions; const apiKey env.CUSTOM_API_KEY; const response await fetch(url, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${apiKey} }, body: JSON.stringify({ model: options.model, messages, temperature: options.temperature ?? 0.7 }) }); if (!response.ok) { const errorText await response.text(); throw new Error(Custom upstream error: ${response.status} ${errorText}); } return await response.json(); }有了适配器之后路由层就变得更灵活了。在chat.js里做一个简单的模型映射即可// 文件路径src/routes/chat.js升级版 import { chat as openaiChat } from ../providers/openai; import { chat as customChat } from ../providers/custom; const modelProviderMap { gpt-4o-mini: openai, gpt-4o: openai, custom-model-1: custom }; export async function handleChatCompletion(request, env) { const body await request.json(); const { messages, model } body; if (!model || !messages) { return new Response( JSON.stringify({ error: { message: model and messages are required, type: invalid_request_error } }), { status: 400, headers: { Content-Type: application/json } } ); } const providerName modelProviderMap[model]; if (!providerName) { return new Response( JSON.stringify({ error: { message: Unsupported model: ${model}, type: invalid_request_error } }), { status: 400, headers: { Content-Type: application/json } } ); } let result; if (providerName openai) { result await openaiChat(messages, { model }, env); } else if (providerName custom) { result await customChat(messages, { model }, env); } return new Response(JSON.stringify(result), { status: 200, headers: { Content-Type: application/json, Access-Control-Allow-Origin: * } }); }这段代码采用了一张简单的映射表将不同模型名称指向不同的处理器。当新增模型服务商时只需要新增适配器并更新映射表主流程代码不需要改动。实际生产项目中模型映射表可以放到 Workers KV 中这样你可以在不发布新代码的情况下动态修改模型路由规则。4.7 添加 CORS 支持如果你的网关会从浏览器端调用必须处理跨域问题。可以在入口处统一添加响应头// 文件路径src/utils/cors.js export const corsHeaders { Access-Control-Allow-Origin: *, Access-Control-Allow-Methods: GET, POST, OPTIONS, Access-Control-Allow-Headers: Content-Type, Authorization }; export function handleOptions(request) { if (request.method OPTIONS) { return new Response(null, { status: 204, headers: corsHeaders }); } }在入口文件中针对 OPTIONS 请求直接返回import { handleOptions, corsHeaders } from ./utils/cors; const router Router(); router.all(*, (request) { if (request.method OPTIONS) { return handleOptions(request); } });4.8 统一响应格式为了让客户端处理响应时更简单我将成功和失败两种情况统一格式。成功响应示例{ success: true, data: { id: chatcmpl-123, object: chat.completion, model: gpt-4o-mini, choices: [...] } }失败响应示例{ success: false, error: { message: 上游服务超时, type: upstream_timeout } }这样设计的好处是客户端只需要检查success字段就可以判断请求是否成功不需要去解析 HTTP 状态码。5. 一键部署到 Cloudflare Workers5.1 本地调试在部署到线上之前先在本地启动开发服务器调试wrangler dev启动后Wrangler 会在本地开启一个端口比如http://localhost:8787。你可以用 curl 测试接口curl -X POST http://localhost:8787/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer your-gateway-token \ -d { model: gpt-4o-mini, messages: [ { role: user, content: 你好介绍一下你自己 } ] }如果一切正常你会收到来自模型服务商的响应内容。5.2 手动部署本地调试通过后直接执行部署命令wrangler deploy执行完成后终端会输出一个 Workers 域名形如https://ai-gateway.your-subdomain.workers.dev这个地址就是网关的公网入口。用浏览器打开/health路径能看到一个简单的 OK 响应。5.3 配置 GitHub Actions 自动部署手动部署每次都要在本地执行命令不够自动化。更推荐的做法是配置 GitHub Actions在每次 push 到 main 分支时自动部署。首先在 GitHub 仓库的Settings - Secrets and variables - Actions中配置以下 SecretsCLOUDFLARE_API_TOKEN你之前创建的 API Token。CLOUDFLARE_ACCOUNT_IDCloudflare 控制台中的 Account ID。然后创建文件.github/workflows/deploy.ymlname: Deploy Worker on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkoutv4 - name: Setup Node.js uses: actions/setup-nodev4 with: node-version: 20 - name: Install dependencies run: npm ci - name: Deploy to Cloudflare Workers run: npx wrangler deploy env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}这个工作流的逻辑很清晰监听 main 分支的 push 事件。签出代码。安装 Node.js 环境。安装依赖。执行wrangler deploy部署。以后你只需要把代码 push 到 GitHub 仓库的 main 分支GitHub Actions 会自动完成部署真正实现“一键云上部署”。5.4 配置自定义域名如果你有自己的域名并且域名托管在 Cloudflare可以在控制台为 Worker 绑定自定义域名。在 Cloudflare 控制台中进入你的 Worker 服务点击Settings - Domains Routes - Add输入你想绑定的域名比如ai-api.example.com保存后等待 DNS 生效即可。绑定成功后你访问的地址就变成了你自己的域名不再需要通过workers.dev子域名对外提供服务。6. 前端管理面板6.1 为什么需要管理面板网关上线后总不能每次看日志都去 Cloudflare 控制台。这里我顺手做了一个极简管理面板部署在 Cloudflare Pages 上用来展示网关的基本状态和调用统计。6.2 面板页面示例管理面板只包含一个简单的 HTML 页面!-- 文件路径dashboard/index.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 / meta nameviewport contentwidthdevice-width, initial-scale1.0 / titleAI Gateway Dashboard/title style body { font-family: system-ui, sans-serif; max-width: 800px; margin: 0 auto; padding: 24px; } .card { border: 1px solid #e5e7eb; border-radius: 8px; padding: 16px; margin-bottom: 16px; } .status { font-weight: bold; } .online { color: #16a34a; } .offline { color: #dc2626; } /style /head body h1AI Gateway Dashboard/h1 div classcard h2网关状态/h2 p classstatus idhealthStatus检测中.../p /div div classcard h2模型配置/h2 ul idmodelList ligpt-4o-mini - OpenAI/li ligpt-4o - OpenAI/li licustom-model-1 - Custom/li /ul /div script src./app.js/script /body /html6.3 面板前端逻辑// 文件路径dashboard/app.js async function checkHealth() { try { const response await fetch(/health); const statusEl document.getElementById(healthStatus); if (response.ok) { statusEl.textContent 在线; statusEl.classList.add(online); } else { statusEl.textContent 异常; statusEl.classList.add(offline); } } catch (err) { const statusEl document.getElementById(healthStatus); statusEl.textContent 离线; statusEl.classList.add(offline); } } checkHealth();这个页面非常简单实际项目中可以扩展为请求量图表、错误率统计、模型调用占比等更丰富的展示。把dashboard目录部署到 Cloudflare Pages 即可。7. 常见问题与排查思路7.1 常见问题速查表问题现象常见原因解决思路部署时提示Missing API TokenCLI 未登录或未配置 Token执行wrangler login或在 CI 中配置 Secrets调用返回 401 Unauthorized网关 Token 不匹配检查请求头 Authorization 是否携带正确的 Bearer Token返回Model not found模型名称未在映射表中配置检查modelProviderMap确认模型名与上游一致上游请求超时第三方接口响应慢或网络不稳增加超时处理适当重试查看上游状态页部署后代码未生效自动部署失败或环境变量未更新查看 GitHub Actions 日志确认 Secrets 是否配置浏览器跨域报错缺少 CORS 头在响应中增加Access-Control-Allow-Origin免费额度被耗尽请求量超过每日限制进入控制台查看用量考虑升级计划或加限流7.2 详细排查步骤案例一部署后访问返回 500出现 500 错误首先查看 Worker 的日志。在 Cloudflare 控制台进入 Worker点击Logs菜单可以看到实时的请求日志和异常堆栈。常见原因是环境变量没有读取到。检查wrangler.toml中的vars是否已经包含所有需要的变量如果是通过 CI 部署需要确认 GitHub Secrets 是否配置正确。案例二请求 OpenAI 时报 401这种情况通常是OPENAI_API_KEY配置错误。可以在本地环境变量文件.dev.vars中确认一下OPENAI_API_KEYsk-xxxxxxxx GATEWAY_TOKENmy-gateway-token另外注意某些服务商的密钥需要通过不同的请求头传递。有的要求Authorization: Bearer有的要求自定义请求头这些细节需要查阅上游文档。案例三模型请求速度很慢如果网关本身逻辑很简单但请求延迟很高大概率是上游服务本身的响应时间慢。可以在前端上报请求开始和结束时间对比一下直连上游和通过网关访问的耗时差距。也有可能是 Cloudflare Workers 节点距离上游服务商的接口较远导致的网络延迟。这种情况可以尝试在fetch请求中指定cf参数或者调整 Worker 的访问地区设置。8. 最佳实践与安全建议8.1 密钥管理永远不要把密钥写死在代码里这是最容易犯的错误也是后果最严重的问题。开发阶段可以把密钥放到.dev.vars中这个文件不要提交到 Git生产环境的密钥通过 Cloudflare 控制台设置或者在 GitHub Actions 中通过 Secrets 注入。一旦发现密钥泄露立即到上游服务商的控制台吊销并重新生成同时更新网关配置。8.2 添加访问控制与限流对外的网关上至少要有两层保护第一层是网关自身的 Token 鉴权拒绝未授权的请求。第二层是按调用方维度限流防止某个调用方消耗全部额度。Cloudflare 提供了 Rate Limiting 规则可以在控制台中针对/v1/chat/completions路径配置速率限制。免费计划有一定的配额个人项目完全够用。生产环境如果并发量大建议将网关 Token 升级为短期 JWT并配合签名校验机制。8.3 日志与监控Cloudflare Workers 自带日志功能但只能保留最近一段时间的数据无法做长期趋势分析。建议在网关代码中主动记录结构化日志例如{ time: 2025-01-01T12:00:00Z, path: /v1/chat/completions, model: gpt-4o-mini, status: 200, latency_ms: 340 }如果你的项目运行量不大也可以直接把关键指标发送到免费的可观测性平台或者用 Workers KV 做简单的计数统计。8.4 合理使用上游模型聚合网关最大的优势是灵活性但这不代表可以滥用。一些上游服务商对单账号的请求速率有限制网关层如果只是单纯转发多个客户端同时调用时仍然可能触发上游限流。这时可以在网关层做两件事对同一上游做排队或节流。在多个上游 Key 之间做负载均衡。本文的示例没有覆盖这两点但如果你的网关流量逐步增长这些是下一步值得研究和实现的方向。8.5 代码可维护性网关的代码量虽然不大但涉及到多个上游服务商时很容易变成一堆 if-else。建议从第一天开始就使用适配器模式每个上游服务商一个文件保持主流程干净。另外给每个适配器补充清晰的注释标明接口地址、认证方式、返回差异方便后续维护。9. 总结与下一步方向这篇文章实现了以下几件事用一个 Worker 代码搭建了兼容 OpenAI 格式的 AI 聚合网关。实现了模型路由、鉴权、统一错误处理、CORS 支持。支持手动部署和 GitHub Actions 自动部署。通过 Cloudflare 免费额度实现了零成本云上部署。给出了常见问题的排查思路和工程级安全建议。如果你的目标只是个人学习和使用目前这个网关已经可以满足大部分需求。下一步可以考虑的方向包括将模型映射配置搬进 Workers KV实现动态路由。增加请求日志落库分析调用趋势。增加缓存层对重复请求直接返回缓存结果节省上游调用费用。集成更多模型服务商比如 Anthropic、Gemini 等。增加多 Key 自动轮询机制提升上游可用性。动手搭一个属于你自己的 AI 聚合网关是理解 API 网关设计、边缘计算部署和服务治理的很好项目实践。如果这篇文章对你有帮助可以先收藏备用后续遇到部署或使用问题随时回来查阅。