koishi-plugin-local-vitspro 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/package.json +2 -1
  3. package/readme.md +91 -106
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1 (2026-11-02)
4
+
5
+ - 文档:重写 README,聚焦「插件做什么 / 机制怎么转 / 怎么安装与配置 / 常见问题」,补 npm 与 GitHub 徽章,末尾标注改自 `SilverWolfHoney/koishi-plugin-local-vits`。代码无改动。
6
+
3
7
  ## 0.1.0 (2026-11-02)
4
8
 
5
9
  首个可自用版本,按 `开发计划.md` v1.2 实现。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-local-vitspro",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "多后端 TTS 聚合插件:自动解包 chatluna 消息、长文本切片与音频拼接、按实测推理速度自适应超时与熔断,支持 vits-simple-api / GPT-SoVITS / fish-audio / QQ 声聊",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -89,3 +89,4 @@
89
89
  }
90
90
  }
91
91
 
92
+
package/readme.md CHANGED
@@ -1,24 +1,78 @@
1
1
  # koishi-plugin-local-vitspro
2
2
 
3
- > 仓库:[github.com/bigxingyun/koishi-plugin-local-vitspro](https://github.com/bigxingyun/koishi-plugin-local-vitspro) · 问题反馈:[Issues](https://github.com/bigxingyun/koishi-plugin-local-vitspro/issues)
3
+ [![npm](https://img.shields.io/npm/v/koishi-plugin-local-vitspro)](https://www.npmjs.com/package/koishi-plugin-local-vitspro)
4
+ [![downloads](https://img.shields.io/npm/dm/koishi-plugin-local-vitspro)](https://www.npmjs.com/package/koishi-plugin-local-vitspro)
5
+ [![license](https://img.shields.io/npm/l/koishi-plugin-local-vitspro)](https://github.com/bigxingyun/koishi-plugin-local-vitspro/blob/main/LICENSE)
6
+ [![stars](https://img.shields.io/github/stars/bigxingyun/koishi-plugin-local-vitspro)](https://github.com/bigxingyun/koishi-plugin-local-vitspro)
4
7
 
5
- 多后端 TTS 聚合插件,针对 [koishi-plugin-local-vits](https://github.com/SilverWolfHoney/koishi-plugin-local-vits)(luna-vits 的本地化改版)的三个已知问题重写:
8
+ Koishi 的多后端 TTS 聚合插件。把 `vits-simple-api`、`GPT-SoVITS v2`、`fish-audio`、`QQ 声聊` 收进同一个 `vits` 服务,供 [chatluna](https://github.com/ChatLunaLab/chatluna)、[chatluna-character](https://github.com/ChatLunaLab/chatluna-character) 直接调用。
6
9
 
7
- 1. **序列化格式被当成正文**:chatluna / chatluna-character 传进来的 `<message><voice>…</voice></message>` 会被正确解包,长句能整条出语音;
8
- 2. **熔断不合理**:按「后端实测推理速度 × 本次文本长度」计算超时,失败后有限退避 + 半开探测,每次降级都有提示;
9
- 3. **长文本直接推理**:插件层切片(中文逗号 / 中文句号 / 英文逗号 / 英文句号 / 不切),再把音频拼回一条。
10
+ 它重点解决三件事:
10
11
 
11
- 支持后端:`vits-simple-api`、`GPT-SoVITS v2`(`/tts` API)、`fish-audio`、`QQ 声聊`。gradio 暂不支持。
12
+ - **长句不被序列化格式切碎**——chatluna 传来的 `<message><voice>…</voice></message>` 会被解包成纯文本,整段合成;
13
+ - **超时按后端真实速度算**——用基准测速拟合「字数 → 耗时」,不同后端、不同文本长度给不同预算,而不是拍一个固定 60 秒;
14
+ - **后端抽风时快速降级**——有限退避 + 半开探测,降级有提示、可查状态,不会把 bot 卡住。
12
15
 
13
- 一句话简介:让 chatluna 的长回复能整条念出来,并且在后端抽风时快速降级而不是把 bot 卡住。
16
+ ## 工作机制
17
+
18
+ ### 1. 输入归一化
19
+
20
+ ```
21
+ <output><message quote="123"><voice id="1">今天天气不错…</voice></message></output>
22
+ │ 解包容器 / 反转义 / 去 Markdown / 去元素 / 去 emoji
23
+ ▼
24
+ 今天天气不错…
25
+ ```
26
+
27
+ `<voice id>` 会作为讲者提示;二次序列化的 `&lt;message&gt;` 也能还原。
28
+
29
+ ### 2. 长文本切片
30
+
31
+ 整段不超过 `split.maxSegmentChars`(默认 120)就不切;超过才在窗口内找**最后一个句末标点**切开:
32
+
33
+ - 支持 `zh_period`(中文句号)/ `zh_comma`(中文逗号)/ `en_period`(英文句号)/ `en_comma`(英文逗号)/ `none`(不切);
34
+ - 引号、括号、URL、小数、英文缩写内部不切;
35
+ - 过短的段并入相邻段,段数超过上限时合并尾段。
36
+
37
+ ### 3. 音频聚合
38
+
39
+ 多段合成后,若都是**同参数 PCM WAV**,直接拼接成一条语音;参数不一致(或 mp3 等格式)时降级为多段发送,首段作为回复、其余用 `session.send()` 补发。
40
+
41
+ ### 4. 自适应超时
42
+
43
+ `vitsbench` 按长度阶梯(默认 6 / 12 / 24 字)实测后端,最小二乘拟合:
44
+
45
+ ```
46
+ 估算耗时 = a + b × 字数
47
+ 超时 = clamp(overhead + factor × 估算耗时, min, max)
48
+ ```
49
+
50
+ 拟合结果写回内存,下一条消息立即生效。也可以用 `timeout.mode = manual` 固定超时,两者是并列模式。
51
+
52
+ ### 5. 熔断与降级
53
+
54
+ 每个后端一份状态机:连续失败到阈值进入 `open`,冷却后只放一个探测请求(`half-open`),成功即恢复。
55
+
56
+ - `AUTH`(401/403)一次即永久熔断,`tts on` 或改配置才恢复;
57
+ - `BAD_REQUEST`(400/422)不熔断,只降级并记日志;
58
+ - 降级提示按原因 10 分钟节流;
59
+ - 工具路径(chatluna-character)抛 `VitsDegradeError` 让调用方用解析好的文字兜底;服务路径(chatluna 渲染器)直接回纯文本,避免整条回复被 `middleware_error` 吃掉。
14
60
 
15
61
  ## 安装
16
62
 
63
+ 在 Koishi 控制台的「插件市场」搜索 `local-vitspro` 安装,或:
64
+
17
65
  ```bash
18
66
  npm i koishi-plugin-local-vitspro
19
67
  ```
20
68
 
21
- 在 `koishi.yml` 里启用:
69
+ 升级:
70
+
71
+ ```bash
72
+ npm update koishi-plugin-local-vitspro
73
+ ```
74
+
75
+ ### 最小配置
22
76
 
23
77
  ```yaml
24
78
  plugins:
@@ -28,6 +82,7 @@ plugins:
28
82
  - type: vits-simple-api
29
83
  label: 本地
30
84
  url: http://127.0.0.1:23456
85
+ autoPullSpeaker: true
31
86
  speakers:
32
87
  - name: 测试
33
88
  id: 0
@@ -35,130 +90,60 @@ plugins:
35
90
  languages: [ZH]
36
91
  ```
37
92
 
38
- 插件实现 `@initencounter/vits` 的 `vits` 服务,chatluna 与 chatluna-character 不需要改任何配置。
93
+ 配好后发 `tts speakers` 确认讲者,再发 `vitsbench` 让超时模型吃到真实速度。
39
94
 
40
- ## 配置
95
+ ## 支持的后端
41
96
 
42
- 配置全部是 Koishi Schema,控制台里可以直接改。几个关键点:
43
-
44
- | 配置 | 默认 | 说明 |
45
- | --- | --- | --- |
46
- | `defaultSpeaker` | 空 | 不指定讲者时用谁;`tts speakers` 可查完整 key |
47
- | `backends[]` | `[]` | 后端列表,`type` 决定字段(见下) |
48
- | `split.mode` | `zh_period` | 切片模式:`none` / `zh_period` / `zh_comma` / `en_period` / `en_comma` |
49
- | `split.maxSegmentChars` | `120` | 单段上限;整段文本低于该值时不会切 |
50
- | `aggregate.type` | `auto` | `auto` 能拼就拼(WAV 同参数),否则多段发送 |
51
- | `timeout.mode` | `auto` | `auto` 按测速模型算超时;`manual` 用固定 `timeout.manual` |
52
- | `breaker.mode` | `auto` | `auto` 退避 + 半开;`manual` 固定冷却;`off` 永不熔断 |
53
- | `degrade.tool` | `error` | 工具路径(character)降级时抛错,让它用解析好的文字兜底 |
54
- | `degrade.service` | `text` | chatluna 渲染器路径降级时回文字(抛错会丢掉整条回复) |
55
- | `toolCall.minChars` / `maxChars` | `2` / `300` | 工具调用低于/高于该字数不合成 |
56
-
57
- **后端字段速查**
58
-
59
- | 后端 | 必填 | 常用 |
97
+ | 后端 | 必填 | 说明 |
60
98
  | --- | --- | --- |
61
- | `vits-simple-api` | `url` | `apiKey`(X-API-KEY)、`username`/`password`(Basic Auth)、`autoPullSpeaker` |
62
- | `GPT-SoVITS2` | `url` | `config.*` 全局请求参数;讲者里可写 `gpt_weights`/`sovits_weights`/`ref_audio_path`/`prompt_text` |
63
- | `fish-audio` | `apiKey` | `baseUrl`、讲者 `reference_id` |
64
- | `qq-voice` | `accountId` | 讲者 `characterId`;只能群聊,走 OneBot |
65
-
66
- 讲者条目允许写任意额外字段:vits-simple-api 的 `noise` / `noise_w` / `length` / `sdp_ratio` / `preset` / `prompt_lang` / `reference_audio`,GPT-SoVITS 的 `speed` / `top_k` / `temperature` 等都会被透传。
67
-
68
- ### 文件模式
69
-
70
- 后端多、想直接改文本时,可以设 `backendsSource: file`,插件会读 `backendsFile`(默认 `data/local-vitspro/config.yml`)并在文件变化后热更新。文件结构就是 `backends[]` 的 YAML 数组。
71
-
72
- > 文件模式只读自己的路径,**不会**读取旧插件(luna-vits / local-vits)的 `data/local-vits/`,也不提供旧配置迁移。
73
-
74
- ## 启动顺序(后端比 Koishi 启动慢)
75
-
76
- TTS 后端经常比 Koishi 晚几十秒才监听端口,所以:
77
-
78
- - 插件加载时会拉一次讲者;**拉不到不会把空列表当成成功**,而是标记为「未拉全」;
79
- - 之后按 5s → 9s → 16s… 指数退避在后台重试,最长 2 分钟一次,拉到即停;
80
- - 讲者列表还有 60s 的 TTL:后端新增了讲者,最多一分钟后自动生效;
81
- - 真实消息进来时如果还没有讲者,会先强制刷新一次(5s 冷却),仍拿不到才降级为文字;
82
- - 手动补拉:`tts reload` / `tts speakers`(`authority: 3`)。
99
+ | `vits-simple-api` | `url` | 支持 `X-API-KEY`、HTTP Basic Auth、自动拉取讲者(`/voice/speakers`) |
100
+ | `GPT-SoVITS2` | `url` | `/tts` v2 接口,按需切换 GPT / SoVITS 底模 |
101
+ | `fish-audio` | `apiKey` | 云端服务,按 `reference_id` 选音色 |
102
+ | `qq-voice` | `accountId` | QQ 声聊,仅群聊,依赖 OneBot |
83
103
 
84
- 日志里会看到 `讲者列表还没拉全(TTS 后端可能尚未启动),N 秒后重试`,后端起来后变成 `讲者列表已就绪:N 个`。
104
+ 讲者条目可以写任意额外参数(`noise`、`noise_w`、`length`、`sdp_ratio`、`preset`、`prompt_text`、`speed` 等),会透传给对应后端。
85
105
 
86
106
  ## 指令
87
107
 
88
108
  | 指令 | 说明 | 权限 |
89
109
  | --- | --- | --- |
90
- | `vitspro <text>`(别名 `lunavits`) | 合成语音,`-s <讲者名或 id>` | 1 |
91
- | `vitsbench [backend]` | 基准测速,`-n` 每档次数、`-s` 讲者、`-r` 清样本、`-f` 清熔断 | 3 |
110
+ | `vitspro <text>`(别名 `lunavits`) | 合成语音,`-s` 指定讲者 | 1 |
111
+ | `vitsbench [backend]` | 基准测速并写回超时模型,`-n` 次数、`-s` 讲者、`-r` 清样本、`-f` 清熔断 | 3 |
92
112
  | `tts` | 状态:后端、讲者数、拟合参数、熔断、建议超时 | 3 |
93
- | `tts on` / `tts off` / `tts 开` / `tts 关` | 开启并清熔断 / 临时关闭语音 | 3 |
113
+ | `tts on` / `tts off` | 开启并清熔断 / 临时关闭语音 | 3 |
94
114
  | `tts speakers` | 讲者列表(含 `id`),会强制重新拉取 | 3 |
95
- | `tts reload` | 立即重新拉取讲者列表(后端刚启动时用) | 3 |
96
- | `tts debug <text>` | 打印归一化、切片、决策与超时,**不发请求** | 3 |
115
+ | `tts reload` | 立即重新拉取讲者列表 | 3 |
116
+ | `tts debug <text>` | 打印归一化、切片、决策与超时,不发请求 | 3 |
97
117
  | `tts reset [backend]` | 清空测速样本与熔断状态 | 3 |
98
118
 
99
- ## 测速与熔断
119
+ ## 与 chatluna 配合
100
120
 
101
- `vitsbench` 会按 `bench.ladder`(默认 6 / 12 / 24 字)逐档合成,取中位耗时,再用最小二乘拟合:
121
+ 插件实现了 `@initencounter/vits` 的 `vits` 服务,chatluna 与 chatluna-character 不需要改动,直接选 `voice` / `mixed-voice` 渲染模式即可。
102
122
 
103
- ```text
104
- 估算耗时 = a + b × 字数
105
- 超时 = clamp(overhead + factor × 估算耗时, min, max)
106
- ```
123
+ 另外可选注册 chatluna 工具 `vits_say`(`chatlunaTool.enabled`),让模型主动把某句话变成语音。依赖全部是可选 peer + 动态 `import`,没装 chatluna 也能正常加载。
107
124
 
108
- 拟合结果写回内存,**下一条真实消息立刻生效**。`ttsbench` 的报告长这样:
125
+ ## 常见问题
109
126
 
110
- ```text
111
- 基准测试:1 个后端,每档 2 次取中位
112
- · 本地
113
-  6 字:1.2s(成功 2 次)
114
-  12 字:1.7s(成功 2 次)
115
-  24 字:2.8s(成功 2 次)
116
-  拟合:a=720ms b=83.0ms/字 · 样本 6 · 建议超时(60字) 10.7s · closed
117
- ```
127
+ **后端比 Koishi 启动慢,讲者列表空着**
118
128
 
119
- 熔断是每后端(可选每讲者)一个状态机:
129
+ 不会。拉取失败会标记为「未拉全」并后台按 5s → 9s → 16s… 退避重试,最长 2 分钟一次;拉到即停,最多一分钟后还会自动刷新。也可以手动 `tts reload`。
120
130
 
121
- - 连续 `failureThreshold` 次失败 → 熔断,冷却后只放一个半开探测;
122
- - `AUTH`(401/403)一次即永久熔断,`tts on` 或改配置才恢复;
123
- - `BAD_REQUEST`(400/422)不熔断,只降级;
124
- - 降级时按 `notice.interval`(默认 10 分钟)给一次中文提示。
131
+ **没声音 / 只有文字**
125
132
 
126
- ## 切片与聚合
133
+ 先发 `tts` 看后端状态与熔断;`AUTH` 熔断检查账密或 API Key;再用 `tts debug <文本>` 看决策与超时。降级时会有一次中文提示(可关)。
127
134
 
128
- - 整段文本 ≤ `split.maxSegmentChars` 时不切;超过才按标点在窗口内找最后一句切开;
129
- - 引号 / 括号 / URL / 小数 / 英文缩写内部不切;
130
- - 过短的段并入相邻段,段数超过 `split.maxSegments` 时合并尾段;
131
- - 多段音频:WAV 且参数(编码/声道/采样率/位深)一致才拼接,否则 `aggregate.type=auto` 降级为多段发送(`multi` 时首段作为返回值,其余用 `session.send` 补发)。
135
+ **一句长话变成了好几条语音**
132
136
 
133
- ## 与 chatluna / chatluna-character 配合
137
+ 说明 WAV 参数不一致、走了多段降级。确认后端都返回同采样率 WAV,或把 `aggregate.type` 设为 `concat` 观察日志。
134
138
 
135
- - **工具调用路径**(character 的输出 / `character_reply` 工具):输入里带 `<message>` / `<voice>` 标记时一律按工具路径处理;
136
- - 越界(太短 / 太长)或熔断时按 `degrade.tool` 处理,默认抛 `VitsDegradeError`(带 `code` 与已归一化的 `text`)。character 会用它解析好的文字元素兜底,所以用户看到的仍是干净文字;
137
- - **chatluna 核心渲染器路径**(`outputMode = voice`):没有标记、带 `session`,走 `degrade.service`,默认回纯文本——因为这条路径抛错会被 chatluna 的 `_handleMiddlewareError` 接住并替换成 `middleware_error`,整条回复会丢;
138
- - 可选注册 chatluna 工具 `vits_say`(`chatlunaTool.enabled`):装了 `koishi-plugin-chatluna` 才会注册,依赖全部动态 `import`,没装不影响加载。
139
+ **想用旧插件的配置**
139
140
 
140
- ## 已知限制
141
+ 不支持迁移,也不读取旧路径。直接在控制台按上面的示例配一遍,或把后端列表写成 YAML 并设 `backendsSource: file`。
141
142
 
142
- - 开发环境没有真实 TTS 后端:单元测试与冒烟测试用 **stub HTTP** 校验请求格式(URL / 表单字段 / JSON 字段 / 鉴权头)与音频聚合,**未对着真实后端做联调**,第一次接真后端时建议先跑 `vitsbench`;
143
- - gradio 后端暂不支持(需要时会单开一个里程碑);
144
- - 不做自动翻译(旧插件的 `autoTranslate` 未迁移);
145
- - 不做音频转码:只拼 PCM WAV,mp3 等格式走多段发送;
146
- - QQ 声聊依赖 OneBot 的 `send_group_ai_record`,私有实现差异较大。
143
+ ## 来源
147
144
 
148
- ## 开发
149
-
150
- ```bash
151
- npm i
152
- npm run typecheck # tsc --noEmit
153
- npm run test # vitest(117 个用例,纯 Node,不需要 koishi 运行时)
154
- npm run smoke # 构建产物 + 真实 koishi Context 冒烟(11 项)
155
- npm run verify # 以上三个一起跑
156
- ```
157
-
158
- > `smoke` 走 CJS:koishi 4.18 的 ESM 产物在 Node 24 下加载会因 loader 的 default 互操作失败,而 koishi 实际加载插件用的就是 CJS。
145
+ 本项目改自 [SilverWolfHoney/koishi-plugin-local-vits](https://github.com/SilverWolfHoney/koishi-plugin-local-vits)——它是 [ChatLunaLab/luna-vits](https://github.com/ChatLunaLab/luna-vits) 的本地化改版,多后端抽象、适配器思路与指令设计都来自上游,感谢原作者。
159
146
 
160
147
  ## License
161
148
 
162
- MIT
163
-
164
-
149
+ [MIT](./LICENSE)