OMSociety/dsh-xiaoai-bridge ↗★ 1
dsh-xiaoai-bridge
XiaoAI speaker bridge for DeepSeek Harness: wake word, speech capture, DSH conversation and speaker playback, with the Python bridge hosted as a local process by the plugin. 适合拥有已刷机小爱音箱、想通过本地Python桥接器进行语音交互的用户。
インストール
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:OMSociety/dsh-xiaoai-bridgeドキュメント
README 全文を読む ↗配置项说明
设置页在 DSH 里,分 8 个区,顺序就是下面表格的顺序。页面上的联动(比如关掉「连续对话」后「退出词」收起来)只影响显示,值本身保留。
保存怎么走:设置页保存时提交逐项操作(POST /plugin/xiaoai/config 的 ops,形如 {"op":"set"|"unset","path":[...]},「重置为默认」就是 unset)。写入前会严格校验,不合法直接 400 并说明哪一项不对(dsh-xiaoai-bridge config: ),值不会写进去。
坏值怎么活:运行时读配置会逐条和默认值比,越界或类型不对就回退默认值,并按坏值集合去重只告警一次:dsh-xiaoai-bridge: unusable config repaired with defaults: = ()。插件不会因为一个坏值起不来,但设置页里仍然显示你填的那个值——日志里出现这一行,就说明它没生效。
配置怎么生效
配置有三条通道,改之前先看这一项走哪条:
| 通道 | 覆盖哪些设置 | 什么时候生效 |
|---|---|---|
渲染出的 /config.py | 唤醒词、对话保持时长、连续对话、退出词、唤醒应答、退出应答、兜底播报文本、会话键、设备名、语音合成方式、豆包 App ID 与音色/音频格式/流式/语速、语音识别后端、行动准则与语音消息附加提示 | 写盘即热重载:桥接器每秒轮询它的 mtime,一般 1 秒内生效 |
| 桥接器子进程的环境变量 | 日志级别、静默启动、本地 API 服务的开关与监听地址/端口、音箱名称与音箱地址、音箱连接鉴权(开着时写 DSH_XIAOAI_TOKEN),以及两枚凭据 | 进程启动时快照:改完要重启桥接器(设置页只重渲染配置,不会替你重启) |
| 只影响插件自己的会话 | 会话工作区、Agent 预设、任何会话都能让小爱说话、播报与回复器那一组 | 新会话按新值组建;已经在跑的会话保留它启动时的预设与路由 |
渲染出的 config.py 是覆盖层,不是模板:它把仓库里的 bridge/config.py 当模块加载,然后只覆盖上面那几个键。文本框留空表示这一项不写,桥接器继续用自己的默认值(而不是写一个空值进去)。插件还故意不碰这些键:dsh.rule_prompt(自动播放与连续对话用的那条约束)、openai.*、asr.doubao.*,以及 vad / kws / audio_input / xiaoai 这些段——要调就直接改仓库里的 bridge/config.py,改完同样会被热重载,见音箱侧的调参。
凭据怎么放:两枚秘密都存在 DSH 的凭据库里,插件启动桥接器时才解析出来放进子进程环境(XIAOAI_API_TOKEN、DOUBAO_ACCESS_KEY)。本地 API 服务的访问令牌在设置页里只填凭据名(字母或下划线开头的标识符,默认 XIAOAI_API_TOKEN),真值由插件在第一次启动时生成并写进凭据库;豆包访问令牌反过来——凭据名固定在 doubaoAccessKeyCredential 里(默认 DOUBAO_ACCESS_KEY),令牌本身直接粘在卡片的密码框里,由插件写进凭据库。同一枚访问令牌在「音箱连接鉴权」开着时还会以 DSH_XIAOAI_TOKEN 交给桥接器,用来校验拨上 4399 的设备。所以设置表和渲染出的 config.py 里都没有明文令牌。换凭据名或换令牌之后都要重启桥接器,新值才会被带上。
两种语音合成方式:
| 语音合成方式 | 写进配置 | 实际用谁 | 什么时候选 |
|---|---|---|---|
| 小爱原生(默认) | xiaoai | 音箱自带的合成 | 不想配豆包凭据,音色由设备决定 |
| 豆包语音合成 | doubao | 火山引擎的豆包语音合成 | 想要固定音色、复刻音色或统一语速 |
选中的值一定会写进渲染出的 config.py,不存在「留空跟随模板」。以前的「朗读音色」(ttsSpeaker)已经删掉:它和这个开关本来是同一件事的两套入口——桥接器的旧规则就是看那个音色值是不是 xiaoai 来决定走哪条路。豆包音色现在只在选「豆包语音合成」时由「豆包音色」给,插件不再写 dsh.tts_speaker,桥接器模板里那个默认值原样留着。
注意:写一个桥接器不认识的值不会报错:它只记一条
Unknown tts_provider=...,然后按音色回退。「没报错」不等于「接上了」。
基本
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
启用插件 enabled | 布尔 | 开 | 总开关。关掉后插件不再注册工具与技能,桥接器以 DSH_ENABLE=0 启动(xiaoai_speak 会直接回「插件已在设置中禁用」)。 |
音箱名称 deviceName | 文本 | 小爱音箱 | 显示用的名字,会出现在会话标题里,同时作为 XIAOAI_DEVICE_NAME 传给桥接器。它不参与设备绑定:设备键只看音箱地址,改它不会换会话。 |
音箱地址 deviceHost | 文本 | 192.168.1.191 | 音箱在局域网里的地址,作为 XIAOAI_DEVICE_HOST 传给桥接器,插件按它区分设备、决定这条语音进哪个会话。它不是插件去拨号的地址(是设备自己连 4399),填错的表现是语音落到别的设备键、会话归属错乱,而不是连不上音箱。 |
音箱连接鉴权 speakerAuth | 开关 | 开 | 4399 是设备拨进来的音频通道,开着时要求客户端带上与「本地 API 服务」里「访问令牌凭据名」同一枚令牌,否则握手被拒(桥接器日志报 401)。多数音箱上跑的官方客户端不带请求头,把设备侧 /data/open-xiaoai/server.txt 写成 ws://:4399?token= 就行;coderzc 那个 fork 也可以在设备上加 OPEN_XIAOAI_TOKEN。改完要重启桥接器(它在子进程启动时快照)。关掉它等于允许同一局域网里任何主机往音箱注音、触发「已连接」并顶掉正在用的音箱槽位——只有设备实在带不了令牌时才关。 |
会话键 sessionKey | 文本 | agent:main:dsh-xiaoai-bridge | 桥接器侧的会话标识(形如 agent::),只喂桥接器(日志前缀、按会话覆盖音色)。DSH 侧的会话由插件按音箱设备区分,所以改这里不会换掉音箱对话所在的会话。 |
会话工作区 sessionCwd | 工作区选择 | 不指定(跟随默认工作区) | 音箱会话归到哪个工作区分组,agent 的工作目录也是它。宿主只服务有绝对 cwd 的会话,所以这里是选择器而不是文本框。改完对新会话生效。 |
音箱会话的 Agent 预设 agentPreset | 文本 | xiaoai | 音箱那个会话按哪个预设组建。留空用宿主默认预设;填了但没装不是错误:这次回落宿主默认,并留一条 agent-preset-missing 诊断。已经在跑的会话保留它创建时的预设。 |
唤醒与语音
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
唤醒词 wakeKeywords | 多行文本 | 你好肥鱼 | 每行一个;逗号、顿号也算分隔符。命中即进入 DSH 对话。它同时写进桥接器的 wakeup.keywords(喂给唤醒词模型)与 dsh.wakeup_keywords(路由到 DSH 后端)。改完 1 秒内热生效。别把全角逗号写进词里:它会被当分隔符切掉,那个词就永远唤不醒。 |
对话保持时长(秒) wakeupTimeout | 整数 1-600 | 20 | 一次唤醒之后,这次对话保持多久。 |
连续对话 continuousConversation | 布尔 | 关 | 关:一句话一次唤醒。开:一次唤醒可以接着说下一句,直到静默超时或说出退出词。这一项即使关掉也会照写进配置,免得桥接器模板里的默认值反过来压过设置页。 |
语音识别后端 asrBackend | 枚举 | sense_voice | 另两个选项是 paraformer 与 fire_red_asr。选完后按「后端 + 量化 + 模型目录」重建识别器;选了本机没装模型的后端不会把音箱弄哑:继续用已装好的识别器,只警告一次,并记一条 asr-model-unavailable。识别语言固定为中文(auto 会把短音频判成日文,插件不暴露这个开关)。 |
语音合成方式 ttsProvider | 枚举 | xiaoai(小爱原生) | 两个选项:小爱原生 / 豆包语音合成,区别见上面那张表。 |
豆包语音合成
这一组只在「语音合成方式」选「豆包语音合成」时显示,选「小爱原生」时整组隐藏(值还留着,切回「豆包语音合成」就原样回来)。它不折叠,也没有自己的标题——上面那个选项已经写着「豆包语音合成」,再给一组标题只是重复。只有在想要豆包音色(固定音色、复刻音色、统一语速)时才要配。
去哪拿:在火山引擎控制台开通豆包语音合成、创建应用,拿到 App ID 与 Access Token(App ID 与 Access Token 的位置见控制台使用 FAQ https://www.volcengine.com/docs/6561/196768 ,可选音色见音色列表 https://www.volcengine.com/docs/6561/1257544 )。App ID 是普通设置项;Access Token 直接粘在设置页的「豆包访问令牌」里,由插件写进 DSH 凭据仓,不进设置文件、不进日志。
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
豆包 App ID doubaoAppId | 文本 | 空 | 控制台里这个应用的 App ID。留空表示沿用桥接器模板里的占位值(等于没配)。 |
| 豆包访问令牌 | 密码输入框(写进 DSH 凭据) | 未设置 | 粘贴控制台里的 Access Token,点「保存令牌」由插件写进 DSH 凭据仓的 DOUBAO_ACCESS_KEY——凭据名固定在设置项 doubaoAccessKeyCredential 里(默认 DOUBAO_ACCESS_KEY),页面上不再让人填。令牌不进设置文件、不进草稿、不进日志,卡片上只显示「已配置 / 未配置」,旁边有「清除」。保存或清除后要重启桥接器:插件启动桥接器时把凭据解析成环境变量 DOUBAO_ACCESS_KEY,桥接器环境变量优先、取不到才回退渲染配置里的 tts.doubao.access_key。 |
豆包音色 doubaoSpeaker | 文本 | 空 | 用豆包时朗读的音色 ID(例如 zh_female_vv_uranus_bigtts);留空沿用桥接器配置里的默认音色。桥接器按音色前缀自动判定资源类型;复刻音色填控制台给的 S_xxxxxxxx。 |
豆包音频格式 doubaoAudioFormat | 枚举 | 沿用配置(留空) | 可选:沿用配置 / 自动 / PCM / MP3 / OGG Opus。留空表示不写,用桥接器模板里的 pcm;「自动」按文本长短在 PCM 与 MP3 之间挑。音箱本地播放用 PCM 首音最快。 |
边合成边播放 doubaoStream | 布尔 | 开 | 开:边合成边播,首音更快;关:整段合成完再播。这一项即使关掉也会照写进配置。 |
豆包语速 ttsSpeed | 数字 0.5-2 | 1 | 豆包朗读速度。只对豆包生效,小爱原生不看它。 |
注意:
App ID或缺访问令牌时,桥接器会明确报Doubao TTS credentials are not configured,不会静默换回小爱原生。
应答与兜底
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
唤醒应答 wakeupReplyText | 文本 | 肥鱼来了 | 唤醒词命中时先念的一句。留空表示不写,用桥接器模板默认(同样是「肥鱼来了」)。 |
退出应答 exitReplyText | 文本 | 肥鱼走了 | 连续对话结束时念的一句。 |
退出词 exitKeywords | 多行文本 | 退出、停止、再见 | 每行一个,说出任意一个就结束这次对话。只在「连续对话」打开时出现在页面上,值仍然保留。 |
兜底播报文本 fallbackText | 文本 | 连不上电脑,请稍后再试 | 桥接器还在跑、但联系不上插件时念的话:DSH 没在运行,或者插件路由不可达。 |
播报与回复器
音箱念出来的话不是模型的原始回复,而是先经过一次「回复器」调用做口语化润色(关掉「自动念出回复」之后,这一组里只有「任何会话都能让小爱说话」与「审批等待提示语」还有意义)。回复器是一次独立的模型调用,默认跟随该会话的模型路由。
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
自动念出回复 autoSpeak | 布尔 | 开 | 模型这一轮没调 xiaoai_speak 时,把回复正文交给回复器润色后念出来。关掉后只有模型主动调工具才出声,回复器那一组选项会收起来(值保留)。审批等待提示语不受这个开关影响:审批是工具卡在屏幕上等人处理,这一句照念(见下一行)。 |
任何会话都能让小爱说话 speakFromAnySession | 布尔 | 关 | 关:xiaoai_speak 只在音箱发起的会话里可用,工具与技能也只注册在那一层。开:桌面与网页会话也能调它。 |
播报字数上限 spokenMaxChars | 整数 40-2000 | 300 | 一条播报最多多少字;超了先让回复器精简一次,仍然超就截断。 |
审批等待提示语 approvalText | 文本 | 需要你到电脑上确认一下 | 工具卡在宿主审批流上时念的固定一句,关掉「自动念出回复」也照念(审批意味着有工具正等你到电脑上点一下)。审批请求的正文永远不会被念出来。 |
回复器模型 replyerProvider / replyerModel | 下拉(DSH 模型目录) | 跟随会话默认模型 | 从这台 DSH 已配置的模型里挑一个给回复器用;选项按 provider 分组,选中后写成「provider/model」两个键。选「跟随会话默认模型」等于把两项留空、不指定路由。选项直接读 DSH 的模型目录(读不到、目录为空、当前值已不在目录里,卡片都会写明并给「重试」)。切换只对回复器即时生效——语音会话的 agent 路由在会话创建时就定了,要重启 DSH 才换。 |
回复器参考轮数 replyerHistoryTurns | 整数 0-50 | 6 | 回复器能看到最近多少轮对话(只用于润色,不影响主会话的上下文)。 |
回复器失败提示语 replyerFailureText | 文本 | 回复器调用失败 | 回复器连续失败时改念这一句,免得把没润色的原文念出去。 |
人格与提示词
这一组都是文本提示词,改完立刻影响下一次播报。「人格设定」「说话风格」与「输出限制」只进回复器请求(只管念出来的话);「行动准则」与「语音消息附加提示」由桥接器追加在每条语音输入后面(影响音箱会话里模型怎么答)。
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
人格设定 personality | 多行文本 | 内置身份句 | 回复器的身份设定,默认是「你是一个通过小爱音箱和用户说话的语音助手,你的回答会被直接念出来。」改成别的内容后,回复器请求里会多出一行「关于你自己:…」。 |
说话风格 replyStyle | 多行文本 | 内置风格句 | 回复器怎么措辞,默认是「用日常、口语化的说法讲出来,就像对着用户说话一样。」 |
行动准则 behaviorStyle | 多行文本 | 内置准则 | 渲染成配置里的「行动准则:…」一段,由桥接器追加在每条语音输入后面:约束回答的写法——别用 Markdown、代码、emoji、颜文字、括号动作与 URL,一般 50 字以内、最多不超过 300 字,只有确实要逐字念出的内容才调 xiaoai_speak。提醒「回复会被念出来」的是下面那条「语音消息附加提示」。 |
输出限制 outputLimits | 多行文本 | 内置限制 | 写进回复器请求的硬性约束:只输出要念的话,不要 emoji、颜文字、Markdown 标记、括号里的动作或心理描写、URL、@ 提及,不要换行;长度是两档要求——一般 50 字以内、一两句话讲完,只有确实需要长回复时才展开,最多不超过 300 字。 |
语音消息附加提示 voiceRuleText | 多行文本 | 内置提示 | 桥接器把它追加在每条语音输入后面,告诉模型这条消息来自音箱、回复正文会被念出来。留空表示不写,用桥接器模板默认。 |
桥接器进程
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
随插件启动桥接器 autoStart | 布尔 | 开 | DSH 启动时顺手把桥接器进程拉起来。关掉后需要桥接器的调用会失败(xiaoai_speak 不会替你拉起进程)。 |
静默启动 silentStart | 布尔 | 关 | 开:桥接器连上音箱时不再出声(不发连接提示、不播启动提示音)。只在「随插件启动」打开时出现。 |
日志级别 logLevel | 枚举 | INFO | 桥接器进程的日志级别:DEBUG / INFO / WARNING / ERROR。走环境变量,改完要重启桥接器。 |
桥接器目录 bridgeDir | 文本(高级) | 空 | 桥接器源码目录;留空用插件包内的 bridge/。指到自己的 checkout 时,虚拟环境与模型都在那边。 |
Python 解释器 pythonPath | 文本(高级) | 空 | 跑桥接器的解释器;留空用 /.venv/Scripts/python.exe(Windows)。桥接器要求 Python 3.12 以上。 |
本地 API 服务
这一组管桥接器进程里那个 HTTP API Server,插件用它播报与探活(xiaoai_speak 走的就是它)。默认只监听本机。
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
启用本地 API 服务 apiServerEnabled | 布尔 | 开 | 关掉后桥接器不提供 API,xiaoai_speak 会直接回「桥接器 API Server 已在设置中关闭」。 |
监听地址 apiServerHost | 文本 | 127.0.0.1 | API Server 的监听地址。保持 loopback 最安全:改成 0.0.0.0 等于把音箱的播放与唤醒交给整个局域网。 |
监听端口 apiServerPort | 整数 1-65535 | 9092 | API Server 的端口。 |
访问令牌凭据名 apiServerTokenCredential | 文本(标识符) | XIAOAI_API_TOKEN | 存放访问令牌的 DSH 凭据名,不是令牌本身。插件用它给 /asr 做 bearer 门禁,并把它交给桥接器。改完要重启桥接器,环境变量在进程启动时快照。 |
安全边界
能触达 POST /plugin/xiaoai/asr 的调用方,等于拿到了这个 agent 的输入通道:请求正文会被当成一句用户消息。这条路径上插件不做任何语义拦截,也没有给语音轮次单独收窄工具集——一句话得到的权限,等于它落进去的那个会话的权限。
机械保障只有两条:/asr 的 bearer 门禁(fail closed:没配置令牌一律 503,bearer 对不上回 401,且不看来源地址),以及宿主既有的审批流(审批前音箱先念固定的一句提示,审批请求的正文永不念出,决定由宿主做)。其余插件路由(/config、/bridge/*、/data/wipe)按设计不做鉴权:它们和 DSH 在同一台机器、同一个信任边界里。要更强的约束只能靠会话与 agent 层(预设、审批策略、沙箱)。
最小可用配置
{
"enabled": true,
"deviceName": "小爱音箱",
"deviceHost": "192.168.1.191",
"wakeKeywords": "你好肥鱼",
"wakeupTimeout": 20,
"continuousConversation": false,
"agentPreset": "xiaoai",
"asrBackend": "sense_voice",
"ttsProvider": "xiaoai",
"autoSpeak": true,
"fallbackText": "连不上电脑,请稍后再试",
"autoStart": true,
"silentStart": false,
"logLevel": "INFO",
"apiServerEnabled": true,
"apiServerHost": "127.0.0.1",
"apiServerPort": 9092,
"apiServerTokenCredential": "XIAOAI_API_TOKEN"
}
这是设置页里最常改的一组键。页面保存时提交的是逐项 ops,这份 JSON 是同一组键的可读写法;要用 POST /plugin/xiaoai/config 提交就放进 patch 字段(patch 与 ops 不能同时给)。
配置怎么生效
配置有三条通道,改之前先看这一项走哪条:
| 通道 | 覆盖哪些设置 | 什么时候生效 |
|---|---|---|
渲染出的 /config.py | 唤醒词、对话保持时长、连续对话、退出词、唤醒应答、退出应答、兜底播报文本、会话键、设备名、语音合成方式、豆包 App ID 与音色/音频格式/流式/语速、语音识别后端、行动准则与语音消息附加提示 | 写盘即热重载:桥接器每秒轮询它的 mtime,一般 1 秒内生效 |
| 桥接器子进程的环境变量 | 日志级别、静默启动、本地 API 服务的开关与监听地址/端口、音箱名称与音箱地址、音箱连接鉴权(开着时写 DSH_XIAOAI_TOKEN),以及两枚凭据 | 进程启动时快照:改完要重启桥接器(设置页只重渲染配置,不会替你重启) |
| 只影响插件自己的会话 | 会话工作区、Agent 预设、任何会话都能让小爱说话、播报与回复器那一组 | 新会话按新值组建;已经在跑的会话保留它启动时的预设与路由 |
渲染出的 config.py 是覆盖层,不是模板:它把仓库里的 bridge/config.py 当模块加载,然后只覆盖上面那几个键。文本框留空表示这一项不写,桥接器继续用自己的默认值(而不是写一个空值进去)。插件还故意不碰这些键:dsh.rule_prompt(自动播放与连续对话用的那条约束)、openai.*、asr.doubao.*,以及 vad / kws / audio_input / xiaoai 这些段——要调就直接改仓库里的 bridge/config.py,改完同样会被热重载,见音箱侧的调参。
凭据怎么放:两枚秘密都存在 DSH 的凭据库里,插件启动桥接器时才解析出来放进子进程环境(XIAOAI_API_TOKEN、DOUBAO_ACCESS_KEY)。本地 API 服务的访问令牌在设置页里只填凭据名(字母或下划线开头的标识符,默认 XIAOAI_API_TOKEN),真值由插件在第一次启动时生成并写进凭据库;豆包访问令牌反过来——凭据名固定在 doubaoAccessKeyCredential 里(默认 DOUBAO_ACCESS_KEY),令牌本身直接粘在卡片的密码框里,由插件写进凭据库。同一枚访问令牌在「音箱连接鉴权」开着时还会以 DSH_XIAOAI_TOKEN 交给桥接器,用来校验拨上 4399 的设备。所以设置表和渲染出的 config.py 里都没有明文令牌。换凭据名或换令牌之后都要重启桥接器,新值才会被带上。
两种语音合成方式:
| 语音合成方式 | 写进配置 | 实际用谁 | 什么时候选 |
|---|---|---|---|
| 小爱原生(默认) | xiaoai | 音箱自带的合成 | 不想配豆包凭据,音色由设备决定 |
| 豆包语音合成 | doubao | 火山引擎的豆包语音合成 | 想要固定音色、复刻音色或统一语速 |
选中的值一定会写进渲染出的 config.py,不存在「留空跟随模板」。以前的「朗读音色」(ttsSpeaker)已经删掉:它和这个开关本来是同一件事的两套入口——桥接器的旧规则就是看那个音色值是不是 xiaoai 来决定走哪条路。豆包音色现在只在选「豆包语音合成」时由「豆包音色」给,插件不再写 dsh.tts_speaker,桥接器模板里那个默认值原样留着。
注意:写一个桥接器不认识的值不会报错:它只记一条
Unknown tts_provider=...,然后按音色回退。「没报错」不等于「接上了」。
最小可用配置
{
"enabled": true,
"deviceName": "小爱音箱",
"deviceHost": "192.168.1.191",
"wakeKeywords": "你好肥鱼",
"wakeupTimeout": 20,
"continuousConversation": false,
"agentPreset": "xiaoai",
"asrBackend": "sense_voice",
"ttsProvider": "xiaoai",
"autoSpeak": true,
"fallbackText": "连不上电脑,请稍后再试",
"autoStart": true,
"silentStart": false,
"logLevel": "INFO",
"apiServerEnabled": true,
"apiServerHost": "127.0.0.1",
"apiServerPort": 9092,
"apiServerTokenCredential": "XIAOAI_API_TOKEN"
}
这是设置页里最常改的一组键。页面保存时提交的是逐项 ops,这份 JSON 是同一组键的可读写法;要用 POST /plugin/xiaoai/config 提交就放进 patch 字段(patch 与 ops 不能同时给)。