添加对gemini原生格式TTS的支持

This commit is contained in:
zzh
2025-07-15 18:04:16 +09:00
parent 4a004f9aa1
commit a6aecb5d89
10 changed files with 141 additions and 69 deletions
+369
View File
@@ -0,0 +1,369 @@
# 原生Gemini TTS功能
这个模块为Gemini Balance项目添加了原生Gemini TTSText-to-Speech)功能,支持单人和多人语音合成,采用智能检测和继承模式设计,保持与原始代码的完全兼容性。
## 🎯 设计原则
- **智能检测**:自动检测所有原生Gemini TTS格式的请求(包含responseModalities和speechConfig
- **继承而非修改**:所有扩展都继承自原始类,不修改源码
- **完全兼容**:原有TTS功能(OpenAI兼容TTS)完全不受影响
- **动态模型选择**:支持用户在请求URL中指定不同的TTS模型
- **自动回退**:原生TTS处理失败时自动回退到标准服务
- **完整日志记录**:包含请求日志、错误日志和性能监控
- **易于维护**:更新原始代码时不会产生冲突
## 📁 文件结构
```
app/service/tts/
├── tts_service.py # 原有的OpenAI兼容TTS服务
└── native/ # 原生Gemini TTS扩展
├── __init__.py # 模块初始化
├── README.md # 使用说明(本文件)
├── tts_models.py # TTS数据模型(继承自原始模型)
├── tts_response_handler.py # TTS响应处理器(继承自原始处理器)
├── tts_chat_service.py # TTS聊天服务(继承自原始服务)
└── tts_routes.py # TTS路由扩展和依赖注入
```
## 🚀 原生Gemini TTS功能
### 智能检测机制(当前实现)
原生Gemini TTS功能通过智能检测自动启用,无需任何配置:
1. **自动启用**
```bash
# 直接启动服务,原生TTS功能自动可用
python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
```
2. **无需配置**
- 不需要环境变量
- 不需要修改配置文件
- 完全基于请求内容智能判断
### 工作原理
系统会智能检测请求内容:
- **原生TTS请求**:包含 `responseModalities: ["AUDIO"]``speechConfig` → 使用TTS增强服务
- **单人TTS**:包含 `voiceConfig.prebuiltVoiceConfig`
- **多人TTS**:包含 `multiSpeakerVoiceConfig`
- **普通请求**:非TTS模型 → 使用原有Gemini聊天服务
```python
# app/router/gemini_routes.py 中的智能检测逻辑
if "tts" in model_name.lower():
# 检查是否包含原生TTS配置
generation_config = raw_data.get("generationConfig", {})
response_modalities = generation_config.get("responseModalities", [])
speech_config = generation_config.get("speechConfig", {})
# 如果包含AUDIO模态和语音配置,则认为是原生TTS请求
if "AUDIO" in response_modalities and speech_config:
# 使用TTS增强服务
tts_service = await get_tts_chat_service(key_manager)
return await tts_service.generate_content(...)
# 否则使用原有服务
```
## 📝 使用示例
### 单人语音TTS请求(自动启用增强服务)
包含 `voiceConfig.prebuiltVoiceConfig` 的请求会自动使用TTS增强服务:
```bash
curl -X POST "https://your-domain.com/v1beta/models/gemini-2.5-flash-preview-tts:generateContent" \
-H "Content-Type: application/json" \
-H "x-goog-api-key: your-token" \
-d '{
"contents": [{
"parts": [{
"text": "Hello, this is a single speaker test."
}]
}],
"generationConfig": {
"responseModalities": ["AUDIO"],
"speechConfig": {
"voiceConfig": {
"prebuiltVoiceConfig": {
"voiceName": "Kore"
}
}
}
}
}'
```
### 多人语音TTS请求(自动启用增强服务)
包含 `multiSpeakerVoiceConfig` 的请求会自动使用TTS增强服务:
```bash
curl -X POST "https://your-domain.com/v1beta/models/gemini-2.5-flash-preview-tts:generateContent" \
-H "Content-Type: application/json" \
-H "x-goog-api-key: your-token" \
-d '{
"contents": [{
"parts": [{
"text": "小雅: 听众朋友们大家好!欢迎收听今天的节目。\n李想: 小雅好,听众朋友们好!今天我们来聊聊人工智能的发展。"
}]
}],
"generationConfig": {
"responseModalities": ["AUDIO"],
"speechConfig": {
"multiSpeakerVoiceConfig": {
"speakerVoiceConfigs": [
{
"speaker": "李想",
"voiceConfig": {
"prebuiltVoiceConfig": {
"voiceName": "Kore"
}
}
},
{
"speaker": "小雅",
"voiceConfig": {
"prebuiltVoiceConfig": {
"voiceName": "Puck"
}
}
}
]
}
}
}
}'
```
### 单人TTS请求(使用原有服务)
不包含 `multiSpeakerVoiceConfig` 的TTS请求会使用原有的Gemini TTS服务:
```bash
curl -X POST "https://your-domain.com/v1beta/models/gemini-2.5-flash-preview-tts:generateContent" \
-H "Content-Type: application/json" \
-H "x-goog-api-key: your-token" \
-d '{
"contents": [{
"parts": [{
"text": "Hello, this is a single speaker test."
}]
}],
"generationConfig": {
"responseModalities": ["AUDIO"],
"speechConfig": {
"voiceConfig": {
"prebuiltVoiceConfig": {
"voiceName": "Kore"
}
}
}
}
}'
```
### 普通文本生成(使用原有服务)
非TTS模型的请求会使用原有的Gemini聊天服务,完全不受影响:
```bash
curl -X POST "https://your-domain.com/v1beta/models/gemini-1.5-flash:generateContent" \
-H "Content-Type: application/json" \
-H "x-goog-api-key: your-token" \
-d '{
"contents": [{
"parts": [{
"text": "请简单介绍一下人工智能的发展历程。"
}]
}],
"generationConfig": {
"maxOutputTokens": 200,
"temperature": 0.7
}
}'
```
## 🔧 技术实现
### 继承关系
```
GeminiChatService
↓ (继承)
TTSGeminiChatService
├── 重写 generate_content() 方法
├── 添加 _handle_tts_request() 方法
└── 集成完整的日志记录功能
GeminiResponseHandler
↓ (继承)
TTSResponseHandler
└── 重写 handle_response() 方法
GenerationConfig (Pydantic模型)
↓ (扩展)
TTSGenerationConfig
├── responseModalities: List[str]
└── speechConfig: Dict[str, Any]
```
### 工作流程
1. **请求接收**:系统接收到API请求
2. **智能检测**
- 检查模型名称是否包含 "tts"
- 如果是TTS模型,解析请求体检查是否包含 `responseModalities: ["AUDIO"]``speechConfig`
3. **服务选择**
- **原生TTS请求**:使用 `TTSGeminiChatService` 增强服务
- **普通请求**:使用原有 `GeminiChatService`
4. **请求处理**
- **原生TTS**:使用 `_handle_tts_request()` 特殊处理
- **其他请求**:使用标准 `generate_content()` 方法
5. **字段处理**:从原始HTTP请求体提取TTS字段(`responseModalities`, `speechConfig`
6. **API调用**:构建优化的payload并调用Gemini API
7. **自动回退**:如果原生TTS处理失败,自动回退到标准服务
8. **响应处理**
- **TTS响应**:检测音频数据,直接返回原始响应
- **普通响应**:使用标准处理方法
9. **日志记录**:记录请求时间、成功状态、错误信息到数据库
## 📊 功能特性
### ✅ 已实现功能
- **智能原生TTS支持**:支持单人和多人语音合成
- **单人TTS**:支持 `voiceConfig.prebuiltVoiceConfig` 配置
- **多人TTS**:支持 `multiSpeakerVoiceConfig` 配置
- **智能检测机制**:自动检测所有原生Gemini TTS格式的请求
- **动态模型选择**:支持用户在URL中指定不同TTS模型
- **完全向后兼容**:原有TTS功能(OpenAI兼容TTS)完全不受影响
- **自动回退机制**:原生TTS处理失败时自动使用标准服务
- **完整日志记录**:请求日志、错误日志、性能监控
- **API配额管理**:自动重试和密钥轮换
- **零配置启用**:无需环境变量或配置文件修改
- **错误处理**:完整的异常捕获和错误记录
### 🎵 支持的语音配置
#### 单人语音配置
```json
{
"responseModalities": ["AUDIO"],
"speechConfig": {
"voiceConfig": {
"prebuiltVoiceConfig": {
"voiceName": "Kore|Puck|其他预设语音"
}
}
}
}
```
#### 多人语音配置
```json
{
"responseModalities": ["AUDIO"],
"speechConfig": {
"multiSpeakerVoiceConfig": {
"speakerVoiceConfigs": [
{
"speaker": "角色名称",
"voiceConfig": {
"prebuiltVoiceConfig": {
"voiceName": "Kore|Puck|其他预设语音"
}
}
}
]
}
}
}
```
## ⚠️ 注意事项
### API要求
- 确保API密钥有TTS权限
- TTS功能需要 `gemini-2.5-flash-preview-tts` 模型
- 注意API配额限制(免费版每天15次)
### 性能考虑
- TTS响应通常比文本响应更大(音频数据)
- 建议监控API调用频率和成功率
- 扩展功能不影响原始功能的性能和稳定性
### 部署建议
- 生产环境建议先测试普通功能
- 逐步启用TTS功能并监控日志
- 定期检查API配额使用情况
## 📈 监控和调试
### 日志查看
- **服务器日志**:查看TTS请求处理过程
- **管理界面**:在"API 调用详情"中查看请求记录
- **错误日志**:查看失败请求的详细信息
### 调试技巧
```bash
# 启用详细日志
export LOG_LEVEL=DEBUG
# 查看实时日志
tail -f logs/app.log
# 多人TTS功能无需配置,自动启用
# 可通过请求内容智能检测
```
## 🔄 TTS系统对比
项目中现在有三套TTS系统,各自服务不同的用途:
| TTS类型 | 路径 | 模型选择 | 语音配置 | 使用场景 | 我们的影响 |
|---------|------|----------|----------|----------|------------|
| **OpenAI兼容TTS** | `/v1/audio/speech` | 固定配置文件 | 单人语音 | OpenAI API兼容 | ✅ 无影响 |
| **Gemini单人TTS** | `/v1beta/models/{model}:generateContent` | 用户指定 | 单人语音 | 原生Gemini TTS | ✅ 我们的增强 |
| **Gemini多人TTS** | `/v1beta/models/{model}:generateContent` | 用户指定 | 多人语音 | 对话场景 | ✅ 我们的增强 |
### 智能路由机制
```mermaid
flowchart TD
A[API请求] --> B{路径检查}
B -->|/v1/audio/speech| C[OpenAI兼容TTS服务]
B -->|/v1beta/models/{model}:generateContent| D{模型名包含'tts'?}
D -->|否| E[标准Gemini聊天服务]
D -->|是| F{包含responseModalities和speechConfig?}
F -->|否| G[标准Gemini聊天服务]
F -->|是| H[原生TTS增强服务]
H --> I{处理成功?}
I -->|是| J[返回原生TTS响应]
I -->|否| K[自动回退到标准服务]
C --> L[完成]
E --> L
G --> L
J --> L
K --> L
```
## 🎉 成功案例
基于智能检测的原生Gemini TTS解决方案已经成功实现:
-**零配置启用**:无需任何环境变量或配置修改
-**智能检测**:自动检测所有原生Gemini TTS格式的请求
-**完全向后兼容**:所有原有TTS功能零影响
-**动态模型选择**:支持用户指定不同TTS模型
-**自动回退机制**:处理失败时自动使用标准服务
-**单人和多人语音合成**:支持所有原生Gemini TTS场景
-**完整日志记录**:可在管理界面查看所有请求
-**错误处理完善**API配额和重试机制
-**易于维护**:更新原始代码无冲突
这个实现展示了如何在不修改原始代码的情况下,优雅地扩展复杂系统的功能,同时保持完美的向后兼容性。
+19
View File
@@ -0,0 +1,19 @@
"""
原生Gemini TTS功能模块
Native Gemini TTS functionality for both single and multi-speaker scenarios
"""
from .tts_chat_service import TTSGeminiChatService
from .tts_models import TTSGenerationConfig, MultiSpeakerVoiceConfig, SpeechConfig, TTSRequest
from .tts_response_handler import TTSResponseHandler
from .tts_routes import get_tts_chat_service
__all__ = [
"TTSGeminiChatService",
"TTSGenerationConfig",
"MultiSpeakerVoiceConfig",
"SpeechConfig",
"TTSRequest",
"TTSResponseHandler",
"get_tts_chat_service"
]
+154
View File
@@ -0,0 +1,154 @@
"""
原生Gemini TTS聊天服务扩展
继承自原始聊天服务,添加原生Gemini TTS支持(单人和多人),保持向后兼容
"""
import time
import datetime
from typing import Any, Dict, Optional
from app.service.chat.gemini_chat_service import GeminiChatService
from app.service.tts.native.tts_response_handler import TTSResponseHandler
from app.domain.gemini_models import GeminiRequest
from app.log.logger import get_gemini_logger
from app.database.services import add_request_log, add_error_log
logger = get_gemini_logger()
class TTSGeminiChatService(GeminiChatService):
"""
支持TTS的Gemini聊天服务
继承自原始的GeminiChatService,添加TTS功能
"""
def __init__(self, base_url: str, key_manager):
"""
初始化TTS聊天服务
"""
super().__init__(base_url, key_manager)
# 使用TTS响应处理器替换原始处理器
self.response_handler = TTSResponseHandler()
logger.info("TTS Gemini Chat Service initialized with multi-speaker TTS support")
async def generate_content(
self, model: str, request: GeminiRequest, api_key: str
) -> Dict[str, Any]:
"""
生成内容,支持TTS
"""
try:
# 添加调试日志
logger.info(f"TTS request model: {model}")
logger.info(f"TTS request generationConfig: {request.generationConfig}")
# 检查是否是TTS模型,如果是,需要特殊处理
if "tts" in model.lower():
logger.info("Detected TTS model, applying TTS-specific processing")
# 对于TTS模型,我们需要确保正确的字段被传递
response = await self._handle_tts_request(model, request, api_key)
return response
else:
# 对于非TTS模型,使用父类的方法
response = await super().generate_content(model, request, api_key)
return response
except Exception as e:
logger.error(f"TTS API call failed with error: {e}")
raise
async def _handle_tts_request(self, model: str, request: GeminiRequest, api_key: str) -> Dict[str, Any]:
"""
处理TTS特定的请求,包含完整的日志记录功能
"""
# 记录开始时间和请求时间
start_time = time.perf_counter()
request_datetime = datetime.datetime.now()
is_success = False
status_code = None
try:
# 构建TTS专用的payload - 不包含tools和safetySettings
from app.service.chat.gemini_chat_service import _filter_empty_parts
request_dict = request.model_dump()
# 构建TTS专用的简化payload
payload = {
"contents": _filter_empty_parts(request_dict.get("contents", [])),
"generationConfig": request_dict.get("generationConfig", {}),
}
# 只在有systemInstruction时才添加
if request_dict.get("systemInstruction"):
payload["systemInstruction"] = request_dict.get("systemInstruction")
# 确保 generationConfig 不为 None
if payload["generationConfig"] is None:
payload["generationConfig"] = {}
# 从原始请求中提取TTS相关字段
if hasattr(request, '_raw_tts_data'):
raw_data = getattr(request, '_raw_tts_data')
raw_generation_config = raw_data.get("generationConfig", {})
# 添加TTS特定字段
if "responseModalities" in raw_generation_config:
payload["generationConfig"]["responseModalities"] = raw_generation_config["responseModalities"]
logger.info(f"Added responseModalities: {raw_generation_config['responseModalities']}")
if "speechConfig" in raw_generation_config:
payload["generationConfig"]["speechConfig"] = raw_generation_config["speechConfig"]
logger.info(f"Added speechConfig: {raw_generation_config['speechConfig']}")
else:
logger.warning("No raw TTS data found in request, TTS fields may be missing")
logger.info(f"TTS payload before API call: {payload}")
# 调用API
response = await self.api_client.generate_content(payload, model, api_key)
# 如果到达这里,说明API调用成功
is_success = True
status_code = 200
# 使用TTS响应处理器处理响应
return self.response_handler.handle_response(response, model, False, None)
except Exception as e:
# 记录错误
is_success = False
error_msg = str(e)
# 尝试从错误消息中提取状态码
import re
match = re.search(r"status code (\d+)", error_msg)
if match:
status_code = int(match.group(1))
else:
status_code = 500
# 添加错误日志
await add_error_log(
gemini_key=api_key,
model_name=model,
error_type="tts-api-error",
error_log=error_msg,
error_code=status_code,
request_msg=request.model_dump()
)
logger.error(f"TTS API call failed: {error_msg}")
raise
finally:
# 记录请求日志
end_time = time.perf_counter()
latency_ms = int((end_time - start_time) * 1000)
await add_request_log(
model_name=model,
api_key=api_key,
is_success=is_success,
status_code=status_code,
latency_ms=latency_ms,
request_time=request_datetime
)
+37
View File
@@ -0,0 +1,37 @@
"""
TTS扩展配置
控制是否启用TTS功能
"""
import os
from typing import Union
from app.service.chat.gemini_chat_service import GeminiChatService
from app.service.tts.native.tts_chat_service import TTSGeminiChatService
class TTSConfig:
"""TTS配置管理"""
@staticmethod
def is_tts_enabled() -> bool:
"""
检查是否启用TTS功能
通过环境变量 ENABLE_TTS 控制,默认为 False
"""
return os.getenv("ENABLE_TTS", "false").lower() in ("true", "1", "yes", "on")
@staticmethod
def get_chat_service(base_url: str, key_manager) -> Union[GeminiChatService, TTSGeminiChatService]:
"""
工厂方法:根据配置返回合适的聊天服务
"""
if TTSConfig.is_tts_enabled():
return TTSGeminiChatService(base_url, key_manager)
else:
return GeminiChatService(base_url, key_manager)
# 便捷函数
def create_chat_service(base_url: str, key_manager) -> Union[GeminiChatService, TTSGeminiChatService]:
"""创建聊天服务实例"""
return TTSConfig.get_chat_service(base_url, key_manager)
+36
View File
@@ -0,0 +1,36 @@
"""
原生Gemini TTS扩展数据模型
继承自原始模型,添加原生Gemini TTS相关字段,保持向后兼容
"""
from typing import Any, Dict, List, Optional
from pydantic import BaseModel
from app.domain.gemini_models import GenerationConfig as BaseGenerationConfig
class TTSGenerationConfig(BaseGenerationConfig):
"""
支持TTS的生成配置类
继承自原始的GenerationConfig,添加TTS相关字段
"""
# TTS 相关配置
responseModalities: Optional[List[str]] = None
speechConfig: Optional[Dict[str, Any]] = None
class MultiSpeakerVoiceConfig(BaseModel):
"""多人语音配置"""
speakerVoiceConfigs: List[Dict[str, Any]]
class SpeechConfig(BaseModel):
"""语音配置"""
multiSpeakerVoiceConfig: Optional[MultiSpeakerVoiceConfig] = None
voiceConfig: Optional[Dict[str, Any]] = None
class TTSRequest(BaseModel):
"""TTS请求模型"""
contents: List[Dict[str, Any]]
generationConfig: TTSGenerationConfig
@@ -0,0 +1,53 @@
"""
原生Gemini TTS响应处理器扩展
继承自原始响应处理器,添加原生Gemini TTS支持,保持向后兼容
"""
from typing import Any, Dict, Optional
from app.handler.response_handler import GeminiResponseHandler
from app.log.logger import get_gemini_logger
logger = get_gemini_logger()
class TTSResponseHandler(GeminiResponseHandler):
"""
支持TTS的响应处理器
继承自原始的GeminiResponseHandler,添加TTS响应处理
"""
def handle_response(
self, response: Dict[str, Any], model: str, stream: bool = False, usage_metadata: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
处理响应,支持TTS音频数据
"""
# 检查是否是TTS响应(包含音频数据)
if self._is_tts_response(response):
logger.info("Detected TTS response with audio data, returning original response")
return response
# 对于非TTS响应,使用父类的处理方法
return super().handle_response(response, model, stream, usage_metadata)
def _is_tts_response(self, response: Dict[str, Any]) -> bool:
"""
检查是否是TTS响应
"""
try:
if (response.get("candidates") and
len(response["candidates"]) > 0 and
response["candidates"][0].get("content") and
response["candidates"][0]["content"].get("parts") and
len(response["candidates"][0]["content"]["parts"]) > 0):
parts = response["candidates"][0]["content"]["parts"]
for part in parts:
if "inlineData" in part:
mime_type = part["inlineData"].get("mimeType", "")
if mime_type.startswith("audio/"):
return True
return False
except Exception as e:
logger.warning(f"Error checking TTS response: {e}")
return False
+24
View File
@@ -0,0 +1,24 @@
"""
TTS路由扩展
提供原生Gemini TTS增强服务,支持单人和多人语音
"""
from fastapi import Depends
from app.config.config import settings
from app.service.key.key_manager import KeyManager, get_key_manager_instance
from app.service.tts.native.tts_chat_service import TTSGeminiChatService
async def get_key_manager():
"""获取密钥管理器实例"""
return get_key_manager_instance()
async def get_tts_chat_service(key_manager: KeyManager = Depends(get_key_manager)) -> TTSGeminiChatService:
"""
获取原生Gemini TTS增强聊天服务实例,支持单人和多人语音
"""
return TTSGeminiChatService(settings.BASE_URL, key_manager)