这段时间我一直在做 AriaType,一个桌面语音输入工具。
一句话解释:按住快捷键,说话,松开,然后文字出现在当前光标位置。
听起来很简单,对吧?
一开始我也这么想。找个 STT 模型,接个麦克风,转成文字,再粘贴到输入框里。Demo 两天就能跑。
但如果你真的想做一个像 Typeless 那样“用户愿意长期用”的语音输入产品,就会发现:模型只是开始,准确率也不是模型一个人决定的。
真正难的是整条链路。
Typeless 公开讲的卖点是:自然说话,变成清晰、润色后的文字;去掉 filler words、重复表达;支持多语言;在不同 App 里使用;并强调隐私设计。它的体验目标不是“把声音转成字”,而是“把你想表达的东西变成可直接发送的文字”。
这也是 AriaType 现在的方向。
1. 准确率不是 WER,而是“能不能直接用”
做语音输入,很多人第一反应是看 WER。
WER 当然重要。AriaType 也已经有一份 STT benchmark 需求文档,计划用中英文、短音频、噪声环境样本来比较不同引擎的 WER 和冷启动延迟。
但真实产品里的“准确”,不是这么简单。
用户不会说:“这个模型 WER 是 7.2%,还不错。”
用户只会想:
“这句话我能不能直接发?”
比如 STT 输出:
嗯我觉得这个分析错误可能是由于标点符号引起的
字面上看没差太多,但在技术写作里,“分析错误”可能应该是“分词错误”。这不是普通 ASR 能稳定解决的问题,它需要上下文,需要后处理,需要知道用户在写什么。
所以 AriaType 现在把语音输入拆成两层:
第一层是 STT,尽量保持原始、准确的转写。
第二层是 Polish,把口语变成可用文字:去 filler、修标点、修明显错字、修上下文里的同音误识别。
这件事很关键:不要指望 STT 模型一次性产出最终文案。
STT 负责听清楚,Polish 负责写得像人。
2. 音频链路会毁掉一个好模型
我之前踩过一个很典型的坑:短句被截断。
不是模型不行,是音频没送完整。
AriaType 的录音链路大概是这样:
麦克风采集原始 PCM,然后转成 16kHz 单声道,经过降噪、VAD,再按 chunk 发给 STT 引擎。用户松开快捷键后,关闭通道,等待最终转写。
看起来很标准。
但早期实现里,运行时 chunk 是 500ms。对于长语音问题不大,但对于短句就很危险。用户按下快捷键说一句“好的收到”,可能还没攒够完整 chunk,或者尾部残留在 buffer 里,松手时没有被正确 flush。
结果就是:模型还没发挥,音频先丢了。
后来 AriaType 把运行时 chunk 调整到 200ms,并且专门处理 stop-time tail flush:用户松手时,最后一段尾音即使被普通 VAD 认为不够强,也要走完 resample/denoise,然后强制送给下游。
这类问题非常反直觉。
你以为自己在调模型,其实是在修水管。
如果音频采集、重采样、VAD、chunk、尾包 flush 任意一环处理不好,再贵的模型也只能识别残缺输入。
3. 低延迟不一定等于好体验
我在另一篇文章 How I Built AriaType Without Writing Any Code 里写过一个教训:一开始我让 AI 选“最低延迟”的方案,它推荐了双向流式接口。
听上去很合理。
语音输入嘛,当然越快越好。
但测试后发现,中文准确率不够理想。原因也很直接:在流式识别里,模型更依赖局部上下文,遇到中文同音词、长句结构、技术术语时更容易猜错。
后来 AriaType 明确了一条产品优先级:
准确率 > 稳定性 > 体验 > 速度。
这直接影响了 Volcengine 的接口选择。项目里现在明确使用 bigmodel_nostream,而不是更低延迟但准确率更差的双向接口。
这也是做非玩具产品时最容易被忽略的地方。
Demo 追求“快”。
产品追求“少返工”。
用户宁愿多等 300ms,也不想每句话改 3 个字。尤其中文输入场景里,一个同音词错了,整句话的可信度就没了。
4. 引擎要能换,而不是绑定一个模型
如果你的产品一开始就写死某个 STT 模型,后面一定会痛苦。
AriaType 现在支持本地 STT 和云 STT。本地有 Whisper / SenseVoice,云端可以接 Volcengine、Deepgram,也保留了自定义 endpoint 的空间。
关键不在“支持多少供应商”,而在接口怎么抽象。
现在 AriaType 的录音链路统一成了一个 send_chunk() + finish() 模型:
本地引擎收到 chunk 后先 buffer,finish() 时批量转写。
云引擎收到 chunk 后转发到 WebSocket,finish() 时等待最终结果。
上层录音管线不关心你是本地还是云端。
这听起来像普通工程抽象,但对语音产品很重要。因为 STT 供应商变化非常快,价格、延迟、中文效果、多语言能力都可能变。
一个真实产品不能赌单一模型永远最好。
它应该能在准确率、成本、隐私、延迟之间切换策略。
5. 上下文要谨慎用,否则会变成污染源
Typeless 这类产品好用的一个原因,是它不是单纯听写,而是知道你在写什么类型的内容。
AriaType 也在做 context-aware:当前窗口、当前任务、当前字段都可能帮助输出更贴近场景。
但上下文不是越多越好。
项目里 Polish 的 SystemContext 对窗口内容有一个很保守的设计:把它当成“untrusted reference context”,也就是不可信的参考上下文。
它可以提供产品名、文件名、联系人、术语等词汇提示,但不能被当成用户命令执行。也不能把窗口里的无关文字复制进输出。
这点非常重要。
比如你正在网页里输入,页面上有一段广告文案。如果模型把它当成 prompt,那语音输入就会被污染。
所以 AriaType 的策略是:
上下文可以修正明显接近的词。
上下文不能重写用户意图。
这也是语音输入产品和普通聊天机器人的差异。语音输入应该像输入法,不应该像一个擅自发挥的助理。
6. 真正的体验差距,在“插入文字”这一秒
很多语音输入 Demo 到转写结束就停止了。
但真实用户不关心你后台拿到了什么字符串。
他们关心文字有没有出现在当前输入框里。
这一步非常脏,也非常产品化。
桌面环境里,你要处理当前 App、当前焦点、权限、剪贴板、键盘模拟、输入法、失败恢复。macOS 还涉及麦克风权限、Accessibility 权限、窗口上下文权限。
AriaType 的 README 里写得很直接:它不是另一个输入框,而是桌面 voice layer。文本要落在当前光标位置,而不是要求用户复制粘贴。
这就是“玩具”和“产品”的分界线。
玩具只需要展示结果。
产品必须把结果送到用户正在工作的地方。
如果插入失败,用户不会说“STT 模型不错”。用户只会说“这个工具不可靠”。
7. 没有日志和测试,AI 也救不了你
AriaType 是我用 Agentic Coding 做出来的项目,我没有手写大部分代码。
但这不是“让 AI 随便写”。
我给它设了三条边界:SDD、TDD、Tool Automation。
SDD 是把规则写进文档,比如准确率优先、Spec-first、禁止假完成。
TDD 是用测试和 harness 锁住行为,比如录音状态机、音频 chunk、tail flush、空录音处理。
Tool Automation 是让脚本和编译器做确定性检查,比如 i18n、typecheck、clippy、cargo test。
还有一个东西非常关键:日志。
AriaType 的日志规范要求关键链路都有结构化日志:录音开始、音频处理、STT、Polish、注入、失败、fallback。因为语音输入的问题很多不是静态代码能看出来的,而是运行时才出现。
模型看代码,常常只能猜。
模型看日志,才能定位。
这也是我现在越来越确定的一点:AI 可以写很多代码,但系统必须给 AI 留观察窗口。
没有日志,没有测试,没有清晰的状态机,AI 写得越快,混乱扩散得越快。
最后:别做“语音转文字”,要做“语音输入”
这是我做 AriaType 后最大的体会。
“语音转文字”是一个模型能力。
“语音输入”是一个产品系统。
它至少包括:
音频采集是否完整。
VAD 是否吞字。
STT 是否适合当前语言。
后处理是否保持原意。
上下文是否帮忙而不是捣乱。
文字是否可靠插入当前 App。
失败后是否能定位原因。
这些都做完,用户才会觉得:我可以真的不用键盘。
Typeless 的吸引力也在这里。它卖的不是 ASR,而是“自然说话,直接变成可用文字”。
AriaType 目前还在 active development。macOS 版本已经可用,Windows 还在推进。Ghost-Language 这种学习用户语言习惯的能力也还只是 Draft,咱就不讨论细节了。
但方向已经很清楚:
如果想做 Typeless 级别的语音输入产品,不要只问“用哪个模型”。
更应该问:
从麦克风到光标,中间每一环有没有资格进入用户的日常工作流?
如果答案是没有,那它就还只是 Demo。