Files
sionrui/openspec/changes/add-siliconflow-voice-provider/design.md

180 lines
5.7 KiB
Markdown
Raw Normal View History

2026-02-01 17:56:10 +08:00
# 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