Files
sionrui/openspec/changes/add-siliconflow-voice-provider/design.md
2026-02-01 17:56:10 +08:00

180 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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