代码优化
This commit is contained in:
179
openspec/changes/add-siliconflow-voice-provider/design.md
Normal file
179
openspec/changes/add-siliconflow-voice-provider/design.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user