@kmchai/cortico-world-voice 0.3.0 → 0.3.2
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.
- package/CHANGELOG.md +9 -0
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/models.ts +1 -1
- package/src/triggers.ts +30 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## 0.3.2
|
|
2
|
+
|
|
3
|
+
- IndexTTS2 的模型提示词明确:`emotion` / `instruct` 传自然语言,由服务映射到 `emotion_text`;不要在正文写 `emotion_text` 或方括号标签。服务 0.4.2 起这两个 options 不再由模型页覆盖。
|
|
4
|
+
|
|
5
|
+
## 0.3.1
|
|
6
|
+
|
|
7
|
+
- 自动朗读不再把 `end_turn` 当成实质工具调用。模型说完正文再调 `end_turn` 结束这一轮时,正文照常按概率朗读;仍会压掉与 `web_search` 等实际工具同轮出现的中间正文。
|
|
8
|
+
- 网关 0.1.1 已把 World 的 `outputTap` 扇进主 session;此前网关 bot 里概率朗读拿不到正文。
|
|
9
|
+
|
|
1
10
|
## 0.3.0
|
|
2
11
|
|
|
3
12
|
- 系统 Config tab 移除「音色与情绪」组;音色、情绪控制、默认情绪、默认表演指令、语速与副语言标签移到「模型」页。
|
package/README.md
CHANGED
|
@@ -77,7 +77,7 @@ World 页的「模型」tab 选模型与音色,并按服务 `/v1/models` 返回
|
|
|
77
77
|
|
|
78
78
|
只想响应工具与显式请求、不要随机朗读:保留 `autoSpeak: true`,把 `autoProbability` 设为 `0`。把 `autoSpeak` 整个关掉会让 tap 不再挂载,显式关键词也就看不到正文了;这时只保留 `voice_speak` 工具触发。
|
|
79
79
|
|
|
80
|
-
自动朗读依赖 Persona 把已挂载 World 的 `outputTap()` 扇进主 session
|
|
80
|
+
自动朗读依赖 Persona 把已挂载 World 的 `outputTap()` 扇进主 session;`cormini`、Coopanion 与 `Kmch AI · 网关` 0.1.1 起都这样做。只想要工具触发时,把 `autoSpeak` 关掉即可。模型说完正文再调 `end_turn` 不影响朗读;与搜索等实际工具同轮的中间正文仍不念。
|
|
81
81
|
|
|
82
82
|
## 情绪、语速与标签
|
|
83
83
|
|
|
@@ -91,7 +91,7 @@ World 页的「模型」tab 选模型与音色,并按服务 `/v1/models` 返回
|
|
|
91
91
|
| `tags` | 不发情绪参数,只用正文标签。 | 正文标签。 |
|
|
92
92
|
| `none` | 都不发。 | — |
|
|
93
93
|
|
|
94
|
-
OpenAI 兼容条目把 `instructions` / `emotion` / `speed`
|
|
94
|
+
OpenAI 兼容条目把 `instructions` / `emotion` / `speed` 直接放进请求。IndexTTS2 把 `instructions` 或 `emotion` 映射成 `use_emotion_text: true` 与 `emotion_text`;这两个参数由语音的「默认情绪 / 默认表演指令」或单句 `emotion` / `instruct` 驱动,不在模型参数面板里单独出现。其他模型可在服务面板里覆盖 profile 的字段映射,语音不用改配置。
|
|
95
95
|
|
|
96
96
|
`response_format` 每次都带;`model` 与 `voice` 配了才带,工具参数里的音色优先于配置默认值。
|
|
97
97
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kmchai/cortico-world-voice",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Cortico World: voice tools, probabilistic reading, emotion and paralinguistic tags, text filtering and local playback; speech is synthesized by the Kmch AI service over HTTP",
|
|
6
6
|
"author": "kmchai",
|
package/src/models.ts
CHANGED
|
@@ -28,7 +28,7 @@ const INDEXTTS2: ModelInfo = {
|
|
|
28
28
|
match: ['index-tts2', 'index_tts2', 'index-tts2.5', 'index_tts2_5', '2.5'],
|
|
29
29
|
label: 'IndexTTS2',
|
|
30
30
|
tags: false,
|
|
31
|
-
prompt: '
|
|
31
|
+
prompt: '情绪用 voice_speak 的 emotion 或 instruct 传自然语言,服务会映射到 emotion_text;不要在正文写 emotion_text 或 [Laughter] 这类方括号标签。采样/时长参数在语音模型页配置,不要写进正文.',
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
const GENERIC: ModelInfo = {
|
package/src/triggers.ts
CHANGED
|
@@ -8,6 +8,8 @@ import type { VoiceConfigSection } from './types.ts';
|
|
|
8
8
|
|
|
9
9
|
/** 工具参数里可能放正文的字段,按顺序取第一个字符串。 */
|
|
10
10
|
const TEXT_FIELDS = ['script', 'question', 'text', 'content', 'message', 'body'] as const;
|
|
11
|
+
/** 只结束这一轮的控制工具,不算「这一轮做了别的事」。 */
|
|
12
|
+
const FLOW_TOOLS = new Set(['end_turn']);
|
|
11
13
|
|
|
12
14
|
export function splitList(raw: string): string[] {
|
|
13
15
|
return raw
|
|
@@ -60,15 +62,27 @@ interface StreamLike {
|
|
|
60
62
|
|
|
61
63
|
export class VoiceTap implements OutputTap {
|
|
62
64
|
private text = '';
|
|
63
|
-
|
|
64
|
-
private readonly
|
|
65
|
+
/** 这一轮出现过的函数调用:output_index -> name;名字未知时存空串。 */
|
|
66
|
+
private readonly callNames = new Map<number, string>();
|
|
67
|
+
/** 没有 output_index 的函数调用;无法判断是不是 end_turn,按实质调用处理。 */
|
|
68
|
+
private orphanCall = false;
|
|
65
69
|
private readonly emitted = new Set<number>();
|
|
66
70
|
|
|
67
71
|
constructor(private readonly opts: TapOptions) {}
|
|
68
72
|
|
|
73
|
+
private noteCall(index: number, name: string): void {
|
|
74
|
+
if (index < 0) {
|
|
75
|
+
if (name && FLOW_TOOLS.has(name)) return;
|
|
76
|
+
this.orphanCall = true;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (name) this.callNames.set(index, name);
|
|
80
|
+
else if (!this.callNames.has(index)) this.callNames.set(index, '');
|
|
81
|
+
}
|
|
82
|
+
|
|
69
83
|
/** 工具调用完成:同一个 output_index 只交出去一次。 */
|
|
70
84
|
private takeCall(index: number, name: string, args: string): void {
|
|
71
|
-
if (!name || name.startsWith('voice_')) return;
|
|
85
|
+
if (!name || name.startsWith('voice_') || FLOW_TOOLS.has(name)) return;
|
|
72
86
|
if (this.emitted.has(index)) return;
|
|
73
87
|
if (!toolNames(this.opts.cfg()).has(name)) return;
|
|
74
88
|
const text = textOfToolArgs(args);
|
|
@@ -85,38 +99,39 @@ export class VoiceTap implements OutputTap {
|
|
|
85
99
|
return;
|
|
86
100
|
}
|
|
87
101
|
if (e.type === 'response.output_item.added' && e.item?.type === 'function_call') {
|
|
88
|
-
this.
|
|
89
|
-
if (e.item.name) this.names.set(index, e.item.name);
|
|
102
|
+
this.noteCall(index, e.item.name ?? '');
|
|
90
103
|
return;
|
|
91
104
|
}
|
|
92
105
|
// 有的传输只给参数结束事件,不带完整 item;名字从 added 里取。
|
|
93
106
|
if (e.type === 'response.function_call_arguments.done') {
|
|
94
|
-
const name = this.
|
|
107
|
+
const name = this.callNames.get(index) ?? '';
|
|
108
|
+
this.noteCall(index, name);
|
|
95
109
|
if (name) this.takeCall(index, name, e.arguments ?? '');
|
|
96
110
|
return;
|
|
97
111
|
}
|
|
98
112
|
if (e.type === 'response.output_item.done' && e.item?.type === 'function_call') {
|
|
99
|
-
this.
|
|
100
|
-
|
|
113
|
+
const name = e.item.name ?? this.callNames.get(index) ?? '';
|
|
114
|
+
this.noteCall(index, name);
|
|
101
115
|
if (name) this.takeCall(index, name, e.item.arguments ?? '');
|
|
102
|
-
this.names.delete(index);
|
|
103
116
|
}
|
|
104
117
|
}
|
|
105
118
|
|
|
106
119
|
onRoundEnd(): void {
|
|
107
120
|
const text = this.text;
|
|
108
121
|
this.text = '';
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
// 只有结束轮次的调用不算实质动作;纯文本回复照样可以自动朗读。
|
|
123
|
+
const nonFlow = this.orphanCall
|
|
124
|
+
|| [...this.callNames.values()].some((name) => name === '' || !FLOW_TOOLS.has(name));
|
|
125
|
+
this.callNames.clear();
|
|
126
|
+
this.orphanCall = false;
|
|
112
127
|
this.emitted.clear();
|
|
113
|
-
if (
|
|
128
|
+
if (!nonFlow && text.trim()) this.opts.onText(text, 'text');
|
|
114
129
|
}
|
|
115
130
|
|
|
116
131
|
onAbort(): void {
|
|
117
132
|
this.text = '';
|
|
118
|
-
this.
|
|
119
|
-
this.
|
|
133
|
+
this.callNames.clear();
|
|
134
|
+
this.orphanCall = false;
|
|
120
135
|
this.emitted.clear();
|
|
121
136
|
this.opts.onAbort?.();
|
|
122
137
|
}
|