
1. 为什么我要把14个免费模型通道塞进一个入口手里攒了一堆免费模型额度这件事本身就挺让人头疼的。我平时写代码、查资料、做文档摘要不同任务对模型的要求完全不一样——有的任务需要长上下文有的任务需要快响应有的任务纯粹是拿来跑批量翻译的。结果就是浏览器里开着五六个标签页每个标签页对应一个平台每次切换都要重新粘贴一遍提示词时间全浪费在复制粘贴上了。后来我干脆花了一个周末把手上能用的14个免费通道全部整合到一个本地网关里对外只暴露一个入口内部按任务类型自动路由到最合适的模型。这套东西我管它叫WorkBuddy 免费模型路由网关。核心思路很简单你只管把请求发给本地的一个地址至于这个请求最终落到哪个模型上由网关根据任务特征自动判断。这件事解决的核心痛点有三个。第一是入口统一不管你有多少个免费额度来源对外只有一个 API 地址所有工具、脚本、编辑器插件只需要配置一次。第二是按任务路由代码补全走低延迟通道长文总结走大上下文通道批量翻译走高并发通道各取所长。第三是额度均衡避免某个通道被薅秃了其他通道还闲着网关层面做一轮简单的负载分配。适合谁来参考呢如果你手上有多个免费模型额度又不想每次手动切换如果你在用 WorkBuddy 或者类似的本地工作台工具想把它背后的模型调用统一管理起来如果你单纯对本地网关、请求路由这套东西感兴趣——那这篇内容应该能给你省下不少试错时间。我下面会把整个搭建过程、配置文件怎么写、路由规则怎么定、踩过哪些坑全部摊开讲。2. 整体架构设计与路由思路拆解2.1 为什么选择本地网关而不是直接改工具配置最直接的做法是每个工具单独配置模型地址比如编辑器插件配一个、命令行工具配一个、WorkBuddy 工作台再配一个。但这样做的后果是配置分散改一次要改好几个地方而且没法做统一的路由和额度管理。本地网关的好处在于收敛层。所有请求先到网关网关再决定往哪转发。这样做有几个实际收益一是配置只维护一份新增或下线通道只需要改网关的配置文件二是可以在网关层做日志记录哪个通道用了多少额度一目了然三是路由逻辑可以随时调整不用动上层工具的任何设置。我选的是用一个轻量级的本地服务来做这件事监听本机的一个端口对外提供和主流模型接口兼容的请求格式。这样不管是 WorkBuddy 还是其他支持自定义接口地址的工具都能直接对接。2.2 14个通道的分类逻辑14个通道不是随便堆在一起的我按三个维度做了分类。第一个维度是任务类型。代码类任务对延迟敏感对上下文长度要求中等文档总结类任务对上下文长度要求高对延迟不敏感翻译类任务对并发要求高对单次响应质量要求中等。这三个类型基本覆盖了我日常百分之八十的使用场景。第二个维度是通道特性。有的通道响应快但上下文短有的通道上下文长但响应慢有的通道并发高但单次质量一般。我把每个通道的特性都记录在配置文件里路由的时候根据任务类型去匹配最合适的通道。第三个维度是额度余量。免费额度是有限的有的通道每天刷新有的通道每月刷新。网关在路由的时候会优先选择额度余量充足的通道避免某个通道提前耗尽。这三个维度组合起来就形成了一张路由决策表。实际运行的时候网关先判断任务类型然后在对应类型的通道池里按额度余量排序选最充裕的那个发出去。2.3 路由决策的优先级规则路由规则我定了四条优先级从高到低依次是显式指定优先如果请求里明确带了模型标识直接走指定通道不做任何自动路由。这是给需要精确控制的场景留的口子。任务类型匹配根据请求内容自动判断任务类型代码类、长文类、翻译类分别走对应的通道池。额度余量排序同一类型池内按剩余额度从高到低排序优先用余量多的。失败降级如果首选通道请求失败自动降级到同池的下一个通道最多重试三次。这四条规则写在一个路由配置文件里改起来很方便。我试过调整优先级顺序比如把额度余量提到任务类型前面结果发现代码补全经常被路由到长文通道上延迟明显变高后来又改回来了。提示路由规则不要设得太复杂超过五条优先级之后维护成本会急剧上升而且排查问题的时候很难定位到底是哪条规则生效了。3. 核心配置文件与通道接入实操3.1 models.json 的结构设计整个网关的核心就是一个models.json文件所有通道信息、路由规则、额度配置都写在这里面。我先把结构拆开讲然后再给完整示例。顶层是三个字段channels放所有通道的定义routing放路由规则settings放全局设置。channels是一个数组每个元素代表一个通道包含通道名称、接口地址、认证信息、能力标签、额度信息这几个字段。能力标签是我自己定义的用来标记这个通道适合什么任务。比如code表示适合代码任务long-context表示适合长文任务translate表示适合翻译任务。一个通道可以同时有多个标签。额度信息包含两个字段quota_total表示总额度quota_used表示已用额度。网关每次请求成功后会更新quota_used路由的时候用quota_total - quota_used算余量。{ channels: [ { name: channel-a, endpoint: https://api.example-a.com/v1/chat/completions, api_key: sk-xxxxxxxx, tags: [code, fast], quota_total: 1000, quota_used: 0, timeout: 30 }, { name: channel-b, endpoint: https://api.example-b.com/v1/chat/completions, api_key: sk-yyyyyyyy, tags: [long-context, translate], quota_total: 500, quota_used: 0, timeout: 60 } ], routing: { rules: [ { match: explicit, action: direct }, { match: task:code, pool: [code, fast] }, { match: task:long, pool: [long-context] }, { match: task:translate, pool: [translate] } ], fallback_retries: 3 }, settings: { listen_port: 8787, log_level: info, quota_reset_cron: 0 0 * * * } }这个结构我改过好几版。最早的时候把路由规则写死在代码里后来发现每加一个通道就要改代码太麻烦就全部挪到配置文件里了。现在新增通道只需要在channels数组里加一项路由规则完全不用动。3.2 通道接入的通用步骤接入一个新通道我总结下来是四步。第一步是确认接口兼容性。大部分免费通道提供的都是和主流接口兼容的格式请求体和响应体结构基本一致。如果不兼容需要在网关层做一层格式转换。我遇到过几个通道的响应格式略有差异比如把choices字段换了个名字这种就在网关里加一个适配器处理。第二步是填写通道定义。把接口地址、认证密钥、能力标签、额度信息填进models.json。能力标签这一步很关键填错了会导致路由到不合适的通道上。我的经验是宁可多打几个标签也不要漏打因为路由的时候是按标签匹配的。第三步是连通性测试。网关提供了一个测试接口发一个最简单的请求过去看能不能正常返回。这一步能提前发现认证失败、地址写错、超时设置不合理等问题。第四步是额度校准。免费通道的额度统计方式各不相同有的按请求次数算有的按 token 数算。我统一按请求次数来统计每次成功请求quota_used加一。如果通道本身有额度查询接口可以写一个定时任务去同步真实额度。3.3 任务类型自动判断的实现任务类型判断是路由的核心环节。我的做法是在网关层做一个轻量级的分类器根据请求内容里的特征来判断。代码类任务的特征比较明显请求里包含代码块标记、包含常见的编程关键词如function、class、import、def、或者请求的system提示里提到了代码相关的要求。满足其中任意一条就标记为代码类。长文类任务的特征是请求内容长度超过一定阈值我设的是 2000 个字符。超过这个长度大概率是文档总结、长文分析之类的任务需要走大上下文通道。翻译类任务的特征是请求里包含明确的翻译指令比如“翻译成”、“translate to”、“用中文表达”之类的关键词。这三个分类器都是基于规则的没有用模型来判断原因是规则判断足够快而且可解释性强。如果判断错了看一眼规则就能知道为什么。我试过用一个小模型来做分类准确率确实高一点但引入了一个额外的模型调用延迟增加了不少后来还是换回了规则判断。注意任务类型判断不要做得太细三类足够了。我一开始分了七八类结果发现很多类之间边界模糊经常误判反而增加了排查成本。4. 完整实操流程与关键环节记录4.1 环境准备与依赖安装我是在一台 Linux 机器上跑的这套网关系统版本是 Ubuntu 22.04。选 Linux 的原因是长期运行稳定而且方便用系统服务来管理进程。如果你用的是 Windows 或者 macOS流程基本一样只是服务管理方式不同。依赖方面只需要两样东西一个是运行时环境我用的是 Node.js 18因为生态成熟、写起来快另一个是进程管理工具我用的是系统自带的 systemd也可以用 pm2 之类的工具替代。安装步骤很简单先把运行时装好然后建一个项目目录把网关代码放进去最后配置成系统服务让它开机自启。# 安装 Node.js 18 curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo apt-get install -y nodejs # 创建项目目录 mkdir -p /opt/workbuddy-gateway cd /opt/workbuddy-gateway # 初始化项目 npm init -y npm install express axios这里用 Express 来做 HTTP 服务用 Axios 来做请求转发。这两个库都很成熟文档齐全遇到问题容易查到解决方案。4.2 网关服务的核心代码实现网关的核心逻辑分三块接收请求、判断任务类型、转发到目标通道。我把这三块拆成三个函数来写主流程很清晰。接收请求的部分负责解析请求体提取出消息内容和可选的模型标识。如果带了模型标识直接走指定通道如果没有进入任务类型判断。任务类型判断的部分就是前面说的规则分类器根据消息内容打上任务标签。转发部分根据任务标签去models.json里找匹配的通道池按额度余量排序选第一个发出去。如果失败降级到下一个最多重试三次。const express require(express); const axios require(axios); const fs require(fs); const app express(); app.use(express.json({ limit: 10mb })); const config JSON.parse(fs.readFileSync(./models.json, utf-8)); function detectTaskType(messages) { const text messages.map(m m.content).join( ); if (/|function |class |import |def /.test(text)) return code; if (text.length 2000) return long; if (/翻译|translate to|用中文表达/.test(text)) return translate; return code; } function pickChannel(taskType) { const pool config.channels .filter(ch ch.tags.includes(taskType)) .sort((a, b) (b.quota_total - b.quota_used) - (a.quota_total - a.quota_used)); return pool[0]; } app.post(/v1/chat/completions, async (req, res) { const taskType detectTaskType(req.body.messages); let retries config.routing.fallback_retries; let channel pickChannel(taskType); while (retries 0 channel) { try { const response await axios.post(channel.endpoint, req.body, { headers: { Authorization: Bearer ${channel.api_key} }, timeout: channel.timeout * 1000 }); channel.quota_used 1; fs.writeFileSync(./models.json, JSON.stringify(config, null, 2)); return res.json(response.data); } catch (err) { retries - 1; channel pickChannel(taskType); } } res.status(503).json({ error: all channels failed }); }); app.listen(config.settings.listen_port, () { console.log(Gateway listening on port ${config.settings.listen_port}); });这段代码我跑了大概两周中间改过几次。最大的改动是把额度更新从内存改成了写文件因为有一次进程重启之后额度统计全丢了导致路由到了已经耗尽的通道上。写文件虽然慢一点但数据不会丢。4.3 配置成系统服务并开机自启代码跑起来之后下一步是让它作为系统服务长期运行。我写了一个 systemd 的 service 文件放在/etc/systemd/system/目录下。[Unit] DescriptionWorkBuddy Free Model Gateway Afternetwork.target [Service] Typesimple WorkingDirectory/opt/workbuddy-gateway ExecStart/usr/bin/node index.js Restartalways RestartSec5 Userroot [Install] WantedBymulti-user.target写完之后执行三条命令重载服务配置、启动服务、设置开机自启。sudo systemctl daemon-reload sudo systemctl start workbuddy-gateway sudo systemctl enable workbuddy-gateway启动之后用systemctl status看一眼状态确认是 active 就说明跑起来了。然后用 curl 发一个测试请求看能不能正常返回。curl -X POST http://127.0.0.1:8787/v1/chat/completions \ -H Content-Type: application/json \ -d {messages:[{role:user,content:写一个快速排序函数}]}如果返回了正常的响应内容说明整条链路是通的。如果报错先看网关日志再看通道本身的连通性一层一层排查。4.4 把 WorkBuddy 接到网关上网关跑起来之后最后一步是把 WorkBuddy 的模型地址指向本地网关。在 WorkBuddy 的设置里找到模型配置项把接口地址改成http://127.0.0.1:8787/v1认证密钥随便填一个非空值就行因为认证是在网关层做的WorkBuddy 这边不需要真实密钥。改完之后在 WorkBuddy 里发一个测试请求看能不能正常返回。如果返回了说明整条链路打通了。之后你在 WorkBuddy 里的所有模型调用都会经过网关自动路由到最合适的通道上。我实测下来代码补全的延迟比之前手动切换通道的时候低了大概百分之三十因为网关总是选当前最快的通道。长文总结的失败率也降了不少因为网关会自动降级重试不会因为单个通道超时就整个失败。5. 常见问题与排查技巧实录5.1 请求全部失败怎么排查这是最常见的问题表现是网关返回 503所有通道都试过了但都失败。排查思路是从外到内一层一层看。先看网关日志确认请求有没有到达网关。如果日志里没有记录说明请求根本没发过来检查 WorkBuddy 那边的接口地址填对了没有。如果日志里有记录但显示所有通道都失败逐个通道测试连通性。用一个最简单的 curl 命令直接请求通道的接口地址看能不能通。如果不通检查接口地址、认证密钥、网络连通性。如果通道本身是通的但网关转发失败检查网关的请求体格式和通道要求的格式是否一致。有的通道对请求体的字段有额外要求比如必须带model字段这种就在网关层补上。5.2 路由到了错误的通道上表现是代码任务被路由到了长文通道上延迟明显变高。排查方法是看网关日志里记录的任务类型判断结果确认分类器有没有误判。常见的误判场景是请求内容里同时包含了代码和长文特征。比如你发了一段代码让模型解释内容长度超过了 2000 字符分类器可能先匹配到长文类型。解决办法是调整分类器的判断顺序把代码类判断提到长文类前面。我踩过的另一个坑是翻译类判断的关键词太宽泛请求里只要出现“翻译”两个字就被标记为翻译类结果有些代码注释里带了“翻译”的请求也被误判了。后来把关键词改成了更精确的匹配模式问题就解决了。5.3 额度统计不准怎么办额度统计不准的表现是路由到了已经耗尽的通道上请求失败。原因是网关的额度统计和通道的真实额度有偏差。偏差来源主要有两个一是网关统计的是请求次数但通道可能按 token 数算额度二是网关统计有延迟比如并发请求的时候多个请求同时读到同一个余量值。解决办法是加一个额度校准任务定时去通道的额度查询接口拉真实额度覆盖网关的本地统计。如果通道没有额度查询接口就手动校准定期检查一下各通道的实际余量手动更新models.json里的quota_used字段。提示额度校准的频率不用太高每天一次足够了。太频繁反而会增加通道的请求压力有些通道对额度查询接口也有频率限制。5.4 常见问题速查表问题现象可能原因排查方法解决方式网关返回 503所有通道失败逐个通道 curl 测试修复失败通道或增加新通道延迟明显变高路由到了慢通道查看网关日志的任务类型调整分类器规则或通道标签额度提前耗尽统计偏差或并发竞争对比网关统计和真实额度加额度校准任务请求格式报错通道格式不兼容对比请求体字段差异网关层加格式适配服务频繁重启内存泄漏或崩溃查看 systemd 日志加异常捕获和内存监控这张表是我在实际运维中慢慢攒出来的基本上覆盖了百分之九十以上的问题。遇到新问题的时候先查表表里没有的再从头排查。6. 几个让我少走弯路的实操心得第一个心得是配置文件一定要做版本管理。我一开始没做改错了配置之后想回滚都回不去。后来把models.json放进了 git 仓库每次改动都提交一次出问题直接回滚到上一个版本省了很多事。第二个心得是日志要记全但要分级。我最早的时候所有日志都打在一个文件里排查问题的时候翻半天。后来改成了分级日志info 级别只记请求路由结果debug 级别记完整的请求体和响应体。平时看 info 就够了排查具体问题的时候再开 debug。第三个心得是通道不要一次性全接进来。我一开始把 14 个通道全配上了结果路由逻辑复杂了很多排查问题也麻烦。后来改成了分批接入先接三个最常用的跑稳了再加新的。这样每次只引入一个变量出问题容易定位。第四个心得是超时设置要因通道而异。不同通道的响应速度差别很大统一设一个超时值要么导致慢通道频繁超时要么导致快通道等太久。我的做法是每个通道单独设超时快通道设 15 秒慢通道设 60 秒这样各得其所。第五个心得是降级重试要有上限。我最早设的是无限重试结果某个通道挂了之后网关一直在重试把其他通道的额度也耗光了。后来改成了最多重试三次超过就返回失败让上层决定怎么处理。这套网关我跑了大概两个月中间迭代了七八个版本现在算是比较稳定了。最大的感受是免费额度虽然香但管理成本不低如果没有一套自动化的路由和额度管理机制手动切换的时间成本可能比省下来的钱还高。把这件事自动化之后我基本上不用再关心哪个通道还剩多少额度只管发请求就行网关会帮我选最合适的通道。