news 2026/10/4 23:32:55

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
华硕路由器变身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 admin@192.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 2>&1而不是写进/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

单次模板调用只能解决"一句话换一句话"。真正的提示流编排器,关键在"流"这个字:一次用户请求可以被拆成多步,每步用不同模型或不同模板,上一步的输出作为下一步的输入。最典型的模式是"先分类,再生成":

  1. 第一步,让本地小模型做意图识别,输出一个 JSON 结构,比如{"intent":"summary","language":"zh"}。
  2. 第二步,编排器根据intent选择对应的总结模板,再把原文丢给云端大模型,得到结构化摘要。
  3. 第三步,把结果缓存并返回给客户端。

这个模式的价值在于:把便宜的小模型用在高频简单任务上,把贵的大模型用在真正需要深度思考的任务上。路由器上的编排器不负责算力,只负责按规则分配。多步流可以用一个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 2>&1 &

然后确认端口在监听:

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 2>&1 & 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 2>&1 & 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 分支,内存 512MB,Node 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 做版本管理,以及如何让它和智能家居平台对接。如果你跑通了,欢迎回来交流你踩过的坑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 23:03:21

Shell编程基石:echo、read、printf、test四大命令的实战避坑指南

1. 为什么要花一整篇去讲这四个命令做Shell编程这几年,我见过太多脚本跑着跑着就崩的案例,十有八九都出在echo、read、printf、test这四个命令上。你说它们简单?确实,语法一眼就能看完。但恰恰是这种“看着简单”的错觉&#xff0…

作者头像 李华
网站建设 2026/10/4 22:53:50

EEG睡眠分期CNN模型:端到端训练与部署实战

简介:本资源是一份面向本科毕业设计与人工智能课程实践的深度学习睡眠状态检测项目实现,聚焦EEG信号分类任务,适用于人工智能、生物医学工程及相关专业学生开展期末大作业或课程设计。压缩包共3个文件,含2个核心Python脚本&#x…

作者头像 李华
网站建设 2026/10/4 22:53:18

基于Gabor滤波+PCA+LDA+SVM的人脸表情识别完整方案

简介:基于Gabor滤波、PCALDA降维与SVM分类的人脸表情/微表情识别系统,以Python实现并搭配PyQt图形界面,适合人脸识别方向的初学者、科研人员及毕业设计开发者使用。整套资源共808个文件,压缩包约35.85MB,包含749张JPG图…

作者头像 李华
网站建设 2026/10/4 22:51:18

VSCode搭建C/C++环境全指南:编译器配置、JSON文件与多文件构建

简介:面向需要在 Visual Studio Code 中搭建 C/C 开发环境的初学者与进阶开发者,这份资源包针对编译器路径配置、调试器接入、插件设置等常见痛点,提供可直接参考的配置方案与示例代码。压缩包共 25 个文件,主要包含 9 个 json 配…

作者头像 李华