前言¶
DeepSeek Harness(DSH)的理念是一切皆插件,agent 能做什么取决于装了哪些插件。但跑长任务时有个常见的不便:agent 的产出都在屏幕上——任务完成了、卡在某个决定上了、或者它觉得有件事值得说,你并不知道,除非一直盯着窗口。
常规的 TTS 方案是单向朗读:打开开关,agent 的输出被念出来,人是被动接收的一方。dsh-voice-call 把这个关系倒了过来:agent 拥有一个自己的声音,它决定什么时候开口,用 offer_call 给你打一个电话;你握着接听键——接听、拒接、稍后再说,不接就绝不播放。下面介绍它的能力、安装步骤和用法。
这是什么¶
dsh-voice-call 是 PandaPolo 维护的 DSH 插件,MIT 许可证,Fork 自 Jesse-njx/dsh-voice,在其基础上新增了通话域、crispasr 合成后端与本地播放,并针对 rc.6 的插件事件与后台任务限制做了修复。
两个设计点先说清楚:
1、本地优先,可完全离线。语音合成跑在本机 CrispASR + Qwen3-TTS GGUF 引擎上,产物是 ~/.dsh/voice/ 目录下的普通音频文件。
2、任何音频行为都不会自动运行。要出声,要么是模型自己调用了工具,要么是你接听了一次来电。
核心功能¶
offer_call:由 agent 发起的通话¶
工具签名是 offer_call({ text, voice? })。调用后来电开始振铃,人类应答:接听,则由后台任务合成并播放;拒接或选择稍后再说,工具会把决定作为结果返回给 agent,由它决定接下来怎么处理。
说与不说、说什么、用哪个音色,由 agent 决定;是否真的有声音从扬声器出来,由你决定。
来电卡片(v0.2)¶
配置 callMode: card 后,来电以浮层卡片形式振铃:双环脉冲动画、来电者身份、想说的话预览;振铃超时自动记为 missed,不会无限挂着。没有网页客户端连接时,自动回落为弹窗询问。
speak:直接朗读¶
speak({ text, voice?, rate? }) 由后台任务直接朗读,真实本地播放:Windows 用 PowerShell SoundPlayer,macOS 用 afplay,Linux 用 aplay。语速用 rate 控制。
transcribe:语音转文字¶
transcribe({ source, to? }) 把语音转成文字,作为用户消息进入会话。后端可选 whisper-local / openai / macOS 原生。to 参数可以把转写结果跨会话投递,需要 dsh-crosstalk 插件。
限制:录音入口 transcribe({ record }) 仅 macOS 支持,Windows 与 Linux 调用会明确报错。
/voice 命令¶
会话内输入 /voice 可以查询状态、用 on|off 切换朗读回复开关、用 speak <text> 让它立刻说一句。朗读回复默认关闭。
内置音色与合成后端¶
crispasr 后端内置 9 个 Qwen3-TTS CustomVoice 音色,其中 2 个是中文方言:aiden、dylan(北京话)、eric(四川话)、ono_anna、ryan、serena、sohee、uncle_fu、vivian。
合成后端不止一个:edge-tts 只合成不播放;fake 用于没有模型时的联调。
安装与启用¶
前置要求:
- Node.js ≥ 20(插件运行要求;运行本项目测试需要 22.18+)
- pnpm 9+
- dsh CLI:
@deepseek-ai/dsh0.1.2-rc.1 - CrispASR ≥ 0.8.28 与 Qwen3-TTS GGUF 模型——没有这组引擎和模型,就没有本地合成音色
- 已配置可用的 LLM API 凭据(agent 本身依赖)
1、安装插件:
dsh plugin --profile web add dsh-voice-call
插件已发布到 npm(dsh-voice-call@0.2.0),上述命令从 npm 拉取。
2、写配置。在 profile(默认 web)目录的 cordis.patch.yml 中,按 id 更新 dsh-voice-call 这一项。注意:同一个 id 只能出现一次,重复 insert 会导致启动崩溃。
Windows 示例(来自插件 README,路径按实际目录改):
- id: dsh-voice-call
config:
tts:
backend: crispasr # 本地神经 TTS 引擎
voice: dylan # 默认音色,取内置 9 音色之一
crispasr:
bin: D:\crispasr\crispasr.exe
model: D:\crispasr\models\qwen3-tts-12hz-0.6b-customvoice-q8_0.gguf
codec: D:\crispasr\models\qwen3-tts-tokenizer-12hz-q8_0.gguf
callMode: card
readReplies: false # 朗读回复开关
durableEvents: false # rc.6 上必须保持 false
audioDir: ~/.dsh/voice
tts.crispasr 下的 bin、model、codec 分别是引擎可执行文件与两个 GGUF 模型的绝对路径;durableEvents 控制会话事件日志,默认关闭,在 rc.6 上必须保持 false,否则会话历史可能无法继续加载;audioDir 是音频输出目录,默认 ~/.dsh/voice。
3、重启 dsh web。经过上面的步骤,打开一个会话就可以验证了。
典型用法¶
1、查状态。会话内输入:
/voice
应显示 stt / tts / readReplies / audioDir 状态。
2、朗读测试。让 agent:「用 speak 工具说‘你好’。」听到声音即成功。
3、通话测试。告诉 agent:「你有 offer_call 工具——有什么值得说的就打电话给我。」来电后点接听,声音从扬声器播出。
4、排查配置。执行:
dsh --profile web --dump-config
查看合成后的完整配置树,确认插件的配置是否真的生效。
适用场景与注意事项¶
适合谁:
- 跑长任务、希望 agent 在值得开口时主动打给你的人;
- 想要本地、可离线的 TTS 链路,产物是普通音频文件的人;
- 在 macOS 上想用语音给 agent 发消息的人——转写结果会作为用户消息进入会话。
注意事项:
1、插件以当前 dsh 进程的权限运行。安装任何第三方插件前,建议先检查源码与许可证;本项目为 MIT。
2、rc.6 上 durableEvents 必须保持 false。
3、cordis.patch.yml 中同一个 id 只能出现一次,重复插入会导致启动崩溃。
4、没有 CrispASR + Qwen3-TTS GGUF 模型就没有本地合成音色;还没有模型时可以先用 fake 后端联调。
5、录音(transcribe({ record }))仅 macOS 支持。
小结¶
dsh-voice-call 把「agent 发声」做成了双方各有控制权的交互:agent 有拨号权,自己决定何时来电、说什么;你有接听权,不接就没有声音。加上本地合成、离线可用、产物是普通音频文件,整条链路都留在自己的机器上。安装一条命令,配置集中在 cordis.patch.yml,按上文步骤即可跑通。