ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

华硕路由器变身AI边缘网关:提示流编排器部署实战

华硕路由器变身AI边缘网关:提示流编排器部署实战 先说结论这篇文章讲的不是把一个大模型权重塞进华硕路由器——那不可能任何一台家用路由器的闪存和内存都装不下几 B 甚至几十 B 的参数。真正落地的是把AI 提示流编排器这种大脑调度层搬到路由器上做一个轻量边缘网关局域网里所有设备把提问统一丢给路由器由它负责套模板、去重、路由到不同AI 引擎、再返回结果。整个项目跑在Merlin梅林插件体系里代码全开源属于我的开源系列第 12 篇。为什么要这么折腾因为它解决了一个很实际的问题现在家里有本地推理引擎GPU 盒子也有云端大模型 API设备一多每个 App 都要单独配密钥和环境非常烦。把所有请求收口到路由器上之后密钥只在路由器上存一份设备端只需要知道把问题发给路由器。同时还能做缓存、限流、故障回退。适合谁看如果你手上有华硕路由器而且已经刷了 Merlin平时喜欢折腾开源软件那这套方案基本可以直接复制。还没刷 Merlin 的也可以先收藏等哪天动手了再回来翻。1. 为什么把 AI 提示流编排器部署在华硕路由器上方案与架构拆解1.1 标题拆开看三件事其实是一件事你可能第一眼会疑惑提示流编排器、华硕路由器、轻量边缘网关这三样东西是怎么凑到一起的其实它们是一条链路。所谓提示流编排器简单理解就是一个提示词加工厂 路由调度台局域网设备把需求发过来它负责把需求填充进预先写好的提示模板然后决定把这段对话交给哪个上游 AI 引擎最后把返回结果回传给设备。轻量边缘网关这个角色是路由器天然的位置。你可以把路由器想象成小区门口的快递中转站住户手机、电脑、智能音箱不用知道每一单该走哪家快递公司统一把包裹交给中转站由中转站根据地址、时效、预算去分配线路。AI 编排器干的就是同一件事——只不过传输的是请求和响应快递员变成了本地推理引擎和云端大模型 API。很多人会问为什么不直接在电脑上跑一个服务因为路由器有一个不可替代的天然属性——它是整个局域网的必经枢纽而且 7x24 小时开机非常适合做统一入口。电脑关机、NAS 休眠都没关系路由器几乎永远在线。这套方案适合两类人一类是家里已经有本地推理设备比如带 GPU 的小主机想让全家设备共享这套能力另一类是订阅了云端大模型 API想统一管理密钥、避免每台设备都各自暴露密钥的人。1.2 边缘网关的数据流向先看清楚一条请求是怎么走的手机 / 电脑 / 智能家居 | 发起 HTTP 请求只带问题和参数 v 华硕路由器Merlin 插件 / AI 提示流编排器 | 加载模板、查询缓存、选择引擎、执行回退 -------------------------------- | | v v 本地推理引擎局域网 GPU 盒子 云端大模型 API这条链路里编排器本身不产生智能它只做调度。好处可以归结为三个收敛密钥收敛、请求收敛、策略收敛。密钥收敛不用说API Key 只在路由器上存一份设备端看不到也不怕哪台设备丢了自己去泄露。请求收敛是所有请求的历史和缓存都集中在路由器上重复问题可以直接命中缓存不用次次打到上游。策略收敛就更有意思了回退逻辑、限流阈值、模型优先级只需要改一处配置全屋设备立刻生效不用挨个设备去更新。1.3 为什么选 Node.js 而不是 Python做技术选型的时候我一开始试的是 Python。华硕路由器在 Merlin 固件下确实可以通过 Entware 装 python3但实测下来有两个问题一是安装体积大python3 加依赖塞进 JFFS 闪存里非常占空间装一个 fastapi 折腾下来可能几十 MB二是内存和启动速度不理想路由器 CPU 本就不强热加载调试一次要等好几秒。折腾几次之后我放弃了 Python转向 Node.js。Node.js 在我这个场景下的优势非常明显单进程常驻内存大约 25-40 MB安装包小npm 生态成熟。更重要的是Node 的异步模型非常适合这种转发请求、等上游、回传结果的网关场景不会因为一个请求在等云端就把整个服务卡住。我后面把整个网关用 Node 内置模块写完没有引入任何第三方依赖这在资源受限的路由器上非常重要。如果你已经有树莓派或者 NAS同一套代码也可以跑在那里路由器只做转发但既然系列主题是塞进路由器后面全部以路由器直接运行 Node 服务来讲。2. Merlin 插件基座准备JFFS 分区、Entware 与目录规划2.1 前置条件Merlin 固件与 JFFS 分区这个项目依赖Merlin梅林固件因为它保留华硕官方管理界面的同时开放了完整的脚本执行能力。手头如果是常见华硕型号可以在 Merlin 官方支持列表里找到对应固件刷机过程这里不展开网上有成熟的救援模式教程但有一条必须提醒刷机前备份原厂配置刷完进入后台后第一件事是打开 JFFS 支持。登录路由器后台依次打开系统管理 - 系统把启用 JFFS 分区支持选为是应用后路由器会自动重启。重启之后通过 SSH 登进去先确认挂载mount | grep jffs看到/jffs挂载成功就可以继续。JFFS 是路由器内置闪存上划出来的可写分区用来存放配置和脚本掉电不丢。顺手把 SSH 服务打开系统管理 - 系统 - 服务启用 SSH端口默认 22 就行。后面所有操作都通过 SSH 完成如果你不熟悉命令行这恰恰是一个很好的练手机会因为 Merlin 环境其实就是精简版 Linux。2.2 用 Entware 安装 Node.js 运行时Entware 可以理解为家用路由器圈的包管理器。Merlin 固件里安装 Entware 通常两条路一是直接在官方面板里找到 Entware 相关的安装入口二是 SSH 到设备后从官方仓库拉安装脚本执行。不同路由器架构对应不同安装包装好后统一落在/opt目录下。# 进入路由器 SSH ssh admin192.168.50.1 # 安装 Entware以 Entware 官网当前提供的安装脚本为准 wget -O - http://entware.net/installer/entware_install.sh | sh # 刷新软件源 /opt/bin/opkg update # 安装 Node.js /opt/bin/opkg install node # 验证版本 /opt/bin/node -v安装完 node 后有一个小坑node命令不会自动进入 shell 的 PATH因为当前用的是路由器默认环境。要么每次都用/opt/bin/node绝对路径要么手动把/opt/bin:/opt/sbin加进环境变量。我的建议是启动脚本里全部使用绝对路径不同固件版本环境变量差异很大绝对路径最稳。2.3 项目目录、权限与日志策略我规划的目录结构放在/jffs/addons/ai-orchestrator/下/jffs/addons/ai-orchestrator/ ├── server.js ├── config.json ├── prompts/ │ ├── code-review.json │ ├── summary.json │ └── classify.json ├── watchdog.sh └── cache/放在/jffs而不是/opt的原因很简单这个目录就是路由器系统留给用户持久化配置的地方固件升级不会清掉重启也不会丢。如果后续你给路由器插了 USB 硬盘Entware 可能被移动到 USB 上反而容易路径混乱。把项目固定放在/jffs路径永远稳定。配置文件权限必须处理config.json里装着上游引擎的 API Key权限太松的话局域网里能 SSH 的用户都能读到。执行chmod 700 /jffs/addons/ai-orchestrator chmod 600 /jffs/addons/ai-orchestrator/config.json日志策略是很多新手容易忽略的JFFS 是闪存有写入寿命频繁写日志等于损耗硬件。所以服务运行日志要重定向到/tmp这是路由器的内存文件系统重启即清不伤闪存。后面启动命令里我会统一用 /tmp/ai-orchestrator.log 21而不是写进/jffs。3. 提示流编排器核心实现模板引擎、多引擎路由、缓存限流实战3.1 配置驱动引擎列表怎么声明整个编排器是配置驱动的也就是说加一个模型、改一个优先级、调一个超时时间只需要改config.json不用动代码。这个设计很重要路由器上调试环境不友好代码越少动越好。下面是一份典型配置{ host: 0.0.0.0, port: 8080, cacheTtl: 300, timeout: 30000, rateLimit: { windowMs: 60000, max: 30, message: request too frequent }, engines: [ { name: local-llm, type: chat-completions, baseUrl: http://192.168.50.10:11434/v1, apiKey: internal, model: qwen2.5:7b, weight: 1 }, { name: cloud-llm, type: chat-completions, baseUrl: https://api.example.com/v1, apiKey: sk-xxxx, model: cloud-chat, weight: 2 } ] }引擎类型我用的是统一的chat-completions对应现在行业里通用的大模型对话补全接口格式。为什么不用各家私有 SDK因为路由器上的 Node 进程越干净越好用统一 HTTP 接口就能对接本地推理引擎和云服务不需要为每家单独装一个 SDK。这也是边缘网关的典型做法兼容性优先把复杂能力留给上游。weight字段用来做默认路由数字小的优先所以local-llm会在日常请求中优先被使用。3.2 模板系统{{var}} 就够用的轻量渲染提示流编排器的第一个核心能力是模板。我的诉求很简单不在路由器上跑一个完整的模板引擎库只需要支持{{变量}}替换。像 Mustache、Handlebars 这类库确实强大但解析成本高还要额外装依赖在这个场景里属于过度设计。下面是一个模板文件示例取名code-review.json{ id: code-review, name: 代码评审, system: 你是一名有 15 年经验的资深代码评审专家请从正确性、性能、安全性三个维度输出评审意见。, user: 下面是待评审代码\n\n{{code}}\n\n\n请输出1. 主要问题 2. 风险评级 3. 修改建议。, route: { prefer: cloud-llm, fallback: local-llm } }渲染函数用正则一行搞定function fill(template, vars) { return (template || ).replace(/\{\{(\w)\}\}/g, (_, key) { return vars[key] ! undefined ? String(vars[key]) : ; }); }注意{{code}}这样的变量在 JSON 字符串里对应消息里的换行需要写成\n。渲染函数只做替换不做复杂的条件判断和遍历因为模板复杂之后调试成本会转嫁到路由器上违背轻量的初衷。模板里也没有多轮历史字段因为边缘网关第一版不需要维护对话状态每个请求都是无状态的一次性补全需要长时间上下文的功能应该交给专门的应用而不是塞进路由器。3.3 智能路由与自动回退路由逻辑决定了一个请求最终交给哪台引擎。我在 server.js 里实现的是最简单的优先 回退策略先看模板里的route.prefer如果没有就用引擎数组顺序调用失败后自动尝试下一个。function buildRoute(tpl) { const ordered []; const prefer tpl.route tpl.route.prefer; if (prefer) { const match CONFIG.engines.find(e e.name prefer); if (match) ordered.push(match); } CONFIG.engines.forEach(e { if (!ordered.includes(e)) ordered.push(e); }); return ordered; }为什么把回退做在编排器而不是客户端因为客户端往往是脚本、智能家居或者随手写的 App它们不应该感知这台引擎挂了我换一台吧这种基础设施问题。回退是网关的职责客户端只会看到响应最多多一个engine字段告诉你这次是谁回的。这个字段我实测非常有用调试时一眼就能看出流量走了哪条链路。3.4 多步提示流从一个 prompt 变成一条 pipeline单次模板调用只能解决一句话换一句话。真正的提示流编排器关键在流这个字一次用户请求可以被拆成多步每步用不同模型或不同模板上一步的输出作为下一步的输入。最典型的模式是先分类再生成第一步让本地小模型做意图识别输出一个 JSON 结构比如{intent:summary,language:zh}。第二步编排器根据intent选择对应的总结模板再把原文丢给云端大模型得到结构化摘要。第三步把结果缓存并返回给客户端。这个模式的价值在于把便宜的小模型用在高频简单任务上把贵的大模型用在真正需要深度思考的任务上。路由器上的编排器不负责算力只负责按规则分配。多步流可以用一个flow字段描述核心解释器是一个循环async function runFlow(tpl, vars) { let context { ...vars }; for (const step of tpl.flow) { const stepTpl PROMPTS[step.promptId]; const rendered render(stepTpl, context); const engine pickEngine(stepTpl, CONFIG.engines); const data await callEngine(engine, [rendered.system, rendered.user]); // step.output 决定把结果写进 context 的哪个字段 context[step.output] data.choices[0].message.content; } return context; }实际编码时需要对中间返回的 JSON 做解析容错、对缺失字段做重试但骨架就是这个样子。第一版可以先只做单步跑通之后再扩展成多步演进成本很低。3.5 缓存与限流边缘网关的两道闸门缓存是我认为整个项目最值钱的功能。家庭环境里家人经常问相似的问题比如今天天气适合跑步吗这种请求如果每次都打到上游引擎既慢又费钱。我在编排器里做的是完全确定性的缓存把渲染后的 system 消息和 user 消息拼起来算 SHA1同一模板加同一变量内容在 TTL 内直接返回上次结果。const cacheKey crypto.createHash(sha1) .update(system | user) .digest(hex);这里没有做语义相近的向量缓存因为路由器上去跑 embedding 不现实家庭重复问题的量级通常用不着语义匹配。想升级的话可以在局域网 GPU 盒子上跑一个向量接口把语义缓存下沉到那边。限流则更简单按来源 IP 做滑动窗口计数超过阈值返回 429。路由器天然是所有局域网流量的必经点这个限流不需要防火墙配合就能生效。3.6 注册开机自启并接入局域网客户端全部代码写完放到/jffs/addons/ai-orchestrator/server.js先手动启动验证/opt/bin/node /jffs/addons/ai-orchestrator/server.js /tmp/ai-orchestrator.log 21 然后确认端口在监听netstat -tlnp | grep 8080客户端请求长这样curl -X POST http://192.168.50.1:8080/prompt \ -H Content-Type: application/json \ -d {promptId:code-review,vars:{code:const x 1;}}开机自启用 Merlin 的services-start脚本内容如下#!/bin/sh if [ -f /jffs/addons/ai-orchestrator/server.js ]; then /opt/bin/node /jffs/addons/ai-orchestrator/server.js /tmp/ai-orchestrator.log 21 fi保存为/jffs/scripts/services-start加执行权限。再加一个 watchdog 防止进程意外退出用小脚本#!/bin/sh if ! pgrep -f node /jffs/addons/ai-orchestrator/server.js /dev/null; then /opt/bin/node /jffs/addons/ai-orchestrator/server.js /tmp/ai-orchestrator.log 21 fi保存为/jffs/addons/ai-orchestrator/watchdog.sh然后在services-start里注册 cron 任务让路由器每两分钟检查一次cru a aiWatch */2 * * * * /jffs/addons/ai-orchestrator/watchdog.sh这样路由器重启后服务会自动恢复进程意外退出也会在两分钟内被拉起来。4. 实测记录与避坑指南华硕路由器上的稳定运行经验4.1 当前实测环境我自己跑这套方案的环境是华硕 AX 系列路由器刷 Merlin 388 分支内存 512MBNode v18。上游有两台引擎局域网内一台 GPU 小主机跑着开源大模型暴露在192.168.50.10:11434云端还挂了一个兼容 chat-completions 格式的大模型 API。实测连续运行两周进程稳定在 30 个左右 TCP 连接内存占用约 45MB空载时 CPU 几乎为 0并发请求上来时 CPU 短暂冲到 20% 左右。这个负载对路由器来说没有压力也不影响正常上网体验。4.2 常见问题速查表现象可能原因解决办法进程启动后一访问就退出配置 JSON 语法错误先用电脑上的node -e JSON.parse(...)静态校验配置接口返回 404模板 id 拼写错误或 prompts 目录未加载检查 PROMPTS 对象是否包含该 id本地引擎请求超时引擎地址写错或 GPU 机器休眠用 curl 直接访问 baseUrl 测试必要时调大 timeout上游返回 401API Key 配置错误先在工作站上用同一把 Key 调通再同步到路由器路由器重启后服务没了services-start 没执行权限chmod x /jffs/scripts/services-start/jffs 空间告急日志或 node_modules 写进了闪存日志改到 /tmp依赖尽量用原生模块node 命令找不到Entware 的 PATH 未配置启动脚本里全部用/opt/bin/node绝对路径4.3 内存、闪存与日志的取舍心得第一点心得是关于 Node 版本。Entware 仓库里的 Node 有时不是最新版如果你用了太新的语法会直接报错。我在初版里用了 Node 18 的全局 fetch实测部分固件上的 Node 16 不认后来把所有上游请求改成用http/https模块手写才彻底解决了兼容问题。这是边缘设备开发的常态永远用最通用的 API。第二点心得是别把调试器开在路由器上。在 JFFS 上跑node --inspect调试日志和心跳文件会频繁写闪存属于慢性损耗硬件。正确流程是把server.js和config.json复制到电脑上本地 Node 调试好再回传路由器。整个项目没有第三方依赖所以电脑和路由器上的行为几乎一致调试效率高很多。第三点是缓存策略的细节我刻意没有把缓存写到磁盘而是放在内存 Map 里。虽然路由器重启后缓存会丢但换来的是闪存零写入这是值得的。TTL 设成 300 秒足够覆盖家里人短时间内重复问同样问题的场景又不至于让信息过期太久。如果你确实想把缓存持久化我的建议是放 USB 硬盘而不是 JFFS理由还是闪存寿命。这次从 0 到 1 把提示流编排器搬上华硕路由器的过程我最大的感受是真正难的不是写 HTTP 服务和路由算法而是想清楚哪些东西应该放在边缘、哪些应该放在上游。路由器是极好的边缘节点但它资源有限所以编排器只做调度、缓存和网关不做推理本地 GPU 盒子做便宜快速的推理云端模型处理复杂任务。这种分工让整个系统既省钱又有扩展空间。如果有人想复现我的建议是第一步先在电脑上把 server.js 和两个模板跑起来第二步再上路由器。整个过程最花时间的往往不是技术而是对提示流本身的思考——你的用户会提什么问题哪些问题应该走哪个引擎哪些结果值得缓存。把这些想清楚路由器上的那几十行代码只是例行公事。最后的最后提醒一句折腾路由器固件和脚本之前先把配置备份好尤其是 JFFS 里已有的脚本和密钥一次误操作清零会很肉疼。这个系列我会继续分享如何给编排器加一个 Web 管理界面、如何把模板放进 Git 做版本管理以及如何让它和智能家居平台对接。如果你跑通了欢迎回来交流你踩过的坑。
返回列表