7.2 语音对话闭环


7.2 语音对话闭环

本节摘要:零件齐了:7.1 的录音加转写、6.2 的本地对话 API、再加一个可选的 TTS。本节把它们焊成完整回路——按一下说话,几秒后听到回答。先给回路图与各环节职责(转写是耳朵、llama-server 是大脑、TTS 是可选的嘴);然后是代码骨架:一个主循环串起 record → transcribe → ask → speak 四个函数(写法示意);最后是实时性预算表——从按下按钮到听见回答,每个环节花几秒、超支了砍哪里(转写降档、生成减 token、TTS 换轻引擎或关闭)。闭环跑通的那一刻,你就拥有了一个本地版"能听会说的小 ChatGPT"——nanochat 四阶段口径的推理界面至此完整。

学习目标

阅读完本节,你应当能够:

  1. 画出语音对话闭环的完整回路图并说出各环节职责。
  2. 跑通串起录音、转写、对话、TTS 的主循环骨架。
  3. 用实时性预算表定位超时环节并为各环节配档位。

一、完整回路图

┌──────────────────────── 语音对话闭环 ────────────────────────┐ │ │ │ ① 按下说话(录音) 16kHz 单声道,3~6 秒 [7.1] │ │ │ │ │ ▼ │ │ ② Whisper 转写 声音→文字(档位 tiny/base/small)[7.1] │ │ │ │ │ ▼ │ │ ③ 装进 messages 文字 + 历史 → 第 2 章契约 │ │ │ │ │ ▼ │ │ ④ llama-server POST /v1/chat/completions [6.2] │ │ │ │ │ ▼ │ │ ⑤ 回答文本 ──────────► 屏幕显示(保底输出,永不缺席) │ │ │ │ │ ▼(可选) │ │ ⑥ TTS 播放 文字→语音(本地引擎或浏览器合成) │ │ │ └──────────────────────────────────────────────────────────────┘

设计原则两条:文字显示永远保留(TTS 失败或关闭时,闭环降级不瘫痪);messages 历史由主循环维护(6.2 客户端的分工原则原样搬来——客户端只管 messages,不碰模板)。

二、代码骨架

# voice_chat.py —— 语音对话闭环骨架(写法示意,依赖与接口以各官方 README 为准) import wave import requests import sounddevice as sd import whisper SR, SECS = 16000, 5 URL = "http://127.0.0.1:8080/v1/chat/completions" asr = whisper.load_model("small") # 7.1 档位表 def record() -> str: # ① 录音 → 临时 wav audio = sd.rec(int(SECS * SR), samplerate=SR, channels=1, dtype="int16") sd.wait() with wave.open("data_voice/q.wav", "w") as w: w.setnchannels(1) w.setsampwidth(2) w.setframerate(SR) w.writeframes(audio.tobytes()) return "data_voice/q.wav" def transcribe(path: str) -> str: # ② 转写 → 文字 return asr.transcribe(path, language="zh", fp16=False)["text"].strip() def ask(history: list[dict]) -> str: # ③④ 对话腿 → 回答 r = requests.post(URL, json={ "messages": history, "temperature": 0.5, # 语音场景偏稳(4.2 场景表) "max_tokens": 120, # 语音回答宜短——也省实时预算 }, timeout=60) r.raise_for_status() return r.json()["choices"][0]["message"]["content"] def speak(text: str) -> None: # ⑥ 可选 TTS——换成你的引擎即可(示意) try: import pyttsx3 # 本地离线 TTS 之一,接口以其文档为准 pyttsx3.speak(text) except Exception: print("(TTS 未启用,仅文字)") if __name__ == "__main__": history = [{"role": "system", "content": "你是一个简洁的中文语音助手。"}] while True: input("按回车开始录音…") q = transcribe(record()) if not q: continue # 静音幻觉防线(7.1) print(f"你:{q}") history.append({"role": "user", "content": q}) a = ask(history) history.append({"role": "assistant", "content": a}) print(f"助手:{a}") speak(a)

骨架刻意保持"每环节一个函数、主循环只做编排"——任何一个环节换实现(Whisper 换 whisper.cpp、TTS 换云端音色、6.2 换别的服务)都不惊动其他环节。

三、实时性预算表

从按下回车到听见回答(示意估算,CPU 为普通笔记本、GPU 为单张消费级显卡;实测随硬件浮动):

环节 CPU 预算 GPU 预算 超支砍哪里
录音 固定 3~6 秒 同左 缩短录音时长
Whisper small 转写 2~6 秒 <1 秒 降档 base/tiny(7.1 表)
124M Q8_0 生成 120 token 2~8 秒 1~2 秒 减 max_tokens;升量化档(6.1 表)
TTS 合成播放 1~3 秒 同左 换轻引擎或关闭
合计目标 ≤10 秒 ≤4 秒

联动结论:预算表把前面所有选档决策串起来——转写档(7.1)、量化档(6.1)、生成参数(4.2)不再各选各的,而是共同对"合计目标"负责。这也是 0.2"先小后大"纪律的另一种说法:每个环节都用最小够用的档位,加起来才是快。

四、工程化延伸路标

骨架到产品之间还有一程,本书只留路标(均以各官方仓库 README 为准):VAD 自动断句(检测说话结束,替代按回车);流式转写与流式生成(边说边转、边生成边播);whisper.cpp(llama.cpp 姊妹项目,CPU 高效跑 Whisper 的路线,与第 6 章同生态);打断处理(用户开口即停播)。这些都不改变回路的骨架,只替换环节实现。

本节要点回顾

  1. 回路 = 录音→转写→messages→llama-server→(可选)TTS;文字显示保底,主循环管历史。
  2. 骨架纪律:每环节一个函数,换实现不动主循环;空转写直接跳过。
  3. 预算表统一所有选档:CPU 合计约 10 秒、GPU 约 4 秒,超支按表砍对应环节。

能听会说的本地小 ChatGPT 成形——最后一程是改造实验:让这套流水线说好中文(8.1)、探一探小模型的能力边界(8.2),再到对齐训练门前看一眼路标(8.3)。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U