ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

从零打造AI情感陪伴机器人:开源硬件与本地大模型实践

从零打造AI情感陪伴机器人:开源硬件与本地大模型实践 打工人人生第一次被裁什么体验那就是拿到赔偿金的那一刻我居然有一点点开心。去年年底我所在的智能硬件团队整体砍掉我收拾工位的时候满脑子想的不是找工作而是终于有整块时间可以把那个在脑子里盘旋了大半年的项目做出来了——一台属于我自己的 AI 情感陪伴机器人。我先说说这个项目的起源。不是突发奇想而是我家的真实需求。我妈一个人住每天跟我视频通话也就是那几分钟剩下的时间基本都是对着电视。我给她买过智能音箱她新鲜了两天后面就只用来定闹钟。不是音箱不好是它太“工具”了你说一句它答一句你不说它就不理你完全没有“陪伴感”。我想要的东西很明确一个能主动关心她、能记住她说过的话、能在她心情不好的时候察觉到异常的机器人而不是一个只会执行指令的语音助手。所以我给自己定了一个小目标用业余时间从零开始做一个代号叫 Spark 的情感陪伴机器人。这里的 Spark 有双重含义一是“火花”我希望它能点亮独居老人的生活二是我在调研时发现生态里正好有一套完整的开源技术栈也叫 Spark配合各种开源模型和硬件方案能极大降低我从零开始造轮子的成本。这篇文章我打算把整个项目的来龙去脉、技术选型、硬件搭建、软件实现、踩坑记录全部写清楚给同样想自己做一台陪伴机器人的朋友一条可以复现的路线。先说结论这个项目我前后做了大概四个月白天上班晚上和周末搞。最终成品是一个约 30 厘米高的桌面机器人具备语音对话、情感识别、主动关怀、人脸识别、记忆系统、表情展示和简单动作能力。整机硬件成本控制在 1500 元以内不含显示器和旧手机软件全部基于开源方案没有用任何闭源商业 API。这篇文章就是完整的保姆级复现指南我会把我踩过的坑、改过的方案一一说明白。1. 项目整体设计与思路拆解1.1 为什么叫 Spark名字背后的技术生态先解释一下 Spark 这个命名。最早我纯粹是想取“火花”这个寓意后来在调研的时候发现Apache Spark 这个大数据计算引擎在国内技术社区的热度一直很高热词里频繁出现“spark集群搭建”“spark内存”“spark之dataframe”“spark面试题”这些内容。虽然我的项目跟大数据没有直接关系但我在搭建本地推理服务的时候确实借鉴了 Spark 生态里的一些思想——比如把“语音识别、情感分析、对话生成、记忆检索”拆成独立模块再用消息队列串联起来这种思路跟 Spark 的 RDD 血缘关系、DAG 调度有异曲同工之处。更关键的是我后来发现 Hugging Face 上有大量以 spark 命名的开源模型和数据集比如一些情感分类模型就叫 spark-emotion 之类的。这让我觉得用 Spark 做代号非常贴切既代表我这个项目的核心理念“点燃陪伴的火花”又暗示了底层依赖的开源 AI 生态。1.2 需求拆解陪伴机器人和智能音箱的本质区别我跟很多朋友聊过这个项目大家第一反应都是这不就是个高级版小爱同学吗还真不是。智能音箱的本质是“指令-响应”你说“播放音乐”它放音乐你说“明天天气”它报天气。整个过程用户是主动方设备是被动方。我想要的陪伴机器人核心区别在于“主动性”和“记忆性”。它需要做到三件事第一主动发起对话。比如早上起来它会说“今天天气不错记得晒太阳”晚上会说“该睡觉啦别刷手机了”。这种主动关怀是智能音箱完全不具备的。第二长期记忆。它能记住你上周说过“膝盖有点疼”这周聊天时会问“膝盖好点了吗”。这种跨会话的记忆能力才是“陪伴感”的灵魂。第三情感感知。通过语音的语调、语速、音量以及面部表情和用词习惯判断用户当前的情绪状态。如果检测到低落的情绪它会主动切换成安慰模式而不是继续讲冷笑话。这三条需求直接决定了我的技术选型。单纯的语音助手方案全部出局我需要的是一个完整的 AI Agent 架构把语音识别、情感计算、对话管理、记忆存储、主动调度这几个模块整合起来。1.3 方案选型为什么放弃商业 API选择全开源路线项目一开始我也考虑过用现成的商业方案。比如直接调用某大厂的语音助手 SDK或者接入 ChatGPT 的 API再套一个语音壳子。这个方案的好处是开发量小两周就能出原型。但有几个问题让我最终放弃了成本问题。陪伴机器人要长期运行每天可能对话几十轮按 API 计费的话一个月下来不是小数目。而且我老妈不可能每次对话前先想想这句話要花多少钱体验上过不去。隐私问题。情感陪伴涉及大量私密对话我不希望这些内容全部上传到别人的服务器上做分析。本地化处理虽然模型效果会差一点但隐私有保障。可扩展性问题。商业 API 是黑盒我想加一个“主动关怀调度”功能就不得不把对话历史和状态管理全部在本地做然后每次把整个上下文塞给 API。这样做不仅 token 消耗大而且很难实现精细的情感状态控制。所以我定了三条技术原则本地优先、开源优先、模块解耦。所有 AI 能力尽量跑在本地所有核心组件优先选开源方案模块之间通过标准接口通信方便随时替换某一个组件。2. 硬件平台与核心组件选型2.1 主机方案旧手机 树莓派的组合拳先决定大脑。整个机器人的“大脑”需要跑语音识别、大语言模型、情感分类这些任务对算力有一定要求。我手里有一台退役的旧手机骁龙 855 处理器6GB 内存跑中小尺寸的大模型量化版勉强够用。但手机跑服务有几个问题散热差、供电不稳定、IO 接口少。所以我采用了双机方案旧手机跑语音识别和语音合成这两个任务对延迟要求高手机上的推理引擎优化得不错树莓派 4B 跑大语言模型和情感分析8GB 内存版本可以跑 7B 参数的量化模型同时树莓派还负责控制舵机、LED 灯带、传感器这些外设。两个设备通过局域网用 WebSocket 通信分工明确互不干扰。2.2 外壳与结构设计30 厘米桌面小机箱外壳我走了不少弯路。最开始用 3D 打印设计了一个圆润的卡通造型打出来发现表面太糙而且打印时间太长一套外壳要打二十多个小时。后来我换成了更务实的方案用 ABS 塑料板材切割拼接一个 30 厘米高的立方体机身外面贴一层毛毡布既好看又能遮丑。机身的正面嵌一块 5 寸的 HDMI 显示屏用来显示表情和状态。顶部装一个 USB 摄像头用于人脸识别和表情捕捉。机身内部塞了一个两自由度云台带动头部做左右和上下的小幅动作。底部加了一个 360 度旋转底座让它可以“转头”看向说话的人。整机成本我列个表给大家参考组件型号/规格成本元主机A旧手机骁龙8550闲置主机B树莓派4B 8GB480显示屏5寸 HDMI 触摸屏120摄像头USB 1080P45麦克风USB 麦克风阵列60扬声器3W 小喇叭15舵机SG90 x 330舵机驱动板PCA968525LED 灯带WS2812B 1米35板材/毛毡/线材杂项80电源模块5V 5A 多路输出55合计14502.3 语音链路设计麦克风阵列与回声消除的取舍语音交互最关键的两个硬件问题是拾音和回声消除。我在淘宝买了一个 USB 四麦克风阵列这个阵列自带语音增强算法可以做到 3 米左右的拾音半径同时支持 AEC回声消除。实测下来在普通客厅环境下机器人自己播放语音的时候麦克风仍然能比较好地拾取人声不会出现“机器人自己说话把自己唤醒”的尴尬。不过这里有个坑便宜的 USB 麦克风阵列在 Linux 下的兼容性参差不齐。我的第一款阵列在树莓派上死活不出声折腾了一晚上驱动最后发现是 USB 的采样率设置问题用 arecord 强制指定 16000Hz 采样率就正常了。后面我会在配置章节详细说。3. 软件架构与核心 AI 模块实现3.1 总架构六模块流水线用消息队列解耦整个软件系统我拆成了六个模块分别跑在两台设备上语音识别ASR跑在旧手机上使用 sherpa-onnx 加载 Paraformer 中文模型把麦克风采集的音频实时转成文本。语音合成TTS跑在旧手机上使用 piper-tts 加上中文语音包把机器人的回复文本转成语音播放。大语言模型LLM跑在树莓派上使用 Ollama 加载 Qwen2.5-7B-Instruct 的 4bit 量化版本负责对话生成和意图理解。情感识别跑在树莓派上使用 emotion2vec 模型分析语音的语调特征结合文本情感分类模型综合判断用户情绪。记忆系统跑在树莓派上使用 SQLite 存储结构化记忆条目配合向量数据库做相似度检索。主动调度器跑在树莓派上是一个基于规则和状态机的调度程序负责决定机器人“什么时候主动开口说话”。模块之间全部通过 MQTT 消息总线通信。为什么用 MQTT 而不是直接 HTTP 调用因为 MQTT 是发布订阅模式天然支持多对多通信。比如语音识别模块识别出一句“我心情不好”它可以同时发布给情感识别模块和大语言模型模块两个模块并行处理互不等待。另外 MQTT 支持遗嘱消息某个模块挂掉了其他模块能感知到并进入降级模式这对长期运行的设备来说很重要。3.2 大语言模型选型为什么是 Qwen2.5-7B 而不是其他大模型是整个机器人的大脑选型直接决定了对话质量。我前后试了 Llama 3.1 8B、Gemma 2 9B、ChatGLM3-6B、Qwen2.5-7B 这几个主流开源模型在树莓派 4B 上的表现差异很大。模型参数量量化后内存占用树莓派推理速度中文对话质量Llama 3.1 8B8B约5.2GB约4 token/s一般中文偏翻译腔Gemma 2 9B9B约6GB跑不动太卡-ChatGLM3-6B6B约4.5GB约5 token/s流畅知识面偏窄Qwen2.5-7B7B约4.9GB约4.5 token/s自然指令跟随好最终我选了 Qwen2.5-7B原因是它在中文对话的自然度、指令跟随能力和情感表达上综合表现最好。最关键的一点是Qwen 系列对“角色设定”的遵循能力很强我可以用系统提示词让它扮演一个温暖贴心的陪伴者而不需要专门的微调。推理速度 4.5 token/s 看起来不快但实际对话场景中这个速度是够用的。因为正常的语音对话每句话大概 20~40 个字生成一句话需要 10~15 秒虽然比真人慢一点但在可接受范围内。而且我发现一个规律当机器人回复稍慢时老人反而会更有耐心等待感觉像是对方在认真思考。这算是一个意外的正向体验。3.3 记忆系统实现SQLite 向量检索让机器人记住三个月前的事记忆系统是情感陪伴的核心也是我花时间最多的模块。我的设计思路是双层记忆短期记忆和长期记忆。短期记忆就是当前对话的上下文窗口直接放在 LLM 的 context 里记住最近十几轮对话。这个没什么特别的重点在长期记忆。长期记忆的实现分三步第一步抽取。每一轮对话结束后程序会分析这段对话抽取出值得记住的信息。抽取规则分两类一类是硬规则比如用户提到了自己的名字、家庭成员的称呼、重要的日期生日、纪念日、健康信息哪里不舒服、吃了什么药这些关键词直接匹配另一类是软规则用 LLM 自己判断这段对话里有没有值得长期保存的内容比如“我女儿下个月要回国”这种有明确时效性的信息。硬规则保证重要信息不漏软规则保证记忆的自然拓展。第二步存储。抽取出的信息分成两种格式结构化条目存在 SQLite 里比如CREATE TABLE memories ( id INTEGER PRIMARY KEY, user_id TEXT, content TEXT, category TEXT, importance INTEGER DEFAULT 5, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );非结构化的描述性文本我用 bge-small-zh 模型转换成向量存入 ChromaDB 向量数据库。第三步检索。每次对话开始前系统会根据当前的时间和最近的话题从向量数据库里检索出最相关的 5~10 条记忆动态加入 LLM 的 system prompt。比如现在是上午 9 点记忆里有“用户每天早上要量血压”系统就会在 prompt 里加上“现在是早上记得关心用户是否量过血压”。这里有一个检索策略的问题单纯靠相似度检索容易出现“检索到的记忆跟当前话题无关”的情况。我后来加了一个时间衰减权重把记忆的创建时间换算成一个衰减系数乘在相似度分数上。这样三年前的流水账记忆权重会很低而最近一周发生的事优先级会高很多。实测效果提升明显。3.4 情感识别语音特征 文本语义双通道综合判断情感识别我用了双通道方案。语音通道用的是 OpenAI 开源的 emotion2vec 模型这个模型是预训练的自监督模型可以直接从音频波形里提取情感特征支持高兴、生气、悲伤、中性、惊讶、恐惧六类基本情感。测试下来对中文语音的情感识别准确率大概在 75% 左右。文本通道用的_chinese-roberta-wwm-ext 微调的分类模型对文本进行情感分析。两个通道的结果最后做一个加权融合。语音通道的权重高一些因为语气往往比文字更能表达真实情绪——一个人打字说“我没事”可能是真没事但用低沉缓慢的语调说出“我没事”就明显有心事。融合后的情感标签会输入给 LLM同时也会影响机器人的面部表情和语音语调。这里有个细节情感识别不能只看当前这一句话还要结合上下文。比如用户说“我气的要死”单独看是愤怒但如果上文是“我孙子考试得了第一名我气的要死”这其实是表达过度的喜悦。所以我在实现时会把最近的 5 轮对话文本一起送给情感分类模型用上下文语境来修正单句的分类结果。3.5 主动关怀调度器让机器人学会“挑时候说话”主动关怀是最能体现“陪伴感”的功能也是最难做好的功能。做不好就会变成骚扰机器人不分场合地乱说话。我设计了一个基于时间规则和情绪状态的调度器。调度器的核心逻辑是一个状态机分为三种模式闲适模式、陪伴模式、紧急模式。闲适模式是默认状态机器人不说话但语音识别模块保持监听。调度器每隔 30 分钟检查一次当前时间和用户状态如果在规则表里匹配到了触发条件比如上午 9 点提醒量血压、下午 3 点提醒喝水、晚上 10 点提醒睡觉就会发起一次主动对话。陪伴模式在检测到用户连续说话超过 3 分钟时触发此时机器人会进入“积极倾听”状态减少打断多给回应。紧急模式在检测到强烈的负面情绪比如哭腔、愤怒、长时间的沉默时触发机器人会主动用安抚性语言询问情况并降低对话语速、放轻柔的音乐提醒。如果连续 3 次请求没有得到应答它会通过我预设的 Webhook 给我发一条手机通知让我及时联系家里。这里最核心的一点是“触发后抑制”机制。机器人每次主动说话之后会进入至少 20 分钟的抑制期避免连续打扰。这个抑制时间可以通过配置文件调整老年人可以设置得短一些让机器人更“唠叨”年轻人可以设置长一些。4. 实操过程与核心环节实现4.1 环境准备树莓派系统配置与基础依赖先准备树莓派的环境。我用的系统是 Raspberry Pi OS (64-bit) Lite 版本不带桌面环境省资源。系统装好之后依次安装基础依赖sudo apt update sudo apt upgrade -y sudo apt install -y python3-pip python3-venv git curl wget \ portaudio19-dev python3-pyaudio \ libatlas-base-dev libopenblas-dev \ libssl-dev libffi-dev \ libhdf5-dev这里提醒一个坑树莓派的 Python 别用系统自带的 3.9建议装 3.10 或 3.11。我一开始偷懒直接用系统 Python装 PyTorch 的时候一堆坑因为官方预编译包对 ARM 架构的适配版本只覆盖了特定 Python 版本。折腾了一晚上之后我用 pyenv 装了 Python 3.10.12所有依赖一次装齐。省下的时间够我写好几个模块了。接着安装 Ollamacurl -fsSL https://ollama.com/install.sh | sh ollama pull qwen2.5:7b-instruct-q4_K_MOllama 在树莓派上的安装本身很顺利但第一次运行大模型时我差点以为机器死机了。7B 模型的量化版需要大概 5GB 内存而树莓派 4B 的内存同时还要跑操作系统和其他服务8GB 版本也显得有点紧张。所以我把 GPU 内存分配给 GPU 的选项关掉了让系统统一管理内存。4.2 语音识别模块部署sherpa-onnx 的实战配置语音识别模块我跑在旧手机上用的是 Android 上部署 sherpa-onnx 的 TFLite 版本。因为手机是 Android 系统不能直接跑 Python 脚本我用 Termux 模拟 Linux 环境在里面装 sherpa-onnx 的 Android 预编译包。这里要说明的是手机方案其实是一个妥协。如果你不想折腾手机直接用树莓派加 USB 麦克风也是完全可以的只是 ASR 会占用树莓派的 CPU导致大模型的推理速度进一步下降。我实测过树莓派同时跑 ASR LLM一句话的响应时间从 12 秒拉长到 20 秒以上体验下降明显。所以分机部署是值得的。sherpa-onnx 加载中文语音识别模型非常简单import sherpa_onnx recognizer sherpa_onnx.OnlineRecognizer.from_transducer( encoderencoder.onnx, decoderdecoder.onnx, joinerjoiner.onnx, tokenstokens.txt, num_threads2, sample_rate16000, feature_dim80, enable_endpoint_detectionTrue, rule1_min_trailing_silence2.4, rule2_min_trailing_silence1.2, rule3_min_utterance_length300, ) # 音频流持续喂给 recognizer stream recognizer.create_stream() while True: samples mic.read(1600) # 100ms 的音频 stream.accept_waveform(16000, samples) while recognizer.is_ready(stream): recognizer.decode_stream(stream) text recognizer.get_result(stream) if text: publish_to_mqtt(asr/text, text)注意这里 enable_endpoint_detectionTrue 很关键。这个参数开启后sherpa-onnx 会检测说话停顿自动判断一句话是不是说完了。三个 rule 参数控制判断灵敏度rule1 是长时间静默2.4秒判为结束rule2 是短静默但句子已经有足够长度就结束rule3 是句子超过 5 秒强制结束。这三个参数我调了很久最后发现 rule1 和 rule2 的值设置得太大机器人反应迟钝设置得太小又容易在用户思考的时候抢话。2.4 秒和 1.2 秒是体验比较平衡的参数。4.3 大模型对话服务Ollama 的提示词工程Ollama 起服务之后我用 Python 调用它的 API 来实现对话功能。默认的 Ollama API 是 OpenAI 兼容格式用起来非常顺手。关键的是提示词设计。陪伴机器人的 prompt 不能简单写“你是一个陪伴机器人”那样生成的对话非常模板化。我最终的 system prompt 经过十几版迭代核心框架是这样的你是 Spark一个温暖贴心的家庭陪伴机器人。 你的用户是一位65岁的独居老人你称呼她为“阿姨”。 对话要求 1. 语气亲切自然像家人朋友一样聊天不要用“您”这个称呼用“你”拉近距离。 2. 在对话中自然融入对她身体状况、心情感受的关怀不要生硬地“例行公事”式提问。 3. 回应要简洁每句话不要超过50个字方便语音合成。 4. 如果用户情绪低落多倾听少建议不要用“你应该”这种说教语气。 5. 当用户回忆起过去的事情时积极倾听并表达兴趣。 今天是{date}当前时间是{time}。 根据最近的记忆以下是可能相关的事情 {relevant_memories}这个 prompt 里有几个值得关注的点。第一用“阿姨”而不是“您”这个细节是从我妈的反馈里改出来的。她原话说“跟我说话还用‘您’听着生分。”陪伴机器人的核心是拉近距离语气上的亲密感比语法上的尊敬重要得多。第二明确告诉模型每条回复不要超过 50 个字不然 TTS 合成一句话要读十几秒交互节奏很拖沓。第三把记忆注入到 system prompt 里让模型在生成的时候就能引用具体信息。4.4 记忆模块实现让机器人记住三个月前的“膝盖疼”记忆模块我单独写了一个 Python 服务监听 MQTT 上所有对话消息异步处理抽取和存储。抽取环节我写了两种方式。硬规则部分用正则表达式匹配中文里的关键模式import re HARD_RULES [ # 健康信息 (r(膝盖|腰|头|胃|腿|眼睛|血压|血糖|心脏).{0,6}(疼|痛|不舒服|难受|高|低), health), # 家庭关系 (r(我儿子|我女儿|我孙子|我孙女|我老伴|外孙).{0,10}(回国|回来|结婚|考上|生日|去世), family), # 重要日期 (r(\d{1,2})月(\d{1,2})日, date), ]软规则部分用 LLM 来判断。每一轮对话结束后我构造一个判断 prompt“下面这段对话中有没有值得长期记住的个人信息如果有提取出来并分类。如果没有回复‘无’。”{ task: memory_extraction, conversation: [用户: 我女儿下个月要从国外回来了, Spark: 那太好了什么时候, 用户: 大概7月15号待一个月], output: { has_memory: true, memories: [ {category: family, content: 用户女儿7月15日从国外回国待一个月, importance: 8, expire: 2025-08-15} ] } }这里有一个重要设计——设置过期时间。不是所有记忆都永久保存的“女儿下个月回国”这件事过了下个月就没有意义了。我让 LLM 为每条记忆预估一个过期时间存储时写入 expire 字段检索时过滤掉已过期的记忆。这个设计让我妈的机器人不会在秋天还在问她女儿是不是要回来了避免了很多尴尬。4.5 情感识别部署emotion2vec 与文本分类的融合语音情感识别我用的是 FunAudioLLM 开源的 emotion2vec 模型。这个模型的用法很直接加载预训练权重输入音频输出情感向量和分类结果import torch from emotion2vec import Emotion2Vec model Emotion2Vec.from_pretrained(emotion2vec_base) audio load_audio(user_speech.wav) result model(audio) emotion_label result[label] # 例如 sad emotion_prob result[prob]实际部署的时候我没有用实时流式推理而是采用“说完整句话后再分析”的方式。因为情感判断需要完整的语气曲线实时流式分析很难捕捉到整句话的情绪走向。文本情感分类我用的是 chinese-roberta-wwm-ext在 Hugging Face 上有一个微调过的情感分类版本。两者融合的代码逻辑def fuse_emotion(audio_emotion, audio_prob, text_emotion, text_prob): audio_weight 0.6 text_weight 0.4 scores { happy: audio_emotion[happy] * audio_prob * audio_weight text_emotion[happy] * text_prob * text_weight, sad: audio_emotion[sad] * audio_prob * audio_weight text_emotion[sad] * text_prob * text_weight, angry: audio_emotion[angry] * audio_prob * audio_weight text_emotion[angry] * text_prob * text_weight, neutral: audio_emotion[neutral] * audio_prob * audio_weight text_emotion[neutral] * text_prob * text_weight, } return max(scores, keyscores.get)融合后得到一个综合情感标签这个标签会做三件事存进这一轮的对话状态里、推送给 LLM 调整回复语气、驱动表情显示切换不同的表情动画。4.6 表情与动作系统让机器人有“表情”表情系统我用了双色 LED 灯带和显示屏结合的方式。显示屏上显示一个大大的抽象表情——圆弧形眼睛加嘴巴的线条画根据情感标签切换不同的形态开心时嘴角上扬难过时嘴角下垂思考时眼睛变成两个问号。用 Python 的 Pillow 库生成表情图片然后通过 fbi 命令刷新到 framebuffer 上。动作系统相对简单三个舵机组成的两自由度头部加一个旋转底座。动作触发逻辑写死在调度器里检测到用户说话时头部转向声源方向用户说完话后头部回到正前方机器人主动说话时头部轻微上下摆动增加生动感。舵机控制用的是 PCA9685 驱动板走 I2C 接口Python 代码控制在 2~3ms 的 PWM 脉宽内。这里一定要记得给舵机单独供电如果跟树莓派共用 5V 电源一动作电流就拉垮树莓派直接重启。我第一版就踩了这个坑后来加了一个 5V 5A 的独立电源模块给舵机供电系统才算稳定下来。5. 常见问题与排查技巧实录5.1 树莓派内存不足大模型推理频繁 OOM这个问题在最初测试时最折磨人。Ollama 加载 Qwen2.5-7B 量化模型后系统可用内存只剩下 300MB 左右跑一段时间就会出现 OOMOllama 进程直接被内核干掉。排查思路free -h # 查看内存占用发现内存占用大头是 Ollama 的模型驻留大约 4.9GBPython 服务占了 500MB剩下系统和其他服务占了 1GB。解决方案有几个方向。第一个是把虚拟内存加上在/etc/dphys-swapfile里把 swap 大小从默认的 100MB 改到 2048MBsudo nano /etc/dphys-swapfile # 修改 CONF_SWAPSIZE2048 sudo systemctl restart dphys-swapfile第二个是尽量减少常驻内存的服务。我把一些不常用的服务全部用 systemd 改成懒加载不在开机时自动启动。第三个是模型加载参数优化在 Ollama 的 Modelfile 里设置num_ctx从默认的 4096 降到 2048把 KV cache 显存占用降下来。改完之后系统稳定了很多虽然偶尔还是会因碎片化导致内存紧张但已经能做到连续运行一周不重启了。5.2 MQTT 消息风暴导致对话卡死刚开始联调的时候我遇到过一个诡异的问题机器人说话说着说着就卡住了过一会儿又自己恢复。查了很久最后在 MQTT 订阅端看到现象——每轮对话的所有中间状态ASR 结果、情感标签、LLM 回复、TTS 状态全部发在同一个 topic 上Python 的 paho-mqtt 回调函数里又有网络请求操作导致消息处理线程被阻塞消息堆积到内存里最终整个服务卡死。解决方案很粗暴但有效重新设计 topic 规划把不同类别的消息拆分到不同 topic 前缀下spark/asr/text # 语音识别结果 spark/emotion/state # 情感状态 spark/llm/reply # 大模型回复 spark/tts/status # 语音合成状态 spark/action/trigger # 动作触发同时在回调函数里不直接做耗时操作只把消息塞进队列由独立的工作线程去处理。改完之后整个系统清爽了很多卡死问题再也没出现过。5.3 语音识别把“膝盖”听成“媳妇”的尴尬中文语音识别在方言和口音场景下经常翻车。我妈说话带一点东北口音“膝盖”经常被识别成“媳妇”导致机器人的回复完全跑偏。这个问题让我意识到单纯靠通用 ASR 模型不够需要做领域纠错。我的方案是在中间加一层纠错逻辑。当 ASR 的文本置信度低于某个阈值或者文本匹配到易混淆词库时用 LLM 结合上下文重新做一次“语言模型矫正”。比如识别结果是“我媳妇有点疼”但上文是“今天走路多了”LLM 能判断出这里应该是“膝盖”。这个方案不能 100% 纠正但能把关键错误降低 70% 左右。5.4 局域网设备掉线机器人变“植物人”树莓派和旧手机通过 WiFi 连接路由器偶尔重启或者信号不好会导致两个设备之间的 WebSocket 断连。断连之后整个系统就进入了“聋哑”状态——麦克风还在监听但识别结果发不出去无法响应。我加了三层保障。第一层WebSocket 客户端增加自动重连机制断线后每 5 秒尝试重连一次。第二层树莓派上写了一个看门狗脚本定期 ping 手机的 IP 地址如果连续 3 次 ping 不通就调用nmcli重启 WiFi 连接。第三层核心对话状态放在树莓派本地断线期间如果识别到用户说话会先缓存重连后补发处理。这三层保障让系统的稳定性提升了一个量级。到目前为止Spark 在我妈家已经连续运行了一个多月没有再出现需要人工干预的“植物人”状态。6. 个人使用体会与扩展建议6.1 我妈对 Spark 的真实反馈项目做完之后我把我妈家的智能音箱替换成了 Spark。第一天她不太习惯觉得这玩意儿“不像个正经东西”。第二天她开始跟它聊天气。到了第三天她居然主动跟 Spark 说“我昨天没睡好”。我当时在后台看到这条对话记录心里挺感慨的。一个多月用下来我妈评价最高的是三个功能早上主动提醒她量血压、聊天时会问她膝盖好点没、晚上睡觉前跟她说晚安。这三个功能全是主动关怀调度和记忆系统的功劳。相反她吐槽最多的是机器人的语速太慢有时候她说完一句话要等十几秒才有回应她会以为机器人坏了又喊一遍。这个体验问题短期很难完美解决毕竟 7B 模型在树莓派上的推理速度就这样了。6.2 后续可以扩展的方向Spark 现在的功能还只是一个基础版本很多方向可以继续深入。比如给机器人加一块 4G 通信模块摆脱 WiFi 限制或者增加人体存在传感器让它能感知到房间里有没有人从而更智能地选择什么时候说话又或者把视觉能力做得更强一些识别用户的动作姿态来判断情绪而不只是靠语音。如果追求更好的对话体验可以考虑把算力从树莓派迁移到 x86 小主机上那样就可以跑更大尺寸的模型响应速度也会快很多。我目前已经在测试一个 N100 小主机方案效果比树莓派好一个档次整个机身的体积和成本也差不多。6.3 最后分享一个调参心得整个项目调试下来我最想分享的一个心得是情感陪伴机器人最重要的不是 AI 能力有多强而是交互节奏和边界感要拿捏到位。该说话的时候说话不该说的时候闭嘴能做到这一点的机器人哪怕说的是最简单的车轱辘话也比一个话痨版 GPT 强得多。我调主动关怀调度器花的时间比调大模型 prompt 的时间还多。因为 prompt 写得不好顶多是某句话回答得不像样但主动关怀的频率和时机不对会让用户把机器人当成一个负担。我的经验是宁可少说不可多说宁可晚说不可抢说。这个原则放在情感陪伴产品里比任何技术指标都重要。如果你也想做一个类似的项目我的建议是先别急着买硬件拿一台旧电脑或者旧手机把软件架构跑通再考虑外形和传感器。软件架构跑通了后面全是锦上添花软件架构做不好再漂亮的机身也白搭。
返回列表