1. 项目概述与核心价值

最近在折腾智能家居和语音交互,发现市面上的语音助手要么是“大厂全家桶”,要么就是本地化部署门槛高得吓人。直到我偶然在GitHub上看到了这个名为“ChatGPT-voice-control”的项目,眼前瞬间一亮。这项目说白了,就是让你能用自己电脑的麦克风,像跟Siri或小爱同学聊天一样,直接跟ChatGPT对话,而且它还能用语音回答你。最关键的是,它把语音识别、大模型对话、语音合成这几个核心环节串了起来,形成了一个完整的、可本地部署的闭环。对于我这种既想保护隐私,又喜欢折腾自定义功能的开发者来说,简直是量身定做。

这个项目的核心价值在于它的“可塑性”和“独立性”。它不依赖于任何特定的智能音箱硬件,你的电脑就是终端。你可以自由选择背后的语音识别引擎(比如Whisper)、对话大模型(当然是ChatGPT的API,或者你也可以尝试接入其他开源模型)以及语音合成服务。这意味着,你可以根据对响应速度、识别准确度、语音自然度的不同要求,来灵活搭配技术栈。比如,追求极致速度可以用更小的Whisper模型,追求拟人化可以用更高级的TTS服务。整个项目就像一个乐高积木,把AI语音交互的核心模块清晰地拆解并连接起来,对于想深入理解语音对话系统工作原理,或者想打造一个专属、无广告、不收集隐私的桌面语音助手的朋友来说,是一个绝佳的实践起点。

2. 项目整体架构与设计思路拆解

2.1 核心工作流:从声音到智慧的循环

要理解这个项目,首先得把它想象成一个“声音处理流水线”。整个流程可以清晰地分为四个阶段,形成一个完整的闭环:

  1. 语音采集与前端处理 :这是流水线的起点。你的电脑麦克风持续监听环境声音。项目需要解决“如何开始录音”和“如何结束录音”的问题。通常有两种设计思路:一是采用“唤醒词”机制,比如你说“Hey Computer”才开始正式录音;二是采用“按键触发”,比如按住某个键说话,松开结束。这个项目通常采用后者,因为实现更简单,误触发率低。录音结束后,会得到一段原始的PCM或WAV格式的音频数据。

  2. 语音转文本 :这是第一个AI魔法环节。上一步得到的音频文件,被送入一个语音识别模型。项目默认或推荐使用的是OpenAI的Whisper模型。这个阶段的核心挑战是准确性和速度。你需要选择Whisper的模型尺寸( tiny , base , small , medium , large ),模型越大越准,但速度越慢,消耗资源也越多。在本地运行,就需要在速度和精度间做权衡。

  3. 文本对话与理解 :识别出的文本,现在只是一串字符。接下来,这串字符被发送到“大脑”——也就是ChatGPT的API。这里项目扮演了一个“智能中控”的角色。它不仅仅是把用户的话原样转发,通常还会构造一个包含“系统指令”和“对话历史”的完整Prompt。系统指令用来设定AI的角色和行为(比如“你是一个有帮助的助手,回答要简洁”),对话历史则让AI拥有上下文记忆能力,实现多轮连贯对话。这是整个系统显得“智能”的关键。

  4. 文本转语音与播放 :ChatGPT返回了一段文本答复。流水线的最后一步,就是让这段文本“开口说话”。这里会调用一个文本转语音服务,将答复文本合成音频。同样有多种选择,可以是OpenAI自家的TTS API,声音自然但需付费;也可以是本地TTS引擎如 pyttsx3 (免费但机械音重),或微软的Edge TTS(免费且质量不错)。生成的音频文件最终通过电脑的扬声器播放出来,完成一次交互。

注意 :这个架构的美妙之处在于“模块化”。每一个环节(STT, LLM, TTS)都是相对独立的,你可以像更换积木一样替换其中的组件。例如,把Whisper换成其他语音识别服务,把ChatGPT换成Claude或本地部署的Llama,把TTS换成更拟人的声音。这给了开发者极大的自定义空间。

2.2 技术栈选型背后的考量

为什么这个项目会选择这样一套技术栈?我们来拆解一下每个选择背后的逻辑:

  • 语音识别:Whisper

    • 为什么是它? Whisper是OpenAI开源的语音识别模型,在多种语言和口音上表现出色,抗噪能力较强。对于个人项目而言,它的开源属性意味着可以免费本地部署,避免了将语音数据上传到第三方服务的隐私顾虑。虽然本地运行需要一定的计算资源(尤其是更大的模型),但换来了数据不出门的安心。
    • 模型尺寸选择 :在项目配置中,你通常会看到一个指定Whisper模型大小的参数(如 model=”base” )。对于大多数桌面应用, base small 模型在准确度和速度上是一个很好的平衡点。如果你的CPU比较强,或者对精度要求高,可以尝试 medium tiny 模型最快,但识别率下降明显,适合对延迟极其敏感的场景。
  • 对话核心:ChatGPT API

    • 为什么是它? 直接、强大、生态成熟。ChatGPT的对话能力是目前公认的第一梯队,通过API调用可以稳定地获得高质量、符合逻辑的文本回复。相比于自行部署一个同等能力的开源大模型,使用API的成本(无论是金钱还是技术复杂度)在项目初期要低得多。它让开发者能快速搭建起可用的原型,专注于语音交互流程本身,而非在模型训练和调试上耗费精力。
    • 成本控制 :这是使用API时必须考虑的一点。项目代码中需要妥善管理 max_tokens (回复最大长度)等参数,避免单次问答消耗过多token。同时,可以考虑实现一个简单的对话长度管理机制,定期清理过长的历史上下文,以控制成本。
  • 语音合成:灵活可配置

    • 方案对比
      • OpenAI TTS API :质量高,声音自然,有不同音色可选。缺点是会产生额外费用,且依赖网络。
      • 本地TTS引擎(如pyttsx3) :零成本,离线可用。缺点是声音机械感强,可调节参数有限,听起来不那么“智能”。
      • 微软Edge TTS :一个很好的折中方案。通过模拟Edge浏览器请求,可以免费获得质量相当不错的合成语音,支持多种语言和音色。虽然也需要网络,但没有直接费用。
    • 选型建议 :对于体验优先的原型,推荐先用OpenAI TTS或Edge TTS获得最佳效果。如果追求完全离线,再考虑 pyttsx3 或更高级的本地TTS模型(如VITS,但部署复杂)。

3. 环境准备与核心依赖解析

3.1 基础Python环境搭建

这个项目是Python写的,所以一个干净、管理方便的Python环境是第一步。我强烈推荐使用 conda venv 创建虚拟环境,避免包版本冲突。

# 使用 conda 创建环境(假设你已安装Anaconda或Miniconda)
conda create -n voice-assistant python=3.9 -y
conda activate voice-assistant

# 或者使用 venv
python -m venv voice-assistant-env
# Windows 激活
voice-assistant-env\Scripts\activate
# Linux/Mac 激活
source voice-assistant-env/bin/activate

创建好环境后,先升级一下pip,这是个好习惯。

pip install --upgrade pip

3.2 关键依赖库深度解读

项目的 requirements.txt 里通常会列出所有依赖。我们挑几个核心的讲讲,明白它们各自是干什么的,出错了也好排查。

  1. openai : 这个库是调用ChatGPT API和Whisper API(如果你选择云端版)的官方桥梁。安装时注意版本,不同版本的API调用方式可能有细微差别。通常项目会指定一个版本范围。

    pip install openai
    

    关键点 :安装后,你需要设置环境变量 OPENAI_API_KEY ,或者在代码中直接指定你的API密钥。没有这个密钥,对话功能就瘫痪了。

  2. whisper : OpenAI的语音识别库。安装它会自动下载你指定的模型。

    pip install openai-whisper
    

    注意 :首次运行使用Whisper的代码时,它会根据你指定的模型名称(如 ”base” )从网络下载模型文件(几百MB到几个GB不等)。请确保网络通畅,且有足够的磁盘空间。模型会默认下载到用户目录下的某个缓存文件夹。

  3. SpeechRecognition / pyaudio : 这是用于麦克风录音的库。 SpeechRecognition 是一个对多种语音识别引擎(包括本地Whisper)进行封装的友好库,而 pyaudio 是其底层依赖,用于处理音频流。在Windows上安装 pyaudio 有时会碰壁,最稳妥的方法是去 这里 下载对应你Python版本和系统位数的 .whl 文件,然后用pip本地安装。

    # 例如,对于 Python 3.9 64位 Windows
    pip install PyAudio‑0.2.11‑cp39‑cp39‑win_amd64.whl
    pip install SpeechRecognition
    
  4. pyttsx3 / edge-tts : 文本转语音库。 pyttsx3 是离线的, edge-tts 是在线的。

    # 安装 edge-tts
    pip install edge-tts
    # 或安装 pyttsx3
    pip install pyttsx3
    

    选择建议 :如果你决定用Edge TTS, edge-tts 库是首选。它提供了命令行和Python API两种使用方式,非常方便。

  5. sounddevice / pydub : 用于高级音频播放和处理。 sounddevice 提供了更底层的音频设备控制, pydub 则擅长音频文件格式转换和简单剪辑。如果你的项目只是播放一个TTS生成的MP3文件,用系统命令或简单的播放库也行,但这两个库能让你的音频处理更专业。

    pip install sounddevice pydub
    

3.3 配置文件与密钥管理

一个健壮的项目不会把API密钥等敏感信息硬编码在代码里。通常你会看到一个配置文件(如 config.yaml .env 文件)的示例。

  • .env 文件方式(推荐) : 在项目根目录创建一个名为 .env 的文件,内容如下:

    OPENAI_API_KEY=sk-your-secret-key-here
    # 其他配置,如默认模型、语音设置等
    MODEL_NAME=gpt-3.5-turbo
    TTS_VOICE=en-US-AriaNeural
    

    然后在Python代码中使用 python-dotenv 库来加载:

    from dotenv import load_dotenv
    import os
    load_dotenv()
    api_key = os.getenv("OPENAI_API_KEY")
    
  • YAML/JSON配置 :用于存储非敏感的运行时配置,比如Whisper模型大小、录音采样率、触发按键、历史对话轮数等。将这些参数外部化,使得调整设置时无需修改代码。

实操心得 :务必在项目一开始就处理好密钥管理。将 .env 文件添加到你的 .gitignore 中,防止不小心将密钥提交到公开仓库,造成经济损失和安全风险。

4. 核心代码模块剖析与实现

4.1 语音监听与录音触发模块

这是用户交互的第一道门。其核心目标是:稳定、低延迟地捕获用户的语音指令,并准确判断何时开始、何时结束。

实现方案 :大多数类似项目采用“按键触发”模式,因为它逻辑简单,可靠性高。我们可以使用 pynput 库来全局监听键盘事件。

from pynput import keyboard
import threading
import sounddevice as sd
import numpy as np
import wavio

class VoiceRecorder:
    def __init__(self, samplerate=16000, channels=1):
        self.samplerate = samplerate
        self.channels = channels
        self.is_recording = False
        self.frames = []
        self.stream = None

    def start_recording(self):
        """开始录音"""
        print("[INFO] 录音开始...")
        self.is_recording = True
        self.frames = []
        # 打开音频输入流
        self.stream = sd.InputStream(
            samplerate=self.samplerate,
            channels=self.channels,
            callback=self.audio_callback
        )
        self.stream.start()

    def stop_recording(self):
        """停止录音并保存文件"""
        if self.stream and self.is_recording:
            self.stream.stop()
            self.stream.close()
            self.is_recording = False
            print("[INFO] 录音结束,处理中...")
            # 将内存中的音频数据转换为numpy数组并保存
            audio_data = np.concatenate(self.frames, axis=0)
            filename = "user_input.wav"
            wavio.write(filename, audio_data, self.samplerate, sampwidth=2)
            return filename
        return None

    def audio_callback(self, indata, frames, time, status):
        """音频流回调函数,不断将数据加入frames列表"""
        if self.is_recording:
            self.frames.append(indata.copy())

# 键盘监听
recorder = VoiceRecorder()

def on_press(key):
    try:
        if key == keyboard.Key.space:  # 假设空格键是录音键
            if not recorder.is_recording:
                recorder.start_recording()
    except AttributeError:
        pass

def on_release(key):
    if key == keyboard.Key.space:
        if recorder.is_recording:
            audio_file = recorder.stop_recording()
            if audio_file:
                # 触发后续的识别流程
                process_audio(audio_file)

# 启动监听器
listener = keyboard.Listener(on_press=on_press, on_release=on_release)
listener.start()

关键点解析

  1. sounddevice 回调 :使用回调函数模式采集音频,效率高,延迟低。音频数据以 numpy 数组的形式实时流入 self.frames 列表。
  2. 按键状态管理 :通过 is_recording 布尔值确保录音状态的正确切换,防止重复触发。
  3. 文件保存 :录音停止后,将列表中的多个音频片段拼接成一个完整的数组,并使用 wavio 库保存为WAV文件。Whisper对WAV格式支持很好。

注意事项

  • 录音质量:采样率 16000 Hz (16kHz)对于语音识别通常足够,且能减少数据量和处理时间。Whisper模型期望的输入也是16kHz。
  • 按键冲突:确保你选择的触发键(如空格键)不会与系统中其他应用的热键冲突。可以考虑使用不常用的功能键(如F2)或组合键(Ctrl+Shift)。
  • 反馈机制:在开始和结束录音时,最好有一个视觉或听觉的反馈。比如在控制台打印信息,或者播放一个简短的提示音,让用户知道系统状态。

4.2 语音识别模块集成

拿到 user_input.wav 文件后,下一步就是把它变成文字。这里我们集成Whisper。

import whisper
import torch
import warnings
warnings.filterwarnings("ignore")  # 忽略一些警告

class SpeechToTextEngine:
    def __init__(self, model_size="base", device=None):
        """
        初始化Whisper模型
        :param model_size: whisper模型大小,可选 tiny, base, small, medium, large
        :param device: 运行设备,'cuda' 或 'cpu',默认为None自动选择
        """
        self.model_size = model_size
        if device is None:
            self.device = "cuda" if torch.cuda.is_available() else "cpu"
        else:
            self.device = device
        print(f"[INFO] 加载Whisper-{model_size}模型到 {self.device} 设备...")
        # 加载模型,首次运行会自动下载
        self.model = whisper.load_model(model_size, device=self.device)
        print("[INFO] 模型加载完毕。")

    def transcribe(self, audio_file_path):
        """
        将音频文件转录为文本
        :param audio_file_path: 音频文件路径
        :return: 识别出的文本字符串
        """
        try:
            # 使用Whisper进行转录
            # fp16=False 在CPU上运行时更稳定
            result = self.model.transcribe(audio_file_path, fp16=(self.device == "cuda"))
            text = result["text"].strip()
            print(f"[STT] 识别结果: {text}")
            return text
        except Exception as e:
            print(f"[ERROR] 语音识别失败: {e}")
            return None

# 使用示例
if __name__ == "__main__":
    stt_engine = SpeechToTextEngine(model_size="base")  # 根据你的硬件选择模型
    text = stt_engine.transcribe("user_input.wav")
    if text:
        # 将文本传递给下一个环节
        pass

参数调优与经验

  • device 选择 :代码中自动检测CUDA。如果你有NVIDIA显卡且安装了正确版本的PyTorch CUDA版,它会使用GPU,速度极大提升。如果没有,则回退到CPU。
  • fp16 参数 :半精度浮点数。在GPU上开启( fp16=True )可以显著提升速度并减少显存占用。在CPU上运行必须设为 False ,否则可能出错或速度更慢。
  • 模型选择 tiny base 模型在CPU上也能较快运行。 small medium 在CPU上就有点慢了,适合有GPU的环境。 large 模型对资源要求很高,除非你对精度有极致要求,否则不推荐在个人电脑上使用。
  • 语言指定 transcribe 方法支持 language 参数(如 language=”zh” )。如果你明确知道用户会说中文,指定语言可以提高识别准确率和速度。如果不指定,Whisper会先检测语言。

4.3 与ChatGPT的对话管理模块

这是项目的“大脑”。我们需要构建一个能管理对话历史、构造Prompt、并调用API的类。

import openai
from openai import OpenAI
import os
from dotenv import load_dotenv

load_dotenv()

class ChatGPTHandler:
    def __init__(self, api_key=None, model="gpt-3.5-turbo", system_prompt="你是一个有用的助手。", max_history=10):
        """
        初始化ChatGPT处理器
        :param api_key: OpenAI API密钥,默认为None从环境变量读取
        :param model: 使用的模型,如 gpt-3.5-turbo, gpt-4
        :param system_prompt: 系统提示词,用于设定AI角色
        :param max_history: 保留的最大对话历史轮数(用户+助手)
        """
        self.api_key = api_key or os.getenv("OPENAI_API_KEY")
        if not self.api_key:
            raise ValueError("未提供OPENAI_API_KEY,请在.env文件中设置或传入参数。")
        self.client = OpenAI(api_key=self.api_key)
        self.model = model
        self.system_prompt = system_prompt
        self.max_history = max_history
        self.conversation_history = []  # 存储消息列表
        self._initialize_conversation()

    def _initialize_conversation(self):
        """初始化对话,加入系统提示"""
        self.conversation_history = [
            {"role": "system", "content": self.system_prompt}
        ]

    def add_user_message(self, text):
        """添加用户消息到历史"""
        self.conversation_history.append({"role": "user", "content": text})
        self._trim_history()

    def add_assistant_message(self, text):
        """添加助手消息到历史"""
        self.conversation_history.append({"role": "assistant", "content": text})
        self._trim_history()

    def _trim_history(self):
        """修剪对话历史,只保留最近的N轮对话(注意保留系统消息)"""
        # 系统消息是第一条,我们需要保留它
        system_msg = self.conversation_history[0]
        # 剩下的消息是用户和助手的交替
        other_msgs = self.conversation_history[1:]
        # 只保留最近的 max_history 条(如果max_history是10,则保留最多10条用户+助手消息)
        if len(other_msgs) > self.max_history:
            other_msgs = other_msgs[-self.max_history:]
        self.conversation_history = [system_msg] + other_msgs

    def get_response(self, user_input, temperature=0.7, max_tokens=500):
        """
        获取ChatGPT的回复
        :param user_input: 用户输入文本
        :param temperature: 创造性,0-2之间,越高越随机
        :param max_tokens: 回复的最大token数,控制长度
        :return: 助手回复的文本
        """
        # 1. 将用户输入加入历史
        self.add_user_message(user_input)

        try:
            # 2. 调用ChatGPT API
            response = self.client.chat.completions.create(
                model=self.model,
                messages=self.conversation_history,
                temperature=temperature,
                max_tokens=max_tokens,
                stream=False  # 非流式,一次性返回
            )
            # 3. 提取回复内容
            assistant_reply = response.choices[0].message.content.strip()
            # 4. 将助手回复加入历史
            self.add_assistant_message(assistant_reply)

            print(f"[LLM] 助手回复: {assistant_reply[:100]}...")  # 打印前100字符
            return assistant_reply

        except openai.APIConnectionError as e:
            print(f"[ERROR] 连接OpenAI API失败: {e}")
            return "抱歉,网络连接似乎有问题。"
        except openai.RateLimitError as e:
            print(f"[ERROR] API速率超限: {e}")
            return "请求过于频繁,请稍后再试。"
        except openai.APIError as e:
            print(f"[ERROR] OpenAI API错误: {e}")
            return "服务暂时不可用,请稍后重试。"

# 使用示例
chat_handler = ChatGPTHandler(
    model="gpt-3.5-turbo",
    system_prompt="你是一个简洁高效的桌面语音助手,回答请尽量简短直接。",
    max_history=6
)
user_text = "今天天气怎么样?"
reply = chat_handler.get_response(user_text)

核心逻辑与避坑指南

  1. 对话历史管理 :这是实现多轮对话的关键。我们用一个列表 conversation_history 来存储所有消息。每条消息都是一个字典,包含 role system , user , assistant )和 content 。每次交互后,都要及时更新这个历史。
  2. 历史长度修剪 :API调用是按Token收费的,历史越长,单次请求越贵,且可能超出模型上下文窗口。 _trim_history 方法确保只保留最近的若干轮对话。注意要永远保留第一条 system 消息。
  3. 错误处理 :网络请求可能失败,API可能超限。必须用 try-except 包裹API调用,并给用户一个友好的错误提示,而不是让程序崩溃。
  4. 系统提示词 system_prompt 是塑造AI行为的利器。你可以在这里定义助手的性格、回答风格、知识范围等。例如,设置为“你是一个专业的Linux运维专家,用中文回答技术问题。”,AI的回答就会更具专业性。
  5. temperature 参数 :控制回复的随机性。对于语音助手这种追求准确、可靠的场景,建议设置在 0.5~0.8 之间。太低(如0.1)会导致回复刻板重复;太高(如1.2)会导致回复天马行空,甚至胡言乱语。

4.4 文本转语音与音频播放模块

最后一步,把AI的文本回复变成声音。这里以功能强大且免费的 edge-tts 为例。

import edge_tts
import asyncio
import os
from pydub import AudioSegment
from pydub.playback import play
import tempfile

class TextToSpeechEngine:
    def __init__(self, voice="zh-CN-XiaoxiaoNeural", rate="+0%", volume="+0%"):
        """
        初始化Edge TTS引擎
        :param voice: 语音名称,参考 edge-tts --list-voices
        :param rate: 语速调整,例如 +10% 加快, -10% 放慢
        :param volume: 音量调整,例如 +10% 加大, -10% 减小
        """
        self.voice = voice
        self.rate = rate
        self.volume = volume

    async def _synthesize_speech(self, text, output_file):
        """异步合成语音的核心函数"""
        communicate = edge_tts.Communicate(text, self.voice, rate=self.rate, volume=self.volume)
        await communicate.save(output_file)

    def synthesize_and_play(self, text):
        """
        合成语音并立即播放(阻塞式)
        :param text: 需要合成的文本
        """
        if not text or text.strip() == "":
            print("[TTS] 文本为空,跳过语音合成。")
            return

        print(f"[TTS] 正在合成语音: {text[:50]}...")
        # 创建一个临时文件来存放合成的音频
        with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as tmp_file:
            temp_path = tmp_file.name

        try:
            # 运行异步合成函数
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            loop.run_until_complete(self._synthesize_speech(text, temp_path))
            loop.close()

            # 使用pydub加载并播放音频
            audio = AudioSegment.from_mp3(temp_path)
            print("[TTS] 开始播放...")
            play(audio)  # 这是一个阻塞调用,播放完毕才会继续

        except Exception as e:
            print(f"[ERROR] 语音合成或播放失败: {e}")
        finally:
            # 播放完成后,删除临时文件
            try:
                os.unlink(temp_path)
            except:
                pass

    def synthesize_to_file(self, text, output_path):
        """
        仅合成语音到文件,不播放
        :param text: 需要合成的文本
        :param output_path: 输出文件路径(如 output.mp3)
        """
        # 实现类似,但只保存文件
        pass

# 使用示例
tts_engine = TextToSpeechEngine(voice="zh-CN-XiaoxiaoNeural")  # 使用晓晓的中文语音
reply_text = "今天是晴天,气温在20到25摄氏度之间,非常适合户外活动。"
tts_engine.synthesize_and_play(reply_text)

技术细节与选择

  1. 异步处理 edge_tts 的API是异步的。我们通过 asyncio 来运行它。注意在非异步的同步函数中调用异步函数的方法(创建新事件循环)。
  2. 语音选择 edge-tts 提供了非常丰富的语音,支持多种语言和方言。你可以通过命令行 edge-tts --list-voices 查看所有可用的语音。中文推荐 zh-CN-XiaoxiaoNeural (晓晓,年轻女声)或 zh-CN-YunyangNeural (云扬,男声),听起来比较自然。
  3. 临时文件 :合成语音需要生成一个音频文件。我们使用 tempfile 模块创建一个临时的MP3文件,播放完毕后立即删除,避免磁盘空间被无用文件占用。
  4. 播放库 pydub playback.play() 函数在大多数桌面环境下工作良好,但它是一个阻塞函数。这意味着在播放音频的几秒钟内,你的程序主线程会停在那里。对于需要同时做其他事情(比如继续监听按键)的复杂应用,你可能需要使用多线程或异步播放(如 pyaudio 直接播放字节流)。
  5. 备选方案 :如果你需要离线功能,可以集成 pyttsx3 。它的接口更简单,但声音质量差很多。替换时,只需修改 synthesize_and_play 方法内部的实现即可,对外接口保持不变,这体现了模块化设计的好处。

5. 系统集成与主循环构建

现在,我们已经有了录音、识别、对话、语音合成四个核心模块。接下来需要把它们像管道一样连接起来,并放入一个持续运行的主循环中。

import time
import threading
from queue import Queue

class VoiceControlledChatGPT:
    def __init__(self):
        print("[SYSTEM] 初始化语音控制ChatGPT系统...")
        # 初始化各个模块
        self.recorder = VoiceRecorder(samplerate=16000)
        self.stt_engine = SpeechToTextEngine(model_size="base")
        self.chat_handler = ChatGPTHandler(
            system_prompt="你是一个友好的桌面语音助手。请用清晰、简洁的中文回答用户的问题。如果问题需要较长解释,请先给出核心结论。",
            max_history=8
        )
        self.tts_engine = TextToSpeechEngine(voice="zh-CN-XiaoxiaoNeural")

        # 用于线程间通信的队列(例如,将识别出的文本从监听线程传到主线程)
        self.task_queue = Queue()
        self.is_running = True

        # 启动键盘监听线程
        self.keyboard_thread = threading.Thread(target=self._keyboard_listener_loop, daemon=True)
        self.keyboard_thread.start()
        print("[SYSTEM] 系统初始化完成。按住空格键说话,松开结束。")

    def _keyboard_listener_loop(self):
        """独立的键盘监听线程"""
        from pynput import keyboard
        def on_press(key):
            if key == keyboard.Key.space and not self.recorder.is_recording:
                self.recorder.start_recording()
        def on_release(key):
            if key == keyboard.Key.space and self.recorder.is_recording:
                audio_file = self.recorder.stop_recording()
                if audio_file:
                    # 将任务放入队列,由主线程处理
                    self.task_queue.put(("process_audio", audio_file))
        with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
            while self.is_running:
                time.sleep(0.1)  # 防止CPU空转
            listener.stop()

    def _process_audio_task(self, audio_file_path):
        """处理音频任务:识别 -> 对话 -> 语音合成"""
        # 1. 语音转文本
        user_text = self.stt_engine.transcribe(audio_file_path)
        if not user_text:
            print("[SYSTEM] 未识别到有效语音,或识别失败。")
            return

        # 2. 与ChatGPT对话
        print(f"[USER] {user_text}")
        assistant_reply = self.chat_handler.get_response(user_text)

        # 3. 文本转语音并播放
        if assistant_reply and assistant_reply != "":
            self.tts_engine.synthesize_and_play(assistant_reply)

        # 4. (可选)清理临时音频文件
        try:
            os.remove(audio_file_path)
        except:
            pass

    def run(self):
        """主运行循环"""
        try:
            while self.is_running:
                # 检查任务队列
                if not self.task_queue.empty():
                    task_type, task_data = self.task_queue.get()
                    if task_type == "process_audio":
                        # 在新线程中处理任务,避免阻塞主循环
                        processing_thread = threading.Thread(
                            target=self._process_audio_task,
                            args=(task_data,),
                            daemon=True
                        )
                        processing_thread.start()
                time.sleep(0.05)  # 短暂休眠,降低CPU占用
        except KeyboardInterrupt:
            print("\n[SYSTEM] 接收到中断信号,正在关闭...")
        finally:
            self.is_running = False
            print("[SYSTEM] 系统已停止。")

if __name__ == "__main__":
    assistant = VoiceControlledChatGPT()
    assistant.run()

架构设计亮点

  1. 多线程设计 :键盘监听和音频处理都是可能耗时的I/O操作。使用多线程可以防止界面“卡死”。主循环负责调度,键盘监听在一个独立线程,每次的“识别-对话-合成”流程也放在单独的线程中处理。这样,即使一次对话处理需要几秒钟,你仍然可以随时按下空格键开始新的录音(新的任务会进入队列等待)。
  2. 任务队列 :使用 Queue 实现线程间通信是线程安全的经典模式。监听线程将任务放入队列,主线程从队列取出并分配处理。这解耦了事件触发和事件处理。
  3. 资源清理 :在 _process_audio_task 的最后,我们删除了录音生成的临时WAV文件。这是一个好习惯,避免运行一段时间后磁盘上堆积大量临时文件。
  4. 优雅退出 :主循环监听 KeyboardInterrupt (通常是Ctrl+C),当用户想退出时,将 is_running 设为False,各线程检测到该标志后会结束循环,程序安全退出。

6. 进阶优化与功能扩展思路

一个基础版本跑起来后,你可以从以下几个方面对它进行强化和定制,让它变得更实用、更强大。

6.1 性能与体验优化

  • 语音端点检测 :替代“按键触发”,实现真正的“唤醒词”或“VAD”。可以使用 silero-vad 等库,实时分析音频流,自动检测人声的开始和结束。这能带来更自然的交互体验。
  • 流式识别与回复 :现在的流程是“说完->识别整句->等AI回复->听完”,延迟感明显。可以优化为:
    • 流式STT :使用Whisper的流式API或 faster-whisper 库,用户一边说,一边就看到识别出的文字在屏幕上实时显示。
    • 流式LLM :调用ChatGPT API时设置 stream=True ,AI的回复会像打字机一样一个字一个字地返回。你可以将其与流式TTS结合,实现“边生成边播放”,极大降低响应延迟。
  • 本地LLM替换 :为了彻底摆脱网络依赖和API费用,可以尝试用本地运行的大模型(如通过 ollama 运行的 llama3 qwen 等)替换ChatGPT API。这需要一台性能不错的机器(尤其是GPU),并且回复质量可能有所下降,但隐私性和成本是最大优势。
  • 前端界面 :为它制作一个简单的图形界面(用 tkinter PyQt 或网页)。在界面上显示对话历史、当前状态(监听中、识别中、思考中、播放中),并提供配置选项(如切换语音、调整语速)。

6.2 功能扩展场景

  • 系统指令与技能插件 :强化 system_prompt ,让AI具备特定领域知识。更进一步,可以设计一个“技能插件”系统。例如,当用户说“打开浏览器”,系统指令告诉AI:“如果你判断用户想执行电脑操作,请以 COMMAND: open_browser 格式回复”。主程序解析到 COMMAND: 前缀,就不进行TTS,而是执行相应的Python函数(如用 webbrowser.open() 打开浏览器)。
  • 上下文记忆持久化 :目前对话历史保存在内存中,程序关闭就没了。可以将 conversation_history 定期保存到文件(如JSON)或数据库,下次启动时加载,实现跨会话的记忆。
  • 多模态支持 :OpenAI的API已经支持图像输入。你可以扩展录音模块,使其也能捕获摄像头截图。当用户说“看看我桌子上有什么”时,程序将图片和问题一起发给GPT-4V模型,它就能描述图像内容。这打开了通往“具身智能助手”的大门。

6.3 部署与打包

  • 配置文件 :将所有可调参数(API密钥、模型路径、语音选择、热键设置)移入一个外部的YAML或JSON配置文件,方便用户修改而不用碰代码。
  • 打包成可执行文件 :使用 PyInstaller cx_Freeze 将整个项目打包成一个 .exe (Windows)或可执行文件(Mac/Linux)。这样,没有Python环境的朋友也能一键运行你的语音助手。
  • 开机自启 :编写脚本或配置系统服务,让这个助手在电脑开机后自动在后台运行,随时待命。

这个项目从技术上看,是几个成熟AI组件的巧妙拼接;但从产品角度看,它为你提供了一个完全私有的、可高度定制的AI交互入口。你可以把它改造成你的个人效率工具、学习伙伴,甚至是智能家居的中控大脑。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐