ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI伙伴全栈实战:云端Agent、边缘视觉与端侧硬件的三层架构详解

AI伙伴全栈实战:云端Agent、边缘视觉与端侧硬件的三层架构详解

我最早做AI伙伴项目时,踩过一个特别大的坑:以为“AI伙伴”就是接一个大模型API,能聊天就完事了。结果做到第三个月,这台机器人才算真正“活”过来——它能在我喊它名字时转头,能认出桌上的水杯,能在断网时依然听懂“关灯”这种简单指令。这一路让我彻底想明白一件事:一个能用的AI伙伴,本质上是云端Agent、边缘视觉、端侧硬件三层协作的结果,而不是某一个模型或某一块开发板的独角戏。这篇文章我就把这个全栈拆解完整写出来,包括每一层到底在解决什么问题、层与层之间怎么协作、我踩过哪些坑,给同样在做AI硬件应用、想做全栈AI项目的朋友一条可以直接参考的路线。

先说清楚这篇文章适合谁:你可能是写了几年业务后端、想碰硬件的全栈工程师,也可能是玩单片机、想做AI功能但不知道怎么上云的硬件爱好者。两层人都能从这里拿走自己缺的那块拼图。我不会只给概念,尽量把每一步“为什么这么做”也讲透。

1. 为什么AI伙伴天生就是三层架构:从“喊一声”到“递杯水”的距离

1.1 一次完整的“帮我拿杯水”请求经过了多少道坎

我最初设计AI伙伴时,画过一张特别理想化的流程图:用户说话 → 大模型理解 → 返回回答。放在网页聊天里完全成立,但换到物理世界就全乱套了。

你想一下,“帮我拿杯水”这句话落到真实场景要拆成多少件事。先是麦克风捕捉到声音,这发生在端侧硬件上。然后系统得判断这句话是跟AI伙伴说的,而不是两个人在聊天——这需要唤醒词,也是端侧干的事。声音处理完,要传给云端大模型做语义理解,云端Agent把这个意图拆解成任务:第一,找到水杯;第二,确认水杯是空的还是满的;第三,规划机械臂怎么过去拿。第二步和第三步都需要视觉信息——水杯在哪,这就要靠边缘视觉层实时检测,把“杯子在桌子左前方,距离大约30厘米”这个信息喂给云端Agent做决策。最后执行动作,又是端侧硬件通过电机和舵机完成的。

这一条链路走下来,如果所有事情都堆在端侧小型芯片上,视觉模型根本跑不动;如果全部丢到云端,每一帧画面都上传,延迟和带宽都受不了。唯一的解法,就是把任务按“离物理世界的距离”拆开,各管一段。

1.2 算力、延迟、隐私三本账

我不喜欢空谈架构,直接给你看我做选型时算的三本账。

首先是算力账。端侧一块常见的MCU,比如ESP32-S3,算力大概在几百TOPS以下(其实是几百MFLOPS到几GFLOPS级别),跑个唤醒词勉强够用,跑YOLO级别的视觉模型是不现实的。边缘设备比如RK3588,INT8算力能做到6 TOPS左右,跑轻量视觉模型没问题。云端大模型单次推理可能需要几百TFLOPS级别的算力,但每调用一次都在花钱。

然后是延迟账。用户说完话到AI伙伴给出反应,这个“端到端延迟”直接决定了体验。端侧本地处理最快,10毫秒到几十毫秒;边缘设备处理一帧画面大概30到80毫秒;云端一次大模型推理,算上网络往返,一般在300到800毫秒。这不是我随便给的数字,是我实测不同网络环境下的平均值。

最后是隐私账,这一步经常被忽略。AI伙伴放在家里,摄像头对着客厅,如果每一帧都传上云,用户心里那关过不去,产品合规上也是个麻烦。我的原则是:能本地处理的坚决不上云,必须上云的只传文本和结构化数据。

维度端侧硬件边缘视觉云端Agent
典型算力GFLOPS级TOPS级(6-100 TOPS)大模型专用集群
单次推理延迟10-50ms30-80ms300-800ms
处理数据类型音频、传感器、控制信号视频帧、图像文本、工具调用
核心优势实时响应、低功耗带宽友好、隐私友好语义理解、复杂规划
主要成本硬件成本低设备成本中等按调用量计费

三本账算完,结论就非常清晰了:三层架构不是刻意设计出来的“高级感”,而是算力、延迟、隐私三个约束条件逼出来的最优解。

1.3 三层如何分工才算合理

虽然层数定了,但每一层的边界一定要清楚。我的划分方式是这样的:

  • 云端Agent层:负责语义理解、意图识别、任务规划、长期记忆存储。它不直接碰传感器数据,只接收结构化输入(文本、识别结果),输出结构化指令。
  • 边缘视觉层:负责实时目标检测、人脸识别、手势识别、物体跟踪。它处理的是连续视频流,把非结构化的像素变成结构化的“时空里有什么物体、在什么位置”。
  • 端侧硬件层:负责音频采集、唤醒词检测、传感器读取、电机控制、人机交互反馈。它离物理世界最近,也最容易出现信号、供电、干扰这类“脏问题”。

这里有个很重要的原则:每一层都只向上层提供“处理后的结果”,不要试图跨层干别人的活。比如端侧MCU不要去尝试做大模型的语义理解,哪怕你看到ESP32能跑个微型神经网络,也仅限于唤醒词这种轻任务。边缘视觉不要试图做长期规划,它只需要回答“现在看到了什么”。云端Agent不要试图直接控制舵机,它应该告诉端侧“目标是哪个坐标”,具体怎么走是端侧的事。

2. 云端Agent层:把“能聊天”变成“能办事”

2.1 从LLM API到Agent需要补上哪些零件

很多人以为调大模型API就是在做AI应用,实际差距非常大。裸的LLM只会做一件事:根据输入文本预测输出文本。它不会主动去调用视觉模型,不会记住你上周说过“我喜欢喝冰水”,更不会在你让它拿水杯时自动知道要先定位水杯。

所以从LLM API到真正的Agent,至少要补上四样东西:

第一是工具定义。你得告诉模型:你有一个视觉工具,可以知道物体的位置;有一个端侧控制工具,可以按坐标执行动作。大模型才知道在什么时候调什么工具。

第二是任务规划能力。模型需要把“帮我拿杯水”拆成“调用视觉工具定位水杯 → 调用端侧控制工具执行抓取”这样的步骤。现在主流做法是让模型做ReAct式的思考循环:思考(Thought)→ 行动(Action)→ 观察(Observation)。

第三是记忆管理。多轮对话中,模型不能忘记用户的名字、偏好、刚才的指令。这需要把短期上下文和长期记忆分开管理,用向量数据库存长期记忆。

第四是状态跟踪。Agent得知道当前系统处于什么状态:是正在执行某个动作,还是空闲等待指令。否则用户说“停下来”,Agent都不知道要中止什么。

2.2 工具调用与Function Calling的落地姿势

目前主流的大模型API都已经支持Function Calling(也叫做Tool Use),这是云端Agent最重要的一个工程接口。我实际用的方式是这样:给每个能力定义一个JSON Schema,然后在请求里把这些工具描述传给模型。

以一个“视觉定位物体”工具为例,它的定义长这样:

{ "name": "locate_object", "description": "在当前画面中定位指定物体的2D坐标和大致距离", "parameters": { "type": "object", "properties": { "object_name": { "type": "string", "description": "要寻找的物体名称,比如水杯、遥控器、手机" } }, "required": ["object_name"] } }

模型看到这个定义后,遇到“帮我拿水杯”就会尝试调用locate_object,并填上object_name: "水杯"。接下来Agent代码拦截这个调用,去边缘视觉层问“水杯在哪里”,拿到返回的坐标后再把结果作为观察文本返回给模型。

这个流程的关键在于,模型本身不会执行任何真实动作,它只负责“做决定”,真正干活的是Agent代码和下游各层。我一开始犯过的错是把工具描述写得像散文,模型根本不知道怎么调;后来改成每个工具都附一个“什么时候用”和“典型调用示例”,调用准确率立刻上来了。

2.3 长期记忆与多轮上下文的工程化处理

家用AI伙伴有一个特别实际的烦恼:用户不会每次都把话说全。今天说“把那个杯子拿过来”,明天说“还记得那个红色杯子吗”。这要求Agent具备记忆能力。

我的方案是分两层记忆:短期记忆用最近几轮对话原文,塞进上下文窗口;长期记忆做摘要后存进向量数据库,每次对话开始前先做一次相似度检索,把相关的历史记忆拼进系统提示词。

工程上有个细节:token预算。上下文窗口是有限的,如果每轮都把历史全文塞进去,几轮对话后就把窗口撑爆了。我用的策略是:超过6轮之后,把前面的对话交给一个摘要模型压缩成一段话,替换掉原文。这样既保住了信息,又把token控制在合理范围。

存储层我用的向量数据库是轻量级的,比如sqlite-vss或者Chroma,跑在边缘小主机上都行,不需要专门搞一套分布式数据库。AI伙伴是单用户设备,数据量顶天了几万条向量,没必要上重方案。

2.4 云端Agent的选型与成本控制

模型选型上我给三个档位的建议。预算宽裕、追求最强理解能力的,直接用GPT-4级别或Claude等商业闭源模型,理解力和规划能力确实顶。想控制成本、数据敏感一点的,可以用国产开源模型部署在自己的服务器上,比如Qwen系列,配合OpenAI兼容的Function Calling协议,迁移成本很低。还有一种更轻的方案:如果任务只是指令控制型(关灯、播放音乐、查天气),根本不需要大模型,用规则引擎或小模型意图分类就够了,千万别杀鸡用牛刀。

成本控制上我有一条铁律:能不调用大模型就不调用。比如用户说“帮我开灯”,这句话完全不需要大模型理解,端侧离线命令词直接就能响应。再比如用户的固定作息问询:“现在几点了”这类简单查询,规则处理比大模型便宜一个数量级。

另外,云端Agent不要每次请求都重新发起一轮完整对话。如果上下文没变化,可以用缓存命中直接返回之前的结果。我在项目里做过统计,加了缓存之后,约30%的重复问询直接走缓存,成本肉眼可见地降下来了。

3. 边缘视觉层:在摄像头旁边先把“看见”解决掉

3.1 为什么视觉不能直接丢给云端

视觉是AI伙伴所有感知里最特殊的一块:它的数据量是音频和文本的几百倍。一秒钟的720P视频差不多是几兆比特的码率,如果全量传云端做实时分析,网络带宽首先就吃不消。更现实的问题是画面里常有大段连续帧是静止的,全传上去纯属浪费钱。

我在做第一版时图省事,直接把USB摄像头画面用RTSP推流到服务器,服务器用OpenCV逐帧处理。结果延迟高得离谱——画面从家里到云服务器再回来,光网络就绕了半圈,加上处理时间,端到端超过1秒。后来我把视觉推理挪到边缘设备上,情况立刻好转:本地的帧率稳定在25fps以上,检测延迟降到50ms以内。

边缘视觉最大的价值不是“算得快”,而是“不用把每一帧送出门”。视频流在本地被消化成结构化数据——比如“13点20分05秒,画面里检测到一个杯子,中心坐标(x=400, y=300)”——这个结论很小,只有几百字节,传上云端毫无压力。

3.2 视觉任务拆解:检测、跟踪、识别三层

很多入门者把视觉当成一个整体问题:“我要让机器认识水杯”。实际上视觉层内部也得分层,我在项目里拆成三层:

检测层:用目标检测模型在每一帧里框出物体。这一步回答“画面里有什么、在哪里”。我用的还是YOLO系模型,目前YOLOv8/v11这类在边缘设备上性价比很高。检测层不用追求特别高的精度,只要框的位置大致对就能往下走。

跟踪层:检测是逐帧做的,但画面里的物体会动,摄像头也可能动。跟踪层负责把相邻几帧里的同一个物体关联起来,稳定输出“这个物体从当前帧到下一帧移动到哪里”。我用ByteTrack,因为它轻量、在边缘设备上跑得起,还不需要训练,只要检测结果就能工作。

识别层:到了这一层,才进入“语义”领域——区分哪个是用户、用户的手势是什么、人脸是谁。识别层通常也放在边缘做,但模型更重一些。比如人脸识别我用轻量的MobileFaceNet,跑在RK3588上单次推理约20ms,足够应对单人场景。

这三层流水线式跑下来,边缘视觉层才能为云端Agent提供高质量的感知结果。

3.3 边缘设备选型与模型量化

边缘视觉的设备选型,我对比过市面上主流的几款。如果你只做视觉推理,不考虑接电机和传感器,那RK3588是我最推荐的,理由很直接:性能比树莓派高一个量级(INT8算力6 TOPS),价格处在千元档,社区生态也比较成熟,硬件解码H.264/H.265很流畅。如果你需要跑更大的模型或更强的算力,NVIDIA Jetson Orin Nano也是备选,CUDA生态完善,能跑TensorRT加速,但价格也高了一截,整机功耗也更可观。

设备AI算力典型功耗视觉模型表现相对优势
树莓派5极低(无NPU)5-10WYOLOv8s约2-3 FPS社区资源丰富、外设丰富
RK35886 TOPS(INT8)5-15WYOLOv8s约30 FPS算力功耗比优秀
Jetson Orin Nano40 TOPS7-25WYOLOv8s可上60 FPSTensorRT生态、能跑较大模型

模型部署的前置步骤是量化。我在RK3588上用的是RKNN-Toolkit,把PyTorch模型转成RKNN格式,可以用INT8量化。量化之后模型体积缩小到原来的四分之一,推理速度提升两到三倍,精度损失一般能控制在2%以内。这里有个经验:量化后一定要在真实场景里重新测一遍准确率,不要只看指标。我遇到过模型量化后用在暗光环境里精度骤降的情况,后来在数据集中加入低亮度样本重新量化,才算稳住。

3.4 视频流接入与丢帧处理

视频流接入这块有两个选择:USB摄像头直连,或者网络摄像头走RTSP。我的判断是:只要能上USB摄像头,就别走RTSP。USB的延迟明显更低,驱动也简单,OpenCV里VideoCapture(0)就能搞定。RTSP虽然部署灵活,但缓冲会把延迟拉高100到200ms,对实时性要求高的场景很不友好。

有了视频流之后,最要命的问题来了:推理速度跟不上视频帧率怎么办。RK3588跑YOLOv8s大约是30 FPS,也就是说每33ms能处理一帧。但相机可能是60 FPS的,如果每一帧都送进模型,队列会越积越长,延迟像滚雪球一样膨胀。

我最终采用的策略是拉长间隔:每2帧取1帧送去检测(即30 FPS的检测频率),剩下的帧跳过。如果检测结果在两帧之间没有明显变化,就沿用上一次的结果。这个策略在AI伙伴这类家庭场景里效果很好,因为大多数时候画面是静止的,没必要每帧都重新检测。

4. 端侧硬件层:从唤醒词到执行器的一公里

4.1 端侧SoC选型与功耗发热控制

端侧硬件层是整个AI伙伴的“手脚”和“耳朵”,但它有一个天然的限制:它必须长期在线、功耗低、发热小。这个位置上不可能放一块旗舰开发板,我用的是ESP32-S3作为核心主控。

选它的原因有三条:第一,内置Wi-Fi和蓝牙,不需要外挂通信模块,省了布线和驱动适配;第二,Xtensa双核240MHz处理器跑唤醒词算法够用;第三,深度睡眠模式下功耗能降到微安级,对于“待机时保持唤醒监听”这个需求非常关键。

不过ESP32-S3也有它力不从心的地方。我一开始试图让它直接驱动舵机并同时跑视觉模型,结果CPU占用率直接爆表,唤醒响应变得迟钝。后来我把视觉彻底交给边缘层,端侧只做音频采集、状态控制、指令执行,这份工就轻松多了。分工合理之后,整机待机功耗从2W降到0.3W左右,发热问题也彻底消失了。

这里想提醒一个选型容易被忽略的点:不要贪图“端侧什么都干”。端侧硬件的本分是实时响应和外设控制,而不是计算。一旦计算任务挤占实时任务,用户最直观的感受就是“这个设备很笨,说了半天没反应”。

4.2 唤醒词与离线命令词的实现

AI伙伴需要一个“永远在听、但不打扰人”的机制,这个机制就是唤醒词。我用的是openWakeWord,它可以在ESP32这类MCU上本地跑一个轻量神经网络,专门识别“小艾小艾”这样的自定义唤醒词。整个模型只有几百KB,单次推理10ms左右,功耗可以接受。

唤醒之后,系统进入命令监听状态。这个阶段我同时保留了两条路径:离线命令词路径和云端路径。离线命令词我用冯诺依曼式的关键词匹配,识别“开灯”“关灯”“暂停”“继续”等固定指令,这部分完全不依赖网络,响应时间控制在200ms以内。云端路径则处理开放式对话,唤醒后如果检测到不是离线命令词,就把音频流送去云端做ASR和大模型理解。

这个设计的妙处在于:绝大部分用户的日常指令是重复的——开关灯、调音量、问时间。“关灯”两个字走离线路径,反应贼快;“帮我把桌上的红色杯子拿过来”这种开放式需求走云端路径,理解能力强。两条路径各管各的,互不阻塞。

实际踩过的坑是:ESP32的音频采集经常出现“近端语音小、远端回声大”的问题。起初我以为是麦克风灵敏度不够,后来查了才发现是板载麦克风阵列的采样率配置错了,修完之后声音清晰多了。

4.3 传感器和执行器的接入与避坑

端侧硬件要接的东西不少:麦克风阵列拾音、舵机控制机械臂、LED呈现表情、蜂鸣器做提示音。这些外设每一个都有容易踩的坑。

麦克风阵列最值得注意的是I2S引脚冲突。ESP32-S3的I2S通常和SPI共用引脚,如果不做引脚映射,接上麦克风后会直接影响其他设备的通信。我在PCB设计阶段就提前把I2S引脚固定到GPIO 15/16/17,避开了SPI和UART默认引脚。

舵机这块,最大的坑是供电。三个舵机同时动作时,瞬时电流能到1A以上,如果直接从开发板的3.3V引脚取电,电压会瞬间塌陷,导致MCU重启。我的方案是用独立的5V/3A供电模块给舵机供电,开发板只提供PWM信号,不提供能量。这个改动之后,设备再没出现过“一动就重启”的故障。

电气隔离方面也有个容易被忽视的点:电机启停瞬间会产生反向电动势,容易干扰麦克风采集。我在电机两端加了续流二极管,并让电机地线和模拟地线在电源入口处单点汇合,噪声问题有所缓解。用示波器观察,噪声尖峰从原来的一两伏降到几十毫伏。

4.4 端侧与边缘、云端之间的“家庭总线”

端侧硬件、边缘视觉、云端Agent,三者之间的通信要点在于稳定、轻量、容错。这块我用的是MQTT协议,Broker跑在边缘设备上,端侧和云端都作为Client接入。选MQTT的原因很直接:它是发布/订阅模式,消息带QoS级别,断线重连机制成熟,而且开销小,特别适合ESP32这类嵌入式环境。

消息topic我做了一个简单的分层设计:

  • device/audio/state:发布设备状态(在线、唤醒、监听中)
  • vision/objects/result:边缘视觉发布检测结果
  • agent/command/execute:云端Agent下发执行指令
  • device/sensor/event:端侧发布传感器事件(按钮、人体感应等)

这套总线在我项目里稳定跑了好几个月。一个重要的现实经验是:硬件层做客户端经常会遇到网络切换、路由器重启等问题,所以每60秒的心跳必须做,否则你无法区分“设备掉线”和“设备没消息发”。

5. 三层协作的通信协议与异常降级设计

5.1 消息格式与状态同步设计

三层协作的稳定性,很大程度上取决于消息格式是否清晰。我定义了一套统一的事件消息格式,不管是哪一层发出来的,都遵循同一个结构:

{ "event_id": "a3f2c9d1-7b3e-4f6a-9d2e-1c0b8a7d6e5f", "timestamp": 1729500000000, "source": "vision", "type": "object_detected", "payload": { "object": "cup", "bbox": [320, 210, 380, 260], "confidence": 0.92 } }

event_id是全局唯一标识,用来关联整个链路;source标明消息来自哪一层;payload放结构化数据,不用JSON里套大段文本。整个链路从云端Agent下发“寻找水杯”指令,到边缘视觉上报“水杯在(350, 235)”,再到端侧执行“机械臂前往(350, 235)”,每一步都带着同一个event_id,这样在哪一层出了问题,日志一查就能定位。

状态同步方面,我设计了一个最小的状态机:空闲(IDLE) → 监听(LISTENING) → 理解(THINKING) → 执行(EXECUTING) → 回到空闲。每一层都维护这个状态机的副本,状态变更时通过MQTT广播。端侧硬件根据状态切换显示不同的LED颜色,边缘视觉根据状态决定是否做高频率检测,云端Agent根据状态决定是否接收新的用户输入。

5.2 断网降级:没网的时候AI伙伴还能干什么

网络永远是AI伙伴最大的不稳定因素,没有之一。我遇到过家里宽带断了、路由器重启、甚至云端API欠费(这个比较尴尬)等情况。断网时如果设备直接变砖,用户体验会非常差。所以我在设计早期就把“断网降级”列为硬性需求。

降级策略分几级:

第一级,离线命令词仍然生效。“开灯”“关灯”“暂停”这些固定指令由端侧本地匹配处理,不需要网络。这是最高优先级的兜底,保证最基本的控制能力。

第二级,边缘视觉依然在做,但检测结果不再上云,而是缓存在本地。断网期间如果有人出现在门口,边缘层会记录事件,等网络恢复后再补报。

第三级,云端Agent不可用的情况下,端侧会通过TTS离线语音提示“网络不太好,我现在只能执行固定指令”,避免用户对着设备一直喊却没反馈。

这套降级机制做下来,即便断网十分钟,设备依然具备基础交互能力,不会让用户觉得它是一个“没网就死”的玩具。说实话,这个体验上的安全感,比多接一个功能重要的多。

5.3 延迟预算:一次“拿水杯”的端到端耗时拆解

为了让整个协作过程可控,我给关键路径做了一个延迟预算表。这个表是设计时的指导,也是我后续优化性能的检查清单。

环节耗时估算说明
唤醒词检测100ms本地运行,持续监听
音频上传/ASR200-400ms语音转文字,受网络影响
云端Agent理解与规划300-600ms大模型推理,工具调用
边缘视觉检测与返回50-100ms本地推理,检测目标位置
端侧执行动作500-2000ms电机动作,机械臂运动
端到端合计约1.2-3.2秒视觉依赖越多越慢

从表里能看到,云端大模型推理和端侧机械臂执行是耗时最长的两项。优化方向也对应着两条路:用更快的指令路径(离线命令词)跳过云端推理,用更短的执行路径(预设动作序列)减少机械臂计算时间。

像“关灯”这种固定动作,不走大模型,延迟从1.5秒左右直接降到300毫秒内,用户体感就是“秒回”。而“拿水杯”这种复杂任务,用户其实能接受2秒左右的等待,因为任务本身需要视觉确认和路径规划,等待是合理的。

5.4 事件驱动与数据流的反向打通

三层协作不能只是“云→端”的单向下发指令。实际场景里,端侧硬件的事件经常需要反向通知云端Agent,改变云端的行为。

举个例子:用户按了设备上的物理按键。这个按键事件从端侧发出,通过MQTT通知边缘视觉切换到人脸识别模式,同时通知云端Agent“用户正在物理交互”。云端Agent收到这个事件后调整交互策略,比如优先等待用户下达新指令而不是继续之前的对话。

数据流方向我总结过一张认知图:音频流从端侧往云端走(上行),控制指令从云端往端侧走(下行),视觉结果在边缘本地消化后在网络恢复时补报(旁路)。三条数据流互不干扰又彼此补充,AI伙伴的智能才能发挥出来。

这个双向打通需要特别注意的是协议层面的“事件语义统一”。按键事件、视觉检测事件、云端指令事件,三者本质上是同一件事:系统里发生了一个值得关注的事情。统一用事件消息格式表达后,各层代码可以完全解耦,每层只关心自己订阅的事件类型,不需要互相了解内部实现。这是整个系统后期能持续迭代的关键。

6. 我实际落地时踩过的一串坑:完整排查链路

6.1 端侧听到的语音和云端收到的语音突然不一致

这个问题非常诡异:设备刚装好时一切正常,用了大概两周后,用户喊“小艾小艾”,设备能唤醒,但云端ASR转出来的文字完全对不上——“开灯”变成了“开盾”。我一开始怀疑是网络问题,换成4G热点也一样。

排查思路是逐层排除。先看端侧采集的原始音频文件——我在设备里留了一个调试开关,可以把采集到的音频存成WAV文件。听着发现问题了:近端语音很小,背景噪声很大,就像人在很远的地方说话。再查,发现是麦克风阵列里的一个采样通道在长时间运行后采样率漂移了,导致两路麦克风的相位不一致,波束形成算法失效。

修复方案:把麦克风阵列的驱动改成“定时重同步”模式,每10分钟重新校准一次采样时钟。这个bug我再也没遇到过。这里想说一个经验:MCU上的外设长时间运行会出现各种不可预料的漂移问题,凡是涉及时钟同步的,都要设计定期重校机制。

6.2 边缘视觉框到了水杯,但云端Agent不知道水杯在哪

视觉层上报的坐标是“图像坐标”,就是像素在画面里的位置,比如(400, 300)。但云端Agent需要的是“世界坐标”,比如“桌子中央偏左30厘米”。这两个坐标系如果不做转换,云端拿到的坐标根本没法用于控制。

我第一版直接把视觉坐标发给云端,云端把它塞给机械臂,结果机械臂到处乱抓。排查后发现是坐标系认知不一致,然后在边缘视觉层加了一个坐标转换模块:根据相机的内参(焦距、畸变)和外参(安装高度、俯仰角),把像素坐标换算成以机械臂底座为原点的世界坐标。

这个转换过程涉及相机标定,我用的方法是张正友标定法,打印一张棋盘格,在不同角度拍十几张照片,用OpenCV算出相机内参。整个过程不算复杂,但这一步不做,视觉和控制的结合就无从谈起。

6.3 Wi-Fi下消息延迟忽高忽低,设备时而“反应迟钝”

设备用Wi-Fi连路由器,MQTT消息有时20ms就到,有时要1秒才到,CPU负载并不高,但用户体感就是“卡顿”。最初我怀疑是路由器问题,换了个贵的路由器依旧。

后来我在边缘设备上用Wireshark抓包,发现告警的网络包存在大量TCP重传,并且MQTT的心跳包和业务消息在争抢带宽。原因找到了:家庭路由器开启了Wi-Fi省电模式,ESP32这种低功耗设备每隔一段时间就进入省电休眠,醒来后要先重新同步才能收发数据。

修复方案有两个:一是把ESP32的Wi-Fi模块设置为一直开启,牺牲一点待机功耗换稳定性;二是把MQTT的QoS从0改成1,确保关键指令不丢包。从设备功耗看,待机从0.2W涨到0.5W,但换来的是响应稳定,这波不亏。

6.4 断网后设备“假死”,怎么按都没反应

有段时间设备在断网时会出现“假死”现象:唤醒词没反应、按键没反应,过几分钟又自己恢复了。排查后发现是端侧代码里有一个线程在等待云端响应的过程中,把事件循环堵死了。

问题根因是我把语音采集、按键监听、网络通信都放在同一个FreeRTOS任务里,一旦网络超时时间设得长,整个任务就卡住,外设事件全被阻塞了。

修复方式是把外设事件(唤醒、按键)放到单独的高优先级任务里,网络通信放到低优先级任务里,并给网络请求设置硬性超时(2秒)。这样才能保证即使网络不可用,外设交互依然能响应。这也是我之前提“断网降级”能落地的前提——如果外设事件都被网络阻塞住了,降级策略根本触发不了。

6.5 边缘视觉在连续工作几小时后推理速度下降

RK3588跑YOLOv8s,刚开机时稳定30 FPS,运行一两个小时后掉到20 FPS不到,检测结果也偶尔出现卡顿。最初怀疑是内存泄漏,但排查代码后发现是芯片温度过高触发降频。

RK3588在高负载下温度很容易冲到75℃以上,芯片主动降频保护,推理速度自然掉下来。解决方案是在散热设计上花了点功夫:加了一个小型的金属散热片和5V风扇,让风扇根据温度传感器自动调速。改造之后长时间运行温度稳定在60℃左右,推理帧率稳定在28-30 FPS。

这个坑提醒我:边缘设备部署AI模型,不能只看算力指标,还得考虑散热能力。很多开发板标称的TOPS数字是在理想散热条件下测出来的,实际跑起来能不能持续输出,完全是另一回事。

6.6 多轮对话上下文错乱:会话ID的重要性

项目做到后期,我发现一个隐蔽的bug:用户说“把那个杯子拿过来”,云端Agent回答“哪个杯子”,用户说“就是桌上那个”,Agent居然回答说“桌上没有杯子”。但实际桌上明明有杯子。

排查了很久,终于发现问题发生在会话管理上。当边缘视觉检测到杯子的结果上报给云端Agent时,云端Agent把它写入了当前会话的临时记忆。但是端侧和云端之间的通信是无状态的——每次唤醒后可能建立一个新的会话ID,导致上一轮的记忆在 Session 中没有保留。

修复方案是在端侧增加一个会话合并机制:如果两次唤醒之间的间隔小于5分钟,就沿用同一个会话ID;超过5分钟才新建会话。这样用户连续说两句话,Agent能正确理解“那个”指代的是之前提到的杯子。这个改动让多轮对话的表现提升了不止一个档次。

最后再分享一点我自己的心得

做完整套AI伙伴,我最大的体会是:这个项目真正的难度不在于任何单一技术,而在于把三套完全不同“脾气”的系统拧在一起。云端代码要求抽象、可靠、可扩展;边缘代码要求吃透硬件、理解实时性;端侧代码要求容忍各种物理世界的“不干净”——噪声、断电、Wi-Fi漂移。每个领域都有一套自己的思维方式和调试方法,一个人要同时驾驭三者,确实非常考验“全栈”的成色。

如果让我给刚入坑的朋友一句话建议,那就是:第一版别想着一次做大而全的通用AI伙伴,先定一个具体动作,比如“识别水杯并抓取”,用三层架构把它走通,再逐步增加更多能力。先跑通一条最短路,感受一下三层协作是怎么运转的,比在纸上画一百遍架构图都有用。等这个最小系统稳定了,你自然知道该往哪个方向加厚、加宽。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进