C++实现Whisper+Kimi端到端AI智能语音助手
来源:程序员老廖
第1章 项目概览与运行配置
1.1 项目背景与定位
为什么需要这个项目?
voice_ai_chat 是一个端到端语音对话系统,它将两大核心能力串联:
核心价值:
- 隐私优先:语音数据本地处理,不上传云端
- 低延迟:本地 ASR 避免网络传输延迟
- 低成本:Whisper 本地免费运行,只消耗 LLM API 费用
典型应用场景
- 语音助手/智能客服原型
- 实时会议记录与 AI 摘要
- 无障碍辅助工具开发
- 本地隐私敏感的语音交互应用
1.2 项目结构一览
ai-sdk-cpp-laoliao/applications/voice_ai_chat/ ├── main.cpp # 程序入口:参数解析、初始化、主循环 ├── voice_ai_chat.h # 核心类定义与配置结构 ├── voice_ai_chat.cpp # 核心实现:LLM 工作线程、队列管理 ├── CMakeLists.txt # 编译配置 └── README.md # 项目文档
依赖关系:
main.cpp ├── voice_ai_chat.h/cpp (AI 对话层) ├── realtime_coordinator.h (ASR 协调层) ├── audio_capture.h (音频采集) ├── endpoint_detector.h (VAD 端点检测) └── asr_worker.h (Whisper 识别工作线程)
项目源码领取:全网首发!C++实现Whisper+Kimi端到端AI智能语音助手
1.3 编译与运行
编译步骤
# 1. 确保依赖已安装 sudo apt-get install -y libsdl2-dev pkg-config # Ubuntu/Debian # 或 brew install sdl2 pkg-config # macOS # 2. 进入项目目录 cd ai-sdk-cpp-laoliao # 3. 创建构建目录 mkdir -p build && cd build # 4. 编译 cmake .. make -j$(nproc) voice_ai_chat
运行前的必要配置
必须设置环境变量:
export MOONSHOT_API_KEY="your-moonshot-api-key-here"
获取方式:访问 Moonshot AI 开放平台 注册并创建 API Key。
可选环境变量:
export WHISPER_MODEL_PATH="/path/to/your/model.bin" # 指定模型路径 export VOICE_AI_LOG_LEVEL=debug # 设置日志级别
1.4 运行参数详解
基础运行命令
# 方式1:纯文本模式(调试 LLM 链路) ./applications/bin/voice_ai_chat --stdin # 方式2:语音模式(指定模型路径) ./applications/bin/voice_ai_chat ../../../models/ggml-small.bin # 方式3:语音模式 + 完整参数 ./applications/bin/voice_ai_chat \ ../../../models/ggml-small.bin \ --mode balanced \ --vad-threshold 0.06 \ --threads 8
参数分类速查表
A. 模式选择参数
B. 模型与路径参数
C. VAD (语音检测) 参数
D. 性能参数
E. 文本优化参数
F. Partial 预览参数(建议关闭)
G. 设备与日志参数
1.5 模型选择与推理速度
Whisper 模型对比
Whisper.cpp 支持多种模型,模型越大准确率越高,但速度越慢。
RTF (Real-Time Factor): 处理1秒音频需要的实际秒数。RTF < 1 才能实时。
硬件配置建议
CPU 要求
模式选择指南
根据你的 CPU 性能和实时性要求选择 --mode:
# 场景1:强实时、短句命令(如语音输入框) --mode fast --threads 8 --max-segment-ms 1600 # 特点:延迟最低,准确率略低 # 场景2:普通对话、平衡选择(推荐) --mode balanced --threads 8 --max-segment-ms 2200 # 特点:速度与质量兼顾 # 场景3:长句、质量优先(会议记录) --mode quality --threads 8 --max-segment-ms 2600 # 特点:准确率最高,延迟较大
模式内部参数详解
格式说明:partial参数/final参数。max_tokens=0 表示不限制。
1.6 推荐配置组合
配置 A:极速实时(短句命令)
./voice_ai_chat ../../../models/ggml-small.bin \ --mode fast \ --vad-threshold 0.05 \ --poll-interval-ms 200 \ --threads 8 \ --max-segment-ms 1600
适用:语音输入框、快速命令、即时响应场景
配置 B:平衡模式(推荐日常使用)
./voice_ai_chat ../../../models/ggml-small.bin \ --mode balanced \ --vad-threshold 0.06 \ --poll-interval-ms 250 \ --threads 8 \ --max-segment-ms 2200 \ --prompt-file ../prompts/general.txt \ --lexicon-file ../lexicons/common.txt
适用:日常对话、办公场景
配置 C:质量优先(会议记录)
./voice_ai_chat ../../../models/ggml-medium.bin \ --mode quality \ --vad-threshold 0.06 \ --poll-interval-ms 300 \ --threads 8 \ --max-segment-ms 2600 \ --prompt-file ../prompts/general.txt \ --replacements-file ../replacements/tech.txt
适用:会议记录、访谈转录、质量优先场景
配置 D:低资源设备
./voice_ai_chat ../../../models/ggml-base.bin \ --mode fast \ --vad-threshold 0.08 \ --poll-interval-ms 300 \ --threads 4 \ --max-segment-ms 1800
适用:嵌入式设备、旧电脑、树莓派等
1.7 常见问题诊断
Q1: 说话但没有文本输出?
排查步骤:
- 检查日志级别:--log-level debug
- 确认 VAD 是否检测到语音:看 [VAD-DEBUG] 日志
- 检查音频设备:--list-input-devices 确认使用正确设备
- 调整 VAD 阈值:--vad-threshold 0.05(更灵敏)
Q2: 推理速度太慢?
优化建议(按优先级):
- 换小模型:large → medium → small → base
- 用 fast 模式:--mode fast
- 缩短片段:--max-segment-ms 1600
- 增加线程:--threads 8(不超过物理核心数)
- 检查 CPU:是否在运行其他占用 CPU 的程序?
Q3: 出现很多繁体字?
解决方案:
- 使用 --prompt-file 加载简体提示词
- 使用 --lexicon-file 加载词库纠错
- 后续可考虑集成 OpenCC 进行繁转简
Q4: 输出中出现"常见术语包括"等 prompt 内容?
这是 prompt 泄漏,说明提示词太长。解决方法:
- 改用短提示词:../prompts/general.txt
- 停用 --hotwords-file,改用 --replacements-file
第2章 架构设计与数据流
2.1 整体架构概览
四层架构设计
┌─────────────────────────────────────────────────────────────────────┐ │ Voice AI Chat │ │ (应用层 / 协调层) │ ├─────────────────────────────────────────────────────────────────────┤ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ ASR层 │───▶│ Final文本 │───▶│ LLM层 │ │ │ │ (本地Whisper)│ │ 队列 │ │ (云端Kimi) │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ ├─────────────────────────────────────────────────────────────────────┤ │ ┌─────────────┐ ┌─────────────┐ │ │ │ VAD层 │───▶│ 端点检测器 │ │ │ │ (语音检测) │ │ │ │ │ └─────────────┘ └─────────────┘ │ ├─────────────────────────────────────────────────────────────────────┤ │ ┌─────────────┐ │ │ │ 采集层 │ SDL2 + 环形缓冲区 │ │ │ (麦克风) │ │ │ └─────────────┘ │ └─────────────────────────────────────────────────────────────────────┘
设计哲学:解耦与异步
为什么这样分层?
2.2 数据流详解
阶段1:音频采集层
// AudioCapture 核心职责
class AudioCapture {
// 1. SDL2 初始化音频设备
// 2. 音频回调写入环形缓冲区
// 3. 提供 GetAudioRange() 供 VAD 读取
};
关键设计:
- Ring Buffer:避免内存分配,支持实时读取
- 绝对时间戳:每个采样都有全局时间,便于切片
阶段2:VAD 端点检测层
// EndpointDetector 核心职责
class EndpointDetector {
// 1. 定时从 AudioCapture 读取音频窗口
// 2. 调用 whisper_vad_segments() 检测语音概率
// 3. 判断语音开始/结束,生成 utterance 区间
};
状态流转:
静音状态 ──(vad_prob > threshold)──▶ 说话中 ──(vad_prob < threshold 持续 N ms)──▶ 静音状态
│ │
▼ ▼
触发 "speech started" 触发 "speech ended"
生成 utterance
阶段3:ASR 识别层
// AsrWorker 核心职责(独立线程)
class AsrWorker {
// 1. 从队列取出 utterance job
// 2. 调用 whisper_full_with_state() 识别
// 3. 输出识别结果(partial / final)
};
背压控制:
utterance queue (有界队列)
┌──────────────────────────────────┐
│ [job1] [job2] [job3] ... │
└──────────────────────────────────┘
▲ ▼
EndpointDetector AsrWorker
(生产者) (消费者)
队列满时的策略:丢弃最旧的 job,保证实时性
阶段4:LLM 对话层
// VoiceAIChat 核心职责(独立线程)
class VoiceAIChat {
// 1. 通过回调接收 final 文本
// 2. LLM Worker 线程消费文本队列
// 3. 流式调用 Kimi API
// 4. 管理对话历史
};
数据流向:
ASR final output
│
▼
┌──────────────────┐
│ final_text_queue │ (双端队列,mutex 保护)
└──────────────────┘
│
▼
┌──────────────────┐
│ LLM Worker Loop │ (condition_variable 等待)
└──────────────────┘
│
▼
┌──────────────────┐
│ Kimi stream_text │ (HTTP SSE 流式)
└──────────────────┘
│
▼
控制台输出
2.3 时序图
完整交互时序
2.4 线程模型
线程分布图
主线程 (main) ├── AudioCapture 线程 (SDL2 音频回调线程) ├── AsrWorker 线程 (Whisper 推理) ├── LLM Worker 线程 (VoiceAIChat) └── 主循环 (coordinator.Run())
线程职责与同步
关键同步代码
// LLM Worker 等待队列(voice_ai_chat.cpp)
void VoiceAIChat::LLMWorkerLoop() {
while (true) {
std::string user_text;
{
std::unique_lock<std::mutex> lock(queue_mutex_);
queue_cv_.wait(lock, [this]() {
return !running_.load() || !final_text_queue_.empty();
});
if (!running_.load() && final_text_queue_.empty()) {
return; // 退出信号
}
user_text = std::move(final_text_queue_.front());
final_text_queue_.pop_front();
} // 解锁
ProcessWithAI(user_text); // 处理请求
}
}
// 生产者入队
void VoiceAIChat::EnqueueFinalText(const std::string& text) {
{
std::lock_guard<std::mutex> lock(queue_mutex_);
if (final_text_queue_.size() >= config_.max_pending_final_texts) {
final_text_queue_.pop_front(); // 背压:丢弃最旧
}
final_text_queue_.push_back(text);
}
queue_cv_.notify_one(); // 通知消费者
}
2.5 为什么 Partial 不进 LLM?
Partial vs Final
如果把 Partial 送进 LLM 会怎样?
用户说话:"今天天气怎么样" 时间线: T1: partial -> "今天天" ──┐ T2: partial -> "今天天气怎" ──┼── 都送进 LLM? T3: partial -> "今天天气怎么样" ──┘ T4: final -> "今天天气怎么样" 问题: 1. 上下文污染:LLM 看到 3 次重复请求 2. Token 浪费:多次请求消耗 API 额度 3. 过早触发:话没说完就生成回复
推荐策略
┌─────────────────────────────────────┐ │ ASR 层 (Realtime) │ │ ┌─────────┐ ┌─────────────┐ │ │ │ Partial │─────▶│ 屏幕显示 │ │ (不送 LLM) │ └─────────┘ └─────────────┘ │ │ ┌─────────┐ ┌─────────────┐ │ │ │ Final │─────▶│ LLM Worker │ │ (正式处理) │ └─────────┘ └─────────────┘ │ └─────────────────────────────────────┘
2.6 扩展设计:未来方向
阶段 2 规划
当前架构 目标架构
────────── ──────────
┌────────┐ ┌────────┐
│ ASR │ │ ASR │
│ small │ │ tiny/ │──▶ Partial (预览)
└────┬───┘ │ small │
│ └────┬───┘
▼ │
┌────────┐ ▼
│ LLM │ ┌────────┐
│ (Kimi) │ │ LLM │
└────────┘ │ (Kimi) │
└────────┘
新增:
• 双模型策略 (tiny + small)
• TTS 回播
• Barge-in (打断检测)
可能的架构演进
// 未来可能的多级架构
class VoiceAIChat {
// 层1:ASR (本地)
// 层2:理解/意图 (本地轻量模型或规则)
// 层3:LLM (云端或本地大模型)
// 层4:TTS (本地或云端)
};
第3章 核心模块源码解析
3.1 VoiceAIChat 类概览
类定义(voice_ai_chat.h)
namespace voice_ai {
struct VoiceAIConfig {
std::string moonshot_base_url = "https://api.moonshot.cn";
std::string ai_model = "kimi-k2.5";
std::string ai_system_prompt = "你是 Kimi,一个中文优先...";
std::string moonshot_api_key;
bool enable_streaming = true;
size_t max_pending_final_texts = 8;
realtime::LogLevel log_level = realtime::LogLevel::kInfo;
};
class VoiceAIChat {
public:
explicit VoiceAIChat(const VoiceAIConfig& config);
~VoiceAIChat();
// 禁止拷贝(资源管理语义)
VoiceAIChat(const VoiceAIChat&) = delete;
VoiceAIChat& operator=(const VoiceAIChat&) = delete;
// 生命周期
bool Init();
void Stop();
// 回调设置(用于外部集成)
void SetTranscriptionCallback(TranscriptionCallback callback);
void SetAIResponseCallback(AIResponseCallback callback);
// 输入接口
void SendTextMessage(const std::string& text); // 文本模式
void HandleFinalTranscript(const std::string& text); // ASR 回调
private:
// 内部实现...
};
} // namespace voice_ai
设计模式分析
3.2 初始化流程
Init() 方法
bool VoiceAIChat::Init() {
if (!InitAIClient()) { // 步骤1:初始化 AI 客户端
return false;
}
StartLLMWorker(); // 步骤2:启动工作线程
return true;
}
InitAIClient() 详解
bool VoiceAIChat::InitAIClient() {
// 设置日志(只显示警告和错误)
ai::logger::install_logger(
std::make_shared<ai::logger::ConsoleLogger>(
ai::logger::LogLevel::kLogLevelWarn));
// 获取 API Key(环境变量优先)
const auto api_key = config_.moonshot_api_key.empty()
? GetEnvOrDefault("MOONSHOT_API_KEY", "")
: config_.moonshot_api_key;
if (api_key.empty()) {
realtime::AppLogger::Instance().Error(
"MOONSHOT_API_KEY environment variable not set");
return false;
}
// 创建客户端(使用 std::optional 延迟初始化)
ai_client_.emplace(
ai::openai::create_client(api_key, config_.moonshot_base_url));
if (!ai_client_->is_valid()) {
realtime::AppLogger::Instance().Error("AI client init failed");
return false;
}
// 初始化对话历史(系统提示词)
conversation_history_ = {
ai::Message::system(config_.ai_system_prompt),
};
return true;
}
关键点:
- 环境变量 MOONSHOT_API_KEY 必须设置
- std::optional 实现延迟初始化,避免构造时出错
- 系统提示词在初始化时加入对话历史
3.3 LLM Worker 线程
线程生命周期
void VoiceAIChat::StartLLMWorker() {
bool expected = false;
// compare_exchange_strong: 原子 CAS 操作
// 确保只有一个线程能成功启动
if (!running_.compare_exchange_strong(expected, true)) {
return; // 已经在运行
}
llm_thread_ = std::thread(&VoiceAIChat::LLMWorkerLoop, this);
}
void VoiceAIChat::StopLLMWorker() {
const bool was_running = running_.exchange(false);
queue_cv_.notify_all(); // 唤醒所有等待的线程
if (was_running && llm_thread_.joinable()) {
llm_thread_.join(); // 等待线程结束
}
}
Worker 主循环(生产者-消费者模式)
void VoiceAIChat::LLMWorkerLoop() {
while (true) {
std::string user_text;
size_t pending_after_pop = 0;
{
// 1. 获取锁
std::unique_lock<std::mutex> lock(queue_mutex_);
// 2. 条件等待(原子释放锁并等待)
queue_cv_.wait(lock, [this]() {
// 唤醒条件:停止信号 或 队列非空
return !running_.load() || !final_text_queue_.empty();
});
// 3. 检查退出条件
if (!running_.load() && final_text_queue_.empty()) {
return; // 优雅退出
}
// 4. 消费消息
user_text = std::move(final_text_queue_.front());
final_text_queue_.pop_front();
pending_after_pop = final_text_queue_.size();
} // 5. 自动解锁
// 6. 处理(在锁外执行,避免阻塞入队)
ai_processing_ = true;
ProcessWithAI(user_text);
ai_processing_ = false;
}
}
入队方法(生产者)
void VoiceAIChat::EnqueueFinalText(const std::string& text) {
{
std::lock_guard<std::mutex> lock(queue_mutex_);
// 背压控制:队列满时丢弃最旧的消息
if (final_text_queue_.size() >= config_.max_pending_final_texts) {
realtime::AppLogger::Instance().Warn(
"final 文本队列已满,丢弃最旧的一条");
final_text_queue_.pop_front();
}
final_text_queue_.push_back(text);
} // 锁释放
queue_cv_.notify_one(); // 通知一个等待的消费者
}
为什么用 notify_one 而不是 notify_all?
- 只有一个 LLM Worker 线程
- notify_one 更高效(只唤醒一个线程)
3.4 调用 Kimi API
ProcessWithAI() 主流程
void VoiceAIChat::ProcessWithAI(const std::string& user_text) {
// 1. 检查客户端状态
if (!ai_client_.has_value()) {
return;
}
// 2. 添加到对话历史(User 消息)
{
std::lock_guard<std::mutex> lock(mutex_);
conversation_history_.push_back(ai::Message::user(user_text));
}
// 3. 准备请求参数
ai::GenerateOptions generate_options;
{
std::lock_guard<std::mutex> lock(mutex_);
generate_options.model = config_.ai_model;
generate_options.messages = conversation_history_;
}
// 4. 打印 AI 前缀(带颜色)
{
std::lock_guard<std::mutex> lock(output_mutex_);
std::cout << kAnsiAssistantColor << "Kimi> " << std::flush;
}
// 5. 流式调用
if (config_.enable_streaming) {
ProcessStreaming(generate_options);
} else {
ProcessNonStreaming(generate_options);
}
}
流式处理详解
void VoiceAIChat::ProcessStreaming(ai::GenerateOptions& options) {
ai::StreamOptions stream_options(std::move(options));
auto stream = ai_client_->stream_text(stream_options);
std::string assistant_reply;
bool stream_failed = false;
for (const auto& event : stream) {
if (event.is_text_delta()) {
// 收到文本片段
assistant_reply += event.text_delta;
// 外部回调
if (ai_response_callback_) {
ai_response_callback_(event.text_delta, true);
}
// 实时输出
std::lock_guard<std::mutex> lock(output_mutex_);
std::cout << event.text_delta << std::flush;
} else if (event.is_error()) {
// 流错误处理
stream_failed = true;
realtime::AppLogger::Instance().Error(
"Kimi stream error: " + event.error.value_or("unknown"));
break;
}
}
// 完成处理
std::lock_guard<std::mutex> lock(mutex_);
if (stream_failed || assistant_reply.empty()) {
// 失败时回退 User 消息(对话不保存)
conversation_history_.pop_back();
} else {
// 成功,添加 Assistant 回复到历史
conversation_history_.push_back(
ai::Message::assistant(assistant_reply));
}
}
流式响应的优势:
- 用户体验好:边生成边显示
- 感知延迟低:不需要等全部生成完
- 与 ChatGPT 等产品体验一致
3.5 主程序入口
main() 函数结构(main.cpp)
int main(int argc, char** argv) {
try {
// 1. 配置解析
voice_ai::VoiceAIConfig config;
realtime::RuntimeOptions realtime_options;
ParseArguments(argc, argv, config, realtime_options);
// 2. 环境变量覆盖
ApplyEnvironmentVariables(config, realtime_options);
// 3. 加载外部文件(提示词、词库等)
LoadExternalFiles(realtime_options);
// 4. 初始化日志
realtime::AppLogger::Instance().SetLevel(config.log_level);
// 5. 列出设备(如果指定)
if (realtime_options.list_input_devices) {
return realtime::AudioCapture::ListInputDevices() ? 0 : 1;
}
// 6. 创建应用实例
voice_ai::VoiceAIChat app(config);
// 7. 信号处理(Ctrl+C 优雅退出)
std::signal(SIGINT, HandleSignal);
if (!app.Init()) {
return 1;
}
// 8. 模式选择:文本模式 或 语音模式
if (stdin_mode) {
RunStdinMode(app);
} else {
RunVoiceMode(app, realtime_options);
}
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
return 1;
}
}
语音模式初始化
void RunVoiceMode(voice_ai::VoiceAIChat& app,
const realtime::RuntimeOptions& options) {
// 1. 创建协调器
realtime::RealtimeCoordinator coordinator(options);
// 2. 设置 ASR final 回调(核心集成点)
coordinator.SetFinalTextCallback(
[&app](const std::string& text) {
app.HandleFinalTranscript(text);
});
// 3. 初始化并运行
if (!coordinator.Init()) {
std::cerr << "[MAIN] realtime coordinator init failed\n";
return 1;
}
coordinator.Run(); // 阻塞直到停止
}
核心集成点:SetFinalTextCallback 将 ASR 层和 LLM 层连接起来。
3.6 参数解析详解
命令行解析逻辑
for (int i = 1; i < argc; ++i) {
const std::string arg = argv[i];
if (arg == "--stdin") {
stdin_mode = true;
}
else if (arg == "--ai-model" && i + 1 < argc) {
config.ai_model = argv[++i]; // 后置递增,先取值再移动
}
else if (arg == "--llm-queue-size" && i + 1 < argc) {
config.max_pending_final_texts =
static_cast<size_t>(std::stoul(argv[++i]));
}
// ... 更多参数
else if (!arg.empty() && arg[0] != '-') {
cli_model_path = arg; // 位置参数:模型路径
}
}
环境变量优先级
// 环境变量优先级高于命令行
if (const char* env = std::getenv("WHISPER_MODEL_PATH");
env != nullptr && !std::string(env).empty()) {
realtime_options.model_path = env;
} else if (!cli_model_path.empty()) {
realtime_options.model_path = cli_model_path;
}
// 日志级别
if (const char* env = std::getenv("VOICE_AI_LOG_LEVEL"); env != nullptr) {
config.log_level = realtime::AppLogger::ParseLevel(env);
}
优先级规则:
- 环境变量(最高优先级)
- 命令行参数
- 默认值
3.7 信号处理与优雅退出
全局指针(用于信号处理)
namespace {
voice_ai::VoiceAIChat* g_app = nullptr;
realtime::RealtimeCoordinator* g_coordinator = nullptr;
}
信号处理器
void HandleSignal(int sig) {
if (sig == SIGINT) { // Ctrl+C
std::cout << "\n[MAIN] stopping...\n";
// 按依赖顺序停止
if (g_coordinator != nullptr) {
g_coordinator->Stop(); // 先停采集和 ASR
}
if (g_app != nullptr) {
g_app->Stop(); // 再停 LLM(完成正在进行的请求)
}
}
}
注册信号处理
int main(...) {
// ...
voice_ai::VoiceAIChat app(config);
g_app = &app;
std::signal(SIGINT, HandleSignal); // 注册处理器
// ...
}
为什么先停 Coordinator 再停 VoiceAIChat?
- Coordinator 停止后不再产生新的 final 文本
- VoiceAIChat 可以继续处理队列中的剩余请求
- 避免请求丢失
第4章 关键技术点剖析
4.1 线程安全设计
多线程访问的共享资源
class VoiceAIChat {
// 需要同步保护的成员
std::deque<std::string> final_text_queue_; // LLM 队列
ai::Messages conversation_history_; // 对话历史
std::optional<ai::Client> ai_client_; // API 客户端
// 同步原语
std::mutex mutex_; // 保护 conversation_history_
std::mutex queue_mutex_; // 保护 final_text_queue_
std::mutex output_mutex_; // 保护控制台输出顺序
std::condition_variable queue_cv_; // 队列非空通知
};
锁粒度设计
细粒度锁策略:
// Good: 不同资源用不同锁 std::mutex queue_mutex_; // 只保护队列 std::mutex history_mutex_; // 只保护对话历史 std::mutex output_mutex_; // 只保护输出 // Bad: 一把大锁保护所有 std::mutex big_lock_; // 避免!会造成不必要的阻塞
代码示例:
void VoiceAIChat::ProcessWithAI(const std::string& user_text) {
// 操作1:修改对话历史(需要锁)
{
std::lock_guard<std::mutex> lock(mutex_);
conversation_history_.push_back(ai::Message::user(user_text));
} // 锁立即释放
// 操作2:准备请求参数(不需要锁,因为 conversation_history_ 已复制)
ai::GenerateOptions generate_options;
{
std::lock_guard<std::mutex> lock(mutex_);
generate_options.messages = conversation_history_;
} // 锁立即释放
// 操作3:网络请求(不需要锁,避免阻塞其他线程)
auto stream = ai_client_->stream_text(stream_options);
// 操作4:输出响应(需要 output_mutex_ 保证输出顺序)
for (const auto& event : stream) {
std::lock_guard<std::mutex> lock(output_mutex_);
std::cout << event.text_delta << std::flush;
}
}
死锁避免
原则:
- 锁顺序一致:多个锁时,始终以相同顺序获取
- 避免锁嵌套:尽量使用独立锁,减少嵌套
- 使用 std::lock_guard:RAII 自动释放,避免忘记解锁
// 安全的嵌套锁(如果需要)
void SomeFunction() {
std::lock_guard<std::mutex> lock_a(mutex_a_);
{
std::lock_guard<std::mutex> lock_b(mutex_b_);
// 操作
} // lock_b 先释放
} // lock_a 后释放
// 危险:不同函数锁顺序不一致会导致死锁
void FunctionA() {
std::lock_guard<std::mutex> a(mutex_a_);
std::lock_guard<std::mutex> b(mutex_b_); // 顺序:a -> b
}
void FunctionB() {
std::lock_guard<std::mutex> b(mutex_b_); // 顺序:b -> a ❌
std::lock_guard<std::mutex> a(mutex_a_);
}
4.2 背压控制 (Backpressure)
问题场景
用户快速说话 ASR 识别 LLM 处理
│ │ │
▼ ▼ ▼
句子1 ──────────▶ 文本1 ────────▶ 请求1 (慢)
句子2 ──────────▶ 文本2 ──────▶ 请求2 (等待中)
句子3 ──────────▶ 文本3 ──▶ 请求3 (堆积...)
句子4 ──────────▶ 文本4
...
问题:LLM 处理慢,队列无限增长 → 内存溢出、延迟累积
解决方案:有界队列 + 丢弃策略
void VoiceAIChat::EnqueueFinalText(const std::string& text) {
std::lock_guard<std::mutex> lock(queue_mutex_);
// 背压控制:队列满时丢弃最旧的消息
if (final_text_queue_.size() >= config_.max_pending_final_texts) {
realtime::AppLogger::Instance().Warn(
"final 文本队列已满,丢弃最旧的一条");
final_text_queue_.pop_front(); // 丢弃最旧
}
final_text_queue_.push_back(text); // 加入最新
}
背压策略对比
为什么选"丢弃最旧"?
- 语音对话场景:用户更关心最新说的话
- 旧消息可能已经"过时"(上下文已变)
- 避免延迟累积,保证实时响应
4.3 回调机制设计
回调类型定义
// ASR 结果回调
using TranscriptionCallback =
std::function<void(const std::string& text, bool is_final)>;
// AI 响应回调
using AIResponseCallback =
std::function<void(const std::string& text, bool is_streaming_chunk)>;
回调注册与调用
class VoiceAIChat {
public:
// 设置回调(支持外部扩展)
void SetTranscriptionCallback(TranscriptionCallback callback) {
transcription_callback_ = std::move(callback);
}
void SetAIResponseCallback(AIResponseCallback callback) {
ai_response_callback_ = std::move(callback);
}
private:
void HandleTranscription(const std::string& text, bool is_final) {
// 调用外部注册的回调
if (transcription_callback_) {
transcription_callback_(text, is_final);
}
}
TranscriptionCallback transcription_callback_;
AIResponseCallback ai_response_callback_;
};
使用示例
// 主程序中注册回调
voice_ai::VoiceAIChat app(config);
// 回调1:收到 ASR 结果时打印日志
app.SetTranscriptionCallback(
[](const std::string& text, bool is_final) {
if (is_final) {
LOG_INFO << "Final transcription: " << text;
}
});
// 回调2:收到 AI 响应时发送到 WebSocket
app.SetAIResponseCallback(
[&websocket](const std::string& chunk, bool is_streaming) {
websocket.Send(chunk); // 实时推送到前端
});
Lambda 捕获注意事项
// 危险:捕获局部变量的引用
void Dangerous() {
voice_ai::VoiceAIChat app(config);
std::string prefix = "User: ";
app.SetTranscriptionCallback(
[&prefix](const std::string& text, bool) { // ❌ 引用捕获
std::cout << prefix << text << "\n"; // Dangerous 返回后 prefix 失效
});
} // prefix 被销毁,回调变成悬空引用
// 安全:值捕获 或 捕获指针
void Safe() {
auto app = std::make_shared<voice_ai::VoiceAIChat>(config);
std::string prefix = "User: ";
app->SetTranscriptionCallback(
[prefix, app](const std::string& text, bool) { // ✅ 值捕获
std::cout << prefix << text << "\n"; // 安全,有独立的拷贝
});
}
4.4 条件变量使用模式
生产者-消费者完整模式
template<typename T>
class BoundedQueue {
public:
explicit BoundedQueue(size_t capacity) : capacity_(capacity) {}
// 生产者
void Push(T item) {
std::unique_lock<std::mutex> lock(mutex_);
// 等待队列有空位
not_full_.wait(lock, [this] {
return queue_.size() < capacity_;
});
queue_.push_back(std::move(item));
not_empty_.notify_one(); // 通知消费者
}
// 消费者
T Pop() {
std::unique_lock<std::mutex> lock(mutex_);
// 等待队列有数据
not_empty_.wait(lock, [this] {
return !queue_.empty();
});
T item = std::move(queue_.front());
queue_.pop_front();
not_full_.notify_one(); // 通知生产者
return item;
}
private:
std::deque<T> queue_;
size_t capacity_;
std::mutex mutex_;
std::condition_variable not_empty_;
std::condition_variable not_full_;
};
本项目中的变体(无界队列 + 丢弃)
void VoiceAIChat::LLMWorkerLoop() {
while (true) {
std::string user_text;
{
std::unique_lock<std::mutex> lock(queue_mutex_);
// 条件等待:停止信号 或 队列非空
queue_cv_.wait(lock, [this] {
return !running_.load() || !final_text_queue_.empty();
});
// 检查退出条件
if (!running_.load() && final_text_queue_.empty()) {
return; // 优雅退出
}
// 消费
user_text = std::move(final_text_queue_.front());
final_text_queue_.pop_front();
} // 解锁
ProcessWithAI(user_text); // 处理(在锁外)
}
}
关键点:
- wait() 会自动释放锁并阻塞,被唤醒时重新获取锁
- 使用 lambda 谓词防止虚假唤醒 (spurious wakeup)
- 处理逻辑在锁外执行,减少临界区
4.5 流式 HTTP 处理
SSE (Server-Sent Events) 基础
HTTP 请求
│
▼
POST /v1/chat/completions
Headers:
Authorization: Bearer {api_key}
Accept: text/event-stream ← 关键:请求流式响应
响应(流式):
data: {"choices":[{"delta":{"content":"Hello"}}]}
data: {"choices":[{"delta":{"content":" world"}}]}
data: {"choices":[{"delta":{"content":"!"}}]}
data: [DONE]
流式处理代码
void VoiceAIChat::ProcessStreaming(ai::GenerateOptions& options) {
// 1. 创建流式选项
ai::StreamOptions stream_options(std::move(options));
// 2. 发起流式请求
auto stream = ai_client_->stream_text(stream_options);
// 3. 逐块处理
for (const auto& event : stream) {
if (event.is_text_delta()) {
// 收到文本片段
const std::string& chunk = event.text_delta;
// 实时输出
std::cout << chunk << std::flush;
// 累加完整回复
assistant_reply += chunk;
} else if (event.is_error()) {
// 错误处理
HandleError(event.error);
stream_failed = true;
break;
}
}
// 4. 流结束,更新对话历史
if (!stream_failed) {
conversation_history_.push_back(
ai::Message::assistant(assistant_reply));
}
}
流式 vs 非流式对比
4.6 配置加载优先级
三级配置体系
┌─────────────────────────────────────────┐ │ Level 1: 代码硬编码默认值 │ │ (最低优先级) │ ├─────────────────────────────────────────┤ │ Level 2: 命令行参数 │ │ (./voice_ai_chat --mode fast ...) │ ├─────────────────────────────────────────┤ │ Level 3: 环境变量 │ │ (export WHISPER_MODEL_PATH=...) │ │ (最高优先级) │ └─────────────────────────────────────────┘
实现代码
// 优先级:环境变量 > 命令行 > 默认值
std::string GetModelPath(const std::string& cli_path) {
// 1. 检查环境变量(最高优先级)
if (const char* env = std::getenv("WHISPER_MODEL_PATH")) {
if (!std::string(env).empty()) {
return env;
}
}
// 2. 使用命令行参数
if (!cli_path.empty()) {
return cli_path;
}
// 3. 使用默认值
return "../../../models/ggml-small.bin";
}
// 应用示例
realtime_options.model_path = GetModelPath(cli_model_path);
为什么要这样设计?
4.7 日志系统设计
日志级别
enum class LogLevel {
kDebug, // 详细调试信息(开发用)
kInfo, // 一般信息(运行状态)
kWarn, // 警告(需要注意但非致命)
kError // 错误(功能受影响)
};
日志使用示例
// 不同级别的日志
AppLogger::Instance().Debug("queue size=" + std::to_string(size));
AppLogger::Instance().Info("AI client initialized");
AppLogger::Instance().Warn("队列已满,丢弃旧消息");
AppLogger::Instance().Error("API request failed: " + error);
运行时调整日志级别
# 方式1:命令行 ./voice_ai_chat --log-level debug # 方式2:环境变量 export VOICE_AI_LOG_LEVEL=debug ./voice_ai_chat
生产环境建议
第5章 实验演示
5.1 环境准备
检查清单
快速编译脚本
#!/bin/bash # build.sh - 一键编译脚本 cd ai-sdk-cpp-laoliao mkdir -p build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release make -j$(nproc) voice_ai_chat echo "编译完成,二进制位置:" echo " ./applications/bin/voice_ai_chat"
5.2 实验1:文本模式验证 LLM 链路
目的
在不涉及语音的情况下,先验证 AI 对话功能正常。
步骤
# 1. 设置 API Key export MOONSHOT_API_KEY="your-api-key" # 2. 启动文本模式 ./applications/bin/voice_ai_chat --stdin # 3. 输入测试 === Voice AI Chat (stdin mode) === 输入文本后回车,输入 /exit 退出。 你好,请介绍一下你自己 Kimi> 你好!我是 Kimi,一个 AI 助手... 今天的天气怎么样? Kimi> 我无法获取实时天气信息... /exit
预期输出
你(ASR)> 你好,请介绍一下你自己 Kimi> 你好!我是 Kimi,一个 AI 助手。我可以帮助你解答问题、写作... 你(ASR)> 今天的天气怎么样? Kimi> 我无法获取实时天气信息...
故障排查
5.3 实验2:基础语音模式
目的
验证完整的语音输入 → ASR → LLM 流程。
步骤
# 1. 列出设备(确认麦克风可用) ./applications/bin/voice_ai_chat --list-input-devices # 预期输出: # Input Device 0: HDA Intel PCH: ALC257 Analog (hw:0,0) # Input Device 1: USB Audio Device: USB Audio (hw:1,0) # 2. 基础语音模式(使用默认配置) ./applications/bin/voice_ai_chat \ ../../../models/ggml-small.bin # 3. 说话测试(建议中文短句) # 示例:"你好"、"今天星期几"、"讲个笑话"
观察要点
- VAD 检测:看到 [ENDPOINT] speech started 表示检测到语音开始
- 识别输出:看到 [TEXT] #1 xxx 表示 ASR 成功
- LLM 响应:看到 Kimi> xxx 表示 AI 正在回复
预期完整输出
[VAD-DEBUG] max_prob=0.821, segments=1, speech=yes [ENDPOINT] speech started ... [ENDPOINT] speech ended [QUEUE] enqueue utterance #1 start_ms=... end_ms=... [ASR-WORKER] processing utterance #1 samples=... [TEXT] #1 你好 你(ASR)> 你好 Kimi> 你好!很高兴见到你...
5.4 实验3:模型速度对比
目的
对比不同模型的推理速度,选择合适的模型。
测试脚本
#!/bin/bash
# benchmark.sh - 模型速度对比
MODELS=("tiny" "base" "small")
TEST_AUDIO="test.wav" # 准备一段 5 秒测试音频
echo "=== 模型速度对比测试 ==="
for model in "${MODELS[@]}"; do
echo ""
echo "测试模型: $model"
echo "模型大小: $(ls -lh models/ggml-$model.bin | awk '{print $5}')"
# 使用 time 统计时间
time ./applications/bin/voice_ai_chat \
models/ggml-$model.bin \
--mode fast \
--threads 8 \
2>&1 | grep -E "(real|user|sys|RTF)"
done
预期结果参考
选择建议
# 低配设备(4核以下) ./voice_ai_chat models/ggml-base.bin --mode fast --threads 4 # 标准配置(8核,推荐) ./voice_ai_chat models/ggml-small.bin --mode balanced --threads 8 # 高配设备(追求质量) ./voice_ai_chat models/ggml-medium.bin --mode quality --threads 16
5.5 实验4:VAD 参数调优
目的
找到适合你麦克风和环境的 VAD 参数。
测试命令
# 测试1:高灵敏度(适合安静环境、远场语音) ./applications/bin/voice_ai_chat \ ../../../models/ggml-small.bin \ --mode fast \ --vad-threshold 0.03 \ --poll-interval-ms 150 # 测试2:默认灵敏度 default: --vad-threshold 0.06 --poll-interval-ms 250 # 测试3:低灵敏度(适合嘈杂环境、近场语音) ./applications/bin/voice_ai_chat \ ../../../models/ggml-small.bin \ --mode fast \ --vad-threshold 0.12 \ --poll-interval-ms 300
调优指南
观察指标
# 好的 VAD 表现 [VAD-DEBUG] max_prob=0.921, speech=yes # 说话时有高概率 [ENDPOINT] speech started # 快速检测到开始 ... [VAD-DEBUG] max_prob=0.012, speech=no # 结束后低概率 [ENDPOINT] speech ended # 准确检测结束 # 不好的 VAD 表现(需要调参) [VAD-DEBUG] max_prob=0.051, speech=no # 说话但检测为静音(阈值太高) [VAD-DEBUG] max_prob=0.211, speech=yes # 环境噪音误触发(阈值太低)
5.6 实验5:模式参数对比
目的
理解 fast、balanced、quality 三种模式的区别。
测试方法
# 准备一段测试音频(建议 5-10 秒中文) # 或者使用相同的一句话重复测试 # 测试 fast 模式 echo "=== Fast Mode ===" time ./voice_ai_chat models/ggml-small.bin --mode fast \ --threads 8 2>&1 | grep -E "(real|TEXT)" # 测试 balanced 模式 echo "=== Balanced Mode ===" time ./voice_ai_chat models/ggml-small.bin --mode balanced \ --threads 8 2>&1 | grep -E "(real|TEXT)" # 测试 quality 模式 echo "=== Quality Mode ===" time ./voice_ai_chat models/ggml-small.bin --mode quality \ --threads 8 2>&1 | grep -E "(real|TEXT)"
参数对比实验
# 实验:max-segment-ms 对延迟的影响
for ms in 1600 2200 3000; do
echo "max-segment-ms = $ms"
time ./voice_ai_chat models/ggml-small.bin \
--mode balanced \
--max-segment-ms $ms \
--threads 8
done
结果分析
5.7 实验6:文本优化功能
目的
测试热词、词库、替换规则对识别准确率的提升。
实验6.1:热词提示词
# 1. 创建热词文件 cat > hotwords_tech.txt << EOF 实时性 延迟 吞吐 模型推理 量化 神经网络 EOF # 2. 使用热词运行 ./voice_ai_chat models/ggml-small.bin \ --mode balanced \ --hotwords-file hotwords_tech.txt # 3. 测试:说出这些技术术语,观察识别准确率
实验6.2:术语替换
# 1. 创建替换规则文件 cat > replacements_tech.txt << EOF 实实性 => 实时性 推力 => 推理 模形 => 模型 量化 => 量化 EOF # 2. 使用替换规则运行 ./voice_ai_chat models/ggml-small.bin \ --mode balanced \ --replacements-file replacements_tech.txt # 3. 测试:故意说错词,观察是否被纠正
实验6.3:词库纠错
# 1. 创建词库文件 cat > lexicon_office.txt << EOF # 标准词 <TAB> 别名1,别名2,... 带饭 带翻,代饭 报销 报消 工牌 工牌子 邮箱 油香 EOF # 2. 使用词库运行 ./voice_ai_chat models/ggml-small.bin \ --mode balanced \ --lexicon-file lexicon_office.txt # 3. 测试:说出"带翻",观察输出是否为"带饭"
5.8 实验7:调试与日志
目的
学习使用日志排查问题。
开启详细日志
# debug 级别会显示所有内部状态 ./voice_ai_chat models/ggml-small.bin \ --mode balanced \ --log-level debug \ 2>&1 | tee voice_ai_debug.log
关键日志解读
# 过滤关键日志 # 1. VAD 检测 grep "VAD-DEBUG" voice_ai_debug.log # 2. 端点事件 grep "ENDPOINT" voice_ai_debug.log # 3. 队列操作 grep "QUEUE" voice_ai_debug.log # 4. ASR 处理 grep "ASR-WORKER" voice_ai_debug.log # 5. 最终文本 grep "TEXT" voice_ai_debug.log
常见问题日志模式
问题1:VAD 检测不到语音
# 现象:没有 speech started [VAD-DEBUG] max_prob=0.021, segments=0, speech=no # 解决:降低阈值 --vad-threshold 0.03
问题2:队列堆积
[QUEUE] enqueue utterance #10 ... [QUEUE] skip utterance: too short ... # 或 [QUEUE] skip utterance: empty audio range # 解决:检查音频设备,或降低 max-segment-ms
问题3:LLM 队列满
[warn] final 文本队列已满,丢弃最旧的一条 # 解决:增加 --llm-queue-size,或检查网络
5.9 综合实验:配置你的最佳参数
实验目标
通过系统测试,找到适合你的硬件和场景的最佳参数组合。
测试矩阵
#!/bin/bash
# optimization.sh - 参数优化测试
MODELS=("base" "small")
MODES=("fast" "balanced")
THRESHOLDS=(0.04 0.06 0.08)
for model in "${MODELS[@]}"; do
for mode in "${MODES[@]}"; do
for threshold in "${THRESHOLDS[@]}"; do
echo ""
echo "=========================================="
echo "Model: $model, Mode: $mode, Threshold: $threshold"
echo "=========================================="
# 运行测试(建议录制一段标准测试语音)
timeout 30 ./voice_ai_chat \
models/ggml-$model.bin \
--mode $mode \
--vad-threshold $threshold \
--threads 8 \
--log-level info
done
done
done
评估维度
第6章 拓展与优化方向
6.1 当前架构的局限性
已知限制
6.2 阶段 2 优化方向
6.2.1 双模型策略 (Dual Model)
目标:Partial 用轻量模型快出字,Final 用大模型保质量。
当前(单模型) 目标(双模型)
───────────── ─────────────
┌──────────┐ ┌──────────┐
│ small │──▶ Partial │ tiny │──▶ Partial (快速预览)
│ │──▶ Final └──────────┘
└──────────┘ ┌──────────┐
│ small │──▶ Final (准确结果)
└──────────┘
实现思路:
class DualAsrWorker {
// tiny 模型用于 partial(速度优先)
std::unique_ptr<WhisperModel> partial_model_;
// small/medium 用于 final(质量优先)
std::unique_ptr<WhisperModel> final_model_;
void ProcessPartial(const AudioSegment& audio) {
// 使用 tiny,参数激进
auto result = partial_model_->Infer(audio, fast_params);
callback_(result.text, /*is_final=*/false);
}
void ProcessFinal(const AudioSegment& audio) {
// 使用 small,参数保守
auto result = final_model_->Infer(audio, quality_params);
callback_(result.text, /*is_final=*/true);
}
};
预期收益:
- Partial RTF < 0.2x(真正做到边说边出字)
- Final 准确率保持不变
6.2.2 TTS (文本转语音) 回播
目标:AI 回复用语音播放,实现完整语音对话。
用户语音 ──▶ ASR ──▶ LLM ──▶ TTS ──▶ 扬声器
(新增)
技术选型:
Piper 集成示例:
class TTSPlayer {
public:
bool Init(const std::string& model_path);
// 流式播放(边生成边播放)
void StreamSpeak(const std::string& text,
std::function<void()> on_complete);
// 中断播放
void Stop();
private:
piper::PiperModel model_;
SDL_AudioDeviceID audio_device_;
std::thread play_thread_;
};
// 在 VoiceAIChat 中集成
void VoiceAIChat::ProcessWithAI(const std::string& user_text) {
// ... 调用 LLM ...
// 新增:流式 TTS
tts_player_.StreamSpeak(assistant_reply, []() {
LOG_INFO << "TTS 播放完成";
});
}
6.2.3 Barge-in (打断检测)
目标:用户可以在 AI 说话期间打断,插入新指令。
用户说话 AI 回复 用户打断 AI 新回复
│ │ │ │
▼ ▼ ▼ ▼
──────▶ ─────────▶ XXXXX▶ ─────────▶
ASR TTS播放 VAD检测 新回复
(打断)
实现方案:
class VoiceAIChat {
enum class State {
kListening, // 听用户说
kProcessing, // 处理中(ASR/LLM)
kSpeaking, // AI 正在 TTS 回复
};
std::atomic<State> state_{State::kListening};
void HandleFinalTranscript(const std::string& text) {
if (state_.load() == State::kSpeaking) {
// 用户在 AI 说话时插话
LOG_INFO << "Barge-in detected!";
// 1. 停止当前 TTS
tts_player_.Stop();
// 2. 可选:标记当前 LLM 响应为无效
InterruptCurrentResponse();
}
// 正常处理新输入
EnqueueFinalText(text);
}
};
关键逻辑:
- 说话时保持 VAD 运行
- 检测到新语音时中断当前流程
- 处理打断后的状态恢复
6.2.4 本地 LLM 集成
目标:完全离线运行,不依赖网络和 API。
云端方案 本地方案
──────── ────────
本地 ASR ──▶ 网络 ──▶ Kimi 本地 ASR ──▶ 本地 LLM
(llama.cpp)
llama.cpp 集成:
class LocalLLM {
public:
bool Init(const std::string& model_path);
// 流式生成
void StreamGenerate(const std::string& prompt,
std::function<void(const std::string&)> on_token);
private:
llama_model* model_;
llama_context* ctx_;
};
// 在 VoiceAIChat 中使用
class VoiceAIChat {
std::variant<ai::Client, LocalLLM> llm_; // 云端或本地
void ProcessWithAI(const std::string& user_text) {
std::visit(overloaded{
[&](ai::Client& client) { /* 调用 Kimi */ },
[&](LocalLLM& local) { /* 调用 llama.cpp */ }
}, llm_);
}
};
模型推荐:
6.2.5 OpenCC 繁转简
目标:彻底解决繁体字问题。
#include <opencc.h>
class TextProcessor {
public:
bool Init() {
converter_ = opencc::SimpleConverter("tw2s.json"); // 台湾繁体转简体
return converter_.IsValid();
}
std::string ConvertToSimplified(const std::string& text) {
return converter_.Convert(text);
}
private:
opencc::SimpleConverter converter_;
};
// 在 ASR 输出后调用
std::string raw_text = whisper_get_segment_text(...);
std::string simplified = text_processor_.ConvertToSimplified(raw_text);
6.3 架构演进路线图
当前版本 (Phase 1)
┌─────────────────────────────────────┐ │ ASR (Whisper small) ──▶ Kimi API │ │ • Final 触发 │ │ • 文本输出 │ └─────────────────────────────────────┘
近期版本 (Phase 2)
┌─────────────────────────────────────┐ │ ASR (tiny/small 双模型) │ │ ├──▶ Partial (tiny, 快速预览) │ │ └──▶ Final (small, 准确识别) ──▶ │ │ │ │ LLM (Kimi) ──▶ TTS (Piper) │ │ │ │ Barge-in 打断支持 │ └─────────────────────────────────────┘
远期版本 (Phase 3)
┌─────────────────────────────────────┐ │ ASR (双模型 + OpenCC) │ │ ├──▶ Partial │ │ └──▶ Final ──▶ 本地意图识别 │ │ ├──▶ 本地任务 │ │ └──▶ 需 LLM ──▶ │ │ │ │ │ ┌────────────────────────────┐ │ │ │ LLM Router │ │ │ ├──▶ 本地 LLM (llama.cpp) │ │ │ └──▶ 云端 LLM (Kimi) │ │ │ └──▶ TTS │ │ └─────────────────────────────────────┘
6.4 生产环境部署建议
容器化部署
# Dockerfile
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
libsdl2-dev \
pkg-config \
build-essential \
cmake
COPY . /app
WORKDIR /app
RUN mkdir build && cd build && \
cmake .. && \
make -j$(nproc) voice_ai_chat
ENV MOONSHOT_API_KEY=${MOONSHOT_API_KEY}
ENV WHISPER_MODEL_PATH=/app/models/ggml-small.bin
CMD ["./applications/bin/voice_ai_chat", "/app/models/ggml-small.bin"]
系统服务配置
# /etc/systemd/system/voice-ai-chat.service [Unit] Description=Voice AI Chat Service After=network.target [Service] Type=simple User=voiceai Environment="MOONSHOT_API_KEY=your-key" Environment="WHISPER_MODEL_PATH=/opt/models/ggml-small.bin" WorkingDirectory=/opt/voice-ai-chat ExecStart=/opt/voice-ai-chat/applications/bin/voice_ai_chat Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target
监控指标
// 建议添加的监控指标
struct Metrics {
// ASR 指标
std::atomic<uint64_t> asr_requests_total;
std::atomic<uint64_t> asr_latency_ms_sum;
std::atomic<uint64_t> asr_errors_total;
// LLM 指标
std::atomic<uint64_t> llm_requests_total;
std::atomic<uint64_t> llm_latency_ms_sum;
std::atomic<uint64_t> llm_tokens_generated;
// 队列指标
std::atomic<size_t> queue_size_current;
std::atomic<uint64_t> queue_drops_total;
};
6.5 学习资源推荐
Whisper.cpp 相关
- whisper.cpp GitHub
- ggml 张量库文档
- Whisper 论文
TTS 相关
- Piper TTS
- ChatTTS
- Bert-VITS2
LLM 本地部署
- llama.cpp
- Ollama(更简单的本地 LLM 管理)
- vLLM(高性能推理)
C++ 并发编程
- 《C++ Concurrency in Action》(Anthony Williams)
- cppreference.com 的并发部分