DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

Miyamiz39 /

Miyamiz39/dsh-koein

Verified

Wake-word voice input for DeepSeek Harness: an always-on local keyword spotter (KWS) wakes the agent, then local offline ASR turns what you say next into a submitted message. No API key, no cloud, no typing. 语音唤醒 + 本地语音识别插件。

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@3fd49575

dsh-koein 🎙️

语音输入插件,为 DeepSeek Harness 而做:说唤醒词唤醒,或者点一下麦克风直接开口。收音、识别全在本机完成,识别结果填进输入框,你确认后再发。

关键词:语音唤醒 · wake word · keyword spotting · 本地 ASR · offline speech-to-text · 无 API Key

音频不出本机,不需要 API Key,不需要联网,也不占用输入法——唤醒之后直接说,说完自动出字。

你说:小鲸小鲸    →  提示音  →  你说:帮我把 build 脚本里的端口改成 8080  →  自动发送
      ↑ KWS 常驻监听            ↑ ASR 只在这一段工作

它和别的语音插件有什么不同

这个插件本身就是完整的语音输入——本地流式 ASR、边说边出字幕、说完自动发送,不依赖输入法,也不需要再装别的语音插件。区别在于入口:常见的语音插件要你先点按钮或按热键进入麦克风,它则用一个唤醒词把整条链路点着。

常见语音输入插件 dsh-koein
进入方式 点麦克风按钮 / 按热键 说唤醒词,或点一下麦克风直接说
常驻成本 不常驻(进入才开 ASR) 常驻 KWS,实测解码速度约 50 倍实时(8 秒音频约 150ms)
唤醒判定 整段 ASR 后匹配文本 真正的关键词检测(open-vocabulary KWS),ASR 全程不参与
唤醒词 固定或需改代码 任意中文短语,按模型词表自动切分 token
识别 视插件而定 本地流式 ASR,边说边出字幕

一句话:它把语音输入从"要点一下"变成"说一句"。唤醒之后不用再碰键鼠,也不用去找输入法。

工作原理

一条音频流,两个引擎,用唤醒事件切换:

浏览器麦克风 ──16kHz PCM──▶ 宿主进程(不含原生代码)
                              │  WebSocket ↔ IPC 转发
                              ▼
                          语音引擎子进程(fork)
                              ├── listening: KWS(3.3M 中文模型,常驻)
                              │        命中唤醒词 ──▶ 提示音 + 状态切换
                              └── awake:     ASR(X-ASR 中英双语流式 + 标点)
                                       端点检测(能量 RMS + 静音挂起)
                                       识别完成 ──▶ 注入会话
  • KWS(sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01,int8 编码器 4.8 MB):开放式词表,不重训模型就能换唤醒词。
  • ASR(sherpa-onnx-x-asr-480ms-streaming-zipformer-transducer-zh-en-punct-int8-2026-06-05,int8 约 128 MB):流式输出,中英双语 + 自动标点,界面上能看到实时字幕。480ms 延迟块,CPU 单线程约 8 倍实时。
  • 端点检测:唤醒词已经告诉你用户在说话了,所以不需要再挂一个 VAD 模型——用 RMS 能量 + 静音挂起就够,少一次推理。
  • 常驻内存(实测):引擎子进程约 330 MB(KWS 53 MB + ASR 217 MB + Node 基线 59 MB)。想更省就换小模型,按权重等比下降。

唤醒词之后紧跟着说指令也可以("小鲸小鲸,打开配置文件"):唤醒时会把 150ms 预滚音频喂给 ASR,避免吃掉第一个音素。

为什么语音引擎跑在独立子进程里

这不是洁癖,是必需的。sherpa-onnx-node 自带 onnxruntime.dll 1.27(ORT API 27),而同一 profile 里的记忆插件 dsh-mihaji 通过 @huggingface/transformers 自带 onnxruntime.dll 1.21(API ≤ 21)。Windows 在进程内按 DLL 名解析依赖,谁先加载谁生效:

The requested API version [27] is not available, only API versions [1, 21] are supported.
exit code -1073741819   ← 0xC0000005 访问违例,整个宿主进程崩溃

把引擎放进 fork() 出来的子进程,同时解决两件事:

  1. DLL 命名空间隔离——子进程只加载 sherpa 自己的 ORT,冲突不存在。
  2. 崩溃隔离——原生 addon 再怎么崩,也只是子进程退出;宿主照常运行,界面收到报错,下一次点麦克风自动重启子进程。

test/isolation.test.mjs 静态遍历 src/index.js 的 import 图,断言宿主半永远不导入 sherpa-onnx-node,并真的 SIGKILL 掉子进程验证宿主存活与自动重启。

代价:首次点麦克风要等约 2 秒加载模型(按钮显示"连接中…"),这期间说的唤醒词会被丢弃。

安装

1. 下载模型(约 159 MB,一次性)

cd <插件目录>
node tools/download-models.mjs

默认落到 $DSH_HOME/koein-models。也可以指定目录:

node tools/download-models.mjs D:/models
# 然后配置 modelDir: D:/models

2. 装进 profile

dsh plugin --profile web add <本目录或 npm/git 包名>

dsh plugin add 会自动把包加进该 profile 的 dsh.profile.bundles,并套用它自带的 cordis.patch.yml。

3. 重启

dsh web

插件是 bundle 行,必须重启才生效。

装好后:发送按钮左边会出现一个麦克风图标,它就是全部的语音控制。

一个按钮,两种说话方式

手势 作用
单击 开/关「直接说话」——不用唤醒词,点开就直接说
右键单击 开/关「唤醒词监听」——说唤醒词即可开口

颜色就是状态,不需要额外的指示条:

颜色 含义
灰 停止
绿 现在说话就会进输入框(左键单击直接听写,或唤醒词已命中)
蓝 正在等唤醒词(右键单击后)
红 出错,鼠标悬停看原因

识别中时绿色会轻微呼吸,表示确实听到了。连续说几句会追加到输入框(中文之间不加空格,英文自动加),不会覆盖你已经写好的内容。

两种模式共用一条音频流,所以听写开着时唤醒词监听自动让位。识别出来的文字默认只填进输入框,不直接发送——你看一眼再按回车。想让它说完就发,把 autoSend 改成 true。

"设置 → 语音唤醒"里能看到模型状态和当前生效的唤醒词。

配置

在 $DSH_HOME/profiles/web/cordis.patch.yml 里覆盖(patch 是 whole-replace 语义,要写全字段):

- id: koein
  config:
    wakeWords:
      - 你好小鲸
      - 小鲸小鲸
    autoSend: false
    injectMode: composer
字段 默认 说明
wakeWords ['你好小鲸','小鲸小鲸'] 唤醒词,可任意中文短语
keywordsFile '' 已有的 sherpa-onnx keywords.txt;非空则忽略 wakeWords
keywordsScore 1.0 关键词增强分数,漏检多就调大
keywordsThreshold 0.25 触发阈值,误唤醒多就调大
numTrailingBlanks 1 关键词后空白帧数,唤醒词含重叠 token 时调大(如 8)
modelDir $DSH_HOME/koein-models 模型根目录
numThreads 2 两个引擎各自的推理线程数
autoSend false 识别完直接发送;默认 false 只填进输入框,由你确认后发送
injectMode composer composer = 走输入框正常提交;agent = 宿主直接以用户消息注入会话(后台标签页也能用)
silenceMs 800 说完静音多久判定句子结束
onsetTimeoutMs 4000 唤醒后等开口的最长时间
maxUtteranceMs 20000 单句最长时长
minUtteranceMs 300 更短的语音丢弃(多半是噪声)
energyThreshold 0.012 语音起始能量阈值(RMS),环境噪声大就调大
stayAwakeMs 0 一句话结束后继续聆听的时长,便于连续追问;0 = 每次都要说唤醒词

调唤醒词

唤醒词质量取决于你的声音、麦克风和房间——这是唯一无法用官方测试音频验证的东西。录一段自己念唤醒词的 16kHz 单声道 WAV,然后:

node tools/probe-wake-word.mjs "你好小鲸" 我的录音.wav
# 调参重试
node tools/probe-wake-word.mjs "你好小鲸" 我的录音.wav 2.0 0.15

它会告诉你是否命中、在第几秒命中,方便对着调 keywordsScore / keywordsThreshold。

选词建议:3–4 个音节、声母区分度高、日常对话里不容易顺口说出来。你好小鲸、小鲸小鲸 都不错;单音节词(如"鲸")误唤醒率会高很多。

排错

现象 处理
设置页显示"模型缺失" 跑 node tools/download-models.mjs,或检查 modelDir
唤醒词在设置页显示"不可用" 该短语无法用模型词表表示(如含 emoji),换一个说法
一直误唤醒 调大 keywordsThreshold(如 0.4),或换更长的唤醒词
喊了没反应 用 tools/probe-wake-word.mjs 确认能命中;调低 keywordsThreshold
唤醒后不出字 调大 silenceMs(说话停顿多)或调低 energyThreshold
切到别的会话后发错地方 injectMode: composer 跟随当前会话;跨会话请用 agent 模式
浏览器不给麦克风 页面必须走 https:// 或 localhost,且需要麦克风权限

隐私

  • 音频只在本机进程内从浏览器流到 DSH 宿主,不经过任何网络服务。
  • 唤醒和识别都是本机 sherpa-onnx 推理,无 API Key、无云调用。
  • KWS 全程运行;ASR 只在唤醒之后的那一段音频上运行。
  • 录音不落盘。

开发

npm test        # 4 个测试文件
测试 覆盖
test/isolation.test.mjs 宿主 import 图不含原生 addon;SIGKILL 引擎子进程后宿主存活并自动重启
test/keywords.test.mjs 中文→token 切分与官方 test_keywords.txt 逐行完全一致
test/pipeline.test.mjs 真实 WAV 走完 KWS→唤醒→ASR→出稿,并验证静音不误唤醒
test/socket.test.mjs 真起 HTTP+WebSocket 服务器,灌真实 PCM,验证帧协议与两种注入模式
test/client.test.mjs 用假 window.__ModuleLoader__ 执行 client bundle,注册三个 Slot 并真实渲染
test/profile-resolve.mjs 以 profile 为 cwd 验证包解析、挂载与原生引擎加载

设计上刻意不做打包:宿主半是普通 ESM,浏览器半是手写的 window.__ModuleLoader__.load({id, factory}) 包装(正是打包器会产出的形态)。改完源码重启即可,没有构建步骤。

许可

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit 3fd495751331

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout