# Technical Design: SiliconFlow Voice Provider ## Context 硅基流动(SiliconFlow)是一个提供多种 AI 服务的平台,包括语音合成、语音克隆等功能。本次设计将其作为新的语音供应商集成到现有的多供应商架构中。 **约束条件**: - 必须兼容现有的 `VoiceCloneProvider` 接口 - 不能影响现有的 CosyVoice 供应商功能 - 需要适配硅基流动的 API 差异 **关键 API 差异**: 1. **语音克隆**: 硅基流动需要先上传参考音频,返回 `uri` 作为音色 ID 2. **TTS 合成**: 使用 `/v1/audio/speech` 端点,返回二进制音频数据 3. **认证**: 使用 Bearer Token 格式的 API Key 4. **模型**: 使用 `IndexTeam/IndexTTS-2` 模型 ## Goals / Non-Goals ### Goals - 实现 `SiliconFlowProvider` 完整支持语音克隆和 TTS - 支持硅基流动 `IndexTeam/IndexTTS-2` 模型 - 提供完整的配置支持,可独立开关 - 处理 API 差异,提供统一的服务接口 ### Non-Goals - 不实现语音转文字(STT)功能(已有其他服务) - 不修改现有的 `VoiceCloneProvider` 接口定义 - 不改变前端 API 契约 ## Decisions ### 1. 实现类结构 **架构**: ``` SiliconFlowProvider (implements VoiceCloneProvider) ├── SiliconFlowApi (HTTP 客户端) ├── SiliconFlowProviderConfig (配置类) └── DTO 类 (请求/响应适配) ``` **Why**: - 遵循现有的 `CosyVoiceProvider` 模式 - 分离 API 调用逻辑,便于测试和维护 - 专用 DTO 处理硅基流动 API 差异 ### 2. 语音克隆流程适配 **硅基流动语音克隆 API**: - 端点: `POST /v1/uploads/audio/voice` - 请求参数: `model`, `customName`, `text`, `audio` (base64) - 响应: `{"uri": "speech:customName:xxx:xxx"}` **适配策略**: 1. 将统一请求的 `audioUrl` 下载并转换为 base64 2. 使用 `prefix` 作为 `customName` 3. 使用 `audioUrl` 对应的转录文本作为 `text` 参数 4. 返回的 `uri` 存储为 `voiceId` **代码示例**: ```java @Override public VoiceCloneResult cloneVoice(VoiceCloneRequest request) { // 1. 下载音频文件 byte[] audioData = downloadAudio(request.getAudioUrl()); String base64Audio = Base64.getEncoder().encodeToString(audioData); // 2. 构建硅基流动请求 SiliconFlowVoiceUploadRequest sfRequest = new SiliconFlowVoiceUploadRequest(); sfRequest.setModel("IndexTeam/IndexTTS-2"); sfRequest.setCustomName(request.getPrefix()); sfRequest.setText(getTranscriptionText(request.getAudioUrl())); sfRequest.setAudio("data:audio/mpeg;base64," + base64Audio); // 3. 调用 API SiliconFlowVoiceUploadResponse sfResponse = siliconFlowApi.uploadVoice(sfRequest); // 4. 适配返回结果 VoiceCloneResult result = new VoiceCloneResult(); result.setVoiceId(sfResponse.getUri()); return result; } ``` ### 3. TTS 合成流程适配 **硅基流动 TTS API**: - 端点: `POST /v1/audio/speech` - 请求参数: `model`, `input`, `voice`, `speed`, `sample_rate`, `response_format` - 响应: 二进制音频数据 **适配策略**: 1. 使用 `voiceId` (uri 格式) 作为 `voice` 参数 2. 支持语速调节 (`speed`) 3. 将二进制响应转换为 Base64 返回 ### 4. 配置设计 **配置结构**: ```yaml yudao: voice: providers: siliconflow: enabled: false api-key: ${SILICONFLOW_API_KEY} base-url: https://api.siliconflow.cn default-model: IndexTeam/IndexTTS-2 audio-format: mp3 sample-rate: 24000 connect-timeout: 10s read-timeout: 180s ``` **配置类设计**: ```java @Data @EqualsAndHashCode(callSuper = true) public class SiliconFlowProviderConfig extends VoiceProviderProperties.ProviderConfig { private String baseUrl = "https://api.siliconflow.cn"; private String defaultModel = "IndexTeam/IndexTTS-2"; private String audioFormat = "mp3"; private Integer sampleRate = 24000; private Duration connectTimeout = Duration.ofSeconds(10); private Duration readTimeout = Duration.ofSeconds(180); } ``` ### 5. 错误处理策略 - API 调用失败时记录详细日志 - 统一转换为 `VOICE_TTS_FAILED` 业务异常 - 不暴露硅基流动技术细节给上层 - 支持重试机制(网络错误) ## Risks / Trade-offs | Risk | Mitigation | |------|------------| | 硅基流动 API 变更 | 封装在独立的 API 客户端中,便于更新 | | 语音克隆需要转录文本 | 在创建配音时已有转录流程,复用该文本 | | 音频下载增加延迟 | 可考虑配置是否需要下载,或使用异步处理 | | Base64 编码增加内存占用 | 限制音频文件大小(已有 50MB 限制) | ## Migration Plan ### 阶段一:后端实现 1. 创建 `SiliconFlowApi` HTTP 客户端 2. 创建 `SiliconFlowProviderConfig` 配置类 3. 创建硅基流动专用 DTO 类 4. 实现 `SiliconFlowProvider` 5. 更新 `application.yaml` 配置 ### 阶段二:测试验证 1. 单元测试:`SiliconFlowApi` 调用 2. 单元测试:`SiliconFlowProvider` 适配逻辑 3. 集成测试:语音克隆完整流程 4. 集成测试:TTS 合成完整流程 ### 阶段三:前端支持(已有基础) 1. 验证 `voiceConfig.js` 已支持 `siliconflow` 类型 2. 验证 API 请求已支持 `providerType` 参数 ### 回滚方案 - 通过配置 `enabled: false` 禁用硅基流动 - 删除 `SiliconFlowProvider` 相关代码 - 恢复 `application.yaml` 配置 ## Open Questions 1. **Q**: 语音克隆时是否必须提供转录文本? **A**: 硅基流动 API 需要 `text` 参数,使用配音创建时的转录文本 2. **Q**: 是否需要支持硅基流动的其他模型? **A**: 本次仅支持 `IndexTeam/IndexTTS-2`,后续可扩展 3. **Q**: 音频下载失败如何处理? **A**: 抛出业务异常,提示用户检查音频 URL