dsh-stt-qwen

DSH PLUGIN模型与账号接入

仓库 M-eang/dsh-stt-qwen · npm dsh-stt-qwen

模型与账号接入 未标注验证等级 安全审查:未标注 可信度:未标注 可安装插件
安装这个插件

为 DSH 语音输入插件提供本地 Qwen3-ASR 识别器,录音全程走 127.0.0.1 不出网,替代内置 SenseVoice。

一句话选型结论

按资料字段整理

dsh-stt-qwen 是模型与账号接入类的 DSH 可安装插件,未做沙盒验证,暂无安全审查结论,列出 5 项功能,有可直接复制的安装命令。验证通过不等于安全:插件会读写文件、执行命令或联网,装前先到仓库确认它做了什么;涉及密钥或系统权限的,建议先在隔离环境试装。

分类模型与账号接入 收录类型可安装插件 验证等级未标注验证等级 安全审查未标注
验证等级 UNKNOWN 未标注验证等级
GitHub 星标 0 复刻 0 · 未关闭 issue 0
最近更新 2026-11-14 创建于 2026-11-14
README 36.7 KB 正文体量,越大说明说明书写得越细

安装

可安装插件
在 DSH 里执行,装完用 dsh plugin list 确认已挂上;标了 L4 / L5 的命令已在沙盒里真实跑通
dsh plugin --profile web add github:M-eang/dsh-stt-qwen
  1. 1
    确认版本与运行环境

    Node.js 要求 >=20;版本不匹配时安装会报错。

  2. 2
    执行安装命令

    在 DSH 里执行上面的命令;多包仓库要注意区分具体子包名,别装错包。

  3. 3
    确认已挂载

    用 dsh plugin list 查看列表里是否出现这个插件,出现即挂载成功。

  4. 4
    试跑并检查权限

    加载后跑一个最小用例,确认它能正常工作;同时看它申请了哪些文件、命令或网络权限。

兼容 DSH 版本未标注版本不匹配时安装会报错
Node.js 要求>=20来自仓库 package.json 的 engines 字段
前置依赖无需要同时安装的同级依赖
npm 包名dsh-stt-qwen可用 npm view 查看包信息

能做什么

5 项功能
字段来自项目仓库与公开索引,中文说明由本站标注

dsh-stt-qwen:为 DeepSeek Harness 语音输入接入本地千问识别

  • 本地 Qwen3-ASR 识别,录音不出网
  • 注册为官方 Provider 扩展点
  • 真实上报加载/可用状态
  • 不合格音频提前拒绝
  • 进程随宿主启停与重启

上游标签

deepseek-harnessdshdsh-pluginspeech-to-text

验证等级与安全审查

未标注验证等级
自动化流程结论,不是人工审核
验证等级未标注验证等级上游未给出验证结论,安装前请自行确认
安全审查未标注静态分析结论,未标注不代表已通过审查
仓库健康度未标注按仓库活跃度与最近更新时间给出
可信度未标注综合仓库活跃度与验证结果

选择建议

装之前先看这几条
按条目字段给出的判断,不替你决定用不用

值得选的地方

  • 有可直接复制的安装命令,不用自己翻文档找入口。
  • 列出了 5 项功能,能直接看出它解决什么问题。

先想清楚的地方

  • 安全审查还没结论,装之前先看仓库改了什么、要什么权限。
  • 验证通过不等于安全:插件会读写文件、执行命令或联网,装前先到仓库确认它做了什么。

更适合

  • 需要在模型与账号接入场景里给 DSH 补能力的人。
  • 能接受在本地装依赖、愿意自己确认权限的人。

不太适合

  • 只使用 DSH 官方内置能力、不接受第三方插件的人。

同类对比

6 个同类
同分类里挑最接近的几条,字段以各自详情页为准
插件验证等级安全审查星标兼容版本安装命令
dsh-stt-qwen当前 未做沙盒验证 未标注 0 未标注 有
archify L4 · 沙盒安装通过 低风险 8.0 万 未标注 有
DeepSeek-Reasonix L4 · 沙盒安装通过 低风险 3.6 万 未标注 有
nocobase L3 · 安装规范通过 低风险 2.5 万 0.1.1-rc.2 无
voyager L3 · 安装规范通过 低风险 2.1 万 未标注 无
learn-harness-engineering L4 · 沙盒安装通过 低风险 2.0 万 未标注 有

仓库档案

模型与账号接入
字段来自项目仓库与公开索引,中文说明由本站标注
作者 / 维护方M-eang仓库 dsh-stt-qwen · 默认分支 main
分类模型与账号接入model-inference
收录类型可安装插件声明了 dsh.bundle、可用 dsh plugin add 安装的条目
开发语言JavaScript仓库主语言
许可证MIT商用或二次分发前请确认条款原文
星标 / 复刻 / issue0 / 0 / 0收录时的数值,非实时
创建 / 更新2026-11-14 · 2026-11-14最后推送 2026-11-14
本站收录2026-11-14首次进入本站目录的时间

README 全文

36.7 KB
作者原文,中文版优先;只做安全转义,不改内容

dsh-stt-qwen — 本地千问语音识别器

给 DSH 的「语音输入」插件换一个识别器:不再用内置的 SenseVoice,而是把麦克风录音交给本机跑的 Qwen3-ASR-0.6B(CPU 推理),全程 127.0.0.1,录音不出网。

为什么是插件,而不是改官方包

官方 @deepseek-ai/dsh-experimental-speech-to-text 本身就是一个 Provider 注册表:

ctx.speechToText.register({
info: {
id, name, location: 'host-local' | 'cloud', languages,
// 面板用来提示「要占多少磁盘、大概等多久」;本包不替用户下载模型,所以不报 downloadSources
setupEstimate: { recommendedDiskBytes, minimumMinutes, maximumMinutes },
},
// 可选:语音面板靠它显示「准备好了没」,并允许用户手动催一次加载
preparation: { snapshot(), subscribe(listener), prepare(), cancel() },
transcribe({ audio, language }, signal) => { text, audioSeconds, inferenceSeconds },
})

官方 voice-input-bundle 只内置了一个 sensevoice-local,并在自己的 README 里写明:「额外 Provider 使用不同 id 注册到同一服务;云端识别需要显式增加 Provider 与凭据配置」。所以本包走的就是官方留的扩展点,不动官方一行代码。

面板上的「可用 / 加载中 / 不可用」是插件在报,不是猜的

官方内置的识别器恒报 ready,本包不能这么干:权重是 1.7 GB、首次加载要数秒到几十秒,
还可能「解释器/模型没放对」。所以本包实现了 preparation,phase 每次都从服务真实状态推导,
面板看到的就是真实状态:

phase 什么时候 面板应该显示
unprepared 解释器 / 服务脚本 / 模型目录缺一个,且还没试过 prepare() 不可用 + 缺什么(人话,带路径)
loading 正在起进程 / 加载权重(prepare() 或 startAlive() 进行中) 加载中
ready 服务在跑、/health 已通 可用
standby 环境齐备但服务没加载(或常驻模式下正在退避重启,附下次重试秒数) 未加载(按下麦克风会自动拉起)
failed 试过但失败(或常驻服务被判不可用) 不可用 + 原因

prepare() 是「现在就把权重加载起来」,真的等到 /health 通才 resolve;cancel() 停掉服务。
另外两条与面板无关但同样重要的硬约束,写在代码里也写在这里:

  • 不合格音频在调子进程之前就被拒(单声道 / PCM16 / 16 kHz,与 server/server.py:150 的
    decode_wav 逐条一致)。省一次进程往返,错误消息直接指名道姓,而不是让服务端抛 500。
  • 卸载顺序:先注销识别器,再停服务(unregister → server.stop() → 路由与退出钩子)。
    反过来的话,最后一刻进来的转写请求会打进一个正在退场的子进程。

链路:

浏览器麦克风 → ui-voice-input → api-speech-to-text(Remote) → ctx.speechToText
→ 【本包注册的 qwen3-asr-local】→ 127.0.0.1:随机端口 → qwen-asr(CPU) → 文本回填草稿框

目录:代码和运行时是分开放的

位置 内容 为什么
<插件目录> 插件本体(index.js、server/server.py、测试) 跟着你现有的本地插件目录走
<运行时目录> 曾经是 .venv(Python 3.12 + torch CPU)+ models\(Qwen3-ASR-1.7B / 0.6B);2026-11-01 整体清空,现在只剩密钥与日志 必须纯 ASCII 路径,见下;重建看「复现安装」

> ⚠️ 坑:qwen-asr 依赖 nagisa(dynet 原生库),它用自己的窄字符 API 读包内模型文件。venv 一旦落在带中文的路径(例如 06-AI与项目 下面),import qwen_asr 会直接抛 RuntimeError: Could not read model from ...nagisa_v001.model。把 venv 和模型放到纯 ASCII 目录即可,代码本身留在中文目录没问题。

复现安装

# 1) 纯 ASCII 路径下的隔离环境
$rt = '<运行时目录>'
uv venv "$rt\.venv" --python 3.12
uv pip install --python "$rt\.venv\Scripts\python.exe" `
--index-url https://mirrors.aliyun.com/pypi/simple/ `
-r 'D:\Desktop\06-AI与项目\dsh-stt-qwen\server\requirements.txt'

# 2) 模型权重(ModelScope,1.75 GB)
uv tool run --from modelscope modelscope download `
--model Qwen/Qwen3-ASR-0.6B --local_dir "$rt\models\Qwen3-ASR-0.6B"

想换 1.7B(更准、更慢)就下 Qwen/Qwen3-ASR-1.7B,然后把 profile 里的 modelPath 指过去。

在 DSH 里启用

  1. profile 的 package.json 加依赖:"dsh-stt-qwen": "link:D:/Desktop/06-AI与项目/dsh-stt-qwen"
  2. profile 的 cordis.patch.yml 加一条 insert(见本包 cordis.patch.yml 的写法),把 speech-to-text 的 defaultProvider 改成 qwen3-asr-local(或保持 SenseVoice,在语音面板下拉里手动换成「Qwen3-ASR-0.6B(本地)」)
  3. 在 profile 目录跑一次安装,然后重启 DSH:
$node = 'D:\DeepSeek Harness\resources\runtime\primary-runtime\dependencies\node\bin\node.exe'
$pnpm = 'D:\DeepSeek Harness\resources\runtime\pnpm\bin\pnpm.cjs'
& $node $pnpm install --dir '%USERPROFILE%\.dsh\profiles\desktop'

生命周期:与宿主同生共死

默认(keepAlive: true)本地识别进程就是 DSH 的「附属进程」:

时机 谁动手 怎么动
DSH 启动 插件 apply() 挂载即 startAlive() 拉起 python,后台加载模型(本机 1.7B 约 6 秒);失败按退避自动重试,不阻塞 DSH 启动
DSH 运行中 服务端自己 权重常驻(idleTimeoutSeconds: 0)——随时听写都是热的,代价是内存一直占着(本机实测提交量约 6.7 GB);不会自己退出(keepAlive 把 idleExitAfterSeconds 压成 0)
识别进程被杀 插件监督 几秒内发现意外退出,按 restartBackoffMs 退避重启(1s→2s→5s→…→60s,最多 6 次)
插件卸载(HMR / 优雅退出) 插件 disposer POST /shutdown 请它自己退 → 关 stdin → 3 秒不退就按进程树强收
宿主进程退出 process.on('exit') 钩子 killSync() 同步收掉子进程与它整棵子树(不依赖 await、不发 HTTP)
宿主被强杀 / 崩溃 服务端两条看门狗 stdin EOF(--watch-stdin 1)立刻退;父进程存活检查(--parent-pid <宿主 pid>,psutil 每 5 s 一次)兜住管道被继承的情况

三条保险是叠加的:钩子能跑就跑钩子,跑不了就靠管道和父进程检查 —— 任何一条成立都不会留下孤儿。

踩坑记录:venv 的 python 是两段式的。 Windows 上 <运行时目录>\.venv\Scripts\python.exe
只是个启动器,真正跑 server.py、加载模型、监听端口的是它拉起的基础解释器(os.getpid()
与 child_process 给的 pid 不是同一个)。实测杀掉启动器时真正解释器会跟着走(启动器把它放进了
job object),但插件不依赖这个细节:强收时用 taskkill /F /T(整棵树),并按服务端握手自报的
pid 再补一刀。复现实验:node test/venv-launcher-probe.mjs。

自查:

# 一眼看生命周期现状(running / childPid / interpreterPid / 重启次数 / 上次退出)
Invoke-RestMethod 'http://127.0.0.1:19387/plugin-api/dsh-stt-qwen/status' | ConvertTo-Json -Depth 6
# 真起进程、真杀进程的完整验证(21 项,会开关 6 次 python)
node test/lifecycle-test.mjs
# preparation 契约的离线自测(用替身服务端,不加载权重、不占内存,6 组断言)
node test/preparation-test.mjs
# 队列 / 长音频切分 / 模型下载(全部离线,不加载权重)
node test/queue-test.mjs ; node test/split-test.mjs ; node test/download-test.mjs

配置项

键 默认 说明
providerId qwen3-asr-local 注册 id,也是 defaultProvider 要写的值
displayName Qwen3-ASR-0.6B(本地) 下拉框里显示的名字
pythonPath 包内 .venv 用哪个解释器(放 ASCII 路径)
serverScript 包内 server/server.py 转写服务脚本
modelPath 包内 models/Qwen3-ASR-0.6B 模型目录
dtype float32 float32 / bfloat16 / float16;CPU 上 bf16 不一定更快
device cpu 有 N 卡时可以试 cuda:0
threads 0(自动) 实测 8 线程(物理核)够用,16 线程没有更快
quantize none none / int8。int8 走 torch.ao 动态量化:1.7B 上实测短音频 -28%、长音频 -37%,但常驻内存接近翻倍(fp32 权重和 qint8 副本同时存在);0.6B 上加 int8 反而慢 18%(小模型省下的算力不够补量化开销),所以小模型不要开。开 int8 会强制 dtype: float32 —— ONEDNN 的 qlinear_dynamic 只吃 float 输入,bf16 会直接报 data type of input should be float
processPriority above 服务进程优先级:normal / above / high。转写是短突发任务,above(ABOVE_NORMAL_PRIORITY_CLASS)能压掉「后台扫盘/索引时突然慢一倍」的毛刺
keepAlive true 与宿主同生共死:挂载即拉起、宿主在就不自行退出、被第三方杀掉自动重启。设 false 回到旧的按需拉起 + 空闲自退
preload true 挂载时就把模型拉起来,省掉第一次听写的冷启动(keepAlive: true 时必然为真)
restartBackoffMs [1000,2000,5000,15000,30000,60000] 意外退出后的重启退避(毫秒,最后一项是上限)
restartMaxAttempts 6 连续拉不起来多少次后放弃自动重启(放弃后下一次听写仍会再试一次)
readyTimeoutMs 600000 模型加载就绪的上限
idleTimeoutSeconds 900 空闲多久先「卸权重」;0 = 权重常驻不回收(随时听写都是热的,代价是内存一直占着)。本机 profile 设成 0:实测 1.7B/bf16 提交量 6698 MB,工作集被 Windows 修剪到 653 MB
idleAction release release = 只卸权重(进程留着,重载几秒);exit = 进程直接退出(重载十几秒)。只在 idleTimeoutSeconds > 0 时生效;idleTimeoutSeconds: 0 时整条空闲回收不启动,这一项是死配置
idleExitAfterSeconds 1800 release 之后再空闲这么久就退出进程;0 = 永不退出。keepAlive: true 时这一项被强制成 0 —— 同生共死意味着进程不会自己走掉
maxConcurrent 1 同时在跑的转写数。模型服务是单进程串行的,默认 1 就是它的真实吞吐
maxPending 4 排队上限;满了直接拒绝(错误消息带上限),而不是默默堆着让人以为「按了没反应」
longAudioSplitSeconds 0(关) >0 时:超过这个秒数的音频先在静音点切段、逐段识别再拼起来。见下节取舍
longAudioMinSegmentSeconds 3 切段的最短段长,避免把一段话切得稀碎
modelDownloadBaseUrl 空 模型清单基址(如 https://hf-mirror.com/Qwen/Qwen3-ASR-0.6B/resolve/main)。留空 = 不提供下载、也不广告 downloadSources
modelDownloadFiles [] 清单:相对路径字符串,或 { path, sha256 }。给了 sha256 就必须校验通过才落盘
modelDownloadTimeoutMs 3600000 单文件下载超时

队列:并发与排队都封顶

语音面板是「人按下麦克风」的场景,所以两件事都设了上限,而且满了会说话:

  • maxConcurrent(默认 1):真正同时在跑的转写数。本插件的服务是单进程串行的,把它调大只会让请求都在服务端排队,不会更快。
  • maxPending(默认 4):允许排队的条数。超了直接抛
    转写队列已满(在跑 1 条、排队 4 条,上限 4 条);等当前几次识别结束后再试 —— 面板会原样显示这句话。
  • 现状(在跑几条、排队几条)随时可看:GET /plugin-api/dsh-stt-qwen/status 的 queue 字段。

云端识别器不占队列:它只是一次 HTTPS 调用,不占本机的模型进程与内存;队列是给本地那台「单进程 + 几 GB 内存」的服务用的。

长音频:在静音点切段(默认关)

longAudioSplitSeconds 默认为 0,也就是整段送。这是有意的取舍:

  • 整段送有上下文,通常更准,也是这个插件一直以来的行为;
  • 切段是为了绕开「模型对超长音频明显变慢 / 吃内存」的情况;
  • 打开后切点是算出来的:在每一段上限的「最后三分之一」里找最安静的 20 ms 窗口,尽量不剪在句子中间;找不到静音就按上限硬切。段间用空格拼接(不能用换行——结果会写进输入框,换行可能被当成「发送」)。
  • 段与段是串行跑的,整次听写算队列里的一个任务。
# 超过 30 秒的音频自动切段(整段送更准,确认长音频有问题再打开)
longAudioSplitSeconds: 30
longAudioMinSegmentSeconds: 3

离线验证(合成音频,不加载模型、不占内存):

node test/queue-test.mjs      # 并发/排队上限/出错不卡队列
node test/split-test.mjs      # 切段数量、切点落在静音处、段段是合法 WAV、总时长守恒

模型下载(可选,清单驱动)

默认不下载任何东西(模型手工放置,见「复现安装」)。配置了清单之后,插件提供:

POST /plugin-api/dsh-stt-qwen/model/download         起一次下载(已在跑 → 409)
POST /plugin-api/dsh-stt-qwen/model/download/cancel  取消(保留 .part,下次续传)
GET  /plugin-api/dsh-stt-qwen/status                 里的 download 字段 = 每个文件的进度

行为:.part 增量写盘 → 带 Range 续传(服务端不认 Range 就重头写,不会拼出坏文件)→ 有 sha256 就校验、不符删除 .part → 校验通过才原子改名到模型目录。已存在且校验通过的文件直接跳过。

modelDownloadBaseUrl: https://hf-mirror.com/Qwen/Qwen3-ASR-0.6B/resolve/main
modelDownloadFiles:
- config.json
- { path: model.safetensors, sha256: "<64 位十六进制>" }

为什么是清单驱动,而不是内置一个官方地址:地址和校验值写死在插件里,上游一变就会变成「点了下载却装出坏模型」。基址由你给(国内建议 hf-mirror),插件只负责续传、校验、原子落盘;downloadSources 也只在配置了清单时才广告。

node test/download-test.mjs   # 起一个支持 Range 的本地 HTTP 服务,真跑完整下载/续传/校验失败/跳过

云端模式(不占本机内存,一次 HTTPS 调用)

不想让本机吃 4 GB、也不想等 10 秒级冷启动时,注册一个云端识别器即可:收到完整 WAV → 一次 HTTPS 调用 → 拿文字。
不需要额外插件、不需要新前端:官方那个麦克风按钮本来就把整段录音交给 ctx.speechToText,云端 Provider 只是换了个 transcribe 实现。

三种接口形态(cloudApi)

cloudApi 请求形态 音频怎么进去 典型厂商/模型
dashscope-chat POST {base}/chat/completions(OpenAI 兼容) base64 Data URL 内联在 input_audio.data 阿里云百炼 qwen3-asr-flash
dashscope-native POST {base}/services/aigc/multimodal-generation/generation base64 Data URL 内联在 content[].audio 阿里云百炼 fun-asr-flash 这类
openai-transcriptions POST {base}/audio/transcriptions(multipart) 直接上传 WAV 文件 硅基流动、火山、各类 OpenAI 兼容网关

三种形态都有本地假服务器测试:node test/cloud-mock.mjs(不需要真 key,验证请求体、鉴权头、响应解析)。

配置样例

- id: stt-qwen
name: dsh-stt-qwen
config:
localEnabled: false                 # 纯云端:本机完全不注册本地识别器
cloudEnabled: true
cloudProviderId: qwen3-asr-flash-cloud
cloudDisplayName: "Qwen3-ASR-Flash(云端)"
cloudApi: dashscope-chat
cloudBaseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1"
cloudModel: "qwen3-asr-flash"
cloudApiKeyEnv: DASHSCOPE_API_KEY   # 只写引用名,配置里绝不放密钥
cloudLanguages: ["auto", "zh", "en", "yue", "ja", "ko"]

密钥从哪来(配置里永远只有引用名)

cloudApiKeyEnv 的解析顺序,三选一即可:

  1. DSH 凭据服务(ctx.credentials.resolve)——官方各插件用的就是这个,密钥可写不可读;
  2. cloudApiKeyFile:把密钥单独放一个文件,插件读该路径;
  3. 进程环境变量:setx DASHSCOPE_API_KEY "sk-…" 之后重启 DSH。

都取不到时报错会点名缺哪个引用,值永远不会被日志打印。

两个上限要记住

  • DSH 侧:官方 api-speech-to-text 默认单次录音上限 120 秒 / 4 MB(浏览器到点截断,超了直接拒),可在它那条配置里调;
  • 厂商侧:以 qwen3-asr-flash 为例是 ≤10 MB / ≤5 分钟,且 base64 会膨胀 4/3(16 kHz 单声道 PCM16 = 32 KB/s),所以 Data URL 这条路的实际上限约 3.9 分钟 —— 与 DSH 的 120 秒上限相比很宽裕。

选型结论(2026-10-01 实查官方页,附来源)

只看「本机录好 WAV 直接上传」这个场景,能用的其实不多 —— 很多便宜接口都要求先有一个公网 URL:

厂商 / 模型 接口形态 音频入口 价格 评价
阿里云百炼 qwen3-asr-flash OpenAI 兼容 chat base64 内联 ✅ 0.00022 元/秒(≈0.79 元/小时),另送 10 小时/90 天 选它:形态最省事、价格可接受、模型就是本地那套的云端更大版本
阿里云百炼 fun-asr-flash 原生 multimodal base64 内联 ✅ 0.00022 元/秒 同价备选,可带上下文提示(术语偏置),已一并挂上做 A/B
阿里云百炼 paraformer-v2 异步 submit-poll 必须公网 URL ❌ 0.00008 元/秒(最便宜) 便宜但本机用不了
硅基流动 FunAudioLLM/SenseVoiceSmall OpenAI 兼容 multipart 文件直传 ✅ 免费 便宜,但就是被嫌弃的 SenseVoice 那一族(还有下线公告冲突)
硅基流动 Qwen/Qwen3-ASR-1.7B — — 定价页标免费 接口文档的 model 枚举里没有它,能否调用需实测
火山引擎 豆包 ASR WebSocket 二进制分帧 / 异步要 URL ❌ 1~3.5 元/小时 形态不适配
腾讯云一句话识别 HTTP + TC3 签名 base64 ✅ 3.2 元/千次,每月送 5000 次 单次 ≤60 秒,且要自己拼签名
百度短语音标准版 HTTP + Bearer base64 / RAW ✅ 3.4 元/千次,送 5 万次 单次 ≤60 秒
智谱 GLM-ASR-2512 OpenAI 风格 multipart 文件/base64 ✅ ≈0.0002 元/秒 单次仅 ≤30 秒,太短
MiniMax asr-1.0 multipart 文件直传 ✅ 2.50 元/小时 接口省事,但比阿里贵约 3 倍

按「每天 5 分钟」估算:阿里约 2 元/月(前 10 小时免费≈前 4 个月零成本),腾讯/百度在免费额度内基本 0 元但要被 60 秒卡住。
来源:help.aliyun.com/zh/model-studio/qwen3-asr-flash、.../fun-asr-flash、.../paraformer-v2、api-docs.siliconflow.cn/docs/api/audio-transcriptions-post、siliconflow.cn/pricing、cloud.tencent.com/document/product/1093/35686、cloud.baidu.com/doc/SPEECH/s/Tldjm0i4c、platform.minimaxi.com/docs/pricing/overview.md、volcengine.com/docs/6561/1359370。

密钥放哪:推荐一个文件(改完即生效,不用重启)

# 一行,只放密钥本身,不要引号、不要换行以外的空格
Set-Content -Path '<运行时目录>\dashscope.key' -Value 'sk-xxxxxxxx' -Encoding ascii -NoNewline

插件按「DSH 凭据服务 → 密钥文件 → 环境变量」的顺序取;文件读不到会回落到环境变量而不是直接报错。
环境变量路线也行(setx DASHSCOPE_API_KEY "sk-…"),但那样必须重启 DSH 才读得到。

云端实测(2026-10-01,真 key、真音频、4.2 秒中文样本)

识别器 HTTP 往返 结果 备注
qwen3-asr-flash 0.59 s 一字不差 ✅ 默认;base64 内联,无需公网 URL
fun-asr-flash 0.28 s 一字不差 ✅ 备选;必须显式给 parameters.format=wav,返回结构也不同(output.output.sentence.text)
本地千问 0.6B(对照) 1.5 s 推理 + 3~13 s 加载 ✅ 要占 4.2 GB 内存,所以降级为兜底

云端这条路没有加载、没有空闲回收、不占本机内存和提交量 —— 两段式空闲策略只有本地识别器还用得着
(本机 2026-11-01 起只走云端:localEnabled: false,本地识别器与两段式空闲回收都不再启用)。

两个踩过的坑(都已修在代码里):

  1. fun-asr-flash 的原生 multimodal 形态:只给 input.messages[].content[].audio 会报 format is empty,必须补 parameters: { format: 'wav' };纯 base64(不带 data: 前缀)会 500(服务端拿它当远程路径去 wget)。
  2. 403 Model access denied. 不是代码问题:要么业务空间没开通该模型、要么 RAM 子账号缺权限、要么整个 key 没模型权限(连 qwen-plus 都被拒就属于后者)。用一枚好 key 和坏 key 对比 401/403,能一秒区分「key 无效」和「权限没开」。

复测(2026-11-01,真 key,本机 7800X3D)

删掉本地模型后的实测,走的是插件自己的代码路径(test/real-dashscope-test.mjs → apply() → provider.transcribe):

音频 HTTP 往返(3 次) 说明
4.20 s 中文 0.55 s / 0.79 s / 0.79 s 与官方样本逐字一致
29.43 s 中文 1.02 s / 0.99 s 约 29× 实时(test/cloud_probe.py)
本地 1.7B 时代对照 3.07 s(8.88 s 音频)、2.71 s(14.28 s 音频) 用户抱怨慢的那两次

本机 IPv6 会黑洞 21 秒(踩过,别再踩)

同一条请求,curl -4 稳定 0.13 s,curl -6 有一次 connect=0 / total=21.26 s ——
21 s 正好是 TCP SYN 重试放弃的时长。dashscope.aliyuncs.com 的 AAAA 记录确实存在
(2408:400a:3e:ef02:...),本机 IPv6 到它时通时不通:

  • Python(requests / urllib3)不做 happy-eyeballs,取到第一个地址就死等 → 随机吃满 21~22 s。
    test/cloud_probe.py 因此默认把 socket.getaddrinfo 掐成只返回 A 记录,加 --ipv6 可复现旧行为;
  • Node 侧实测过 dns.setDefaultResultOrder('ipv4first') 对 undici 的 fetch 没有改善
    (test/dns-order-test.mjs);分层探针 test/diag-node-net.mjs 显示 dns/tcp/tls 各层都在 0.1 s 内,
    真正走插件路径时也是 0.55~0.79 s,说明偶发而非稳定,暂不改插件代码;
  • 判据:把 curl -4 与 curl -6 各打三次,只要 -6 出现 connect=0 + 20 s 级 total,
    就是这条坑,不是服务端慢。

实测(i5-12500H,16 线程,无独显)

> 这一节是旧机器 + float32 + 当时开启空闲释放时代的实测数据,留作对照。
> 本机(7800X3D / 31 GB / bf16 / idleTimeoutSeconds: 0)当前的口径见文末「当前部署状态」。

指标 数值
模型加载 13.3 ~ 13.5 s(脱机、文件已在缓存);DSH 启动时并发装载 41.6 s;首次冷读权重约 53 s
4.2 s 中文音频推理 1.40 ~ 1.98 s(约 3× 实时)
端到端(含拉起服务) 15.5 s(其中 13.5 s 是加载模型)
常驻内存(float32) 3.6 GB(当时配置为空闲 900 s 自动释放)
结果 甚至出现交易几乎停滞的情况。(与官方样本一致)

dtype 别乱换:bfloat16 在这台机器上实测推理 7.5 s(float32 只要 1.5 s,慢 5 倍),
内存也没降到预期 —— Alder Lake 没有 BF16 加速,PyTorch 只能软化。继续用 float32。
(Zen 4 相反:7800X3D 上 bf16 比 float32 快一倍多,见「当前部署状态」。)

模型规模与 int8 量化:本机实测(7800X3D / 8 核 8 线程 / 无 CUDA / 31 GB)

> 本节与 test/bench_quant.py、test/compare_models.py、test/smoke_server.py 都需要本地模型目录存在。
> 2026-11-01 起本机已删模型、只走云端,重跑这些脚本前先用 modelscope 把 Qwen3-ASR-0.6B 下回来。

同一台机器、同一个 torch 2.14.1+cpu、threads=8、同一批音频
(test/asr_zh.wav 4.20 s、test/asr_zh_long.wav 29.43 s),脚本是 test/bench_quant.py
和 test/compare_models.py:

方案 4.2 s 音频 29.4 s 音频 RSS 峰值 文本
1.7B bfloat16 1.352 s(RTF 0.322) 6.806 s(0.231) 4.6 GB 基准
1.7B bfloat16 + quantize: int8 0.969 s(0.231) 4.273 s(0.145) 10.8 GB 与基准一致
1.7B 同上,threads: 16 0.901 s 4.331 s 7.0 GB 一致(8 核 8 线程,16 线程没有意义)
0.6B bfloat16 0.599 s(0.143) 3.342 s(0.114) 2.2 GB 与 1.7B 逐字一致(difflib 1.0)
0.6B + quantize: int8 0.709 s(0.169) 3.904 s(0.133) 7.5 GB 一致

结论:小模型比量化更划算。0.6B/bf16 比 1.7B/bf16 快 2.26×(短)/2.04×(长),
常驻内存直接砍半,而且两段测试音频的文本完全一致;1.7B 加 int8 虽然也能快 28%~37%,
但要把 10.8 GB 常驻内存一直占着。只有当你必须保留 1.7B 的稳健性、又不接受它的延迟时,
才值得打开 quantize: int8。

三条踩过的死路(别重走):

  • torch.ao.quantization.quantize_dynamic 在 bfloat16 输入下第一层就炸:
    RuntimeError: qlinear_dynamic (ONEDNN): data type of input should be float. —— 必须 float32 加载;
  • torchao 的 Int8WeightOnlyConfig(0.18.0)在纯 CPU 上没有优化 kernel,实测慢 5 倍(6.47 s / 31.3 s);
  • torchao 的 Int4WeightOnlyConfig 直接 ImportError: Requires mslk >= 1.0.0(本机没有)。

服务端回归:test/smoke_server.py 会拉起真服务、读握手、打 /health、POST 真音频、再 /shutdown,
用来确认 --quantize / --priority 没有破坏协议(要先重建 .venv 并下模型,2026-11-01 已都删):

<运行时目录>\.venv\Scripts\python.exe <插件目录>\test\smoke_server.py --quantize int8

云端探针 test/cloud_probe.py 只用标准库,随便一个 python 3.10+ 就能跑(本机可用
%USERPROFILE%\.dsh\dsh-runtimes\dsh-primary-runtime\dependencies\python\python.exe),
输出「HTTP 状态 + 往返秒数 + 文本」,是判断「key 通不通、到底几秒」的第一手段。

两段式空闲回收(机制说明;本机现已关闭)

> 本机 idleTimeoutSeconds: 0 时整条空闲回收不启动(watch_idle() 直接返回),
> 下面这套只在 idleTimeoutSeconds > 0 时生效;服务端级的回归测试见 test/release_cycle.py。

「可以用语音的时候才加载 + 停手就把内存还回去」拆成两段,因为只卸权重不够干净:

阶段 物理内存(任务管理器看到的) 进程提交量 下次听写要等
刚加载完 4.2 GB 5.5 GB 0
停手 300 s 后(release) ~10 MB(实测 4236 → 8 MB) 5.5 GB 起 ~3 s(实测 2.9~3.2 s,只需重新读权重)
再停手 1800 s 后(exit) 0 0 13~50 s(要重跑一遍 torch/transformers 导入)

release 分支会显式调 EmptyWorkingSet:Windows 的堆对刚释放的大块只在空闲链表里留着,
不 trim 一次,工作集根本不会掉下来(第一版就是卡在这里:权重置空后 RSS 仍是 3.6 GB)。

一直 release 不退出会有个副作用:重载后提交量涨到约 8.5 GB 然后稳定(不是每轮增长,
实测三轮 8365 → 8425 → 8492 MB)。这台机提交限额 31.2 GB、平时已用约 20 GB,
余量不舒服,所以第二段把进程也退掉,彻底还清。

服务端为什么写得这么别扭(踩坑记录)

server.py 里有两条看起来多余、其实都是硬约束的规矩,改代码前务必看一眼:

  1. 模型必须在主线程、且在开始处理任何请求之前加载完。
    把加载放进子线程会让 import torch / scipy.linalg 这类 C 扩展的 DLL 加载
    在 create_module 里零 CPU 冻死。第一次踩的是「看门狗线程里 import psutil」
    与「加载线程里 import torch」并发导入。
  2. 原生导入结束之前,不能有看门狗线程,尤其是读 stdin 的那个。
    实验(test/watchdog-vs-import.mjs,各 2 次):
导入期间另一个线程在干什么 结果
阻塞在 sys.stdin.buffer.read(1)(管道) 卡死 2/2
只是 time.sleep 循环(父进程存活检查) 通过 2/2
没有任何其它线程 通过 2/2

即:一个线程挂在管道的同步读上,会让同进程主线程随后的 DLL 加载冻住。
所以 stdin 看门狗要等到导入结束再启动;就绪信号也因此改成 stdout 的第二行握手
({"port":…} → {"ready":…}),而不是加载期间轮询 /health。

已知限制

  • 只支持完整录音转写:官方语音服务本身没有流式识别接口。
  • CPU 推理意味着长录音会线性变慢(1 分钟音频大致 20 s 左右);追求长音频体验要上 GPU,或改用方案 B(DashScope 云端)。
  • 0.6B 的精度是「够用」档;术语/人名密集的场合,1.7B 或云端 Qwen3-ASR-Flash 的 system 词表偏置会明显更好。
  • 第一次听写要不要等,取决于空闲策略:本机 preload: true + idleTimeoutSeconds: 0(权重常驻),
    所以任何一次听写都不用等加载;只有把 idleTimeoutSeconds 设回正数(空闲卸权重)时,
    「停手超过空闲阈值后的第一次」才要等重载(本机实测约 3 秒,本机 profile 不启用这套)。
  • 服务只监听 127.0.0.1,并要求随机 token,其他本地进程无法直接调用。

排障

先看诊断接口(插件自己开的,本机回环):

Invoke-RestMethod 'http://127.0.0.1:19387/plugin-api/dsh-stt-qwen/status' | ConvertTo-Json -Depth 6

它会告诉你:插件有没有挂载、localEnabled/keepAlive/空闲策略、本地服务在不在跑(lifecycle.running、
childPid、interpreterPid、重启次数、上次退出的原因)、注册了哪些识别器(含模型与 baseUrl)、
以及密钥来源(引用名 / 文件路径 / 文件是否存在 / 环境变量是否设置),还有最近几次听写的结果与音量。
宿主日志的 stdout 捕获在启动早期不一定接得上,所以判断"装没装"以这个接口为准。

生命周期相关的三个自查:

node test/lifecycle-test.mjs          # 21 项:挂载即拉起 / 转写 / 被杀自动重启 / 卸载即停 / 宿主强杀不残留 ×2 / 缺运行环境
node test/preparation-test.mjs        # 6 组:preparation 阶段推进 / 音频准入 / 卸载顺序(离线,不占内存)
node test/venv-launcher-probe.mjs     # 复现「venv python 是两段式」这件事
node test/watchdog-vs-import.mjs 2    # 看门狗线程 vs 原生导入(改服务端之前先看这个)
# 不经 DSH 直接验证链路(真起进程、真转写)
$node = 'D:\DeepSeek Harness\resources\runtime\primary-runtime\dependencies\node\bin\node.exe'
& $node '<插件目录>\test\plugin-dry-run.mjs'

# 三个复现/定位实验(怀疑又踩到导入卡死时用)
& $node 'D:\Desktop\06-AI与项目\dsh-stt-qwen\test\watchdog-vs-import.mjs' 2   # 看门狗线程 vs 导入
& $node 'D:\Desktop\06-AI与项目\dsh-stt-qwen\test\thread-import-race.mjs' 3   # 主线程/子线程导入
& $node 'D:\Desktop\06-AI与项目\dsh-stt-qwen\test\spawn-matrix.mjs' scipy.linalg  # spawn 参数矩阵

# 手动起服务,看模型加载与转写原始返回(需要本地 venv + 模型目录;2026-11-01 已删,重建见「复现安装」)
& '<运行时目录>\.venv\Scripts\python.exe' `
'D:\Desktop\06-AI与项目\dsh-stt-qwen\server\server.py' `
--model '<运行时目录>\models\Qwen3-ASR-0.6B' --port 18121 `
--watch-stdin 0 --parent-pid -1
  • 插件日志前缀是 [dsh-stt-qwen],写在 DSH 宿主日志里(\logs\harness-*.log)。
  • 语音面板报错时,先看是不是 pythonPath / modelPath 写错,或者模型目录用了中文路径。
  • 进程卡住(CPU 不涨)时用 py-spy 抓栈最快:<运行时目录>\.venv\Scripts\py-spy.exe dump --pid(需重建 venv)。

当前部署状态(2026-11-01)

  • 默认走云端:speech-to-text.defaultProvider = qwen3-asr-flash-cloud
    (dashscope-chat + qwen3-asr-flash,一次 HTTPS 往返,实测 0.59 s)。
    2026-11-01 用户决定弃用本地推理,localEnabled: false,本地模型已从磁盘删除;
    改这条配置不需要重启 DSH:profile 被监听,插件会卸载重载并重新注册识别器;
  • 密钥:cloudApiKeyFile = <运行时目录>\dashscope.key(一行密钥,读不到再回落到
    DASHSCOPE_API_KEY)。2026-11-01 已写入并用真音频验证(test/real-dashscope-test.mjs 往返 0.65 s);
    插件每次转写都现读该文件(index.js:788 resolveKey()),所以换 key 不用重启;
  • 本地侧现状:已整体清空(2026-11-01)。<运行时目录>\.venv(1.7 GB)与
    models\Qwen3-ASR-1.7B、models\Qwen3-ASR-0.6B(共 6.2 GB)全部删除,目录现在只剩
    dashscope.key 和几个历史日志(合计 93 KB)。回退本地要重建两步:按「复现安装」重建 venv
    (uv venv + requirements.txt)、重新下模型(modelscope 约 1 分钟),再把 localEnabled
    改回 true、defaultProvider 改回 qwen3-asr-local;
  • 本地时代的实测数据(7800X3D / 8 核 8 线程 / 31 GB / 无 CUDA):1.7B/bf16 4.2 s 音频 1.35 s、
    29.4 s 音频 6.81 s;0.6B/bf16 0.60 s / 3.34 s,文本逐字一致;float32 比 bf16 慢一倍多。
    这些数字只对 CPU 路径成立,硬件细节见下节「Windows 原生 ROCm」;
  • 云端识别器在册:qwen3-asr-flash-cloud(dashscope-chat|qwen3-asr-flash|密钥引用
    DASHSCOPE_API_KEY,实际走 cloudApiKeyFile);fun-asr-flash 那次实测更快(0.28 s),需要时
    按「云端模式」那节加一条同形态的 provider 即可做 A/B。

Windows 原生 ROCm:这台机器其实能上显卡(2026-11-01 复核)

这条 README 以前写过「Windows 上 PyTorch 没有 ROCm」,这句是错的。本机

E:\ComfyUI_windows_portable_amd\python_embeded(Python 3.12.10)里装的是 AMD 官方
ROCm-on-Windows 的 PyTorch 预览轮子:

项 实测值
torch.__version__ 2.16.0a0+rocm10.2.0a20261007(拆分包 amd_torch_device_gfx1100-...、amd_torchvision_device_gfx1100-...)
torch.version.hip 7.17.26401
torch.cuda.is_available() True(HIP 走 CUDA API 表面)
设备 AMD Radeon RX 7900 XT,capability (11, 0) = gfx1100
bf16 4096×4096 matmul 1.65 ms → 83.2 TFLOPS(预热 3 次、均值 20 次)

安装入口(AMD 官方,stable 与 nightly 两条):

pip install --index-url https://stable.repo.amd.com/rocm/whl-next/ "torch[device-all]==2.13.0+rocm10.0.0"
pip install --index-url https://nightly.repo.amd.com/rocm/whl-next "torch[device-gfx1100]" --pre

把 Qwen3-ASR 搬上显卡时踩到的三件事(方向已放弃,留作备忘):

  • qwen_asr 0.0.6 钉死 transformers==4.57.6,而 ComfyUI 环境是 transformers 5.17.0:
    pip install qwen-asr 会降级 transformers、弄坏 ComfyUI,只能 pip install --target DIR + PYTHONPATH;
  • python_embeded 没有 venv 模块(ModuleNotFoundError: No module named 'venv'),建不了独立虚拟环境;
  • 直接用 transformers 5.17 自带的 Qwen3ASR* 跑,会报
    Qwen3ASREncoder expects padded_feature_length to be a multiple of n_window * 2 (100), but got 420:
    模型目录的 preprocessor_config.json 里 feature_extractor_type 写的是 WhisperFeatureExtractor,
    AutoProcessor 于是装出一个不会做 mel 轴对齐的提取器。把它改成 Qwen3ASRFeatureExtractor 即可。
    test/gpu_probe.py 就是这条路线探针,能一路跑到 generate 之前。

卸载

  1. profile 的 cordis.patch.yml 里删掉 stt-qwen 那条 insert,把 defaultProvider 改回 sensevoice-local;
  2. profile 的 package.json 里删掉依赖并重跑一次 pnpm install;
  3. 想彻底清干净就删 <运行时目录>(现在只有 93 KB:一行 dashscope.key + 几个历史安装日志;
    .venv 与两个模型目录 2026-11-01 已删,删前那会儿是 7.9 GB)。

常见问题

8 问
答案由本站依据条目字段整理,不引用上游原文
dsh-stt-qwen 怎么安装?

在 DSH 里执行 `dsh plugin --profile web add github:M-eang/dsh-stt-qwen` 即可。对应的 npm 包名是 `dsh-stt-qwen`。

dsh-stt-qwen 的验证等级是什么意思?

未标注验证等级。上游未给出验证结论,安装前请自行确认

dsh-stt-qwen 安全吗?

未标注。

dsh-stt-qwen 装之前要先确认什么?

先看它会读写哪些文件、执行哪些命令、连接哪些网络地址;验证等级只说明装得上、跑得起来,不等于安全。涉及密钥或系统权限的,建议先在隔离环境试装。

dsh-stt-qwen 需要什么运行环境?

Node.js >=20。环境不满足时安装会失败,建议先确认本机版本。

dsh-stt-qwen 用的是什么许可证?

上游标注为 MIT。商用或二次分发前请到仓库确认许可证原文。

dsh-stt-qwen 有哪些功能?

本地 Qwen3-ASR 识别,录音不出网、注册为官方 Provider 扩展点、真实上报加载/可用状态、不合格音频提前拒绝、进程随宿主启停与重启。

这条记录的数据可靠吗?

本站按仓库名去重后合并整理,字段以作者仓库与 npm 元数据为准;安装命令、仓库地址与 README 保持原文,带 L4 / L5 标记的安装命令已在隔离沙盒里真实跑通。

模型与账号接入
按星标排序,可作替代方案

archify

archify

从仓库或系统描述生成经过校验的自包含交互式架构图、流程图、时序图、数据流图和生命周期图。

L4 · 沙盒安装通过 8.0 万 星

nocobase

nocobase

L3 · 安装规范通过 2.5 万 星

voyager

voyager

Enhancement suite for Gemini, AI Studio, Claude, ChatGPT & DeepSeek —

L3 · 安装规范通过 2.1 万 星

dsh-our-free-model

dsh-our-free-model

为 DeepSeek Harness 挂载免登录、免 API Key 的免费前沿模型通道,装完即用,并附用量看板与 OpenAI 兼容本地转

未标注验证等级 8,064 星

M-eang 的其它插件

4 个

插件的元信息来自作者仓库;分类、验证等级与安全结论的中文说明为中文整理,最近核验 —。