oh-my-pi 的 tts 工具详解:本地 Kokoro-82M 与云端 xAI/DeepInfra 三后端语音合成实战指南
【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi
本文是 oh-my-pi(⌥ Coding agent with the IDE wired in)内置
tts自定义工具(Speech Generation)的完整技术指南。它以 tts 工具文档 为骨架,结合 工具实现、本地模型目录、worker 客户端 与 配置模式 等源码展开。读完本文,你将掌握tts工具的启用方式、六个入参与输出契约、providers.tts四种路由语义、三种后端(本地 / xAI / DeepInfra)各自的实现与限制,以及错误诊断与边界行为,能够直接在自己的会话中配置并使用语音文件合成能力。
一、工具概览:何时注入、做什么
tts是一个“写批准”(approval: "write")的自定义工具,作用是:把一段文本合成语音音频文件并写入output_path。它由 SDK 在会话启动时按需注册:只有当设置speechgen.enabled=true时才会被注入到可用工具集,默认是false(见 sdk.ts 与 settings-schema.ts)。
工具的描述文本本身来自提示词模板 prompts/tools/tts.md,模板中的{{localVoices}}、{{xaiVoices}}、{{maxLength}}占位符在注册时由 tts.ts 调用prompt.render()动态填充(本地 12 个 Kokoro 音色、xAI 5 个内置音色、最大 15000 字符),因此模型看到的描述始终与当前配置一致。
从源码继承关系看,xAI Grok Voice 路径移植自 NousResearch/hermes-agent 的tools/tts_tool.py(MIT 协议),oh-my-pi 在其之上叠加了本地端侧神经 TTS 后端(Kokoro-82M,经 kokoro-js 运行在共享 ONNX worker 上),并通过providers.tts开关统一路由(tts.ts)。
二、输入参数(Schema)
工具通过 omptype 声明参数模式(tts.ts):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
text | string | 是 | 待合成文本,长度约束1..15_000字符 |
voice_id | string | 否 | 音色 ID。xAI 默认为eve;本地后端不使用该字段,改用tts.localVoice设置;DeepInfra 仅在显式设置时才转发给服务端,否则用服务端默认音色 |
language | string | 否 | xAI 语言提示,默认en |
output_path | string | 是 | 输出路径,相对会话 cwd 解析 |
sample_rate | number.integer | 否 | xAI 采样率覆盖(默认 24000);本地与 DeepInfra 后端忽略 |
bit_rate | number.integer | 否 | xAI MP3 比特率覆盖(默认 128000);WAV 以及本地与 DeepInfra 后端忽略 |
值得注意的细节:voice_id在 schema 中没有默认值("voice_id?"而非"voice_id = ..."),这样做的目的是让“显式传了eve”与“没传 voice”可区分——xAI 由服务端应用自身默认、DeepInfra 只在调用方真正设置过 voice 时才转发(tts.ts)。
三、输出契约
- 成功时返回一个文本块,格式为
Saved <bytes> bytes to <path> (voice=<voice>, codec=<codec>, backend=<backend>...),并附带结构化details = { bytes, voiceId, codec, backend }(bytes为写入的字节数,voiceId为实际使用的音色,codec为mp3或wav,backend为local/xai/deepinfra之一)。 - 失败(缺 xAI/DeepInfra 凭证、云端 HTTP 错误、本地 worker 返回
null)时返回isError: true,只含一个文本块、不带details。 - 其余异常(调用方取消、60 秒云端超时、文件写入错误、本地 worker 抛出的异常)直接向外传播,不包装成
isError结果。
四、后端路由:providers.tts的四种取值
providers.tts为枚举设置,默认auto,可选auto/local/xai/deepinfra(settings-schema.ts)。执行入口先读取该设置,再调用纯函数resolveTtsBackend决策(tts.ts):
export function resolveTtsBackend(opts: { preference: string; wantsMp3: boolean; hasXaiCreds: boolean }): TtsBackend { if (opts.preference === "xai") return "xai"; if (opts.preference === "deepinfra") return "deepinfra"; if (opts.preference === "local") return "local"; if (opts.wantsMp3 && opts.hasXaiCreds) return "xai"; return "local"; }四种取值语义:
local:总是走本地端侧后端;输出永远是 WAV/PCM16。xai:总是走 xAI Grok Voice 云端;缺少凭证时返回错误结果。deepinfra:总是走 DeepInfra 的 OpenAI 兼容语音端点;缺少凭证时返回错误结果。auto(默认):优先本地端侧合成,但当调用方请求.mp3且存在 xAI 凭证时改走 xAI——因为只有云端路径能产出 MP3;否则.mp3路径会被写成同级.wav文件。
调用方是否真的需要 MP3,由output_path的后缀决定:大小写不敏感地以.wav结尾则 codec 为wav,否则一律视为mp3(tts.ts)。
该路由逻辑有对应单元测试 test/tts/tts-backend.test.ts,覆盖了“显式 deepinfra 优先于 codec 与 xAI 凭证”“auto + mp3 + 有 xAI 凭证 → xai”“auto 其余情况 → local”三类关键分支。
五、本地后端:完全离线的 Kokoro-82M
本地后端是全流程端侧的:模型权重就绪后不产生任何网络提供商调用,输出永远是 WAV/PCM16。
5.1 模型与音色目录
默认本地模型 key 为kokoro,对应 Hugging Face 仓库onnx-community/Kokoro-82M-v1.0-ONNX,ONNX 精度 q8(权重约 100 MB,兼顾 CPU 推理速度与质量);默认音色为af_heart(models.ts)。同一个模型覆盖所有音色/口音——选音色即选语言/口音,不需要额外下载。
tts.localModel目前仅支持kokoro一个值(通过类型层面的“值必须匹配注册表”编译期校验保证,见 models.ts)。tts.localVoice支持从如下精选的 12 个 Kokoro 音色中选择(基于 Kokoro 自带的overallGrade评分筛选,af_heart为 A 级默认音色):
| 音色 ID | 标签 | 音色 ID | 标签 |
|---|---|---|---|
af_heart(默认) | Heart(美国女声) | am_michael | Michael(美国男声) |
af_bella | Bella(美国女声) | am_fenrir | Fenrir(美国男声) |
af_nicole | Nicole(美国女声) | am_puck | Puck(美国男声) |
af_aoede | Aoede(美国女声) | bf_emma | Emma(英国女声) |
af_kore | Kore(美国女声) | bm_george | George(英国男声) |
af_sarah | Sarah(美国女声) | bm_fable | Fable(英国男声) |
5.2 执行链路
本地合成通过共享的 ONNX tiny-model worker 子进程完成(tts-client.ts):
- 读取
tts.localModel(非法值回退到kokoro)与tts.localVoice(缺省回退af_heart); - 通过
ttsClient.synthesize(modelKey, text, { voice, signal })发起请求,worker 以__omp_worker_tts隐藏子命令被拉起,走transformers.js + onnxruntime运行时调用 kokoro-js 的KokoroTTS.from_pretrained; - 得到
{ pcm: Float32Array, sampleRate }后,用 wav.ts 的encodeWav编码:手写 44 字节 RIFF/WAVE 头 + 小端有符号 16 位单声道采样,浮点样本先钳制到 [-1, 1] 再量化(无外部编码器依赖); - 写入目标文件。
5.3 本地对 MP3 的处理
本地后端刻意不捆绑 MP3 编码器。若请求的目标路径不是.wav(例如speech.mp3),resolveLocalWavPath会把路径改写成同级.wav(speech.wav),并在工具结果中明确提示 “No local MP3 encoder is bundled, so WAV (PCM16) was written instead of the requested container.”(tts.ts)。
本地合成还会忽略每次调用传入的voice_id、language、sample_rate、bit_rate——音色来自设置而非每次调用枚举,这也是设计意图:模型调用无需在每次调用时枚举本地音色 ID。
六、xAI 后端:Grok Voice 云端合成
- 凭证:通过
resolveXAIHttpCredentials解析,可来自/login → xAI Grok OAuth(SuperGrok 或 X Premium+)或环境变量XAI_API_KEY;缺失时返回错误:No xAI credentials. Run /login → xAI Grok OAuth (SuperGrok or X Premium+) or set XAI_API_KEY.。 - 端点:
<baseURL>/tts,Bearer 认证,请求体含{ text, voice_id, language }。 - 内置音色:
ara、eve(默认)、leo、rex、sal;同时接受自定义 xAI 音色 ID,因此 schema 并不用枚举限制voice_id。 - 默认值:音色
eve、语言en、采样率 24000、MP3 比特率 128000。 - 输出格式:MP3 或 WAV 均可。
output_format只在这三项与默认值不一致时才显式发送(继承 hermestts_tool.py的行为):非.wav路径请求 MP3;WAV 与 MP3 分别携带各自的sample_rate/bit_rate覆盖(tts.ts)。 - 结果:直接写入服务端返回的字节。
七、DeepInfra 后端:OpenAI 兼容语音端点
- 凭证:
DEEPINFRA_API_KEY(或/login → DeepInfra);缺失时返回:No DeepInfra credentials. Run /login → DeepInfra or set DEEPINFRA_API_KEY. - 端点:
https://api.deepinfra.com/v1/openai/audio/speech,请求体为{ model, input, response_format, voice? }。 - 模型:默认
hexgrad/Kokoro-82M。 - 音色:
voice字段仅在调用方显式设置了voice_id时才转发(DeepInfra 的音色 ID 与模型绑定,未设置时使用服务端默认音色)。 - 输出:MP3 或 WAV(由
response_format指定),字节直接写入文件。
八、配置项汇总
四个与tts相关的设置全部位于 Providers 设置的 Services 分组(settings-schema.ts):
| 设置 | 类型/取值 | 默认值 | 说明 |
|---|---|---|---|
speechgen.enabled | boolean | false | 总开关,为true时 SDK 才注入tts工具 |
providers.tts | enum:auto/local/xai/deepinfra | auto | 后端路由:本地优先,仅当 MP3 + 有 xAI 凭证时转云端 |
tts.localModel | enum:kokoro | kokoro | 本地端侧模型(Kokoro-82M,q8 ONNX) |
tts.localVoice | enum: 12 个 Kokoro 音色 | af_heart | 本地后端音色 |
九、副作用、取消与超时
- 文件系统:写入
output_path;本地合成遇到非 WAV 目标时改写为同级.wav。 - 网络:xAI 后端调用配置的 Grok Voice HTTP 端点;DeepInfra 后端调用
api.deepinfra.com;本地后端仅在模型权重首次可用前通过 tiny-model 栈下载/缓存权重。 - 会话状态读取:cwd、模型注册表、
providers.tts、tts.localModel、tts.localVoice。 - 取消与超时:云端调用(xAI 与 DeepInfra)使用
AbortSignal.timeout(60_000)与调用方信号合并的 60 秒超时围栏;本地合成接收调用方 abort 信号(tts.ts)。 - 无流式进度:合成为单次请求,不发射
onUpdate进度事件。
十、错误处理速查
- 缺 xAI / DeepInfra 凭证 →
isError结果,提示对应登录命令或环境变量。 - 云端 HTTP 失败(xAI 或 DeepInfra)→
isError结果,格式<xAI TTS|DeepInfra TTS> failed (<status>): <detail>,detail 最多保留前 300 字符。 - 本地 worker 返回
null→isError结果,注明模型 key,提示 worker 可能不可用或模型下载中断。 - 调用方取消、60 秒云端超时、文件写入错误、本地 worker 抛出的异常 → 直接传播(不包装为
isError)。 - 本地请求
speech.mp3会写成speech.wav并在结果中说明,这是有意为之(本地不捆绑 MP3 编码器)。
十一、注意事项
- 文本长度上限为
1..15_000个 JS 字符串字符(schema 约束)。 voice_id与language是 xAI 的 payload 字段;本地音色选择来自设置,因此模型调用无需在每次调用时枚举本地音色 ID。- 若需要 MP3 输出,请确保配置了 xAI(或 DeepInfra)凭证并选择对应云端后端;纯本地模式下只能得到 WAV/PCM16。
【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考