dsh-voice-call —— 让 agent 主动来电,接不接由你决定

前言

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/dsh 0.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 下的 binmodelcodec 分别是引擎可执行文件与两个 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,按上文步骤即可跑通。

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜