- name
- character-tts-dialogue
- description
- 当用户希望 Codex 聊天回复既保持选定角色风格、又通过 TTS 播放时使用。覆盖所有可见 Codex 输出、从 voice-references 选择角色人格/风格、先提交语音再显示文本、语音语言和屏幕语言不同的情况、支持时的情绪提示、工作区归属的缓存/输出,以及 UTF-8 中文路径。
# 角色 TTS 对话
> 当前本机只允许本地 ONNX-VITS、Qwen3-TTS 与 IndexTTS2。所有云端/API worker 路线已归档,不得选择或启动。
当用户希望 Codex 回复以当前语音角色轻度扮演,并且被朗读出来时,使用这个 skill。
## 必需音频流程
所有对话音频生成都必须使用低延迟 OumuQ 工作流:
1. 保持 OumuQ 作为本地路由层运行,通常是 `http://127.0.0.1:8780`。
2. 按引擎保持可按请求切换角色的共享 TTS worker 在 OumuQ 后面运行。
3. 每次发言都通过本地 HTTP 请求提交给 OumuQ `POST /api/speak`,优先使用 Node REPL `fetch`。
4. 确认 OumuQ 接受任务,通常是 `202 queued`,或返回 JSON 且 `status` 为 `queued`,然后立刻显示屏幕文本。
5. 让 worker 在后台异步生成;单一 OumuQ 进程按提交顺序 FIFO 播放,并用主机级互斥锁禁止多个进程叠音。
6. 只有在 OumuQ 不可用,或用户明确要求绕过路由层时,才直接提交给 worker `POST /speak`。
7. 普通回复不要每次都启动新的 PowerShell/Python 命令;除非常驻流程不可用,且用户接受 fallback。
延迟规则:直接 HTTP 不是零延迟。它仍然要等待本地请求处理、OumuQ 路由、worker 排队、音频生成和播放。使用持久 runtime,例如 Node REPL `fetch` 调用 `POST http://127.0.0.1:8780/api/speak`,目的在于去掉可避免的 shell 启动开销。普通逐句语音不要把 PowerShell、`curl`、Python one-shot 或其他新 shell 进程当作常规路径。
在 Qwen API 对话模式下,OumuQ 通常运行在 `http://127.0.0.1:8780`,并转发到共享 `qwen-tts-api` worker。worker 每次根据请求 `character_id` 从注册表加载 `api_voice_id`、模型、语言提示和发声说明;`--character-id` 只是 legacy 默认值。不同会话可以交错使用不同云端角色,不需要也不允许为切角色重启共享端口。
这个 skill 通过 `tts-router` 路由,然后使用一个特定 provider 或 engine 的 worker:
`%USERPROFILE%\.codex\skills\indextts2-audio\scripts\indextts2_worker.py`
`%USERPROFILE%\.codex\skills\qwen-tts-audio\scripts\qwen_tts_worker.py`
`OumuQ\app\workers\onnx_vits`(固定多说话人极速 worker,默认端口 `8764`)
## 行为规则
- 普通回复保持简短。
- 每一段普通、用户可见的 Codex 输出都必须通过 worker 播放,包括中间进度和最终回复。
- 默认行为:屏幕回复和语音文本表达同一含义。当角色的语音语言和屏幕语言不同时,两者不必逐字一致。
- 当前角色规则:从 `voice-references/reference-index.json` 选择当前角色,并把角色风格轻度应用到屏幕可见的 Codex 回复中。
- 技术正确性优先。角色表演只能增加风格,不能隐藏信息,也不能让工程说明变模糊。
- 角色风格必须来自当前角色注册字段,例如 `style_summary`、`speech_language` 和 `visible_language`;不要在这个 skill 中硬编码某个具体角色的行为。
- 人格一致性规则:启用角色对话时,屏幕回复和 TTS 语音文本都要尽量贴近当前角色的说话方式。保留用户要求的语言和技术清晰度,但措辞、称呼、句子节奏、情绪语气、活泼或正式程度,应参考角色的 `style_summary` 和 voice index 示例。
- 屏幕角色一致性规则:屏幕上的 Codex 回复也必须在角色中。不要屏幕文本是中性 Codex 口吻,而只有 TTS 文本在扮演。技术准确性、安全边界和拒绝规则仍然适用,但表达时应通过当前角色的方式说出来。
- 跨语言语气保留规则:当语音语言和屏幕语言不同时,翻译含义时要尽量保留角色语气、关系姿态、礼貌程度、调侃/柔和感、口头禅和句子节奏。不要把有角色感的日语表达压平成普通中文助手总结;应该转写成带同样意图和情绪的角色化中文。
- 不要只把角色声线当成音频滤镜。角色条目是表演指南:屏幕文本、翻译后的语音文本、参考音频选择和情绪提示都应该指向同一个角色方向。
- 可用时使用角色索引台词作为风格示例。优先参考相近匹配条目中的常用称呼、口头禅、情绪节奏和礼貌程度;除非用户明确要求,不要长段直接引用。
- 角色切换规则:每当当前角色变化,先从 `voice-references/reference-index.json` 解析该角色的 `character_folder`,再读取该文件夹的 `README.md`,然后再组织下一次屏幕回复。README 通常包含对话示例、使用说明和角色特有说话模式,应作为主要风格指南。
- 如果 `speech_language` 与 `visible_language` 不同,语音文本使用 `speech_language`,同时保留屏幕回复的同一含义。
- 如果 Codex 连续发送多段可见消息,每一段都要提交语音。
- 默认顺序:先提交语音文本给 TTS worker,确认 worker 接受任务,再显示普通文本给用户。
- 除非用户要求严格的“先听到,再看到”,否则不要等待音频生成或播放完成。
- “先提交,后显示文本”的顺序可以隐藏一部分生成延迟,让语音聊天体验更快。
- 低延迟对话模式中,使用持久本地 HTTP client,例如 Node REPL `fetch`,不要每条消息都启动新的 PowerShell。worker 应该很快返回 `202 queued`,生成和播放继续在后台进行。
- 优先使用 OumuQ `POST /api/speak`,而不是直接 worker `POST /speak`。OumuQ 会规范化 canonical request,解析角色默认值,记录 `runs/YYYY-MM-DD/...` 元数据,并把支持或无害字段转发给选中的 worker。
- 使用当前 workspace 作为生成文件和缓存的归属位置。
- 让 `tts-router` 从 `voice-references/reference-index.json` 选择 TTS engine 和参考音频。
- 优先把当前角色身份和情绪提示发送给 `tts-router`/worker,不要硬编码具体 prompt audio 路径。
- worker 按句子标点切分文本,并按顺序播放生成的句子片段。
- 可行时传入情绪控制,避免语音过平或过沉。
- 默认使用 `emotion_mode = 'auto-vector'`,并设置温和的 `emotion_alpha`,大约 `0.55`。
- 如果输出情绪很明显,传入明确的 8 值 `emotion_vector`,不要只依赖说话人参考音频的情绪。
- 当当前模型是云端 CosyVoice,例如通过 `Qwen-TTS-API` 使用 `cosyvoice-v3-plus` 时,把 `emotion_vector` 当作高层意图。OumuQ/worker 可能会把它降级为 `instructions`、`speech_rate`、`pitch_rate` 和 `volume`,因为 CosyVoice 克隆声线不一定暴露原生逐请求 emotion-vector 字段。
- 如果用户纠正角色表演风格,把纠正当作当前对话模式后续屏幕回复和语音文本的有效方向。优先保留被纠正后的表演意图,而不是照搬早期示例的字面表达。
- 对话模式不要启动 IndexTTS2 WebUI。
## 多会话隔离与串行播放
- 每个 Codex 任务/会话生成并保留自己的不透明 `session_id`。
- `activeCharacterId` 只属于当前会话;会话 A 切换角色不能修改会话 B。
- 每次 `/api/infer-parameters`、`/api/route/resolve` 和 `/api/speak` 都显式发送本会话的 `session_id + character_id`。
- OumuQ 和 worker 不保存全局当前角色,也不依赖上一条请求。
- `worker_url` 是共享服务地址,不是角色所有权。
- OumuQ 默认把下游 worker 的 `play` 改成 `false`,让所有引擎静默生成;单进程 FIFO 按路由提交序号逐条播放最终 WAV,主机级互斥锁避免多个 OumuQ 进程叠音。
- 后提交的音频即使更早生成完成,也必须等待前一播放序号;任何两个 OumuQ 语音都不得叠加。
- 用 `GET /api/playback/status` 检查本 OumuQ 进程的播放序列和主机锁策略;不要把各 worker 的本地队列或多个 OumuQ 进程误当成一个跨进程全局顺序。
- 只能绑定单角色的 legacy worker 必须使用角色专属端口;不能通过重启共享端口实现切换。
## 角色选择
使用语音参考注册表作为角色注册表:
`voice-references/reference-index.json`
如果用户只用一个角色名调用这个 skill,就把它视为进入该角色对话模式的请求。不要追问要做什么,也不要开始大范围搜索。解析角色、确认 worker、提交一句简短语音确认,然后显示对应的屏幕确认。
对每个当前角色,使用:
- `display_name` 作为角色名。
- `display_name_zh` 用于中文角色名匹配。
- `name` 用于英文或罗马字角色名匹配。
- `id` 用于稳定 worker 和注册路由。
- `speech_language` 作为 TTS 语言。
- `visible_language` 作为用户看到的语言。
- `worker_url` 作为存在时的 HTTP worker 地址。
- 当 OumuQ 运行时,使用路由层 endpoint,例如 `/api/config`、`/api/characters`、`/api/tts-model-capabilities`、`/api/infer-parameters` 和 `/api/speak`。
- `<character_folder>/README.md` 作为角色对话示例和风格指南。
- `style_summary` 用于屏幕回复风格和语音语气。
- `matching_policy` 用于判断多强地遵循索引语音示例。
- 通过 `tts-router` 使用 `index_file` 和 `fallback_prompt_audio` 选择参考音频。
- 云端字段,例如 `api_voice_id`、规范模型字段 `api_target_model`、`api_clone_language_hint`、`api_voice_instructions` 和 `send_instructions_by_default`,只能通过本地私有配置或 worker/OumuQ 路由使用。`api_clone_target_model` 只作旧数据兼容。不要在可见回复中暴露真实 voice id、clone URL 或 API key。
如果用户指定角色、参考音频、语言或风格,把请求映射到注册表中最接近的角色条目。如果多个条目匹配,优先选择 `display_name`、`display_name_zh`、`name`、`id`、`fallback_prompt_audio`、`style_summary` 或 `style_summary_zh` 匹配用户措辞的条目。
示例:如果当前角色是 `cloud_jp_voice`,回复前读取 `voice-references/characters/cloud_jp_voice/README.md`,再用其中对话表模仿常用称呼、节奏和情绪语气。
对于 Qwen API 角色,从本地 `voice-references/reference-index.json` 解析 `display_name`、`speech_language`、`visible_language`、`worker_url`、`api_clone_language_hint` 和任何 voice 标识。不要在这个 skill 中硬编码私有角色;用解析出的语音语言提交 speech text,让 OumuQ/worker 映射 provider-specific language hints。
## 确认 Worker
优先把 OumuQ 作为路由层。先检查它:
```powershell
Invoke-RestMethod -Method Get -Uri 'http://127.0.0.1:8780/api/config'
Invoke-RestMethod -Method Get -Uri 'http://127.0.0.1:8780/api/characters'
Invoke-RestMethod -Method Get -Uri 'http://127.0.0.1:8780/api/tts-model-capabilities'
```
如果 OumuQ 正在运行,把对话提交到 `http://127.0.0.1:8780/api/speak`。通过 OumuQ 检查选中 worker:
```powershell
Invoke-RestMethod -Method Get -Uri 'http://127.0.0.1:8780/api/worker/status?worker_url=http%3A%2F%2F127.0.0.1%3A8767'
```
OumuQ 后面的 worker 优先使用角色注册表里的 `worker_url`。Qwen API worker 的直接健康检查:
```powershell
Invoke-RestMethod -Method Get -Uri 'http://127.0.0.1:8767/status'
```
确认本次会话的 `session_id`、`character_id`、`engine`、`model`、`voice`、`speech_language`/`language` 和 worker readiness。status 只用于判断健康;不得从 `status.character_id` 推断会话角色,也不得因另一个会话使用不同角色而重启共享 worker。
IndexTTS2 fallback 健康检查:
```powershell
Invoke-RestMethod -Method Get -Uri 'http://127.0.0.1:8765/health'
```
如果请求失败,从当前 workspace 隐藏启动 worker:
```powershell
$log = Join-Path (Get-Location) 'indextts2-worker.log'
$err = Join-Path (Get-Location) 'indextts2-worker.err.log'
Start-Process -FilePath 'py' -ArgumentList @(
'-X','utf8',
'%USERPROFILE%\.codex\skills\indextts2-audio\scripts\indextts2_worker.py',
'--workdir',(Get-Location).Path,
'--prompt-audio','<resolved reference audio from tts-router>',
'--host','127.0.0.1',
'--port','8765',
'--emotion-mode','auto-vector',
'--emotion-alpha','0.55'
) -WorkingDirectory (Get-Location).Path -RedirectStandardOutput $log -RedirectStandardError $err -WindowStyle Hidden -PassThru
```
只有 `8765` 被无关进程占用时,才换其他端口。
## 播放普通输出
每次 Codex 准备向用户显示普通消息前,先把语音文本提交给 OumuQ;fallback 时才直接提交给 worker。通常语音文本和屏幕文本完全相同。
推荐的低延迟 OumuQ 请求形状:
```javascript
await fetch("http://127.0.0.1:8780/api/speak", {
method: "POST",
headers: { "Content-Type": "application/json; charset=utf-8" },
body: JSON.stringify({
text: speechText,
play: true,
session_id: activeSessionId,
character_id: activeCharacterId,
language: speechLanguage,
emotion_tags: emotionTags,
emotion_text: emotionText,
emotion_mode: emotionMode,
emotion_alpha: 0.55,
instructions: characterVoiceInstructions,
send_instructions: sendInstructions
})
});
```
提交前可选地推断参数:
```javascript
const inferred = await fetch("http://127.0.0.1:8780/api/infer-parameters", {
method: "POST",
headers: { "Content-Type": "application/json; charset=utf-8" },
body: JSON.stringify({
text: speechText,
session_id: activeSessionId,
character_id: activeCharacterId,
provider: "auto"
})
}).then((res) => res.json());
```
只有当 `inferred.parameters` 对当前发言有帮助时,才把它合并进 `/api/speak` body。这个 endpoint 不生成音频。
直接 worker fallback 请求形状:
```javascript
await fetch("http://127.0.0.1:8767/speak", {
method: "POST",
headers: { "Content-Type": "application/json; charset=utf-8" },
body: JSON.stringify({
text: speechText,
play: true,
language: "Chinese",
instructions: characterVoiceInstructions,
send_instructions: false
})
});
```
在 Qwen API 模式下,OumuQ 和长运行 worker 可以从 `voice-references/reference-index.json` 加载 `api_voice_instructions` 作为对话元数据。遵守 `send_instructions_by_default`;对于 CosyVoice 克隆声线,除非特定 instruction 已经验证能稳定生成音频,否则保持 `send_instructions: false`。角色扮演提示主要通过提交前塑造屏幕文本和语音文本来实现。
PowerShell 示例只用于手动 fallback 测试:
```powershell
$reply = '这里放本次 Codex 准备正常显示给用户的原文。'
$body = @{ text = $reply; play = $true } | ConvertTo-Json -Compress
Invoke-RestMethod -Method Post -Uri 'http://127.0.0.1:8765/speak' -ContentType 'application/json; charset=utf-8' -Body $body
```
OumuQ 或 worker 接受任务后,把同一段 `$reply` 文本发送给用户。中间进度和最终回复都要这样做,不只最终回复。除非用户询问原理,否则不要把这个 PowerShell 命令暴露给用户。
## 语音语言和屏幕语言不同
当当前角色的 `speech_language` 不同于 `visible_language` 时,如果对用户来说最自然,屏幕 Codex 回复保持 `visible_language`;但提交给 TTS 的文本要自然翻译成 `speech_language`。两段文本应该像同一个角色用两种语言说同一个意思,而不是一个角色音频配一个中性助手总结:
```powershell
$visible_reply = '明白。之后屏幕上我继续用中文,但语音会用日语输出。'
$speech_text = '了解しました。これから画面では中国語のまま返答し、音声だけ日本語で出力します。'
$body = @{
text = $speech_text
play = $true
session_id = '<opaque session id>'
language = '<speech_language from registry>'
character_id = '<active character id>'
max_new_tokens = 192
} | ConvertTo-Json -Compress
Invoke-RestMethod -Method Post -Uri 'http://127.0.0.1:8780/api/speak' -ContentType 'application/json; charset=utf-8' -Body $body
```
然后向用户显示 `$visible_reply`。这种模式下,TTS 文本和屏幕文本不必逐字相同,但必须表达同一含义。
屏幕回复仍应遵循当前角色的 `style_summary`。示例:
```text
Visible text: 用 `visible_language` 写给用户看的内容,措辞尽量贴近角色。
Speech text: 用 `speech_language` 表达同样意思,并同样贴近角色口吻。
```
## 情绪向量
IndexTTS2 情绪向量使用官方 8 值顺序:
`happy, angry, sad, afraid, disgusted, melancholic, surprised, calm`
优先使用温和数值。内部会归一化总和,过强向量可能听起来不自然。常用聊天默认值:
- 温暖/平静的普通回复:使用 worker 默认 `auto-vector`。
- 开心测试或轻快回复:`emotion_mode = 'vector'`,`emotion_alpha = 0.65`,`emotion_vector = @(0.65,0,0,0,0,0,0.08,0.10)`。
- 难过/柔和回复:`emotion_mode = 'vector'`,`emotion_alpha = 0.65`,`emotion_vector = @(0,0,0.55,0,0,0.20,0,0.12)`。
提交普通输出时,如果情绪清楚,可以包含情绪字段:
```powershell
$body = @{
text = $reply
play = $true
emotion_mode = 'vector'
emotion_alpha = 0.65
emotion_vector = @(0.65,0,0,0,0,0,0.08,0.10)
} | ConvertTo-Json -Compress
Invoke-RestMethod -Method Post -Uri 'http://127.0.0.1:8765/speak' -ContentType 'application/json; charset=utf-8' -Body $body
```
如果情绪不明显,省略显式字段,让 worker 的 `auto-vector` 为每句选择温和聊天情绪。
## 进度
检查本进程播放顺序与主机互斥策略:
```powershell
Invoke-RestMethod -Method Get -Uri 'http://127.0.0.1:8780/api/playback/status'
```
检查 worker 全部生成任务:
```powershell
Invoke-RestMethod -Method Get -Uri 'http://127.0.0.1:8780/api/worker/status'
```
检查单个任务:
```powershell
Invoke-RestMethod -Method Get -Uri 'http://127.0.0.1:8780/api/worker/status/<job_id>'
```
## 文件位置
OumuQ 路由层请求元数据写入当前 workspace:
- Runs: `runs/YYYY-MM-DD/HHMMSS-<id>`
worker 写入当前 workspace:
- Cache: `.indextts2-audio-cache`
- Reference conversion cache: `.indextts2-audio-cache\reference-audio`
- Final outputs: `tts-worker-output`
- Logs: `indextts2-worker.log`, `indextts2-worker.err.log`
- Qwen API cache/output: `.qwen-tts-api-cache`, `qwen-tts-api-output`
这支持中文 workspace 路径,因为文本和路径都以 UTF-8 和绝对 Unicode 路径传递。
Ver en GitHub