☰
本地中文语音智能助手全链路实战:唤醒、ASR、大模型与知识库
2026/10/26 15:04:48 网站建设 项目流程

简介:这是一套基于本地模型的中文语音智能助手 Python 源码,面向希望动手实践语音交互与本地大模型集成的开发者与学习者。项目以「你好傻妞」或「傻妞」为唤醒词,串联关键词唤醒、SenseVoice 离线语音识别、Ollama 大模型对话、Matcha-TTS 语音合成,并可选接入 RAG 本地知识库问答,支持连续多轮对话与 30 秒无交互自动退出,可用于智能家居控制、语音问答、语音笔记等场景。资源包共 21 个文件,包含 7 个 py 源码、5 个 txt 说明、4 个 bin 模型文件、2 个 mp4 演示视频,以及 sqlite3、csv、md 等数据与文档,压缩包约 11.03MB,目录按 KWS、ASR、TTS、RAG 等模块拆分,便于按功能定位代码。目前已有 107 人学习。借助完整源码、演示录屏与模型配置说明,读者可快速跑通唤醒、识别、对话、合成全链路,并理解本地知识库向量构建与检索增强的实现思路。

1. 从一句「你好小助手」到本地全链路:这套中文语音智能助手到底解决什么问题

很多人第一次做语音助手,都是从一个while True加speech_recognition开始的,跑通之后才发现真正的坑在后面:唤醒词误触发、识别延迟高、大模型接口要联网、知识库检索答非所问、合成声音像机器人。这套 smart-voice-assistant 语音智能助手(Python 源码)想解决的,就是把「关键词唤醒 → 语音识别 → 大模型对话 → 本地知识库问答 → 语音合成」这条链路全部收进本地,用 Python 串起来,不依赖外部 API 也能跑。它适合两类人:一类是想入门语音交互、手里只有一台带麦克风的普通电脑的 Python 新手;另一类是做企业大模型私有化部署、需要把语音入口接到内网知识库的工程师。下面我按自己实际搭过一遍的顺序,把选型、代码、参数和翻车点讲清楚,你照着改就能复现。

2. 链路拆解与本地模型选型:为什么不用云端 API

2.1 五个模块的职责边界

一条完整的语音对话链路,拆开看是五个独立模块,每个模块都可以单独替换:

模块输入输出常见本地方案
关键词唤醒持续音频流触发信号Porcupine、openWakeWord、能量阈值
语音识别 ASR一段音频中文文本faster-whisper、FunASR、Vosk
大模型对话 LLM文本回复文本Ollama 本地模型、llama.cpp
本地知识库 RAG问题文本增强上下文Chroma、FAISS + 嵌入模型
语音合成 TTS回复文本音频edge-tts、pyttsx3、ChatTTS

把边界划清楚的好处是:唤醒不灵敏只调唤醒模块,识别不准只换 ASR,回答跑偏只动 RAG。新手最容易犯的错是把五个模块写在一个大函数里,出问题根本没法定位。

2.2 本地模型选型的三个硬约束

选型不是看谁参数大,而是看你的机器扛不扛得住。我一般按三条线卡:

第一是内存。faster-whisper 的small模型大概占 1GB 显存或 2GB 内存,medium翻倍。Ollama 跑 7B 量化模型(q4)大概要 5~6GB 内存。如果只有 8GB 内存的笔记本,ASR 用small、LLM 用 3B 量化模型是上限。

第二是延迟。唤醒到出声,用户能接受的总延迟在 2 秒以内。ASR 占 0.5~1 秒,LLM 首 token 占 0.3~1 秒,TTS 占 0.3 秒。任何一环超了,体验就崩。

第三是中文效果。faster-whisper 对中文支持不错但标点偏弱,FunASR 的 Paraformer 中文更稳。LLM 方面,Qwen 系列的中文对话质量明显好于同尺寸的英文模型。

提示:不要一上来就上最大的模型。先用小模型把整条链路跑通,再逐个替换升级,比一开始就卡在显存不足上强得多。

2.3 环境准备与依赖安装

Python 版本建议 3.10 或 3.11,3.12 部分音频库还没适配。先建虚拟环境,再装依赖:

# 创建并激活虚拟环境 python -m venv venv # Windows venv\Scripts\activate # Linux / macOS source venv/bin/activate # 核心依赖 pip install faster-whisper sounddevice numpy pip install ollama chromadb sentence-transformers pip install edge-tts pvporcupine

这里几个包的作用:sounddevice负责录音和播放,faster-whisper做识别,ollama是本地大模型的 Python 客户端,chromadb存知识库向量,sentence-transformers生成嵌入,edge-tts做合成,pvporcupine做唤醒。

参数说明:faster-whisper安装时会拉取 CTranslate2 运行时,第一次运行会自动下载模型权重,注意留出磁盘空间。pvporcupine需要去官网申请一个 AccessKey,免费额度够个人用。

如果sounddevice装完报找不到 PortAudio,Linux 下补一句sudo apt install libportaudio2,Windows 下一般不会有这个问题。

3. 唤醒与语音识别:把「听到」变成「听懂」

3.1 关键词唤醒的两种实现路径

唤醒模块的本质是:持续监听麦克风,判断当前音频里有没有出现指定关键词。两条路:

一条是用 Porcupine 这类专用唤醒引擎,你给它一个关键词和灵敏度,它返回是否触发。优点是误触发率低、CPU 占用小,缺点是自定义中文唤醒词需要训练或购买。

另一条是自己用能量阈值加 ASR 兜底:先判断音量超过阈值,再送 ASR 识别,看文本里有没有关键词。优点是零成本、中文随便设,缺点是误触发多、一直跑 ASR 费资源。

我一般用混合方案:能量阈值做第一道门,只有音量够了才送 ASR,识别结果里匹配关键词才真正唤醒。这样既省资源又能用中文词。

import sounddevice as sd import numpy as np from faster_whisper import WhisperModel # 加载识别模型,small 适合中文,int8 量化省内存 model = WhisperModel("small", device="cpu", compute_type="int8") WAKE_WORDS = ["你好助手", "小助手", "你好小助手"] SAMPLE_RATE = 16000 ENERGY_THRESHOLD = 0.02 # 音量阈值,环境嘈杂时调高 def record_chunk(seconds=3): """录一段音频,返回 numpy 数组""" audio = sd.rec(int(seconds * SAMPLE_RATE), samplerate=SAMPLE_RATE, channels=1, dtype="float32") sd.wait() return audio.flatten() def is_wake(audio): """判断这段音频是否包含唤醒词""" # 能量门限,静音直接跳过,省一次识别 if np.sqrt(np.mean(audio ** 2)) < ENERGY_THRESHOLD: return False segments, _ = model.transcribe(audio, language="zh", beam_size=1) text = "".join(seg.text for seg in segments) return any(w in text for w in WAKE_WORDS)

逻辑说明:record_chunk录固定时长音频,is_wake先算均方根能量,低于阈值直接返回 False,避免每段静音都跑识别。beam_size=1是贪心解码,比默认的 beam search 快,唤醒场景不需要高精度。

参数说明:ENERGY_THRESHOLD是最需要调的参数。安静房间 0.01 就够,有风扇或空调的房间要 0.03 以上。调太低会一直触发识别,调太高喊破喉咙也不响应。建议先打印几段环境音的能量值再定。

3.2 识别阶段的参数怎么调

唤醒之后进入正式识别,这时候要的是准确率,参数和唤醒阶段不一样:

def transcribe(audio): """正式识别,追求准确率""" segments, info = model.transcribe( audio, language="zh", beam_size=5, # 束搜索,比唤醒阶段大 vad_filter=True, # 开启静音过滤 vad_parameters=dict(min_silence_duration_ms=500), initial_prompt="以下是普通话对话。" # 引导输出简体中文 ) return "".join(seg.text for seg in segments)

beam_size=5提升准确率但更慢,正式识别可以接受。vad_filter=True会自动切掉首尾静音,减少无效计算。initial_prompt是个小技巧,给模型一个中文语境提示,能减少输出繁体或英文的概率。

注意:vad_parameters里的min_silence_duration_ms决定多长的静音算一句话结束。设太小会把一句话切成两段,设太大用户停顿久一点就以为没说完。500ms 是中文对话比较稳的值。

3.3 录音时长与断句的取舍

固定录 3 秒的问题是说短句浪费、说长句被截断。更好的做法是用 VAD 动态判断说话结束:

import webrtcvad def record_until_silence(max_seconds=15, silence_ms=800): """录到用户停止说话为止""" vad = webrtcvad.Vad(2) # 灵敏度 0-3,2 比较均衡 chunk_ms = 30 chunk_size = int(SAMPLE_RATE * chunk_ms / 1000) frames, silent_ms, total_ms = [], 0, 0 with sd.InputStream(samplerate=SAMPLE_RATE, channels=1, dtype="int16") as stream: while total_ms < max_seconds * 1000: frame, _ = stream.read(chunk_size) frames.append(frame) total_ms += chunk_ms if vad.is_speech(frame.tobytes(), SAMPLE_RATE): silent_ms = 0 else: silent_ms += chunk_ms # 已经说过话且静音够久,认为说完了 if silent_ms >= silence_ms and total_ms > 1000: break return np.concatenate(frames).flatten().astype("float32") / 32768.0

逻辑说明:每 30ms 读一帧,用 webrtcvad 判断是不是语音。连续静音超过silence_ms且已经录了至少 1 秒,就认为用户说完了。max_seconds是兜底,防止用户一直不说话卡死。

参数说明:vad的灵敏度 0 最宽松(容易把噪音当语音),3 最严格。silence_ms设 800 对中文比较合适,说话慢的人可以调到 1000。

4. 大模型对话与本地知识库:让回答有依据

4.1 用 Ollama 拉起本地大模型

本地大模型最省事的部署方式是 Ollama。装好之后拉一个中文模型:

# 拉取 Qwen2.5 的 7B 量化版本,中文对话效果好 ollama pull qwen2.5:7b # 启动服务(一般装完自动后台运行) ollama serve

Python 侧调用:

import ollama def chat(question, context=""): """带知识库上下文的大模型对话""" system = "你是一个中文语音助手,回答简洁口语化,不超过三句话。" if context: system += f"\n参考以下资料回答:\n{context}" resp = ollama.chat( model="qwen2.5:7b", messages=[ {"role": "system", "content": system}, {"role": "user", "content": question}, ], options={"temperature": 0.7, "num_predict": 150} ) return resp["message"]["content"]

逻辑说明:system提示词里限制回答长度,因为语音播报太长用户听不下去。有知识库上下文时拼进 system,让模型基于资料回答。

参数说明:temperature0.7 兼顾稳定和灵活,知识库问答可以降到 0.3 减少胡编。num_predict限制生成 token 数,150 大概对应两三句话,防止模型长篇大论。

4.2 本地知识库的构建与检索

知识库的核心是:把文档切块、转成向量、存起来,提问时检索最相关的几块。用 Chroma 加中文嵌入模型:

import chromadb from sentence_transformers import SentenceTransformer # 中文嵌入模型,比默认的英文模型效果好很多 embedder = SentenceTransformer("BAAI/bge-small-zh-v1.5") client = chromadb.PersistentClient(path="./kb") collection = client.get_or_create_collection("docs") def add_document(doc_id, text, chunk_size=300): """把长文档切块后存入知识库""" chunks = [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)] embeddings = embedder.encode(chunks).tolist() collection.add( ids=[f"{doc_id}_{i}" for i in range(len(chunks))], documents=chunks, embeddings=embeddings ) def search(query, top_k=3): """检索最相关的片段""" q_emb = embedder.encode([query]).tolist() res = collection.query(query_embeddings=q_emb, n_results=top_k) return "\n".join(res["documents"][0])

逻辑说明:add_document把文档按 300 字切块,每块单独编码存入。search把问题编码后在向量库里找最接近的 top_k 块,拼成上下文。

参数说明:chunk_size是关键。太小语义不完整,太大检索不精准。中文 300 字左右比较合适。top_k一般 3,太多会稀释相关性还增加 LLM 负担。嵌入模型选bge-small-zh是因为它专门针对中文训练,比多语种模型在中文上更准。

4.3 把检索结果接进对话

def answer(question): """完整问答:检索 + 生成""" context = search(question) if not context.strip(): return chat(question) # 知识库没命中,直接问模型 return chat(question, context=context)

逻辑说明:先检索,命中就用上下文增强,没命中就退回普通对话。这样知识库为空时也不会报错。

提示:知识库检索不准,八成是切块或嵌入模型的问题,不是 LLM 的问题。先单独测search返回的片段相不相关,再怀疑模型。

5. 语音合成与全链路串联:从文本回到声音

5.1 edge-tts 合成中文语音

合成这块,edge-tts的中文音色自然度比pyttsx3好一大截,而且免费:

import edge_tts import asyncio async def speak(text, voice="zh-CN-XiaoxiaoNeural"): """把文本合成语音并播放""" communicate = edge_tts.Communicate(text, voice) await communicate.save("reply.mp3") # 播放,Windows 用 os.startfile,Linux 用 aplay import os os.system("start reply.mp3") # Windows

逻辑说明:edge-tts是异步接口,用asyncio调用。save存成 mp3 再播放,比流式播放简单,适合先跑通。

参数说明:voice可选zh-CN-XiaoxiaoNeural(女声)、zh-CN-YunxiNeural(男声)、zh-CN-YunyangNeural(新闻腔)。语速用rate="+10%"调整,默认偏慢。

5.2 主循环怎么组织

把五个模块串起来,主循环长这样:

import asyncio def main_loop(): print("等待唤醒词...") while True: audio = record_chunk(seconds=2) if not is_wake(audio): continue print("已唤醒,请说话") question_audio = record_until_silence() question = transcribe(question_audio) if not question.strip(): continue print(f"你说:{question}") reply = answer(question) print(f"助手:{reply}") asyncio.run(speak(reply)) if __name__ == "__main__": main_loop()

逻辑说明:外层循环持续监听唤醒,唤醒后录一句完整的话,识别、问答、合成依次执行,然后回到监听。

参数说明:唤醒阶段的record_chunk(seconds=2)是权衡,太短可能录不全唤醒词,太长响应迟钝。2 秒对四字唤醒词够用。

5.3 延迟优化的几个手段

全链路跑起来后如果觉得慢,按这个顺序优化:ASR 换更小的模型或开int8量化;LLM 换更小的模型或减少num_predict;TTS 改成流式播放不等整段合成完。我实测在 16GB 内存的机器上,small+qwen2.5:3b+ edge-tts,从说完到出声大概 1.5 秒,日常够用。

6. 避坑与排查:那些让我重装三次环境的问题

6.1 唤醒词一直误触发

现象:没人说话也频繁唤醒,日志里全是识别结果。

原因:能量阈值设太低,或者环境有持续噪音(风扇、空调、键盘声)被当成语音。

解决:先打印环境音的 RMS 值,把ENERGY_THRESHOLD设到比环境音高 2~3 倍。如果噪音本身波动大,加一个「连续两帧都超阈值才算」的条件,过滤掉瞬时噪声。

6.2 识别结果全是繁体或夹杂英文

现象:明明说的是普通话,识别出来是繁体字,或者中英混杂。

原因:faster-whisper 默认不指定语言时会自动检测,短音频容易检测错。

解决:transcribe里强制language="zh",并加initial_prompt="以下是普通话对话。"。这个提示词能显著降低繁体输出概率。

6.3 知识库检索答非所问

现象:问的是 A,检索出来的却是完全不相关的 B。

原因:切块太大导致一个块里混了多个主题,或者嵌入模型对中文支持差。

解决:把chunk_size降到 200~300,换bge-small-zh这类中文专用嵌入模型。如果文档有标题结构,按标题切块比按字数切效果好得多。

6.4 合成语音播放报错或没声音

现象:edge-tts合成成功但播放没声音,或者os.system报错。

原因:edge-tts需要联网(它调用的是在线合成服务),断网就失败;播放命令在不同系统上不一样。

解决:确认网络可用。播放改成跨平台方案,用playsound或pydub加simpleaudio,别硬编码start命令。如果要完全离线合成,换 ChatTTS 或 pyttsx3,但自然度会下降。

6.5 长时间运行后内存暴涨

现象:跑几个小时内存占用越来越高,最后卡死。

原因:每次识别都新建模型实例,或者音频数组没释放。

解决:模型在程序启动时加载一次,全局复用。录音数组用完及时置空。Chroma 的 client 也用全局单例,别每次查询都PersistentClient。

7. 进阶:把知识库问答做成可验证的闭环

跑通基础链路之后,最值得投入的方向是让知识库问答变得可验证。因为语音助手最容易翻车的地方就是「一本正经地胡说」,而语音场景用户没法像看屏幕那样快速判断真假。我的做法是给每次回答加一个来源标注,合成语音时在结尾带一句「以上信息来自 XX 文档」,同时在日志里记录检索到的原始片段和相似度分数。

具体实现是在search里返回分数:

def search_with_score(query, top_k=3, min_score=0.5): """带相似度分数的检索,低于阈值视为未命中""" q_emb = embedder.encode([query]).tolist() res = collection.query( query_embeddings=q_emb, n_results=top_k, include=["documents", "distances"] ) docs = res["documents"][0] dists = res["distances"][0] # Chroma 返回的是距离,越小越相关,转成相似度 hits = [(d, 1 - dist) for d, dist in zip(docs, dists) if 1 - dist >= min_score] return hits

逻辑说明:Chroma 返回的distances是向量距离,1 - dist近似相似度。设一个min_score阈值,低于它的片段直接丢弃,避免用不相关资料硬答。

参数说明:min_score0.5 是起步值,实际要根据你的嵌入模型和文档调。调太高会经常检索不到,调太低会引入噪音。建议拿十几个真实问题测一遍,看命中率和准确率的平衡点。

验证闭环的另一半是回归测试。我会维护一个小测试集,每条是「问题 → 期望命中的文档片段」,每次改完切块策略或换嵌入模型就跑一遍,看命中率有没有下降。这个习惯帮我避免了好几次「改了一处、坏了一片」的翻车。

还有个实用技巧:把用户问过但知识库没命中的问题单独记下来,定期看这些「盲区」,比盲目扩充文档更有针对性。我一般每周导出一次未命中日志,挑高频问题补进知识库,两三轮下来覆盖率就上来了。

这套东西我从一开始的玩具级 demo 改到现在能日常用,最大的教训是:别追求一步到位上大模型,先把链路跑通、把每个模块的边界和参数摸清楚,再逐个升级。语音交互的体验是木桶效应,任何一环拖后腿整体就废。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询