Voicebox 语音 I/O 全栈设计解析:本地语音输入输出管道的架构决策与实现落地
本篇技术文章基于 Voicebox 仓库中的规划文档 docs/plans/VOICE_IO.md,系统讲解 Voicebox 如何从「AI 声音克隆工作室」升级为「本地语音 I/O 层」:涵盖后端三类新抽象(STT 引擎注册表、LLMBackend 协议、流式转写传输)、Source → Transform → Sink 管道模型,以及 Rust 原生层的全局热键、焦点探测、模拟粘贴与剪贴板保护实现。读完你可以掌握 Voicebox 语音输入侧的完整架构脉络、八阶段落地路线,以及当前仓库中已经落地的关键源码证据。
1. 定位转变:从「克隆」到「语音 I/O」
Voicebox 原本提供语音 I/O 回路中的输出半边:克隆声音、生成语音、施加效果、编排多角色项目。输入半边——语音转文字(STT)、听写、路由——在规划之前只有一个 Whisper 模型接在 Recording & Transcription 面板里。VOICE_IO.md 的核心提案是:把语音输入提升为一等公民,增加更多 STT 引擎、一个听写 shell(全局热键、录音、粘贴、流式)、本地 LLM 后端,以及一条用户可配置的、从「捕获的音频」到「用户想做的任何事」的管道。
规划文档给出的定位转变非常明确:
- 之前:Voicebox 是「the open-source AI voice cloning studio」(开源 AI 声音克隆工作室),克隆是核心卖点;
- 之后:Voicebox 是「the open-source AI voice studio」(开源 AI 语音工作室),能力横跨 输入(STT、听写)、智能(本地 LLM、精炼、persona)、输出(TTS、克隆、效果、Stories)与路由。"cloning" 从顶层描述中退位为普通特性。
文档还给出了一个竞争框架(引自规划文档的表述):Voicebox 覆盖了两家云端厂商的领地——ElevenLabs(语音克隆与 TTS,"agents speak" 一侧)与 WisprFlow(面向效率用户与 Agent 的语音听写,"users talk" 一侧)。两者都是纯云端方案,而 Voicebox 的差异化在于同一个应用内完成 dictation → LLM → TTS(克隆音色)的闭环,且共享同一个模型目录。
为什么是现在(Why now)
规划文档列出了五条时机判断,值得作为背景理解:
- 跨平台本地听写是空档品类:Superwhisper、MacWhisper、Aiko 等工具仅限 macOS,WisprFlow 与 Willow 是云端方案;Windows 安装基座是本地听写产品的差异化楔子;
STTBackend协议已存在:多引擎注册表模式随 TTS 一起交付,添加 Parakeet v3、Qwen3-ASR 只需数天而非数周的后端工作量;- persona 回路是独占能力:对着 Agent 说话、让它用克隆声音回复——拥有听写产品的人没有 TTS,拥有 TTS 产品的人没有好用的听写;
- Agent 生态闭环:Agent harness 已经在用 Voicebox 的 TTS,同一应用再提供 STT 就能补全回路;
- 内部最直接的痛点:手打 2000 字的 TTS 脚本文本非常反人性,直接对着 Voicebox 自己的生成表单听写,是 dogfood 整条 STT 管道的最佳场景,且完全不依赖任何 OS 级 API;
- 此外,端到端语音 LLM(Moshi、GLM-4-Voice、Qwen2.5 Omni、Mini-Omni、Sesame CSM 等,音频进、音频出)正在落地——今天构建的管道就是它们明天的插接框架。
明确的非目标
为避免范围蔓延,文档划定了五条非目标,理解它们有助于把握架构边界:
- 不做云端兜底、不支持「自带 API key」的 STT/LLM——本地即产品;
- 不做独立的托盘听写应用——扩展 Voicebox 而非分叉;
- 不用笔记式布局替换 Stories 编辑器——长录音捕获是管道之上的预设,不是新产品面;
- 不做实时翻译 UI——它可以以后作为一个 transform 存在,但不在本计划内;
- 不做完整的 Agent 编排——Voicebox 提供「语音轨道」,Agent 本身住在别处,通过开发者 API 与之对话。
2. 路线图与当前进展
VOICE_IO.md 以八阶段(Phase 1–8)组织实施路径,并维护了一张状态清单(文档标注最后评审日期 2026-04-21):
| 阶段 | 主题 | 文档记录状态 |
|---|---|---|
| Phase 1 | 基础设施(Audio tab 移入 Settings、预留侧边栏位) | 已完成 |
| Phase 2 | 本地 LLM 后端(LLMBackend 协议 + Qwen3) |
已完成 |
| Phase 3 | 应用内语音输入(CapturesTab 端到端听写) |
部分完成 |
| Phase 4 | Captures 标签页(列表/详情、重转写、精炼、Play-as-voice) | 已完成 |
| Phase 5 | Agent 语音输出 + persona 回路 | 未开始* |
| Phase 6 | STT 引擎扩展(Parakeet v3、Qwen3-ASR、Kyutai) | 未开始 |
| Phase 7 | 外部听写 shell(热键、焦点、粘贴) | macOS 完成,Windows/Linux 待做 |
| Phase 8 | 管道路由、sink、长录音 | 未开始 |
* 需要说明:从当前仓库的文件结构看,backend/routes/speak.py、backend/mcp_server/tools.py、backend/services/personality.py 与 tauri/src-tauri/src/speak_monitor.rs 等文件已经存在,可以推断 Phase 5 相关工作在文档记录之后已推进了相当部分,具体能力以这些源码的当前内容为准。
已完成部分的源码级印证
Phase 2(本地 LLM 后端) 的落地可以从三处代码确认:
- backend/backends/init.py 定义了与
TTSBackend/STTBackend并列的LLMBackendProtocol,并维护LLM_ENGINES = {"qwen_llm": "Qwen3 LLM"}引擎注册表; - backend/backends/qwen_llm_backend.py 提供
PyTorchQwenLLMBackend(transformersAutoModelForCausalLM)与MLXQwenLLMBackend(mlx-lm,4-bit 社区量化)两条实现路径; - backend/routes/llm.py 暴露
POST /llm/generate:模型未缓存时返回 202 并触发后台下载任务,缓存命中时执行单轮对话补全。
Phase 7 macOS 半(热键 + 粘贴)的核心模块在仓库中均可直接定位:
- tauri/src-tauri/src/hotkey_monitor.rs:全局热键 → 听写效果桥接(注意:文档写作时该部分被命名为
chord_engine.rs纯状态机 +rdev监听器,当前代码已演进为外部keytapcrate 的ChordMatcher承担事件 tap 与和弦状态机,HotkeyMonitor只剩「绑定 → 事件 → Tauri 事件」的适配职责); - tauri/src-tauri/src/clipboard.rs:
save_clipboard/write_text/restore_clipboard/current_change_count四个 API,实现多类型剪贴板快照与恢复; - tauri/src-tauri/src/focus_capture.rs:基于
AXUIElementCreateSystemWide的焦点元素快照(PID + bundle id + AX role); - tauri/src-tauri/src/synthetic_keys.rs:通过
CGEventPost在 HID tap 上发送完整四事件 ⌘V 序列(Cmd down → V down 带 flag → V up 带 flag → Cmd up); - tauri/src-tauri/src/accessibility.rs:
AXIsProcessTrusted权限闸门; - 前端对应物是 app/src/components/DictateWindow/DictateWindow.tsx——一个透明、置顶、无边框的 420×64 webview,在应用启动时预创建并隐藏,和弦开始时显示、捕获周期完成时隐藏,错误态药丸自动消失且点击可复制到剪贴板。
Phase 3 与 4 的前端载体分别是 app/src/lib/hooks/useCaptureRecordingSession.ts(CapturesTab 与 Phase 7 浮动药丸共同消费的录音会话 hook)、app/src/components/CapturesTab/CapturesTab.tsx 和设置侧的 app/src/components/ServerTab/CapturesPage.tsx。
计划外但已落地的增强
文档还专门列出了从 Phase 3/4/7 工作中自然长出的几项能力:
- 服务端权威设置(Server-authoritative settings):单例
capture_settings与generation_settings表,客户端只上传音频;STT 模型、精炼开关、精炼 LLM 与自动精炼标志全部在服务端解析,避免多 Tauri webview 之间出现陈旧状态。这一点可在 backend/database/models.py 得到印证:capture_settings表包含llm_model列(默认"0.6B"),即 Qwen3 0.6B / 1.7B / 4B 由用户通过该单例选择; - 后端音频归一化:
POST /captures会把 librosa 能解码的任何格式(webm/opus、m4a 等)先转码为 WAV 再交给 Whisper,绕开 mlx-audio 内部 miniaudio 的格式缺口; - 短录音保护:小于 300 ms 的音频块在客户端短路,误触热键不会上传空 webm;
- 精炼提示词重写:采用更强的「反聊天机器人」措辞,并内联覆盖多句保留与自我修正的示例。精炼服务本体在 backend/services/refinement.py。
3. 后端架构:三个新概念
3.1 扩展 STT 注册表
现有 STTBackend 协议(定义于 backend/backends/init.py)当前抽象了 Whisper。规划要追加三个引擎:
- Parakeet v3——25 种语言、速度极快,是非英语本地 STT 的质量领先者,Python 路径走
nemo_toolkit或transformers; - Qwen3-ASR 0.6B int8——50+ 语言、多语种质量最高,跨平台走
transformers; - Kyutai ASR(可选)——流式优先、体积小、CPU 友好,填补「纯 CPU 笔记本」档位。
全部通过 ModelConfig 注册,复用 TTS 已有的下载、缓存与模型管理 UI,零特判。仓库中该注册表模式已经存在:_get_whisper_configs()(backend/backends/init.py)按 base / small / medium / large / turbo 五档注册 Whisper 模型,_is_model_cached 与 model_load_progress(backend/backends/base.py)统一处理 HF 缓存检测、tqdm 进度补丁与任务生命周期——新 STT 引擎只需照此注册即可获得全套下载/缓存/管理 UI。
3.2 LLMBackend 协议与 Qwen3 本地 LLM
这是 TTSBackend / STTBackend 的镜像,初始实现为 Qwen3 0.6B / 1.7B / 4B,跑在与 TTS 相同的 PyTorch + MLX 基础设施上——一个运行时、一个模型缓存、一个显存故事。
协议定义(backend/backends/init.py):
@runtime_checkable
class LLMBackend(Protocol):
async def load_model(self, model_size: str) -> None: ...
async def generate(
self,
prompt: str,
system: Optional[str] = None,
max_tokens: int = DEFAULT_LLM_MAX_TOKENS, # 默认 512
temperature: float = DEFAULT_LLM_TEMPERATURE, # 默认 0.7
model_size: Optional[str] = None,
examples: Optional[list[tuple[str, str]]] = None,
) -> str: ...
def unload_model(self) -> None: ...
def is_loaded(self) -> bool: ...
其中 examples 参数值得注意:它是可选的 (user, assistant) 对列表,作为正式对话轮次前置到会话中——协议注释明确指出,小模型对 system prompt 中内联示例的模式匹配会「原样复读」,而结构化轮次会被当作数据并泛化;精炼服务(refinement)正依赖这一机制。
模型注册采用「按平台选择 HF 仓库」的策略(backend/backends/init.py 的 _get_qwen_llm_configs,与 backend/backends/qwen_llm_backend.py 中的仓库映射一致):
| 模型 | PyTorch 仓库 | MLX 仓库(Apple Silicon) | 大致体积(PyTorch / MLX) |
|---|---|---|---|
| Qwen3 0.6B | Qwen/Qwen3-0.6B |
mlx-community/Qwen3-0.6B-4bit |
~1.4 GB / ~0.4 GB |
| Qwen3 1.7B | Qwen/Qwen3-1.7B |
mlx-community/Qwen3-1.7B-4bit |
~3.5 GB / ~1.1 GB |
| Qwen3 4B | Qwen/Qwen3-4B |
mlx-community/Qwen3-4B-4bit |
~8 GB / ~2.5 GB |
推理侧细节同样可溯源:PyTorch 路径在 temperature > 0 时启用 do_sample 且 top_p 固定 0.9(backend/backends/qwen_llm_backend.py),并通过 apply_chat_template(..., enable_thinking=False) 关闭 Qwen3 的思考模式以压低延迟。
为什么不选 llama.cpp 或 ollama:仓库已经具备完整的依赖面与模型下载 UX,第二套运行时只会碎片化缓存目录和模型状态 UI;如果未来 CPU-only Windows 的延迟成为问题再回头评估。
3.3 流式转写传输
规划在现有 HTTP /transcribe 旁增加 /transcribe/stream WebSocket 端点:音频帧流入、部分转写流出,同一 FastAPI 进程、同一批已加载模型。好处是把听写延迟移出「每请求 JSON 编码」的关键路径,并且后续交付实时部分转写不需要协议变更。注意当前状态(文档明确标注):今天的听写流程仍是一次携带完整音频 blob 的 POST /captures,WebSocket 流式端点属于 Phase 3 未完成项——/transcribe/stream 尚未交付。
4. 管道抽象:Source → Transform → Sink
所有捕获的音频事件走同一种形状:Source → Transforms → Sink(s)。用户配置的预设(preset)将一个 source 绑定到一条 transform 链与一个或多个 sink:
Source Transform Sink
────────────────── ───────────────── ─────────────────
Hold to speak ──┐ STT model Clipboard + paste
Tap to toggle │ Refinement LLM Capture history
Long-form recorder ├──▶ Persona LLM ──▶ File on disk
File drop │ Translation (later) HTTP webhook
API call (WS / HTTP) ──┘ MCP server sink
TTS loopback (persona)
Platform sinks (later)
文档强调:Source → Transform → Sink 是内部的数据流词汇(与 Unix 管道、Apache Beam、Kafka 同形),不是用户可见语言;UI 将使用 Voicebox 自有措辞(见开放问题 3)。这个形状支撑的具体预设示例:
- Dictation:按住说话 → Parakeet v3 → 轻度精炼 → 剪贴板 + 粘贴 + 历史;
- Code prompt:专用热键 → Whisper Turbo → 技术词汇精炼 → 面向 Claude Code 的 MCP sink;
- Agent voice reply:按住说话 → STT → persona LLM → 克隆音色 TTS → 系统音频输出;
- Long-form capture:双流录音(麦克风 + 系统音)→ 分块 STT → 摘要 LLM → markdown 文件 + 历史。
设计立场是:每一个用户可见功能都坍缩为(source + transform 链 + sinks),会议式捕获不是独立产品,只是一个预设;竞品把集成写死(Trello、Granola),Voicebox 让路由可由用户配置。
5. Rust 原生 shim:听写 shell 的 OS 层
Tauri 无法干净处理的部分被集中到一个平台无关 API 的 Rust 模块集合中(当前仓库以 tauri/src-tauri/src/ 下的独立模块呈现)。主进程持有这些能力,webview 永远看不到平台差异。
5.1 全局热键:keytap ChordMatcher
需求是修饰键-only 的全局热键(例如「按住右 Cmd」「按住 Ctrl」),而 Tauri 的 global-shortcut 插件要求完整组合键。当前实现(tauri/src-tauri/src/hotkey_monitor.rs)有几个值得细看的工程决策:
- 两种语义动作:
ChordAction::PushToTalk(按住录音、松开停止)与ChordAction::ToggleToTalk(按一次开始、再按停止); - 默认绑定刻意区分左右手:macOS 默认右 Cmd + 右 Option(PTT)与加 Space(Toggle),Windows 为右 Ctrl + 右 Shift——保留左手快捷键给 OS 与 App 自身(包括左手的 Cmd+Option+I devtools);
- PTT → Toggle 升级不失声:keytap 在「按住的键集合从短和弦升级为长和弦超集」的同一事件内原子发出
End(PTT) + Start(Toggle)(同一Instant),HotkeyMonitor用 5 ms 的 peek 检测这对事件,并合成为一个Effect::RestartRecording(tauri/src-tauri/src/hotkey_monitor.rs),宿主据此丢弃过渡瞬间的音频并无缝重启会话,而不是把它当成无关的 Stop+Start; - 焦点快照先于窗口操作:
StartRecording时先调用focus_capture::capture_focus()再触碰 DictateWindow,因为窗口 show/重定位理论上可能扰动 keyWindow(tauri/src-tauri/src/hotkey_monitor.rs);随后把focus快照放进dictate:start事件 payload——这正是文档所说「Focus rides thedictate:startevent payload」的实现。
5.2 paste_final_text:一条带防御时序的粘贴链
tauri/src-tauri/src/main.rs 中的 paste_final_text 命令完整实现了文档描述的时序:激活目标进程 → 120 ms settle → 保存剪贴板 → 写入转写文本 → 模拟 ⌘V → 400 ms 等待粘贴消费 → 恢复剪贴板(对应 POST_ACTIVATE_SETTLE_MS / PASTE_CONSUME_MS 两个常量)。源码中还有两处超出文档文字的防御设计:
- 条件恢复:粘贴后比较
clipboard::current_change_count()与写入后的计数,只有在粘贴窗口期内剪贴板未被第三方改动时才执行恢复——避免把用户新复制的内容冲掉;send_paste失败也不影响恢复决策,保证「模拟按键失败」永远不会让用户剪贴板卡在转写文本上; - 跳过规则:焦点 bundle id 是 Voicebox 自身时直接返回
false(应由 step 6 直接注入自有 webview,粘贴只会重复插入或落空);Accessibility 未受信时直接报错,因为CGEventPost会静默丢键、留下被污染的剪贴板却毫无产出。
其余 shim 能力与文档清单一一对应:
- 剪贴板原子保存/恢复(tauri/src-tauri/src/clipboard.rs):macOS 侧快照遍历
pasteboardItems,把每个(uti, bytes)对都复制一份,图片、富文本、文件引用等多类型内容可完整往返;写入转写前保存所有项、粘贴后原子恢复,避免转写覆盖用户进行中的富媒体剪贴板; - 焦点内省(tauri/src-tauri/src/focus_capture.rs):
AXUIElementCreateSystemWide+kAXFocusedUIElement+AXUIElementGetPid,AX 属性键的 CFString 在运行时构造(它们是 CFSTR 宏而非可链接符号),并用NSRunningApplication.activateWithOptions:重新激活目标应用; - 首次运行提示:文档标注「带系统设置深链的首次运行 Accessibility 提示 UI」仍是未完成项;
5.3 目标感知的投递(Target-aware delivery)
粘贴 sink 是一个带分支行为的单一 sink 类型,而不是四个独立 sink:
| 目标 | 投递策略 |
|---|---|
| Voicebox 内部焦点文本域 | 通过事件做直接 React 状态更新,完全不经过剪贴板 |
| 其他应用中焦点文本域 | 经 Accessibility 校验的粘贴:存剪贴板 → 写转写 → 模拟粘贴 → 恢复剪贴板 |
| 未检测到文本焦点 | 仅写剪贴板 + toast 通知("Transcript copied — no text field focused") |
| 平台特殊场景(终端、特定编辑器) | 通用路径行为异常时按 App 覆盖 |
设计动机是明确的:「盲目粘贴、只是碰巧在文本域聚焦时才生效」是容易的错误默认;应有意识地根据焦点元素 role 决定走直接注入、剪贴板+粘贴还是仅剪贴板兜底。
6. 职责分层:Model 在 Python,OS 在 Rust,配置在 React
| 关注点 | 所在层 |
|---|---|
| STT / LLM / TTS 推理 | Python 后端 |
| 模型下载、进度、缓存 | Python 后端 |
| 管道 runner(编排 transform 与 sink) | Python 后端 |
| 麦克风 / 系统音音频捕获 | Rust(Tauri 侧) |
| 音频经 WebSocket 流式送后端 | Rust |
| 全局热键捕获 | Rust(原生 shim) |
| 粘贴模拟、剪贴板保存/恢复 | Rust(原生 shim) |
| 管道预设 UI、捕获历史、设置 | React |
7. 产品面设计
7.1 侧边栏重排与 Captures 标签
原侧边栏为 Generate · Stories · Voices · Effects · Audio · Models · Settings。Audio tab 本质是输出设备与声道路由配置(基础设施而非创作空间),移入 Settings 子标签(ServerSettings/ 已有 Connection、Models、GPU、Update 的子标签模式),腾出的侧边栏位给语音输入。新完整顺序:Generate · Stories · Captures · Voices · Effects · Models · Settings——Captures 位于第 3 位,与 Stories 相邻、紧邻 Voices,形成「输入语音 / 输出语音」的邻接,镜像 Phase 4 "Play as voice" 的心智模型。新标签展示最近捕获(音频 + 转写配对)、活跃预设、听写设置、STT 与 LLM 的模型选择器。
Generate 标签还会配一个首访空状态卡片:无生成历史时渲染、生成过即消失,与 Captures 说明卡平行,传达三条要点——数秒内克隆任何声音、多引擎多语言、Agent-ready(REST + WebSocket API)。
7.2 默认归档
每次捕获把原始音频与最终转写一并保存(模式镜像 data/generations/),可选保留期设置。存储与 UI 模式在 generations 上已存在,此项对 Voicebox 近乎零成本。
7.3 开发者 API 从第一天开始
WebSocket 转写端点是一等公开 API,与 /generate 并排文档化;管道预设可通过 /pipelines/{id}/run 按 ID 寻址,使 Agent harness 与 shell 脚本能调用用户配置的流程;MCP server sink 内建交付,与 Claude Code、Cursor、Cline 的集成是一个复选框而非定制开发。
7.4 Agent 语音输出:speak() 原语
听写是回路的一半(用户说、Agent 听);另一半——Agent 说、用户听——需要一个一等原语而不是被埋进 TTS 回环 sink:
MCP tool: voicebox.speak({ text, profile?, style? })
REST: POST /speak { text, profile_id?, style? }
两条路径都接受可选音色 profile(缺省用用户默认)、可选的交付风格字符串(对支持的引擎),通过系统输出播放音频,并把药丸(pill)切到 speaking 状态。关键设计点:
- 药丸双向:状态从
recording / transcribing / refining / rest扩展到speaking(显示音色名、音色主题色的波形、可见时长)——两个方向共用同一个浮动界面,用户只有一个心智模型; - 可见性强制:静默的后台 TTS 是信任风险,每次 Agent 发起的
speak()都必须浮出药丸,不存在无头「TTS daemon」模式; - 按来源的音色策略:设置里可把特定 MCP 客户端或 API key 绑定到特定音色(Claude Code 用 "Morgan"、Cursor 用 "Scarlett"),不看界面也知道谁在说话;
- 静音 + 速率限制:一键静音所有 Agent 语音,按来源限速防止失控 Agent 长篇独白。
persona 回路是 speak() 的一种用法(STT → LLM → speak(llm_reply));其他用法完全跳过 STT——长任务完成播报、通知、Agent 主动提问。原语刻意比 persona 回路更简单,以便同一条 API 服务两种流程。这也是把「Voicebox 作为机器上每个 Agent 的语音层」从营销话术变成可交付能力的关键:MCP、ACP、A2A 集成都插入这一个原语,无需了解 TTS 模型、GPU 布局或音色 profile。
7.5 捕获与音色样本:不合并,只做单向提升
捕获(capture)与音色样本(voice profile sample)都持有 audio + text,合并的诱惑显而易见——文档的回答是不要,因为元数据与生命周期差异是真实的:
| Capture | Voice profile sample | |
|---|---|---|
| Profile 关联 | 独立 | 绑定单一 profile |
| 文本字段 | 原始转写 + 可选 LLM 精炼版 | 仅精确的 reference_text |
| LLM 精炼 | 经常应用 | 绝不能应用——参考文本必须与音频逐字一致,否则克隆损坏 |
| 数量 | 每天数十条 | 每 profile 约 5 条、半永久 |
| 典型内容 | 用户随便说的 | 常为克隆用的脚本化语句 |
统一表意味着可空的 profile_id、可空的 refined_transcript、可空的 reference_text——一个在不同状态下含义不同的「胖行」,不值得。替代方案是单向 promote 动作(Capture → Sample,零数据模型变动),一个薄端点:
POST /profiles/{id}/samples/from-capture/{capture_id}
读取捕获的音频路径与原始转写,调用既有 add_sample() 服务并预填 reference_text,保存前让用户在对话框里编辑参考文本(转写通常 90% 正确,但克隆要 100%)。捕获在 Captures 标签原样保留——样本是拷贝不是移动。UI 入口是 Send-to 菜单新增的 "Use as voice sample…"(含 "+ New voice" 冷启动选项)。反向(sample → capture)刻意跳过:脚本化样本会污染捕获列表,且用户未必预期自己的样本文本能与真实捕获并排浏览。音频存储去重(内容寻址 data/audio/<sha256>.wav + 引用计数)留待 Phase 8 作为杂项优化——它不是用户可见的,也不是 promote 流程上线的前提。
7.6 语音到语音(Voice-to-voice)就绪度
支撑 persona 回路的 STT → LLM → TTS 链是语音到语音的分阶段近似。真正的端到端语音 LLM 会用单一融合 transform 替换中间三个盒子:音频进、音频出、无文本中介。管道形状天然容纳它——把模型注册为单一 LLMBackend(若协议不同则新增 SpeechLLMBackend),暴露为一种 transform 类型,同样的 sink 不变地继续工作。
8. 八阶段实施路径
v1 原型刻意跳过长期计划中最难的部分(原生 OS shim、全局热键、粘贴注入、新 STT 模型):Phase 1–4 全部是进程内代码,用已有的 Whisper 与既有模型基建,没有 CGEvent tap、没有 SendInput、没有剪贴板时序。以应用内为起点,正是为了绕开听写栈惯常的 OS 层泥潭。
Phase 1 — 基础设施
- Audio tab 移入 Settings 子标签(
ServerSettings/多一个 section); - 为新 Captures 标签预留侧边栏位(命名倾向 Captures);
- Captures 标签挂 feature flag,可合并到
main迭代而不向用户交付半成品。
Phase 2 — 本地 LLM 后端
LLMBackend 协议 + 通过 ModelConfig 注册 Qwen3 0.6B / 1.7B / 4B,复用 HF 下载路径、缓存目录与模型管理 UI。Apple Silicon 走 MLX(4-bit 社区量化),其余平台走 PyTorch(transformers AutoModelForCausalLM),与 TTS 的拆分方式一致。不引入新运行时——无 llama.cpp、无 ollama、无碎片化的模型缓存。
Phase 3 — 应用内语音输入
Voicebox 所有文本输入上的通用麦克风按钮。按住、说话、松开——文本经直接 React 状态更新落入焦点域,不涉及任何 OS API。主打用例:生成表单(听写 2000 字 TTS 脚本而非手打,仅此一条就足以证明功能价值)、音色 profile 描述(用说的来描述音色人格,作为 persona 回路的输入素材)、故事标题 / 预设名等任意自由文本。后端增加 /transcribe/stream WebSocket 端点(音频帧进、部分转写出),复用内存中的 Whisper,可选经 Phase 2 的 LLM 做轻度精炼。
Phase 4 — Captures 标签
从 feature flag 毕业。展示最近捕获(音频 + 转写对),支持回放、换模型重转写、编辑转写、经 LLM 处理输出;归档自动——每次捕获把音频与转写并存。包含 "Play as voice profile" 动作:persona 回路的最简版本,不需要 LLM、不需要新后端端点,只是 Captures 标签上把转写文本发给既有 /generate 端点、用用户选择的音色 profile 播放结果——从 v1 原型起就是类别级差异化(Superwhisper 与 WisprFlow 没有 TTS,做不到;Voicebox 只需一天前端接线)。第一天保持极简:捕获列表、详情视图、模型选择器、Play-as-voice 下拉框;精炼提示词编辑、纠错词典、按来源覆盖都不在此期,等真实需求出现再作为 Tier-2 工作。
Phase 5 — Agent 语音输出 + persona 回路
speak()原语:POST /speak端点 +voicebox.speakMCP 工具,药丸进入speaking状态;设置 UI 覆盖默认音色、按 Agent 绑定音色、全局静音;- Persona 回路:在
speak()上加 LLM 步骤(STT → persona LLM →speak(llm_reply)),音色 profile 获得可选的人格元数据与默认 LLM 行为——克隆身份转换内容而非只是朗读,实现端到端语音到语音。
Phase 4 演示了用户发起方向(Play as voice),本阶段交付 Agent 发起方向——类别定义性能力,也是对 Agent harness 用户最能打动人的卖点。
Phase 6 — STT 引擎扩展
Parakeet v3 与 Qwen3-ASR 注册为额外 STTBackend 实现,可选 Kyutai ASR;多语言覆盖升级到 50+ 种语言;Whisper 保留为合理默认。推迟到这里是因为 v1 阶段 Whisper 已够用且模型选择器 UI 已存在——加几行不改变产品形状。
Phase 7 — 外部听写 shell
原生 shim(全局热键、Accessibility 焦点内省、粘贴模拟、剪贴板原子保存/恢复)。Tauri 侧音频捕获流向 Phase 3 已交付的同一 WebSocket 端点。带目标感知投递的粘贴 sink。这是「爽感」阶段,也是风险最高阶段:粘贴时序、热键可靠性、跨平台焦点检测都是必须钉死的工程问题,做不产品就不工作;Phase 3 的成功让后端管线先行去风险。
Phase 8 — 管道路由、sink、长录音
多 source 类型、用户可配置 transform 链、每预设多 sink:MCP server sink(Agent harness 打法)、HTTP webhook sink、文件 sink、开发者向 /pipelines/{id}/run 端点、Captures 标签内的预设编辑器 UI。双流录音器(麦克风 + 系统音)作为 source 类型;带重叠去重的分块 STT transform;摘要 LLM transform;长录音捕获成为预设而非新标签。平台特定 sink(macOS Apple Notes、Obsidian 等)作为通用 sink 接口后的 opt-in 集成。
9. 开放问题
文档保留六个未决问题,体现规划仍在演化中:
- 标签命名:倾向 Captures——中性、可随听写/长录音/上传音频扩展而不必重命名;"Dictations" 偏窄(办公效率味)、"Notes" 心智模型错误、"Transcriptions" 平淡;
- 精炼的对外词汇:STT 后 LLM 步骤需要用户可见名称,"Refine / polish / rewrite / smart edit" 都是候选,文档内 "Refinement" 仅为占位;
- 预设原语的命名:"Intent" 与 TTS 生成的
instruct字段冲突、"Flow" 太 Zapier、"Route" 太网络味,需要专门的措辞设计; - Persona 元数据的形状:人格直接放音色 profile 上,还是做成独立 persona 结构(包裹 profile + LLM 配置)?前者简单,后者在未来「一音多人格」时更可扩展;
- 长录音捕获的产品面:纯预设还是新标签内的专属入口?倾向预设,但长录音是最有理由拥有自己落地页的功能;
- 热键原语的命名:hold-vs-tap 需要 Voicebox 原生的 UI 措辞;设置界面可以沿用行业标准术语。
10. 架构前置条件
规划有两处依赖 docs/PROJECT_STATUS.md 中的既有工作:
- 平台支持分级(#420, PR #465):原生 shim 能力因平台而异——Wayland 粘贴劣于 X11、Windows 系统音捕获有边缘情况、前台窗口 OCR 按平台门控。分级定义让团队可以带着诚实的用户预期放心发布;
ModelConfig上的平台门控(PROJECT_STATUS 瓶颈 #6):Parakeet 的 Core ML 路径仅 Apple 可用,PyTorch 路径面向 Windows/Linux;这套门控机制也正是当前阻塞 VoxCPM 发布的同一机制。
两者都不必在 Phase 1 之前完成,但都应在 Phase 4 之前完成——因为用户可配置管道会把平台差异暴露给终端用户。
11. 小结:规划文档与仓库现状的对照
回到写作时的仓库实况,这份规划文档的执行度相当高:LLMBackend 协议、Qwen3 三档模型注册、/llm/generate 端点、服务端 capture_settings 单例(含 llm_model 字段)、keytap 驱动的和弦热键监控、带条件恢复的 paste_final_text 粘贴链、DictateWindow 浮动药丸——文档「已完成」栏所列内容在 backend、tauri/src-tauri/src、app/src 中均有一一对应的实现文件;而 /transcribe/stream、/speak 文档化、管道预设原语等未竟项也能在代码与文档状态清单中相互印证。更值得注意的是仓库已出现 backend/routes/speak.py、backend/mcp_server/、tauri/src-tauri/src/speak_monitor.rs 等文档「未开始」栏之外的文件,可以推断 Phase 5 的 Agent 语音输出正在文档记录之后推进。对想深入任一子系统的读者,建议的路径是:先读 backend/backends/init.py 理解三类后端协议与模型注册表,再看 tauri/src-tauri/src/hotkey_monitor.rs 与 tauri/src-tauri/src/main.rs 理解原生听写链,最后以本文第 4 节的 Source → Transform → Sink 形状为地图去对照未来的管道实现。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00