1. 项目缘起当“小爱同学”遇上本地AI大脑“小爱同学今天天气怎么样” “小爱同学播放周杰伦的歌。” “小爱同学定个明天早上7点的闹钟。”这些指令对于任何一个拥有小米智能音箱的用户来说都再熟悉不过了。小爱同学作为国内普及度最高的智能语音助手之一以其便捷的语音交互和丰富的IoT控制能力早已融入千万家庭的日常生活。然而用久了之后你可能会发现它的边界它的回答依赖于云端服务器的知识库和算法对于一些个性化、深度化或者需要实时本地处理的需求就显得有些力不从心。比如你想让它基于你电脑里的一份私人文档进行总结或者让它用你指定的某个开源大模型进行创作又或者单纯地你希望自己的语音数据完全在本地流转不经过任何外部服务器。这个想法就是本次“魔改”项目的起点。我们不是要替换小爱同学本身它优秀的语音唤醒、降噪、音频播放等硬件能力依然是我们宝贵的财富。我们要做的是给它换一个更强大、更自由、更私密的“大脑”——一个部署在你本地电脑或服务器上的AI模型。想象一下你依然用“小爱同学”唤醒它但背后回答你的可能是你精心调校的ChatGLM、Qwen甚至是完全开源的Llama模型。你可以让它帮你分析本地文件、根据你的个人知识库回答问题、运行自定义的脚本甚至连接到你家里的其他本地服务如Home Assistant、Jellyfin影音库实现真正意义上的个性化智能家居中枢。这听起来很极客但实现路径其实已经非常清晰。得益于开源社区的努力我们已经有了像MiService小米服务Python库这样的工具可以模拟小米账号与音箱设备通信也有了诸如SiliconCloud、FastGPT、Ollama等可以轻松在本地部署的AI模型服务框架。本项目的目的就是将这些分散的技术点串联起来形成一个稳定、可复现的“保姆级”解决方案。无论你是想保护隐私还是想解锁AI的无限可能这篇教程都将手把手带你完成从零到一的整个过程。2. 核心架构解析信号如何从麦克风流向AI模型在动手之前我们必须先理解整个系统的数据流向和核心组件。这有助于你在后续配置时清楚每一个步骤的目的而不是机械地复制命令。整个魔改系统的架构可以抽象为以下几个核心环节语音输入 - 语音唤醒与拾取 - 语音转文本 - 文本意图处理 - AI模型推理 - 文本转语音 - 音频输出看起来链条很长但幸运的是小爱音箱已经完美承担了前两步和最后一步的工作。我们的改造主要聚焦在中间环节。2.1 原有云端链路与小爱音箱的角色在标准模式下当你喊出“小爱同学”时唤醒与拾音音箱本地的唤醒芯片被激活开始录制后续的语音指令。音频上传录制的音频数据被加密后通过网络发送到小米的云端语音识别服务器。云端处理云端服务器进行语音识别将音频转为文本然后文本进入自然语言处理模块理解你的意图。执行与反馈根据意图云端服务器可能会查询天气、播放音乐调用音乐服务API、控制设备通过IoT云或生成简单的对话回答。最终生成的文本回复再被转换成语音下发给音箱播放。在这个过程中小爱音箱本质上是一个“功能强大的麦克风和扬声器”核心逻辑全部在云端。2.2 我们的本地化改造链路我们的目标是将第3步“云端处理”替换为“本地处理”。改造后的链路如下拦截音频我们不再将音频发送到小米云端。相反我们需要一个本地服务来“接收”小爱音箱准备上传的音频流。这里有两种主流思路模拟小米云端服务器在本地搭建一个服务欺骗小爱音箱让它以为这个本地服务就是官方的语音识别服务器。这需要对小米的通信协议有深入了解实现难度较高。利用公开的开发者模式更可行的方法是将小爱音箱设置为“蓝牙音箱”或“DLNA播放器”模式然后通过手机或电脑的蓝牙连接它将其作为一个纯粹的音频输入/输出设备。这样音频的捕获和播放就由连接它的主机我们的电脑来控制。本教程将采用这种更通用、更稳定的方式。本地语音识别我们的电脑通过蓝牙接收到来自小爱音箱麦克风的音频流后需要使用本地的语音转文本引擎进行处理。我们可以选择离线的STT引擎如Vosk、Whisper.cpp也可以使用一些提供API的在线服务如果你不介意部分数据出局域网。为了完全本地化我们会优先选择离线方案。文本处理与AI交互识别出的文本会被发送给我们本地部署的AI模型服务。这里就是发挥创意的地方了。你可以连接到一个简单的对话机器人也可以连接到一个能够调用工具如查询数据库、执行命令的智能体框架。本地语音合成AI模型返回的文本答复需要被合成语音。同样我们可以使用本地TTS引擎如Edge-TTS的本地版本、VITS等模型生成音频文件或流。音频播放最后将合成的音频通过蓝牙回传到小爱音箱进行播放。这样你就听到了来自本地AI的“声音”。总结一下核心改造点我们将小爱音箱降级为一个“蓝牙音频设备”而将所有的智能处理语音识别、自然语言理解、语音合成都搬迁到我们本地电脑上的一个集成服务中。这个集成服务就是本教程要构建的核心。3. 环境准备与基础工具选型工欲善其事必先利其器。开始编码前我们需要准备好软硬件环境并选择合适的技术栈。以下清单和建议基于一个典型的家庭环境系统以Windows/macOS/Linux均可但Linux在长期稳定运行方面更有优势。3.1 硬件与网络要求小爱音箱几乎任何一款支持蓝牙功能的小爱音箱都可以如小爱音箱Pro、小爱音箱Play增强版等。请确保你的音箱型号支持蓝牙连接并在手机App中确认该功能已开启。主机一台始终开机的电脑或小型服务器如树莓派4B及以上、英特尔NUC、旧笔记本等。这是运行所有本地服务的“大脑”。建议配置至少4核CPU、8GB内存如果打算运行较大的AI模型则需要更强的CPU和16GB以上内存。拥有NVIDIA GPU支持CUDA会极大加速推理过程。网络小爱音箱和主机需要在同一个局域网内。这是蓝牙连接和后续可能用到的局域网服务发现的基础。3.2 核心软件服务选型这里的选择决定了项目的难度和最终能力。我根据稳定性和社区活跃度推荐一套兼顾功能与易用性的方案语音识别OpenAI Whisper为什么选它Whisper是当前开源领域准确率最高的语音识别模型之一尤其对中文支持良好。它提供了多种规模的模型从 tiny 到 large可以在精度和速度之间权衡。我们可以使用whisper.cpp这个C移植版本它优化了性能更容易在资源受限的设备上运行。备选Vosk。它更轻量启动更快但中文模型准确率略逊于Whisper。大语言模型服务Ollama为什么选它Ollama极大地简化了在本地运行大模型的过程。一条命令就能拉取和运行ChatGLM、Qwen、Llama2、Mistral等主流模型。它提供了简洁的API兼容OpenAI API格式让我们的程序可以像调用ChatGPT一样调用本地模型。管理和切换模型也非常方便。备选text-generation-webui。功能更强大界面友好但配置相对复杂资源占用也更高。语音合成Edge-TTS 命令行工具为什么选它虽然微软Edge-TTS是在线服务但其语音质量高、自然度好且可以通过命令行或库方便调用。对于初步实现这是最快最好的选择。注意这会将你需要合成的文本发送到微软服务器。如果要求绝对本地化则需要寻找如Coqui TTS、VITS-fast-fine-tuning等本地TTS方案但部署和效果调优会复杂很多。本地备选pyttsx3离线但声音机械、Coqui TTS需要自己下载模型。中枢调度程序核心自定义Python脚本我们需要编写一个Python程序作为整个流程的“总指挥”。它的职责是管理蓝牙音频设备的连接与音频流捕获。调用Whisper进行语音识别。将识别文本通过Ollama的API发送给大模型获取回复。调用Edge-TTS将回复文本转为语音。将生成的语音音频通过蓝牙发送给小爱音箱播放。这个脚本将使用pyaudio音频处理、requests调用API、subprocess调用命令行工具等库。3.3 基础环境搭建步骤以下步骤以Ubuntu 22.04为例其他系统请参考相应命令。安装Python及pip确保系统有Python 3.8。sudo apt update sudo apt install python3 python3-pip安装FFmpegWhisper处理音频需要。sudo apt install ffmpeg安装Ollamacurl -fsSL https://ollama.com/install.sh | sh ollama pull qwen:7b # 示例拉取通义千问7B模型可根据你的硬件选择如qwen:0.5b, qwen:1.8b, qwen:7b等 ollama run qwen:7b # 测试运行成功后按CtrlC退出运行后Ollama服务会在本地11434端口启动一个API服务。安装Whisper.cppgit clone https://github.com/ggerganov/whisper.cpp.git cd whisper.cpp make # 下载基础模型例如base模型 ./models/download-ggml-model.sh base编译后会生成main可执行文件用于语音识别。准备Python虚拟环境及依赖python3 -m venv xiaoai-ai-venv source xiaoai-ai-venv/bin/activate pip install pyaudio requests sounddevice pydub edge-ttspyaudio用于捕获和播放音频流在Linux上可能需要额外安装portaudio库sudo apt install portaudio19-dev。sounddevicepydub简化音频处理的辅助库。edge-tts调用Edge-TTS的命令行工具。4. 关键环节实现连接、识别、思考与回应环境就绪后我们开始构建中枢调度程序的核心逻辑。我将分模块解释关键代码段及其作用。4.1 蓝牙连接与音频捕获首先我们需要将小爱音箱与电脑配对并连接为蓝牙音频设备。在电脑的蓝牙设置中搜索设备。在小爱音箱的App里开启蓝牙或者对小爱说“小爱同学蓝牙连接”。找到名为“小米小爱音箱XXX”的设备进行配对和连接。连接成功后它应该会出现在系统的音频输入麦克风和输出设备列表中。在Python中我们需要指定这个蓝牙设备作为音频输入源。sounddevice库可以方便地列出和选择设备。import sounddevice as sd def list_audio_devices(): 列出所有音频设备 devices sd.query_devices() for i, dev in enumerate(devices): print(f{i}: {dev[name]} (输入通道: {dev[max_input_channels]}, 输出通道: {dev[max_output_channels]})) # 运行一次找到你的小爱音箱对应的设备索引号 list_audio_devices()假设你的小爱音箱作为输入设备的索引是2我们可以这样录制一段音频import numpy as np def record_audio(duration5, sample_rate16000, input_device_idx2): 录制指定时长的音频 print(开始录音...) audio_data sd.rec(int(duration * sample_rate), sampleratesample_rate, channels1, dtypefloat32, deviceinput_device_idx) sd.wait() # 等待录制完成 print(录音结束。) return audio_data.flatten(), sample_rate # 测试录制 audio, sr record_audio(duration5)注意蓝牙音频传输会有轻微延迟且音质并非为语音识别优化。在实际应用中你可能需要调整采样率Whisper通常需要16000Hz并添加一个简单的VAD语音活动检测来只在有声音时录制避免处理静音片段。4.2 调用本地Whisper进行语音识别录制好的音频数据需要保存为文件然后交给Whisper.cpp处理。import subprocess import tempfile import scipy.io.wavfile as wav def transcribe_audio_with_whisper(audio_data, sample_rate, whisper_model_path./whisper.cpp/models/ggml-base.bin): 使用whisper.cpp识别音频 :param audio_data: 音频数据数组 :param sample_rate: 采样率 :param whisper_model_path: whisper.cpp模型路径 :return: 识别出的文本 # 1. 将音频数据临时保存为wav文件 with tempfile.NamedTemporaryFile(suffix.wav, deleteFalse) as tmp_wav: wav_filename tmp_wav.name wav.write(wav_filename, sample_rate, (audio_data * 32767).astype(np.int16)) # 转换为16位PCM # 2. 调用whisper.cpp的main程序进行识别 # whisper.cpp路径需要根据你的实际安装位置调整 whisper_cmd [ ./whisper.cpp/main, -m, whisper_model_path, -f, wav_filename, -l, zh, # 指定语言为中文可以提高识别准确率 -otxt # 输出txt文件 ] try: result subprocess.run(whisper_cmd, capture_outputTrue, textTrue, cwd./whisper.cpp) # 输出结果在标准输出中格式为带时间戳的文本我们取最后一行纯文本部分简单处理 output_lines result.stdout.strip().split(\n) # 寻找包含识别结果的行更健壮的做法是解析输出的txt文件 transcription for line in output_lines: if line and not line.startswith([): transcription line.strip() break print(fWhisper识别结果: {transcription}) return transcription except subprocess.CalledProcessError as e: print(fWhisper识别失败: {e}) return finally: # 清理临时文件 import os os.unlink(wav_filename)实操心得Whisper.cpp第一次运行时会加载模型速度较慢后续调用会快很多。对于实时交互可以考虑使用tiny或base模型以换取速度。另外上述代码对输出结果做了简单处理在实际项目中你可能需要更精细地解析main程序的输出或者直接使用Whisper.cpp提供的Python绑定库whisper-cpp-python来获得更结构化的结果。4.3 与本地大模型对话接下来我们将识别出的文本发送给本地运行的Ollama服务。Ollama的API兼容OpenAI格式非常方便。import requests import json def ask_ollama(prompt, modelqwen:7b, ollama_hosthttp://localhost:11434): 向本地Ollama服务发送提问 :param prompt: 用户输入的文本 :param model: 指定的模型名称 :param ollama_host: Ollama服务地址 :return: 模型返回的回复文本 if not prompt: return 我没有听清您说什么。 api_url f{ollama_host}/api/generate payload { model: model, prompt: prompt, stream: False # 为了简单我们关闭流式输出一次性获取结果 } try: response requests.post(api_url, jsonpayload, timeout60) # 设置超时 response.raise_for_status() result response.json() return result.get(response, 模型没有返回有效内容。).strip() except requests.exceptions.RequestException as e: print(f调用Ollama API失败: {e}) return 抱歉我的大脑AI模型暂时无法连接。 except json.JSONDecodeError: print(Ollama返回了非JSON响应。) return AI模型返回了意外的格式。 # 测试 # reply ask_ollama(你好请介绍一下你自己。) # print(reply)注意事项Ollama的/api/generate接口在模型较大或提示词较长时响应可能较慢十几秒甚至更长。对于语音交互这体验很差。有两种优化思路使用更小的模型如qwen:0.5b或llama2:7b响应速度会快很多。采用流式响应将stream设为True然后逐块读取响应可以边生成边播放但需要更复杂的音频流处理。4.4 文本转语音并播放获取到AI的文本回复后我们需要将其转为语音并通过蓝牙音箱播放。这里使用edge-tts。import subprocess import tempfile import time def text_to_speech_and_play(text, output_device_idxNone): 使用edge-tts将文本转为语音并播放 :param text: 要合成的文本 :param output_device_idx: 系统音频输出设备索引None则为默认设备 if not text: return # 1. 使用edge-tts生成语音文件 with tempfile.NamedTemporaryFile(suffix.mp3, deleteFalse) as tmp_mp3: mp3_filename tmp_mp3.name # 选择语音例如中文普通话女声 voice zh-CN-XiaoxiaoNeural tts_cmd [edge-tts, --voice, voice, --text, text, --write-media, mp3_filename] try: subprocess.run(tts_cmd, checkTrue, capture_outputTrue) print(f语音已生成: {mp3_filename}) except subprocess.CalledProcessError as e: print(fTTS生成失败: {e}) return # 2. 播放生成的语音文件 # 使用pydub和pyaudio播放 from pydub import AudioSegment from pydub.playback import play audio AudioSegment.from_file(mp3_filename, formatmp3) # 如果指定了输出设备这里需要更复杂的处理pydub.playback默认使用系统默认设备。 # 一个方案是使用sounddevice直接播放原始数组。 print(开始播放...) play(audio) # 这会在默认输出设备播放 # 3. 清理临时文件 import os time.sleep(0.5) # 稍等片刻确保播放完毕 os.unlink(mp3_filename) # 更优的播放方案使用sounddevice直接指定设备播放 import soundfile as sf def play_audio_with_sounddevice(file_path, output_device_idx): 使用sounddevice指定设备播放音频文件 data, samplerate sf.read(file_path) sd.play(data, samplerate, deviceoutput_device_idx) sd.wait()关键点pydub.playback.play()通常使用系统默认播放设备。为了确保音频一定从连接小爱音箱的蓝牙通道输出你需要使用sounddevice库并指定正确的输出设备索引在list_audio_devices中查找。这需要你将MP3文件解码为PCM数据数组然后调用sd.play。5. 集成与优化打造稳定可用的语音助手将上述模块串联起来并添加一些关键逻辑就形成了我们的主程序。5.1 主循环逻辑设计一个简单的交互循环可以是持续监听蓝牙麦克风当检测到一定音量的声音时简易VAD开始录制直到静音一段时间后停止然后执行识别-AI问答-TTS播放的流程。import numpy as np import sounddevice as sd import queue import threading import time class XiaoAiAIAssistant: def __init__(self, input_device_idx, output_device_idx, silence_threshold0.01, silence_duration1.0): self.input_device input_device_idx self.output_device output_device_idx self.silence_threshold silence_threshold # 静音阈值 self.silence_duration silence_duration # 持续静音多久算结束 self.sample_rate 16000 self.audio_queue queue.Queue() self.is_recording False self.recorded_chunks [] def audio_callback(self, indata, frames, time_info, status): 声音输入回调函数每次收到音频块时调用 if status: print(f音频输入错误: {status}) # 简单的能量检测判断是否在说话 volume_norm np.linalg.norm(indata) / np.sqrt(frames) if volume_norm self.silence_threshold: if not self.is_recording: print(检测到语音开始录制...) self.is_recording True self.recorded_chunks [] self.recorded_chunks.append(indata.copy()) else: if self.is_recording: # 检测到静音检查静音持续时间 # 这里简化处理如果当前块是静音且之前已经录了一些内容则可能结束 # 更完善的VAD需要计时 pass def start_listening(self): 开始监听音频输入 with sd.InputStream(callbackself.audio_callback, deviceself.input_device, channels1, samplerateself.sample_rate, blocksize1024): print(正在监听... 请说话。) while True: time.sleep(0.1) # 主线程保持运行 # 这里可以添加一个手动触发结束录制的逻辑比如按回车键 # 并处理 self.recorded_chunks def process_audio(self, audio_data): 处理录制好的音频数据识别 - AI - 语音播放 # 1. 合并音频块 if not audio_data: return full_audio np.concatenate(audio_data, axis0).flatten() # 2. 语音识别 text transcribe_audio_with_whisper(full_audio, self.sample_rate) if not text or len(text) 2: print(未识别到有效内容。) return # 3. 询问AI print(f用户说: {text}) reply ask_ollama(text) print(fAI回复: {reply}) # 4. 语音合成并播放 text_to_speech_and_play(reply, self.output_device) # 使用示例 if __name__ __main__: # 请先运行 list_audio_devices() 确定设备索引 input_idx 2 # 小爱音箱作为麦克风的索引 output_idx 3 # 小爱音箱作为扬声器的索引 assistant XiaoAiAIAssistant(input_idx, output_idx) # 这里需要实现一个更完善的VAD和录制控制逻辑然后调用 assistant.start_listening()上面的代码框架提供了一个起点。一个可用的版本需要实现一个状态机来管理“待机”、“录音中”、“处理中”等状态并有一个准确的VAD来判定语音的起止。5.2 性能优化与稳定性提升异步处理录音、识别、AI推理、TTS合成、播放这些步骤都是I/O密集型或计算密集型的。使用多线程或异步编程asyncio可以避免阻塞让系统在等待AI回复时仍能监听新的唤醒词。唤醒词检测上述简易VAD容易误触发。可以集成一个本地的唤醒词检测引擎如Porcupine或Snowboy已停止维护但仍有可用模型只有当检测到“小爱同学”或其他自定义唤醒词后才进入录音模式。这能极大提升体验。错误处理与重试网络波动、AI服务重启都可能导致单次调用失败。在主循环中需要加入健壮的错误捕获和重试机制避免程序崩溃。音频前处理蓝牙音频可能有噪音可以在识别前加入简单的降噪或增益处理提升识别准确率。配置化将设备索引、模型路径、API地址、唤醒词灵敏度等参数提取到配置文件中方便不同环境部署。5.3 进阶玩法赋予AI“行动力”目前我们的AI只是一个聊天机器人。你可以通过以下方式让它变得更强大函数调用使用支持function calling的模型如Qwen2.5-7B-Instruct或框架如LangChain。你可以定义一系列函数如get_weather(city)、control_light(device_name, action)让AI在对话中自主决定调用哪个函数并传入正确参数然后由你的主程序执行对应操作如调用Home Assistant的API来控制智能灯。接入本地知识库使用RAG技术。将你的个人文档、笔记进行向量化存储。当用户提问时先从中检索相关片段再连同问题和片段一起发给AI让它生成基于你个人知识的回答。多模态如果你的AI模型支持视觉如LLaVA你甚至可以连接一个USB摄像头让小爱同学“看到”东西并描述它。当然这需要更复杂的集成。6. 部署与长期运行建议开发调试完成后你可能希望这个服务能像真正的智能音箱一样7x24小时运行。使用系统服务在Linux上可以将你的Python脚本封装成一个systemd服务。# /etc/systemd/system/xiaoai-ai.service [Unit] DescriptionXiaoAi AI Assistant Afternetwork.target [Service] Typesimple Useryour_username WorkingDirectory/path/to/your/project EnvironmentPATH/path/to/your/venv/bin ExecStart/path/to/your/venv/bin/python /path/to/your/project/main.py Restartalways RestartSec10 [Install] WantedBymulti-user.target然后使用sudo systemctl enable --now xiaoai-ai来启用和启动服务。日志管理确保你的程序将运行日志输出到文件方便排查问题。可以使用Python的logging模块。资源监控运行大模型会消耗大量内存和CPU。建议监控主机的资源使用情况确保不会因为内存不足而崩溃。可以考虑使用docker来隔离环境并限制资源使用。开机自启动蓝牙连接如果电脑重启蓝牙连接可能会断开。你需要编写脚本在系统启动后自动搜索并连接小爱音箱的蓝牙。这通常可以通过bluetoothctl命令实现。踩坑实录与心得蓝牙延迟与稳定性蓝牙音频的延迟约100-300ms是客观存在的这会导致“说完话到开始处理”以及“播放语音”的延迟感。Wi-Fi音箱如部分小爱型号如果有开放的音频流协议如AirPlay可能会更好但开发复杂度更高。蓝牙是目前性价比最高的方案。AI响应速度7B参数模型在CPU上推理一次生成可能需要10-30秒这完全无法用于对话。这是体验的核心瓶颈。解决方案只能是1) 使用更小的模型如1.8B或0.5B2) 使用GPU加速3) 使用量化后的模型Ollama默认会进行一定量化。在我的测试中Qwen2.5-1.8B-Instruct在Intel N100 CPU上响应时间在3-5秒勉强可接受。唤醒词完全自己实现一个低误唤醒率的唤醒词引擎比较难。一个取巧的办法是继续使用小爱同学原生的唤醒词。当小爱被唤醒后它会播放一声“叮咚”提示音并开始收音。我们可以用程序持续监听当检测到这个特定的“叮咚”声频谱特征时就判定为一次有效唤醒随即开始录制接下来的用户语音。这样就完美复用了原厂优秀的唤醒体验。这需要一些音频信号处理的知识但比从头训练唤醒词模型要简单。完成以上所有步骤你就拥有了一个完全受控于本地、能力可无限扩展的“魔改版小爱同学”。它不再受限于云端技能你可以用它查询本地文档、控制复杂的自定义家居自动化、甚至进行专业的代码讨论。这个项目更像是一个起点一个将前沿AI能力与成熟硬件产品结合的范例。当你打通了这个流程后面如何改造、赋予它什么能力就完全取决于你的想象力和需求了。
本地AI语音助手改造:让小爱同学接入Ollama与Whisper实现私有化智能
1. 项目缘起当“小爱同学”遇上本地AI大脑“小爱同学今天天气怎么样” “小爱同学播放周杰伦的歌。” “小爱同学定个明天早上7点的闹钟。”这些指令对于任何一个拥有小米智能音箱的用户来说都再熟悉不过了。小爱同学作为国内普及度最高的智能语音助手之一以其便捷的语音交互和丰富的IoT控制能力早已融入千万家庭的日常生活。然而用久了之后你可能会发现它的边界它的回答依赖于云端服务器的知识库和算法对于一些个性化、深度化或者需要实时本地处理的需求就显得有些力不从心。比如你想让它基于你电脑里的一份私人文档进行总结或者让它用你指定的某个开源大模型进行创作又或者单纯地你希望自己的语音数据完全在本地流转不经过任何外部服务器。这个想法就是本次“魔改”项目的起点。我们不是要替换小爱同学本身它优秀的语音唤醒、降噪、音频播放等硬件能力依然是我们宝贵的财富。我们要做的是给它换一个更强大、更自由、更私密的“大脑”——一个部署在你本地电脑或服务器上的AI模型。想象一下你依然用“小爱同学”唤醒它但背后回答你的可能是你精心调校的ChatGLM、Qwen甚至是完全开源的Llama模型。你可以让它帮你分析本地文件、根据你的个人知识库回答问题、运行自定义的脚本甚至连接到你家里的其他本地服务如Home Assistant、Jellyfin影音库实现真正意义上的个性化智能家居中枢。这听起来很极客但实现路径其实已经非常清晰。得益于开源社区的努力我们已经有了像MiService小米服务Python库这样的工具可以模拟小米账号与音箱设备通信也有了诸如SiliconCloud、FastGPT、Ollama等可以轻松在本地部署的AI模型服务框架。本项目的目的就是将这些分散的技术点串联起来形成一个稳定、可复现的“保姆级”解决方案。无论你是想保护隐私还是想解锁AI的无限可能这篇教程都将手把手带你完成从零到一的整个过程。2. 核心架构解析信号如何从麦克风流向AI模型在动手之前我们必须先理解整个系统的数据流向和核心组件。这有助于你在后续配置时清楚每一个步骤的目的而不是机械地复制命令。整个魔改系统的架构可以抽象为以下几个核心环节语音输入 - 语音唤醒与拾取 - 语音转文本 - 文本意图处理 - AI模型推理 - 文本转语音 - 音频输出看起来链条很长但幸运的是小爱音箱已经完美承担了前两步和最后一步的工作。我们的改造主要聚焦在中间环节。2.1 原有云端链路与小爱音箱的角色在标准模式下当你喊出“小爱同学”时唤醒与拾音音箱本地的唤醒芯片被激活开始录制后续的语音指令。音频上传录制的音频数据被加密后通过网络发送到小米的云端语音识别服务器。云端处理云端服务器进行语音识别将音频转为文本然后文本进入自然语言处理模块理解你的意图。执行与反馈根据意图云端服务器可能会查询天气、播放音乐调用音乐服务API、控制设备通过IoT云或生成简单的对话回答。最终生成的文本回复再被转换成语音下发给音箱播放。在这个过程中小爱音箱本质上是一个“功能强大的麦克风和扬声器”核心逻辑全部在云端。2.2 我们的本地化改造链路我们的目标是将第3步“云端处理”替换为“本地处理”。改造后的链路如下拦截音频我们不再将音频发送到小米云端。相反我们需要一个本地服务来“接收”小爱音箱准备上传的音频流。这里有两种主流思路模拟小米云端服务器在本地搭建一个服务欺骗小爱音箱让它以为这个本地服务就是官方的语音识别服务器。这需要对小米的通信协议有深入了解实现难度较高。利用公开的开发者模式更可行的方法是将小爱音箱设置为“蓝牙音箱”或“DLNA播放器”模式然后通过手机或电脑的蓝牙连接它将其作为一个纯粹的音频输入/输出设备。这样音频的捕获和播放就由连接它的主机我们的电脑来控制。本教程将采用这种更通用、更稳定的方式。本地语音识别我们的电脑通过蓝牙接收到来自小爱音箱麦克风的音频流后需要使用本地的语音转文本引擎进行处理。我们可以选择离线的STT引擎如Vosk、Whisper.cpp也可以使用一些提供API的在线服务如果你不介意部分数据出局域网。为了完全本地化我们会优先选择离线方案。文本处理与AI交互识别出的文本会被发送给我们本地部署的AI模型服务。这里就是发挥创意的地方了。你可以连接到一个简单的对话机器人也可以连接到一个能够调用工具如查询数据库、执行命令的智能体框架。本地语音合成AI模型返回的文本答复需要被合成语音。同样我们可以使用本地TTS引擎如Edge-TTS的本地版本、VITS等模型生成音频文件或流。音频播放最后将合成的音频通过蓝牙回传到小爱音箱进行播放。这样你就听到了来自本地AI的“声音”。总结一下核心改造点我们将小爱音箱降级为一个“蓝牙音频设备”而将所有的智能处理语音识别、自然语言理解、语音合成都搬迁到我们本地电脑上的一个集成服务中。这个集成服务就是本教程要构建的核心。3. 环境准备与基础工具选型工欲善其事必先利其器。开始编码前我们需要准备好软硬件环境并选择合适的技术栈。以下清单和建议基于一个典型的家庭环境系统以Windows/macOS/Linux均可但Linux在长期稳定运行方面更有优势。3.1 硬件与网络要求小爱音箱几乎任何一款支持蓝牙功能的小爱音箱都可以如小爱音箱Pro、小爱音箱Play增强版等。请确保你的音箱型号支持蓝牙连接并在手机App中确认该功能已开启。主机一台始终开机的电脑或小型服务器如树莓派4B及以上、英特尔NUC、旧笔记本等。这是运行所有本地服务的“大脑”。建议配置至少4核CPU、8GB内存如果打算运行较大的AI模型则需要更强的CPU和16GB以上内存。拥有NVIDIA GPU支持CUDA会极大加速推理过程。网络小爱音箱和主机需要在同一个局域网内。这是蓝牙连接和后续可能用到的局域网服务发现的基础。3.2 核心软件服务选型这里的选择决定了项目的难度和最终能力。我根据稳定性和社区活跃度推荐一套兼顾功能与易用性的方案语音识别OpenAI Whisper为什么选它Whisper是当前开源领域准确率最高的语音识别模型之一尤其对中文支持良好。它提供了多种规模的模型从 tiny 到 large可以在精度和速度之间权衡。我们可以使用whisper.cpp这个C移植版本它优化了性能更容易在资源受限的设备上运行。备选Vosk。它更轻量启动更快但中文模型准确率略逊于Whisper。大语言模型服务Ollama为什么选它Ollama极大地简化了在本地运行大模型的过程。一条命令就能拉取和运行ChatGLM、Qwen、Llama2、Mistral等主流模型。它提供了简洁的API兼容OpenAI API格式让我们的程序可以像调用ChatGPT一样调用本地模型。管理和切换模型也非常方便。备选text-generation-webui。功能更强大界面友好但配置相对复杂资源占用也更高。语音合成Edge-TTS 命令行工具为什么选它虽然微软Edge-TTS是在线服务但其语音质量高、自然度好且可以通过命令行或库方便调用。对于初步实现这是最快最好的选择。注意这会将你需要合成的文本发送到微软服务器。如果要求绝对本地化则需要寻找如Coqui TTS、VITS-fast-fine-tuning等本地TTS方案但部署和效果调优会复杂很多。本地备选pyttsx3离线但声音机械、Coqui TTS需要自己下载模型。中枢调度程序核心自定义Python脚本我们需要编写一个Python程序作为整个流程的“总指挥”。它的职责是管理蓝牙音频设备的连接与音频流捕获。调用Whisper进行语音识别。将识别文本通过Ollama的API发送给大模型获取回复。调用Edge-TTS将回复文本转为语音。将生成的语音音频通过蓝牙发送给小爱音箱播放。这个脚本将使用pyaudio音频处理、requests调用API、subprocess调用命令行工具等库。3.3 基础环境搭建步骤以下步骤以Ubuntu 22.04为例其他系统请参考相应命令。安装Python及pip确保系统有Python 3.8。sudo apt update sudo apt install python3 python3-pip安装FFmpegWhisper处理音频需要。sudo apt install ffmpeg安装Ollamacurl -fsSL https://ollama.com/install.sh | sh ollama pull qwen:7b # 示例拉取通义千问7B模型可根据你的硬件选择如qwen:0.5b, qwen:1.8b, qwen:7b等 ollama run qwen:7b # 测试运行成功后按CtrlC退出运行后Ollama服务会在本地11434端口启动一个API服务。安装Whisper.cppgit clone https://github.com/ggerganov/whisper.cpp.git cd whisper.cpp make # 下载基础模型例如base模型 ./models/download-ggml-model.sh base编译后会生成main可执行文件用于语音识别。准备Python虚拟环境及依赖python3 -m venv xiaoai-ai-venv source xiaoai-ai-venv/bin/activate pip install pyaudio requests sounddevice pydub edge-ttspyaudio用于捕获和播放音频流在Linux上可能需要额外安装portaudio库sudo apt install portaudio19-dev。sounddevicepydub简化音频处理的辅助库。edge-tts调用Edge-TTS的命令行工具。4. 关键环节实现连接、识别、思考与回应环境就绪后我们开始构建中枢调度程序的核心逻辑。我将分模块解释关键代码段及其作用。4.1 蓝牙连接与音频捕获首先我们需要将小爱音箱与电脑配对并连接为蓝牙音频设备。在电脑的蓝牙设置中搜索设备。在小爱音箱的App里开启蓝牙或者对小爱说“小爱同学蓝牙连接”。找到名为“小米小爱音箱XXX”的设备进行配对和连接。连接成功后它应该会出现在系统的音频输入麦克风和输出设备列表中。在Python中我们需要指定这个蓝牙设备作为音频输入源。sounddevice库可以方便地列出和选择设备。import sounddevice as sd def list_audio_devices(): 列出所有音频设备 devices sd.query_devices() for i, dev in enumerate(devices): print(f{i}: {dev[name]} (输入通道: {dev[max_input_channels]}, 输出通道: {dev[max_output_channels]})) # 运行一次找到你的小爱音箱对应的设备索引号 list_audio_devices()假设你的小爱音箱作为输入设备的索引是2我们可以这样录制一段音频import numpy as np def record_audio(duration5, sample_rate16000, input_device_idx2): 录制指定时长的音频 print(开始录音...) audio_data sd.rec(int(duration * sample_rate), sampleratesample_rate, channels1, dtypefloat32, deviceinput_device_idx) sd.wait() # 等待录制完成 print(录音结束。) return audio_data.flatten(), sample_rate # 测试录制 audio, sr record_audio(duration5)注意蓝牙音频传输会有轻微延迟且音质并非为语音识别优化。在实际应用中你可能需要调整采样率Whisper通常需要16000Hz并添加一个简单的VAD语音活动检测来只在有声音时录制避免处理静音片段。4.2 调用本地Whisper进行语音识别录制好的音频数据需要保存为文件然后交给Whisper.cpp处理。import subprocess import tempfile import scipy.io.wavfile as wav def transcribe_audio_with_whisper(audio_data, sample_rate, whisper_model_path./whisper.cpp/models/ggml-base.bin): 使用whisper.cpp识别音频 :param audio_data: 音频数据数组 :param sample_rate: 采样率 :param whisper_model_path: whisper.cpp模型路径 :return: 识别出的文本 # 1. 将音频数据临时保存为wav文件 with tempfile.NamedTemporaryFile(suffix.wav, deleteFalse) as tmp_wav: wav_filename tmp_wav.name wav.write(wav_filename, sample_rate, (audio_data * 32767).astype(np.int16)) # 转换为16位PCM # 2. 调用whisper.cpp的main程序进行识别 # whisper.cpp路径需要根据你的实际安装位置调整 whisper_cmd [ ./whisper.cpp/main, -m, whisper_model_path, -f, wav_filename, -l, zh, # 指定语言为中文可以提高识别准确率 -otxt # 输出txt文件 ] try: result subprocess.run(whisper_cmd, capture_outputTrue, textTrue, cwd./whisper.cpp) # 输出结果在标准输出中格式为带时间戳的文本我们取最后一行纯文本部分简单处理 output_lines result.stdout.strip().split(\n) # 寻找包含识别结果的行更健壮的做法是解析输出的txt文件 transcription for line in output_lines: if line and not line.startswith([): transcription line.strip() break print(fWhisper识别结果: {transcription}) return transcription except subprocess.CalledProcessError as e: print(fWhisper识别失败: {e}) return finally: # 清理临时文件 import os os.unlink(wav_filename)实操心得Whisper.cpp第一次运行时会加载模型速度较慢后续调用会快很多。对于实时交互可以考虑使用tiny或base模型以换取速度。另外上述代码对输出结果做了简单处理在实际项目中你可能需要更精细地解析main程序的输出或者直接使用Whisper.cpp提供的Python绑定库whisper-cpp-python来获得更结构化的结果。4.3 与本地大模型对话接下来我们将识别出的文本发送给本地运行的Ollama服务。Ollama的API兼容OpenAI格式非常方便。import requests import json def ask_ollama(prompt, modelqwen:7b, ollama_hosthttp://localhost:11434): 向本地Ollama服务发送提问 :param prompt: 用户输入的文本 :param model: 指定的模型名称 :param ollama_host: Ollama服务地址 :return: 模型返回的回复文本 if not prompt: return 我没有听清您说什么。 api_url f{ollama_host}/api/generate payload { model: model, prompt: prompt, stream: False # 为了简单我们关闭流式输出一次性获取结果 } try: response requests.post(api_url, jsonpayload, timeout60) # 设置超时 response.raise_for_status() result response.json() return result.get(response, 模型没有返回有效内容。).strip() except requests.exceptions.RequestException as e: print(f调用Ollama API失败: {e}) return 抱歉我的大脑AI模型暂时无法连接。 except json.JSONDecodeError: print(Ollama返回了非JSON响应。) return AI模型返回了意外的格式。 # 测试 # reply ask_ollama(你好请介绍一下你自己。) # print(reply)注意事项Ollama的/api/generate接口在模型较大或提示词较长时响应可能较慢十几秒甚至更长。对于语音交互这体验很差。有两种优化思路使用更小的模型如qwen:0.5b或llama2:7b响应速度会快很多。采用流式响应将stream设为True然后逐块读取响应可以边生成边播放但需要更复杂的音频流处理。4.4 文本转语音并播放获取到AI的文本回复后我们需要将其转为语音并通过蓝牙音箱播放。这里使用edge-tts。import subprocess import tempfile import time def text_to_speech_and_play(text, output_device_idxNone): 使用edge-tts将文本转为语音并播放 :param text: 要合成的文本 :param output_device_idx: 系统音频输出设备索引None则为默认设备 if not text: return # 1. 使用edge-tts生成语音文件 with tempfile.NamedTemporaryFile(suffix.mp3, deleteFalse) as tmp_mp3: mp3_filename tmp_mp3.name # 选择语音例如中文普通话女声 voice zh-CN-XiaoxiaoNeural tts_cmd [edge-tts, --voice, voice, --text, text, --write-media, mp3_filename] try: subprocess.run(tts_cmd, checkTrue, capture_outputTrue) print(f语音已生成: {mp3_filename}) except subprocess.CalledProcessError as e: print(fTTS生成失败: {e}) return # 2. 播放生成的语音文件 # 使用pydub和pyaudio播放 from pydub import AudioSegment from pydub.playback import play audio AudioSegment.from_file(mp3_filename, formatmp3) # 如果指定了输出设备这里需要更复杂的处理pydub.playback默认使用系统默认设备。 # 一个方案是使用sounddevice直接播放原始数组。 print(开始播放...) play(audio) # 这会在默认输出设备播放 # 3. 清理临时文件 import os time.sleep(0.5) # 稍等片刻确保播放完毕 os.unlink(mp3_filename) # 更优的播放方案使用sounddevice直接指定设备播放 import soundfile as sf def play_audio_with_sounddevice(file_path, output_device_idx): 使用sounddevice指定设备播放音频文件 data, samplerate sf.read(file_path) sd.play(data, samplerate, deviceoutput_device_idx) sd.wait()关键点pydub.playback.play()通常使用系统默认播放设备。为了确保音频一定从连接小爱音箱的蓝牙通道输出你需要使用sounddevice库并指定正确的输出设备索引在list_audio_devices中查找。这需要你将MP3文件解码为PCM数据数组然后调用sd.play。5. 集成与优化打造稳定可用的语音助手将上述模块串联起来并添加一些关键逻辑就形成了我们的主程序。5.1 主循环逻辑设计一个简单的交互循环可以是持续监听蓝牙麦克风当检测到一定音量的声音时简易VAD开始录制直到静音一段时间后停止然后执行识别-AI问答-TTS播放的流程。import numpy as np import sounddevice as sd import queue import threading import time class XiaoAiAIAssistant: def __init__(self, input_device_idx, output_device_idx, silence_threshold0.01, silence_duration1.0): self.input_device input_device_idx self.output_device output_device_idx self.silence_threshold silence_threshold # 静音阈值 self.silence_duration silence_duration # 持续静音多久算结束 self.sample_rate 16000 self.audio_queue queue.Queue() self.is_recording False self.recorded_chunks [] def audio_callback(self, indata, frames, time_info, status): 声音输入回调函数每次收到音频块时调用 if status: print(f音频输入错误: {status}) # 简单的能量检测判断是否在说话 volume_norm np.linalg.norm(indata) / np.sqrt(frames) if volume_norm self.silence_threshold: if not self.is_recording: print(检测到语音开始录制...) self.is_recording True self.recorded_chunks [] self.recorded_chunks.append(indata.copy()) else: if self.is_recording: # 检测到静音检查静音持续时间 # 这里简化处理如果当前块是静音且之前已经录了一些内容则可能结束 # 更完善的VAD需要计时 pass def start_listening(self): 开始监听音频输入 with sd.InputStream(callbackself.audio_callback, deviceself.input_device, channels1, samplerateself.sample_rate, blocksize1024): print(正在监听... 请说话。) while True: time.sleep(0.1) # 主线程保持运行 # 这里可以添加一个手动触发结束录制的逻辑比如按回车键 # 并处理 self.recorded_chunks def process_audio(self, audio_data): 处理录制好的音频数据识别 - AI - 语音播放 # 1. 合并音频块 if not audio_data: return full_audio np.concatenate(audio_data, axis0).flatten() # 2. 语音识别 text transcribe_audio_with_whisper(full_audio, self.sample_rate) if not text or len(text) 2: print(未识别到有效内容。) return # 3. 询问AI print(f用户说: {text}) reply ask_ollama(text) print(fAI回复: {reply}) # 4. 语音合成并播放 text_to_speech_and_play(reply, self.output_device) # 使用示例 if __name__ __main__: # 请先运行 list_audio_devices() 确定设备索引 input_idx 2 # 小爱音箱作为麦克风的索引 output_idx 3 # 小爱音箱作为扬声器的索引 assistant XiaoAiAIAssistant(input_idx, output_idx) # 这里需要实现一个更完善的VAD和录制控制逻辑然后调用 assistant.start_listening()上面的代码框架提供了一个起点。一个可用的版本需要实现一个状态机来管理“待机”、“录音中”、“处理中”等状态并有一个准确的VAD来判定语音的起止。5.2 性能优化与稳定性提升异步处理录音、识别、AI推理、TTS合成、播放这些步骤都是I/O密集型或计算密集型的。使用多线程或异步编程asyncio可以避免阻塞让系统在等待AI回复时仍能监听新的唤醒词。唤醒词检测上述简易VAD容易误触发。可以集成一个本地的唤醒词检测引擎如Porcupine或Snowboy已停止维护但仍有可用模型只有当检测到“小爱同学”或其他自定义唤醒词后才进入录音模式。这能极大提升体验。错误处理与重试网络波动、AI服务重启都可能导致单次调用失败。在主循环中需要加入健壮的错误捕获和重试机制避免程序崩溃。音频前处理蓝牙音频可能有噪音可以在识别前加入简单的降噪或增益处理提升识别准确率。配置化将设备索引、模型路径、API地址、唤醒词灵敏度等参数提取到配置文件中方便不同环境部署。5.3 进阶玩法赋予AI“行动力”目前我们的AI只是一个聊天机器人。你可以通过以下方式让它变得更强大函数调用使用支持function calling的模型如Qwen2.5-7B-Instruct或框架如LangChain。你可以定义一系列函数如get_weather(city)、control_light(device_name, action)让AI在对话中自主决定调用哪个函数并传入正确参数然后由你的主程序执行对应操作如调用Home Assistant的API来控制智能灯。接入本地知识库使用RAG技术。将你的个人文档、笔记进行向量化存储。当用户提问时先从中检索相关片段再连同问题和片段一起发给AI让它生成基于你个人知识的回答。多模态如果你的AI模型支持视觉如LLaVA你甚至可以连接一个USB摄像头让小爱同学“看到”东西并描述它。当然这需要更复杂的集成。6. 部署与长期运行建议开发调试完成后你可能希望这个服务能像真正的智能音箱一样7x24小时运行。使用系统服务在Linux上可以将你的Python脚本封装成一个systemd服务。# /etc/systemd/system/xiaoai-ai.service [Unit] DescriptionXiaoAi AI Assistant Afternetwork.target [Service] Typesimple Useryour_username WorkingDirectory/path/to/your/project EnvironmentPATH/path/to/your/venv/bin ExecStart/path/to/your/venv/bin/python /path/to/your/project/main.py Restartalways RestartSec10 [Install] WantedBymulti-user.target然后使用sudo systemctl enable --now xiaoai-ai来启用和启动服务。日志管理确保你的程序将运行日志输出到文件方便排查问题。可以使用Python的logging模块。资源监控运行大模型会消耗大量内存和CPU。建议监控主机的资源使用情况确保不会因为内存不足而崩溃。可以考虑使用docker来隔离环境并限制资源使用。开机自启动蓝牙连接如果电脑重启蓝牙连接可能会断开。你需要编写脚本在系统启动后自动搜索并连接小爱音箱的蓝牙。这通常可以通过bluetoothctl命令实现。踩坑实录与心得蓝牙延迟与稳定性蓝牙音频的延迟约100-300ms是客观存在的这会导致“说完话到开始处理”以及“播放语音”的延迟感。Wi-Fi音箱如部分小爱型号如果有开放的音频流协议如AirPlay可能会更好但开发复杂度更高。蓝牙是目前性价比最高的方案。AI响应速度7B参数模型在CPU上推理一次生成可能需要10-30秒这完全无法用于对话。这是体验的核心瓶颈。解决方案只能是1) 使用更小的模型如1.8B或0.5B2) 使用GPU加速3) 使用量化后的模型Ollama默认会进行一定量化。在我的测试中Qwen2.5-1.8B-Instruct在Intel N100 CPU上响应时间在3-5秒勉强可接受。唤醒词完全自己实现一个低误唤醒率的唤醒词引擎比较难。一个取巧的办法是继续使用小爱同学原生的唤醒词。当小爱被唤醒后它会播放一声“叮咚”提示音并开始收音。我们可以用程序持续监听当检测到这个特定的“叮咚”声频谱特征时就判定为一次有效唤醒随即开始录制接下来的用户语音。这样就完美复用了原厂优秀的唤醒体验。这需要一些音频信号处理的知识但比从头训练唤醒词模型要简单。完成以上所有步骤你就拥有了一个完全受控于本地、能力可无限扩展的“魔改版小爱同学”。它不再受限于云端技能你可以用它查询本地文档、控制复杂的自定义家居自动化、甚至进行专业的代码讨论。这个项目更像是一个起点一个将前沿AI能力与成熟硬件产品结合的范例。当你打通了这个流程后面如何改造、赋予它什么能力就完全取决于你的想象力和需求了。