Skip to content

语音输入(本地 STT) ​

本文引用的源码文件

本文引用的文件

引言 ​

语音输入(本地 STT)让用户用说的代替打字:在侧边栏点 🎤 说话,识别结果只写进输入框草稿,不会自动发送,也不触发任何工具调用。识别完全在本地 Agent 进程内跑 ONNX 模型,音频不出本机、不写盘。

它与 RAG、长期记忆一样属于可选组件:默认关闭,未配置时扩展端不会尝试加载任何语音相关资源,未安装依赖的用户不会为 ONNX 运行时多付一次常驻内存。

目录 ​

  1. 设计目标与边界
  2. 三段式门控
  3. 启用流程
  4. 使用体验
  5. 配置项
  6. 接口一览
  7. 数据与隐私
  8. 资源控制
  9. 故障排查
  10. 结论

设计目标与边界 ​

目标落地方式
音频不出本机采集、编码、推理全在本机;不上传、不写临时文件
装了不该自动生效DEFAULT_ASR_CONFIG.enabled = false,且 UI 门控要求三项同时就绪
不拖累主链路定稿推理在独立 worker 线程,Agent 事件循环不被阻塞
出错不装死依赖缺失 → 按钮直接隐藏;推理失败 → 退回流式文本
内存可控会话空闲回收、录音时长上限、RSS 保护、引擎空闲卸载

明确不做的事:

  • 不做自动重装:sherpa-onnx-node 包体约 300MB+,静默重装会在用户无感知时拉满带宽,只记录 everSucceeded 并提示用户点击「重新安装」。
  • 不做侧边栏麦克风授权弹窗:Chromium 在侧边栏 / 弹窗形态下不允许弹授权框,授权入口放在选项页的标签页上下文里。
  • 不接管发送:识别结果只作为草稿插入,是否发送由用户决定。

三段式门控 ​

🎤 按钮是否出现,由三个彼此独立的信号共同决定,任一为假即隐藏(避免「点了才报错」):

deps.ready(依赖就绪) × modelsReady(模型就绪) × enabled(用户开关)
信号事实来源说明
deps.readyAI_HELPER_ASR_DEPS 未置 0 且磁盘存在 node_modules/sherpa-onnx-node/package.json,并通过子进程探测零成本磁盘判定在前,子进程 require 确认在后
modelsReady/api/asr/status 返回的能力汇总 capability.offline || capability.online至少有一个 offline(定稿)或 online(流式)模型包通过校验
enabledasr-config.json 的 enabled 字段默认 false,只能由用户显式打开

三者由代理端在 /api/asr/status 里汇总成 available 一次,扩展端直接采信;只缺一环时 reason 会区分 deps_missing / models_missing / disabled,配置页据此提示补哪一项。

门控状态在扩展端由 refreshGating() 拉取,点 🎤 前会再复查一次,避免状态陈旧。

启用流程 ​

  1. 安装依赖:选项页「语音」标签页显示检测结果,缺失时点「一键安装」;等价命令行是 aha asr install,或在安装 Agent 时加 --with-asr。
  2. 准备模型:把模型包放进 ~/.ai-helper-agent/asr/models/<id>/(每包一个目录,内含 manifest.json),可点「扫描」重扫,或用「从文件夹导入」;也可点「下载」从内置目录拉取。
  3. 打开开关:在「语音」标签页打开总开关,按需选识别语言(auto / 中文 / 英文 / 日文 / 韩文 / 粤语)。
  4. 授权麦克风:点「去授权」,在打开的标签页里允许一次;未授权时录音条里也会出现「去授权」按钮。

三项就绪后,侧边栏输入框右侧出现 🎤。

使用体验 ​

动作行为
点 🎤 / 按 Alt+V开始录音,录音条展开(音量柱 + 计时 + 状态文本 + 取消 / 完成)
说话过程中流式草稿实时刷新(服务端节流 150ms),已定稿片段不再回改
按 Alt+V / 点「完成」停止采集并发起定稿,用整段高精度模型重转一遍
按 Esc / 点「取消」放弃本次录音,草稿不落输入框
静音超时5 分钟上限自动结束并定稿

录音条的键盘可达性有两处刻意处理:

  • 隐藏前先把焦点交还输入框(releaseBarFocus())。点「取消 / 完成」时焦点留在按钮上,若紧接着给整条设 aria-hidden="true",Chrome 122+ 会拦截并告警 Blocked aria-hidden on an element because its descendant retained focus,键盘与读屏用户的焦点还会被悬空。
  • role="status" / aria-live="polite" 只挂在状态文本 #voiceStatusText 上,而不是整条录音条。否则每 200ms 刷新的计时器会被读屏反复朗读,形成刷屏。

配置项 ​

用户配置落在 ~/.ai-helper-agent/asr/asr-config.json(缺失即视为从未配置,一律按未启用处理):

字段默认值说明
enabledfalse总开关
languageauto取值 auto / zh / en / ja / ko / yue
streamingModelIdnull流式草稿模型包 id;null 表示按能力自动选第一个可用包
offlineModelIdnull定稿模型包 id;同上
idleUnloadMs600000引擎空闲卸载毫秒数,0 表示不卸载

写入走「临时文件 + rename」,避免读到写一半的 JSON;读取时对类型不符的字段一律回落默认值(sanitizeAsrConfig)。

接口一览 ​

HTTP 端点(均需 Bearer Token):

方法路径说明
GET/api/asr/status依赖 / 模型 / 开关 / 已加载引擎状态
POST/api/asr/config修改开关、语言、模型 id、空闲卸载
GET/api/asr/models已扫描模型包列表(非法包附原因)
POST/api/asr/models/scan重新扫描模型目录
POST/api/asr/models/pick从文件夹导入模型包
DELETE/api/asr/models/:id删除模型包
POST/api/asr/models/download按内置目录下载模型
GET/api/asr/models/download/status下载进度
POST/api/asr/models/download/cancel取消下载
GET/api/asr/models/catalog内置推荐模型清单
POST/api/asr/transcribe整段音频一次性转写
POST/api/asr/warmup预热引擎
POST/api/asr/deps/install一键安装依赖
GET/api/asr/deps/install/status安装进度
POST/api/asr/deps/detect重新检测依赖

WebSocket:ws://127.0.0.1:18910/ws/asr/:sessionId,鉴权与 /ws/exec 一致(Header 优先,回退 ?token=),依赖不可用时直接 503 拒连。帧协议与定稿流程见 流式会话与定稿引擎。

数据与隐私 ​

~/.ai-helper-agent/asr/
├── asr-config.json     # 用户配置
├── hash-cache.json     # 模型文件 sha256 缓存(按 path+size+mtime 失效)
└── models/<id>/        # 模型包(manifest.json + ONNX 文件)

音频只在内存中流转:采集为 16kHz PCM,切片后经 WebSocket 上送,既不落盘也不出网。识别文本只写进侧边栏输入框,随会话内容进入正常的对话存储路径。

资源控制 ​

机制约束
单次录音上限MAX_RECORDING_MS,默认 5 分钟
会话空闲回收SESSION_IDLE_MS,默认 30 秒无帧即释放
并发会话MAX_SESSIONS,默认 1(第二个请求直接回 busy)
RSS 保护超过 RSS_LIMIT_BYTES(默认 3.5GB)跳过 offline 定稿,改用流式文本(fallback: true)
引擎空闲卸载idleUnloadMs 默认 10 分钟
依赖探测开销零成本磁盘判定优先;需子进程确认时超时 3 秒

故障排查 ​

🎤 按钮不出现:三项门控里至少有一项为假。打开选项页「语音」Tab,按提示补齐依赖、模型或开关。

提示没有麦克风权限:侧边栏形态下无法弹授权框,去选项页点「去授权」,在标签页里允许一次。

模型包显示「非法」:manifest.json 校验失败,常见原因是 schema 不是 1、engine 不是 sherpa-onnx、kind 不在 online / offline / vad 内、files 列出的文件缺失或为空、sha256 与文件实际摘要不符。错误原因随列表一并返回。

连接被拒(503):依赖不可用。此时扩展端不会开流式通道,需先完成依赖安装。

定稿文本明显弱于预期:可能命中 RSS 保护退化为流式文本。查看 /api/asr/status 的已加载引擎与最近一次结果标记。

结论 ​

语音输入的工程重心不在「接一个 ASR 模型」,而在可选性与不打扰:默认关闭、三段式门控、隐藏而非报错、音频不出本机、推理不占主线程、内存有明确上限。按此边界,它可以作为一个可插拔的输入方式长期存在,而不成为扩展主链路的负担。