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

5.7 KiB
Raw Blame History

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

代码示例:

@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. 配置设计

配置结构:

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

配置类设计:

@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