首页
/ Voicebox 语音 I/O 全栈设计解析:本地语音输入输出管道的架构决策与实现落地

Voicebox 语音 I/O 全栈设计解析:本地语音输入输出管道的架构决策与实现落地

2026-09-06 16:11:49作者:咎竹峻Karen

本篇技术文章基于 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)

规划文档列出了五条时机判断,值得作为背景理解:

  1. 跨平台本地听写是空档品类:Superwhisper、MacWhisper、Aiko 等工具仅限 macOS,WisprFlow 与 Willow 是云端方案;Windows 安装基座是本地听写产品的差异化楔子;
  2. STTBackend 协议已存在:多引擎注册表模式随 TTS 一起交付,添加 Parakeet v3、Qwen3-ASR 只需数天而非数周的后端工作量;
  3. persona 回路是独占能力:对着 Agent 说话、让它用克隆声音回复——拥有听写产品的人没有 TTS,拥有 TTS 产品的人没有好用的听写;
  4. Agent 生态闭环:Agent harness 已经在用 Voicebox 的 TTS,同一应用再提供 STT 就能补全回路;
  5. 内部最直接的痛点:手打 2000 字的 TTS 脚本文本非常反人性,直接对着 Voicebox 自己的生成表单听写,是 dogfood 整条 STT 管道的最佳场景,且完全不依赖任何 OS 级 API;
  6. 此外,端到端语音 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.pybackend/mcp_server/tools.pybackend/services/personality.pytauri/src-tauri/src/speak_monitor.rs 等文件已经存在,可以推断 Phase 5 相关工作在文档记录之后已推进了相当部分,具体能力以这些源码的当前内容为准。

已完成部分的源码级印证

Phase 2(本地 LLM 后端) 的落地可以从三处代码确认:

  • backend/backends/init.py 定义了与 TTSBackend / STTBackend 并列的 LLMBackend Protocol,并维护 LLM_ENGINES = {"qwen_llm": "Qwen3 LLM"} 引擎注册表;
  • backend/backends/qwen_llm_backend.py 提供 PyTorchQwenLLMBackend(transformers AutoModelForCausalLM)与 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 监听器,当前代码已演进为外部 keytap crate 的 ChordMatcher 承担事件 tap 与和弦状态机,HotkeyMonitor 只剩「绑定 → 事件 → Tauri 事件」的适配职责);
  • tauri/src-tauri/src/clipboard.rssave_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.rsAXIsProcessTrusted 权限闸门;
  • 前端对应物是 app/src/components/DictateWindow/DictateWindow.tsx——一个透明、置顶、无边框的 420×64 webview,在应用启动时预创建并隐藏,和弦开始时显示、捕获周期完成时隐藏,错误态药丸自动消失且点击可复制到剪贴板。

Phase 3 与 4 的前端载体分别是 app/src/lib/hooks/useCaptureRecordingSession.tsCapturesTab 与 Phase 7 浮动药丸共同消费的录音会话 hook)、app/src/components/CapturesTab/CapturesTab.tsx 和设置侧的 app/src/components/ServerTab/CapturesPage.tsx

计划外但已落地的增强

文档还专门列出了从 Phase 3/4/7 工作中自然长出的几项能力:

  1. 服务端权威设置(Server-authoritative settings):单例 capture_settingsgeneration_settings 表,客户端只上传音频;STT 模型、精炼开关、精炼 LLM 与自动精炼标志全部在服务端解析,避免多 Tauri webview 之间出现陈旧状态。这一点可在 backend/database/models.py 得到印证:capture_settings 表包含 llm_model 列(默认 "0.6B"),即 Qwen3 0.6B / 1.7B / 4B 由用户通过该单例选择;
  2. 后端音频归一化POST /captures 会把 librosa 能解码的任何格式(webm/opus、m4a 等)先转码为 WAV 再交给 Whisper,绕开 mlx-audio 内部 miniaudio 的格式缺口;
  3. 短录音保护:小于 300 ms 的音频块在客户端短路,误触热键不会上传空 webm;
  4. 精炼提示词重写:采用更强的「反聊天机器人」措辞,并内联覆盖多句保留与自我修正的示例。精炼服务本体在 backend/services/refinement.py

3. 后端架构:三个新概念

3.1 扩展 STT 注册表

现有 STTBackend 协议(定义于 backend/backends/init.py)当前抽象了 Whisper。规划要追加三个引擎:

  • Parakeet v3——25 种语言、速度极快,是非英语本地 STT 的质量领先者,Python 路径走 nemo_toolkittransformers
  • 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_cachedmodel_load_progressbackend/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_sampletop_p 固定 0.9(backend/backends/qwen_llm_backend.py),并通过 apply_chat_template(..., enable_thinking=False) 关闭 Qwen3 的思考模式以压低延迟。

为什么不选 llama.cppollama:仓库已经具备完整的依赖面与模型下载 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::RestartRecordingtauri/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 the dictate:start event 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 两个常量)。源码中还有两处超出文档文字的防御设计:

  1. 条件恢复:粘贴后比较 clipboard::current_change_count() 与写入后的计数,只有在粘贴窗口期内剪贴板未被第三方改动时才执行恢复——避免把用户新复制的内容冲掉;send_paste 失败也不影响恢复决策,保证「模拟按键失败」永远不会让用户剪贴板卡在转写文本上;
  2. 跳过规则:焦点 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 回路

  1. speak() 原语POST /speak 端点 + voicebox.speak MCP 工具,药丸进入 speaking 状态;设置 UI 覆盖默认音色、按 Agent 绑定音色、全局静音;
  2. 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. 开放问题

文档保留六个未决问题,体现规划仍在演化中:

  1. 标签命名:倾向 Captures——中性、可随听写/长录音/上传音频扩展而不必重命名;"Dictations" 偏窄(办公效率味)、"Notes" 心智模型错误、"Transcriptions" 平淡;
  2. 精炼的对外词汇:STT 后 LLM 步骤需要用户可见名称,"Refine / polish / rewrite / smart edit" 都是候选,文档内 "Refinement" 仅为占位;
  3. 预设原语的命名:"Intent" 与 TTS 生成的 instruct 字段冲突、"Flow" 太 Zapier、"Route" 太网络味,需要专门的措辞设计;
  4. Persona 元数据的形状:人格直接放音色 profile 上,还是做成独立 persona 结构(包裹 profile + LLM 配置)?前者简单,后者在未来「一音多人格」时更可扩展;
  5. 长录音捕获的产品面:纯预设还是新标签内的专属入口?倾向预设,但长录音是最有理由拥有自己落地页的功能;
  6. 热键原语的命名: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 浮动药丸——文档「已完成」栏所列内容在 backendtauri/src-tauri/srcapp/src 中均有一一对应的实现文件;而 /transcribe/stream/speak 文档化、管道预设原语等未竟项也能在代码与文档状态清单中相互印证。更值得注意的是仓库已出现 backend/routes/speak.pybackend/mcp_server/tauri/src-tauri/src/speak_monitor.rs 等文档「未开始」栏之外的文件,可以推断 Phase 5 的 Agent 语音输出正在文档记录之后推进。对想深入任一子系统的读者,建议的路径是:先读 backend/backends/init.py 理解三类后端协议与模型注册表,再看 tauri/src-tauri/src/hotkey_monitor.rstauri/src-tauri/src/main.rs 理解原生听写链,最后以本文第 4 节的 Source → Transform → Sink 形状为地图去对照未来的管道实现。

登录后查看全文
热门项目推荐
相关项目推荐