ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

华硕路由器上搭建AI边缘网关:Merlin插件实现提示词编排与统一接入

华硕路由器上搭建AI边缘网关:Merlin插件实现提示词编排与统一接入 如果你家里不止一台设备要调用大模型大概率会遇到这种尴尬手机 App 配一次 Key电脑脚本配一次NAS 上的服务又要单独配提示词模板散落得到处都是。我自己折腾下来最后把 AI 提示流编排器做成了华硕路由器上的一个 Merlin 插件顺便让它充当整个局域网的轻量边缘网关。先说明一下这里说的“AI 引擎”不是把几十 B 的大模型塞进路由器而是把提示词编排、模型路由、缓存、鉴权这些控制逻辑放到路由器上。这套方案适合家里有华硕路由器、又想让所有设备统一走一个 AI 出口的人参考下面我从需求评估、架构设计、插件开发、资源优化到安全配置把整个落地过程完整记录下来。1. 把 AI 网关放上路由器之前先算清这笔账1.1 不是所有“AI 引擎”都适合塞进路由器华硕路由器的硬件底子其实不错现在的 RT-AX86U 这类机器已经有四核 ARM 处理器加 512MB 内存跑常规网络服务绰绰有余。但要说本地跑大模型那就完全是另一回事了。一个 7B 参数的量化模型随便就要 4GB 以上的内存0.5B 的超小模型也要几百 MB路由器这点内存根本不够用强行塞进去只会让 DHCP、DNS 这些基础服务跟着遭殃。所以我在项目一开始就定了一个原则路由器承担的是控制面推理留在后端。也就是说真正的 AI 推理可以放在局域网里那台 NAS 上的 Ollama也可以放到一个兼容 OpenAI 接口的远端推理服务上而路由器上的编排器负责把请求转发过去、把结果缓存下来、把敏感信息脱敏。这个边界想清楚之后整个项目才变得可行。1.2 路由器作为边缘网关的天然优势为什么非要选路由器因为它是家里为数不多真正 7x24 小时开机的 Linux 设备。PC 会休眠NAS 可能因为硬盘策略待机但路由器基本一个月都不重启一次。把它作为一个边缘网关意味着内网任何设备任何时候发起 AI 请求入口都是活的。另一个优势是网络位置。所有内网设备的流量都要经过路由器在这里做统一管控最方便。比如我想限制某台设备每天调用模型 API 的次数或者想记录所有提示词的审计日志放在路由器上都能拿到完整视角。功耗也很低一台路由器跑着网关整体也就十几瓦比专门开一台小主机划算得多。1.3 我的目标场景我实际需要解决的核心问题有几个第一多个设备不要再各自保存 API Key统一让路由器保管第二重复的提示词请求不要每次都打到模型后端浪费钱和延迟第三对要出内网的提示词做脱敏处理避免手机号、邮箱这类信息被直接丢给第三方服务第四后端模型服务出故障时能快速降级。这四个问题综合起来其实就是需要一个 AI 提示流编排器加轻量边缘网关而 Merlin 插件体系恰好提供了在路由器上部署它的路径。2. 提示流编排器的四个模块接入、策略、执行、观测2.1 接入层兼容 OpenAI 的网关协议接入层解决的是“设备怎么调用”的问题。我直接兼容了 OpenAI 的/v1/chat/completions接口格式这样内网任何现有客户端只要改一下base_url就能用不需要额外写 SDK。网关对外主要暴露两个接口POST /v1/chat/completions标准对话接口请求体直接透传给后端模型POST /v1/prompts/name/execute执行一个预先定义好的提示流模板。第二个接口是提示流编排器的核心体现。比如我定义了一个“翻译并润色”的流程调用方只需要传一句话网关会自动补全系统提示词、拼接历史上下文、做脱敏、调用模型、再把最终结果按固定格式返回。这样业务侧就不用关心提示词工程改提示词模板也不需要动业务代码。2.2 策略层模型路由、缓存与限流策略层是网关的“大脑”。它决定一个请求到了之后应该转发到哪台后端、要不要走缓存、以及能不能放行。我在配置里用 YAML 定义路由规则routes: - match: client_ip: 192.168.50.0/24 model: default backend: local-ollama - match: model: high-quality backend: edge-gpu规则很简单默认模型走局域网内的 Ollama指定high-quality的请求走性能更强的远端 GPU 服务。缓存策略更直接缓存 key 由模型名、提示词哈希、关键采样参数组成。命中缓存时网关直接返回完全不经过后端。限流我也放在这一层按客户端 token 维度做令牌桶。比如客厅的智能音箱每分钟最多 5 次请求超出直接返回 429避免某个异常程序把路由器的内存和带宽吃满。2.3 执行层提示流步骤编排路由器上的编排器没有做复杂的 Agent 循环而是采用可重复执行的静态步骤流。每一步对应一个具体动作执行完再进入下一步。我的默认流程长这样flow: - type: truncate max_len: 4000 - type: sanitize rules: - pattern: 1[3-9]\\d{9} replace: [手机号] - type: call_model backend: auto - type: validate require_json: false执行层拿到请求后会按顺序跑这些步骤。sanitize负责脱敏call_model负责选后端并转发validate负责检查输出是否符合预期。这种静态编排的好处是确定性强出错容易排查。路由器上资源有限我不会在这里跑“自我反思”“多轮工具调用”这类高消耗逻辑那些应该放在性能更强的后端去做。2.4 观测层在 512MB 设备上的日志与指标观测层在路由器上不能太铺张。我一开始想过接 Prometheus但实在没必要为一个网关再跑一套监控系统。最后只暴露了一个/v1/status接口返回进程内存、协程数、缓存命中率、最近 5 条错误信息。日志全部写到内存环形缓冲区最多保留 500 条不落盘避免闪存磨损。真要排障时调用一次状态接口就够了。3. Merlin 插件实战从零搭起一个开机自启的网关3.1 为什么选 Go 而不是 Python选型的时候我认真犹豫过。Entware 里其实有 Python 3FastAPI 也能装但依赖树太庞大了装下来闪存占用轻松上百 MB运行时的内存开销也不友好。路由器上的插件尽量要“体积小、启动快、依赖少”所以我最后用 Go 写了网关主体。Go 交叉编译非常舒服开发机上一条命令就能出 ARM64 的静态二进制GOOSlinux GOARCHarm64 go build -trimpath -ldflags -s -w -o aigw ./cmd/aigw编译出来的文件不到 20MB扔到路由器上直接就能跑不需要安装任何运行时环境。3.2 插件的目录结构与安装脚本Merlin 插件的本质就是在固件原有能力之上叠一套自己的脚本和二进制。我的插件目录结构是这样的/opt/aigw/ ├── bin/ │ └── aigw ├── etc/ │ └── config.yaml ├── scripts/ │ ├── start.sh │ └── install.sh └── web/ └── index.html安装脚本负责把文件复制到/opt/aigw并创建 Entware 的 init 链接#!/bin/sh # /opt/aigw/scripts/install.sh mkdir -p /opt/aigw/{bin,etc,scripts,web} cp aigw /opt/aigw/bin/ cp config.yaml /opt/aigw/etc/ cp web/index.html /opt/aigw/web/ cp scripts/start.sh /opt/aigw/scripts/ chmod 755 /opt/aigw/bin/aigw /opt/aigw/scripts/start.sh ln -sf /opt/aigw/scripts/start.sh /opt/etc/init.d/S99aigw nvram set aigw_enable1 nvram commit echo installed, reboot or run: /opt/aigw/scripts/start.sh startnvram set aigw_enable1是为了让用户可以通过路由器管理页面环境变量开关插件不需要直接改启动脚本。3.3 init 脚本与启动时机Entware 的/opt/etc/init.d/S99aigw会在系统启动、挂载完/opt分区之后被调用。启动脚本我写得很克制#!/bin/sh # /opt/aigw/scripts/start.sh BIN/opt/aigw/bin/aigw CFG/opt/aigw/etc/config.yaml LOG/tmp/aigw.log PIDFILE/var/run/aigw.pid start() { if [ -f $PIDFILE ] kill -0 $(cat $PIDFILE) 2/dev/null; then return 0 fi mkdir -p /tmp/aigw/cache /tmp/aigw/log $BIN -config $CFG $LOG 21 echo $! $PIDFILE } stop() { [ -f $PIDFILE ] || return 0 kill $(cat $PIDFILE) rm -f $PIDFILE } case $1 in start) start ;; stop) stop ;; restart) stop; sleep 1; start ;; esac这里有个容易被忽略的细节日志和缓存目录都放在/tmp下而不是/opt。路由器闪存写入寿命有限频繁写日志很容易把分区写坏。/tmp的代价是重启全丢但缓存和日志本来就是可丢弃数据丢一点不影响大局。3.4 最小可用的 Web 管理页很多路由器插件都要做完整的 WebUI但我这里只放了一个静态index.html由 Go 服务自己托管。页面通过/v1/status拉取状态展示内存、缓存命中率、后端健康状态不做任何修改操作。修改配置都靠 SSH 改 YAML这反而更安全也减少 WebUI 带来的攻击面。4. 资源抠索指南内存、闪存与模型调度的取舍4.1 内存预算要精确到 MB路由器上做服务内存预算是必须提前算好的。我以 RT-AX86U 的 512MB 内存来算账项目内存占用系统基础服务与内核约 250MB网关主进程18MB 到 25MBSQLite 缓存上限最多 50MB临时请求缓冲约 10MB剩余可用内存约 150MB 以上这组数据说明一个问题网关本体完全可以常驻但绝对不要往路由器里塞模型文件。我试过把一个 0.5B 的量化模型直接放上去跑后果是内存剩余量直接清零路由器开始丢 ping连管理页面都卡。后来我彻底放弃本地推理把所有模型调用都转发到局域网 NAS 上的 Ollama体验反而更稳定。4.2 保护闪存能放内存就不落盘华硕路由器的闪存颗粒是嵌入式设计写入寿命有限。第一次做这个项目时我把 SQLite 缓存文件放在/opt下运行了两天就发现系统 IO 频繁卡顿。问题就出在每一次模型响应写入缓存都会触发闪存写入长期看完全是自杀式用法。解决办法很简单缓存目录和日志目录全部用tmpfs也就是挂在内存里的临时文件系统。重启清掉就清掉本来缓存也是过期数据。配置文件才需要持久化放/opt/aigw/etc/里但频率也很低。4.3 后端调度与熔断降级调度逻辑只有一个核心目标让请求尽可能在低成本、低延迟的路径上完成。我的选择顺序是缓存命中直接返回局域网 Ollama 健康则转发局域网不正常时降级到远端 GPU 服务全部不可用时返回 503并附带后端健康状态。每个后端都带健康检查每 30 秒探测一次/health。连续三次失败就标记为down调度器不会再往这个后端发新请求。超时控制也很重要我给局域网后端设了 5 秒超时远端后端设了 15 秒避免某个后端口头阻塞把网关协程拖垮。4.4 实测性能数据在 RT-AX86U 上跑了一个月我记录到的大概数据如下场景结果网关空闲状态RSS 18MBCPU 占用 0.3%缓存命中响应小于 5msNAS Ollama 7B 首 token约 1.8 秒并发 20 个未命中请求网关内存峰值 35MB连续运行 30 天无 OOM无进程崩溃这个性能表现对家庭场景来说完全够用。网关本身不是瓶颈瓶颈在于后端推理服务有多快。5. 安全底线局域网 AI 网关的鉴权与脱敏5.1 默认拒绝先加防火墙再谈其他任何监听端口的服务第一件事都是限制访问范围。我的网关绑定的是192.168.50.1:8080只在内网 IP 上监听但这还不够还要在防火墙层面补一刀# /jffs/configs/firewall.user iptables -I INPUT -p tcp --dport 8080 -s 192.168.50.0/24 -j ACCEPT iptables -I INPUT -p tcp --dport 8080 -j DROP规则很粗暴内网网段放行外网直接丢弃。如果哪天真要从外部管理也应该走 SSH 隧道进去看而不是把 8080 暴露到公网。5.2 Token 鉴权与客户端粒度限流防火墙只能挡外部不能防内网设备互相乱调。所以我还在应用层加了 token 鉴权server: tokens: - name: living-room token: xxxx limit: 10 - name: workshop token: yyyy limit: 30客户端调用时把 token 放在Authorization: Bearer ...头里。网关按 token 识别调用方再做独立的速率限制。这样就算某台设备被入侵攻击者也拿不到其他设备的 token还能在状态页看到哪个 token 在滥用。5.3 提示词脱敏与隐私边界这是我最看重的一点。很多请求里的提示词包含手机号、邮箱、地址直接转发给远端模型服务确实有隐私风险。我在网关里加了一个脱敏步骤sanitize: patterns: - regexp: 1[3-9]\\d{9} replace: [手机号] - regexp: [A-Za-z0-9._%-][A-Za-z0-9.-]\\.[A-Za-z]{2,} replace: [邮箱]默认规则对所有即将出内网的请求生效脱敏发生在请求转发之前。日志记录里也存脱敏后的文本原始提示词只短暂存在于内存中请求结束后即释放。5.4 密钥管理的土办法路由器上没有专业密钥管理系统但基本的安全习惯还是要有的。我的做法是插件配置文件的权限设为600只允许 root 读取API Key 不直接写在 YAML 里而是通过环境变量注入/opt/aigw/etc/aigw.env文件权限也是600启动时由脚本导出环境变量。# /opt/aigw/etc/aigw.env export AIGW_EDGE_KEYsk-xxx export AIGW_LOCAL_KEYollama这样即使配置文件被我分享到开源仓库里面也不含任何真实密钥。6. 跑了一个月稳定性和几个让我挠头的坑6.1 整体稳定性还是超出预期我在主力路由器上连续跑了 30 天固件是 Merlin 388 系列后端是局域网 NAS 上的 Ollama 7B 模型。网关进程 RSS 一直稳定在 18MB 到 25MB偶尔高并发时会到 35MB但系统从未出现 OOM。唯一一次事故是我自己调试热加载配置时触发死锁进程假死重启后恢复。整体稳定性在网络插件里算很能打的了。6.2 坑一SQLite 放闪存导致系统 IO 卡顿前面提过缓存目录最初放在/opt结果两天后整个路由器 IO 负载异常。排查下来发现是 SQLite 每次写缓存都会触发闪存写入而且 WAL 模式还会反复写预写日志。修复方案是把整个缓存目录改到/tmp同时把 SQLite 的 journal mode 设为OFF。重启丢缓存换来的是长期稳定这笔账非常划算。6.3 坑二NTP 未同步导致日志时间错乱有一段时间排障时发现日志时间少了 8 小时一开始以为是代码里时区处理有问题折腾半天才发现是路由器在启动早期还没同步 NTP而网关启动得太快拿到的系统时间是错的。后来我在启动脚本里加了等待逻辑如果当前系统时间早于固件编译日期就等待 NTP 同步完成再拉起网关进程。这个坑很隐蔽推荐所有写路由器插件的朋友注意。6.4 坑三大请求体直接把内存打满默认接入层没有限制请求体大小结果有一次测试脚本发了一个 10MB 的请求体网关直接把几十 MB 内存吃掉了。后来我在接入层加了一个max_body_bytes: 1MB的配置超过直接返回 413。对提示词请求来说1MB 已经绰绰有余正常对话请求通常只有几 KB。6.5 后续计划在稳定基础上加功能路由器上做功能要克制但不代表不能扩展。我接下来的方向有几个把网关的缓存同步到局域网内另一台路由器做轻量高可用把提示流从纯静态 YAML 改成支持条件分支让它能根据模型返回结果决定下一步再给管理页面加一个请求回放功能方便复现问题。每一项都会先评估内存和 CPU 预算毕竟这次实践给我最大的体会就是合适的负载放在合适的位置才叫架构反过来再强的硬件也会被乱塞的软件搞死。下一期我会把完整插件源码整理出来希望这套方案能给你的路由器也增加一个靠谱的 AI 出口。
返回列表