180 lines
5.7 KiB
Markdown
180 lines
5.7 KiB
Markdown
# 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
|