纯本地运行的“赛博女友“:基于 Qwen3 + Whisper + Realtime API 的端到端语音对话系统
前言
随着 Qwen3 系列模型和 Realtime API 协议的开源,本地搭建一个低延迟、全离线的实时语音 AI 伴侣已经不再是难事。本文介绍一个名为 cybergirl-bundle(赛博女友整合包) 的开源项目,它把 LLM 推理、语音识别、语音合成、网页前端全部打包成一个双击即用的整合包,目标用户是希望在自己电脑上跑一个"会说话的 AI 伴侣"的开发者。
项目核心特点:
- 全离线:部署完成后断网可用,所有模型在本地推理
- 双击启动:一个
.bat拉起三个服务并自动打开浏览器 - OpenAI Realtime API 协议:浏览器通过 WebSocket 直连语音服务
- GPU 加速:llama.cpp + CUDA 12.4 + PyTorch cu124
- 可分发:整合包内置 uv、Python、ffmpeg,接收方无需预装任何开发环境

一、系统架构
整个系统由三个独立服务组成,通过 HTTP 和 WebSocket 串联:
┌──────────────┐ HTTP /v1 ┌──────────────────┐ WebSocket ┌──────────────┐
│ llama-server│ ◄──────────► │ speech-to-speech │ ◄─────────► │ Browser │
│ (LLM 大脑) │ OpenAI API │ (语音中枢) │ Realtime │ (呼吸球 UI) │
│ 端口 8080 │ │ 端口 8765 │ API │ 端口 7860 │
└──────────────┘ └──────────────────┘ └──────────────┘
Qwen3-4B GGUF Whisper + Qwen3-TTS Web Audio API

1.1 服务①:LLM 推理(llama.cpp)
使用 llama.cpp 的 llama-server.exe 加载 Qwen3-4B-Instruct-2507 的 Q4_K_M 量化版本,对外暴露 OpenAI 兼容的 /v1 接口。关键启动参数:
llama-server.exe -m %MODEL_PATH% -c 8192 -fa on --port 8080 -ngl 99
| 参数 | 含义 |
|---|---|
-c 8192 |
上下文窗口,显存吃紧可降到 4096 |
-fa on |
Flash Attention 加速 |
-ngl 99 |
全部层 offload 到 GPU |
显存充裕(12GB+)时可切换到 Qwen3-30B-A3B-Instruct,MoE 架构在消费级显卡上也有可接受的速度。
1.2 服务②:语音中枢(speech-to-speech)
这是整个系统的核心,基于 speech-to-speech 项目,在一个进程内串联了三件事:
- STT:
openai/whisper-large-v3,中文识别 - LLM 转发:通过
responses-api后端把请求转发给本地 llama-server - TTS:
Qwen3-TTS-1.7B,支持多音色和参考音频克隆
对外暴露一个 WebSocket 端点 ws://localhost:8765/v1/realtime,实现了 OpenAI Realtime API 协议,前端可以像调用 OpenAI 官方 Realtime 接口一样使用它。关键启动参数:
speech-to-speech.exe ^
--mode realtime ^
--stt whisper --stt_model_name openai/whisper-large-v3 ^
--language zh ^
--llm_backend responses-api ^
--model_name qwen3-4b ^
--responses_api_base_url http://127.0.0.1:8080/v1 ^
--responses_api_stream ^
--tts qwen3 --qwen3_tts_backend torch ^
--qwen3_tts_speaker vivian ^
--qwen3_tts_language zh ^
--enable_live_transcription
⚠️ Windows 上必须加
--qwen3_tts_backend torch,因为默认的 ggml 后端依赖 C++ 包,在 Windows 上经常缺失。
1.3 服务③:Web 网页(FastAPI + 静态前端)
[server.py](file:///h:/赛博女友/cybergirl-bundle/web/server.py) 只是一个极简的 FastAPI 静态文件载体,真正的逻辑全在 [index.html](file:///h:/赛博女友/cybergirl-bundle/web/static/index.html) 里。浏览器加载页面后,直接通过 WebSocket 连到 s2s 服务,中间不经过任何代理:
app = FastAPI(title="赛博女友", docs_url=None, redoc_url=None)
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
@app.get("/", response_class=HTMLResponse)
async def index():
return FileResponse(STATIC_DIR / "index.html", media_type="text/html; charset=utf-8")
这种"前端直连 Realtime API"的设计让音频流无需经过 Web 服务转发,延迟更低。
二、核心技术实现
2.1 OpenAI Realtime API 协议落地
前端连接 WebSocket 后,第一件事是发送 session.update 配置会话:
sendJson({
type: "session.update",
session: {
type: "realtime", // 必填,否则被拒绝
instructions: settings.instructions, // 系统提示词
audio: {
input: {
transcription: { model: "whisper-large-v3" },
turn_detection: {
type: "server_vad",
threshold: 0.2, // VAD 灵敏度
prefix_padding_ms: 1000, // 语音前填充
silence_duration_ms: 1000, // 静音判定时长
},
},
output: { voice: settings.voice }, // 实时切换音色
},
},
});
几个踩坑点值得注意:
type: "realtime"字段必填:缺少这个字段请求会被直接拒绝voice字段支持运行时切换:通过再次发送session.update即可热切换音色,无需重连- VAD 参数调优:
threshold=0.2+silence_duration_ms=1000是为了能捕获"拜拜"这种短语音,默认参数会截断到 480ms 导致 whisper 报 “too few tokens”
2.2 Web Audio API 流式播放
TTS 返回的是 PCM 音频块(audio.delta 事件,base64 编码),前端需要用 Web Audio API 实时播放。这里有几个容易踩的坑:
坑1:AudioBufferSourceNode 被 GC 回收
如果创建 AudioBufferSourceNode 后立即 start() 而不保留引用,浏览器可能在播放前就把它当垃圾回收了,表现为长回复播到一半就停。解决方案是维护一个 activeSources 数组,播放结束才释放:
const activeSources = [];
src.onended = () => {
const i = activeSources.indexOf(src);
if (i >= 0) activeSources.splice(i, 1);
};
activeSources.push(src);
src.start(nextPlayTime);
坑2:AudioContext 被浏览器挂起
当标签页切到后台,浏览器会自动 suspend AudioContext,导致 currentTime 停止增长,预调度循环空转。需要在 pump 循环里检测并恢复:
if (playCtx.state !== 'running') {
dbg("WARN", "⚠ playCtx state=" + playCtx.state);
playCtx.resume();
return; // 下一轮再调度
}
坑3:预调度过早导致缓冲被丢弃
浏览器会丢弃调度时间过远的 buffer。需要限制 nextPlayTime - currentTime ≤ PRE_SCHEDULE_S(默认 1 秒)才创建 source,用 pendingChunks 队列 + setTimeout 延迟调度。
2.3 半双工防回环
全双工模式下,AI 说话的声音会被麦克风回采,触发 VAD 误判形成"自言自语"回环。项目的解决方案是半双工静音:
case "response.created":
// AI 开始生成,立即静音麦克风上行
micMutedByHalfDup = true;
break;
case "response.done":
// 不能立即解除,否则 TTS 尾音还会触发 VAD
setTimeout(() => { micMutedByHalfDup = false; }, 1500);
break;
2.4 短语音识别优化
中文里"拜拜"“嗯”"好"这种极短词很容易被 VAD 截断成 480ms 的片段,whisper 会报 token 不足。优化手段:
- VAD 参数放宽:
silence_duration_ms=1000、prefix_padding_ms=1000 - 短音频补静音到 2 秒(前后各补一半)
- whisper 强制
max_new_tokens=30、min_new_tokens=4
三、整合包工程化
这个项目最值得借鉴的是它的整合包工程化思路——让一个依赖 CUDA、PyTorch、多个大模型的应用可以像绿色软件一样分发。
3.1 uv 包管理器 + 内嵌 Python
整个项目用 uv 管理 Python 环境,并把 Python 解释器缓存到项目目录内:
set UV_PYTHON_INSTALL_DIR=%~dp0python-cache
接收方电脑不需要预装 Python,首次运行 首次部署.bat 时 uv 会自动从缓存安装。
3.2 CUDA 版 PyTorch 强制锁定
PyPI 默认的 torch 是 CPU 版,直接装会导致推理慢死。通过 pyproject.toml 强制指定 CUDA 12.4 源:
[[tool.uv.index]]
name = "pytorch-cu124"
url = "https://download.pytorch.org/whl/cu124"
explicit = true
[tool.uv.sources]
torch = { index = "pytorch-cu124" }
torchaudio = { index = "pytorch-cu124" }
3.3 离线模式
启动脚本里设置两个环境变量,强制使用本地缓存模型,避免运行时尝试联网:
set HF_HUB_OFFLINE=1
set TRANSFORMERS_OFFLINE=1
3.4 端口就绪检测
三个服务有启动顺序依赖,用 PowerShell 脚本轮询端口而不是死等:
# wait-port.ps1
while ($elapsed -lt $Timeout) {
try {
Invoke-WebRequest "http://127.0.0.1:$Port/health" -TimeoutSec 2 -ErrorAction Stop
Write-Host "$Name ready"; exit 0
} catch { Start-Sleep -Seconds 2 }
}
3.5 模型分发策略
国内从 HuggingFace 下载极慢,项目改用魔搭社区(ModelScope):
| 模型 | 用途 | 体积 |
|---|---|---|
| Qwen3-4B-Instruct-2507-GGUF | LLM 大脑 | ~2.5GB |
| Qwen3-30B-A3B-Instruct-2507-GGUF | LLM 大脑(进阶) | ~18GB |
| whisper-large-v3 | 语音识别 | ~3GB |
| Qwen3-TTS-1.7B | 语音合成 | ~3.4GB |
whisper 和 TTS 模型预置在 s2s\.hf-cache\ 随包分发,LLM 模型因体积大由用户手动下载。
四、TTS 音色与声音克隆
Qwen3-TTS-1.7B 支持 CustomVoice 模式,内置多个音色:
| 音色 | 性别 | 备注 |
|---|---|---|
vivian |
女 | 默认 |
serena / ono_anna |
女 | — |
aiden / ryan |
男 | — |
进阶用法是参考音频克隆,通过 --qwen3_tts_ref_audio 和 --qwen3_tts_ref_text 指定一段参考音频,TTS 会模仿其音色合成。这个参数与 --qwen3_tts_speaker 互斥。
项目还支持运行时通过
session.update的audio.output.voice字段实时切换音色,无需重启服务。
五、资源占用实测
在 RTX 3090 24GB + 32GB 内存的真实环境下的实测数据:
| 组件 | 显存 | 内存 |
|---|---|---|
| llama-server(Qwen3-4B,-c 32768) | ~7.4GB | — |
| speech-to-speech(whisper + TTS + VAD) | — | ~7.8GB |
| 系统 + 浏览器 | — | ~1.3GB |
| 合计 | ~7.4GB | ~16.5GB |
LLM 的内存占用大头是 KV cache,
-c 32768会吃掉约 4.8GB。降到-c 8192可省 3GB。
8GB 显存的显卡可以跑 Qwen3-4B + 8192 上下文;12GB 显存可以尝试 30B-A3B。
六、部署流程速览
接收方(最终用户)只需三步:
- 解压整合包到任意目录(不要放 OneDrive 同步目录)
- 双击
首次部署.bat(下载 PyTorch 约 2.5GB,耗时 5-15 分钟) - 双击
启动赛博女友.bat,浏览器自动打开
硬件要求:
| 项目 | 最低 | 推荐 |
|---|---|---|
| 显卡 | NVIDIA 8GB 显存 | RTX 30/40 系 12GB+ |
| 内存 | 16GB | 32GB |
| 硬盘 | 25GB | SSD 30GB+ |
仅支持 NVIDIA 显卡,AMD/Intel 显卡无法运行。
七、踩过的坑与解决方案
整理几个实际开发中遇到的关键问题:
| 现象 | 原因 | 解决 |
|---|---|---|
| torch 报 CUDA 不可用 | 装成 CPU 版 | pyproject.toml 锁定 cu124 源 |
| 网页点球没反应 | WS 地址填了 127.0.0.1 |
必须填 localhost:8765 |
| 长回复播到一半停 | AudioBufferSourceNode 被 GC | 维护 activeSources 数组 |
| 切后台后音频停 | AudioContext 被 suspend | pump 循环检测并 resume() |
| "拜拜"识别失败 | VAD 截断太短 | 放宽 VAD 参数 + 短音频补静音 |
| AI 自言自语回环 | 扬声器回采触发 VAD | 半双工静音 + 延迟解除 |
| .bat 中文乱码 | 编码问题 | 必须用 GBK 编码保存 |
| Windows 拒绝运行 bat | 无数字签名 | “更多信息"→"仍要运行” |
八、总结
这个项目展示了一个完整的本地实时语音 AI 工程化方案,几个值得学习的设计:
- 协议选型:直接复用 OpenAI Realtime API,前端可以无缝替换后端
- 整合包思路:uv + 内嵌 Python + 预置依赖,让 CUDA 应用也能像绿色软件分发
- Web Audio API 实战:流式 PCM 播放的 GC、suspend、预调度问题及解法
- 半双工防回环:消费级麦克风场景下的实用方案
适合想要学习端到端语音 AI 系统、或者想在自己电脑上跑一个离线 AI 伴侣的开发者参考。整个系统模块清晰,STT/LLM/TTS 三个组件都可以单独替换,扩展性强。
分享通用链接 https://my.feishu.cn/wiki/J8yCwIDSGifI4jkIg9Yca7Xynuc?from=from_copylink
提取码: rbdb2u
需要的自取
更多推荐

所有评论(0)