liumaolin
commited on
Commit
·
a16e0e5
1
Parent(s):
5c0e715
Introduce core module for API lifecycle management: add configuration, service factories, service manager, and lifespan handlers to streamline application startup, shutdown, and service orchestration.
Browse files- src/VoiceDialogue/api/app.py +89 -134
- src/VoiceDialogue/api/core/__init__.py +15 -0
- src/VoiceDialogue/api/core/config.py +79 -0
- src/VoiceDialogue/api/core/lifespan.py +102 -0
- src/VoiceDialogue/api/core/service_factories.py +120 -0
- src/VoiceDialogue/api/core/service_manager.py +156 -0
- src/VoiceDialogue/services/audio/__init__.py +9 -0
src/VoiceDialogue/api/app.py
CHANGED
|
@@ -1,17 +1,11 @@
|
|
| 1 |
import logging
|
| 2 |
-
from contextlib import asynccontextmanager
|
| 3 |
from typing import Dict, Any
|
| 4 |
|
| 5 |
from fastapi import FastAPI, HTTPException, APIRouter
|
| 6 |
from fastapi.middleware.cors import CORSMiddleware
|
| 7 |
|
| 8 |
-
from
|
| 9 |
-
from
|
| 10 |
-
transcribed_text_queue, text_input_queue, audio_output_queue, audio_frames_queue, user_voice_queue
|
| 11 |
-
)
|
| 12 |
-
from services.speech import SpeechStateMonitor, ASRWorker
|
| 13 |
-
from services.text.text_generator import LLMResponseGenerator
|
| 14 |
-
from utils import get_system_language
|
| 15 |
from .middleware.logging import LoggingMiddleware
|
| 16 |
from .middleware.rate_limit import RateLimitMiddleware
|
| 17 |
from .routes import tts_routes, asr_routes
|
|
@@ -23,138 +17,99 @@ logging.basicConfig(
|
|
| 23 |
)
|
| 24 |
logger = logging.getLogger(__name__)
|
| 25 |
|
| 26 |
-
# 全局状态存储
|
| 27 |
-
app_state: Dict[str, Any] = {}
|
| 28 |
|
|
|
|
|
|
|
| 29 |
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
"""应用启动和关闭的生命周期管理"""
|
| 33 |
-
# 启动时的初始化
|
| 34 |
-
logger.info("正在启动VoiceDialogue API服务...")
|
| 35 |
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
-
#
|
| 39 |
-
|
| 40 |
-
from services.audio.audio_generator import tts_config_registry
|
| 41 |
-
logger.info(f"已加载 {len(tts_config_registry.get_all_configs())} 个TTS配置")
|
| 42 |
-
app_state["tts_configs_loaded"] = True
|
| 43 |
-
except Exception as e:
|
| 44 |
-
logger.error(f"TTS配置加载失败: {e}")
|
| 45 |
-
app_state["tts_configs_loaded"] = False
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
)
|
| 51 |
-
speech_monitor.start()
|
| 52 |
-
app_state['speech_monitor'] = speech_monitor
|
| 53 |
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
language=system_default_language
|
| 57 |
-
)
|
| 58 |
-
asr_worker.start()
|
| 59 |
-
app_state['asr_worker'] = asr_worker
|
| 60 |
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
)
|
| 64 |
-
llm_generator.start()
|
| 65 |
-
app_state['llm_generator'] = llm_generator
|
| 66 |
-
|
| 67 |
-
audio_player = AudioStreamPlayer(audio_playing_queue=audio_output_queue)
|
| 68 |
-
audio_player.start()
|
| 69 |
-
app_state['audio_player'] = audio_player
|
| 70 |
-
|
| 71 |
-
app_state["system_running"] = True
|
| 72 |
-
logger.info("VoiceDialogue API服务启动完成")
|
| 73 |
-
yield
|
| 74 |
-
|
| 75 |
-
# 关闭时的清理
|
| 76 |
-
logger.info("正在关闭VoiceDialogue API服务...")
|
| 77 |
-
app_state["system_running"] = False
|
| 78 |
-
logger.info("VoiceDialogue API服务已关闭")
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
# 创建FastAPI应用
|
| 82 |
-
app = FastAPI(
|
| 83 |
-
title="VoiceDialogue API",
|
| 84 |
-
description="""
|
| 85 |
-
语音对话系统的HTTP API接口
|
| 86 |
-
|
| 87 |
-
## 功能特性
|
| 88 |
-
|
| 89 |
-
* **TTS模型管理**: 查看、加载、删除TTS模型
|
| 90 |
-
* **模型状态监控**: 实时监控模型下载和加载状态
|
| 91 |
-
* **RESTful API**: 标准的REST接口设计
|
| 92 |
-
* **自动文档**: 自动生成的API文档和测试界面
|
| 93 |
-
|
| 94 |
-
## 使用方法
|
| 95 |
-
|
| 96 |
-
1. 查看所有可用的TTS模型: `GET /api/v1/tts/models`
|
| 97 |
-
2. 加载指定模型: `POST /api/v1/tts/models/load`
|
| 98 |
-
3. 查看模型状态: `GET /api/v1/tts/models/{model_id}/status`
|
| 99 |
-
4. 删除模型: `DELETE /api/v1/tts/models/{model_id}`
|
| 100 |
-
""",
|
| 101 |
-
version="1.0.0",
|
| 102 |
-
docs_url="/docs",
|
| 103 |
-
redoc_url="/redoc",
|
| 104 |
-
lifespan=lifespan,
|
| 105 |
-
)
|
| 106 |
|
| 107 |
-
|
| 108 |
-
app.add_middleware(
|
| 109 |
-
CORSMiddleware,
|
| 110 |
-
allow_origins=["*"], # 生产环境中应该设置具体的域名
|
| 111 |
-
allow_credentials=True,
|
| 112 |
-
allow_methods=["*"],
|
| 113 |
-
allow_headers=["*"],
|
| 114 |
-
)
|
| 115 |
|
| 116 |
-
|
| 117 |
-
app
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
v1_router
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
app
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
"
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
""
|
| 145 |
-
|
| 146 |
-
"
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import logging
|
|
|
|
| 2 |
from typing import Dict, Any
|
| 3 |
|
| 4 |
from fastapi import FastAPI, HTTPException, APIRouter
|
| 5 |
from fastapi.middleware.cors import CORSMiddleware
|
| 6 |
|
| 7 |
+
from .core.config import AppConfig
|
| 8 |
+
from .core.lifespan import lifespan
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
from .middleware.logging import LoggingMiddleware
|
| 10 |
from .middleware.rate_limit import RateLimitMiddleware
|
| 11 |
from .routes import tts_routes, asr_routes
|
|
|
|
| 17 |
)
|
| 18 |
logger = logging.getLogger(__name__)
|
| 19 |
|
|
|
|
|
|
|
| 20 |
|
| 21 |
+
def create_app() -> FastAPI:
|
| 22 |
+
"""创建并配置FastAPI应用"""
|
| 23 |
|
| 24 |
+
# 应用配置
|
| 25 |
+
config = AppConfig()
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
+
# 创建FastAPI应用
|
| 28 |
+
app = FastAPI(
|
| 29 |
+
title=config.title,
|
| 30 |
+
description=config.description,
|
| 31 |
+
version=config.version,
|
| 32 |
+
docs_url=config.docs_url,
|
| 33 |
+
redoc_url=config.redoc_url,
|
| 34 |
+
lifespan=lifespan,
|
| 35 |
+
)
|
| 36 |
|
| 37 |
+
# 添加CORS中间件
|
| 38 |
+
app.add_middleware(CORSMiddleware, **config.get_cors_config())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
+
# 添加自定义中间件
|
| 41 |
+
app.add_middleware(LoggingMiddleware)
|
| 42 |
+
app.add_middleware(RateLimitMiddleware)
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
+
# 注册路由
|
| 45 |
+
_register_routes(app)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
+
# 注册异常处理器
|
| 48 |
+
_register_exception_handlers(app)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
+
return app
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
+
|
| 53 |
+
def _register_routes(app: FastAPI):
|
| 54 |
+
"""注册所有路由"""
|
| 55 |
+
# API路由
|
| 56 |
+
v1_router = APIRouter(prefix="/api/v1")
|
| 57 |
+
v1_router.include_router(tts_routes.router, prefix="/tts", tags=["TTS模型管理"])
|
| 58 |
+
v1_router.include_router(asr_routes.router, prefix="/asr", tags=["ASR模型管理"])
|
| 59 |
+
|
| 60 |
+
app.include_router(v1_router)
|
| 61 |
+
|
| 62 |
+
# 根路径和健康检查
|
| 63 |
+
_register_health_routes(app)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def _register_health_routes(app: FastAPI):
|
| 67 |
+
"""注册健康检查路由"""
|
| 68 |
+
|
| 69 |
+
@app.get("/")
|
| 70 |
+
async def root():
|
| 71 |
+
"""根路径健康检查"""
|
| 72 |
+
return {
|
| 73 |
+
"message": "欢迎使用VoiceDialogue API",
|
| 74 |
+
"status": "running",
|
| 75 |
+
"version": "1.0.0",
|
| 76 |
+
"docs_url": "/docs",
|
| 77 |
+
"redoc_url": "/redoc"
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
@app.get("/health")
|
| 81 |
+
async def health_check():
|
| 82 |
+
"""健康检查端点"""
|
| 83 |
+
app_state = getattr(app, 'state', {})
|
| 84 |
+
|
| 85 |
+
return {
|
| 86 |
+
"status": "healthy",
|
| 87 |
+
"tts_configs_loaded": app_state.get("tts_configs_loaded", False),
|
| 88 |
+
"system_running": app_state.get("system_running", False),
|
| 89 |
+
"tts_config_count": app_state.get("tts_config_count", 0),
|
| 90 |
+
"services": _get_service_status(app_state)
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def _get_service_status(app_state: Dict[str, Any]) -> dict:
|
| 95 |
+
"""获取服务状态信息"""
|
| 96 |
+
service_manager = app_state.get("service_manager")
|
| 97 |
+
if service_manager:
|
| 98 |
+
return service_manager.get_service_status()
|
| 99 |
+
return {"total_services": 0, "services": {}}
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def _register_exception_handlers(app: FastAPI):
|
| 103 |
+
"""注册全局异常处理器"""
|
| 104 |
+
|
| 105 |
+
@app.exception_handler(Exception)
|
| 106 |
+
async def global_exception_handler(request, exc):
|
| 107 |
+
logger.error(f"未处理的异常: {exc}", exc_info=True)
|
| 108 |
+
return HTTPException(
|
| 109 |
+
status_code=500,
|
| 110 |
+
detail="内部服务器错误"
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
# 创建应用实例
|
| 115 |
+
app = create_app()
|
src/VoiceDialogue/api/core/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .config import AppConfig, TTSConfigInitializer
|
| 2 |
+
from .lifespan import lifespan, LifespanManager
|
| 3 |
+
from .service_factories import ServiceFactories, get_service_definitions
|
| 4 |
+
from .service_manager import ServiceManager, ServiceDefinition
|
| 5 |
+
|
| 6 |
+
__all__ = [
|
| 7 |
+
'ServiceManager',
|
| 8 |
+
'ServiceDefinition',
|
| 9 |
+
'ServiceFactories',
|
| 10 |
+
'get_service_definitions',
|
| 11 |
+
'AppConfig',
|
| 12 |
+
'TTSConfigInitializer',
|
| 13 |
+
'lifespan',
|
| 14 |
+
'LifespanManager'
|
| 15 |
+
]
|
src/VoiceDialogue/api/core/config.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from typing import Dict, Any
|
| 3 |
+
|
| 4 |
+
logger = logging.getLogger(__name__)
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class TTSConfigInitializer:
|
| 8 |
+
"""TTS配置初始化器"""
|
| 9 |
+
|
| 10 |
+
@staticmethod
|
| 11 |
+
def initialize() -> Dict[str, Any]:
|
| 12 |
+
"""初始化TTS配置"""
|
| 13 |
+
result = {
|
| 14 |
+
"tts_configs_loaded": False,
|
| 15 |
+
"tts_config_count": 0,
|
| 16 |
+
"tts_config_errors": []
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
try:
|
| 20 |
+
from services.audio.audio_generator import tts_config_registry
|
| 21 |
+
config_count = len(tts_config_registry.get_all_configs())
|
| 22 |
+
|
| 23 |
+
result.update({
|
| 24 |
+
"tts_configs_loaded": True,
|
| 25 |
+
"tts_config_count": config_count
|
| 26 |
+
})
|
| 27 |
+
|
| 28 |
+
logger.info(f"已加载 {config_count} 个TTS配置")
|
| 29 |
+
|
| 30 |
+
except ImportError as e:
|
| 31 |
+
error_msg = f"TTS模块导入失败: {e}"
|
| 32 |
+
logger.error(error_msg)
|
| 33 |
+
result["tts_config_errors"].append(error_msg)
|
| 34 |
+
|
| 35 |
+
except Exception as e:
|
| 36 |
+
error_msg = f"TTS配置加载失败: {e}"
|
| 37 |
+
logger.error(error_msg, exc_info=True)
|
| 38 |
+
result["tts_config_errors"].append(error_msg)
|
| 39 |
+
|
| 40 |
+
return result
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class AppConfig:
|
| 44 |
+
"""应用配置类"""
|
| 45 |
+
|
| 46 |
+
def __init__(self):
|
| 47 |
+
self.title = "VoiceDialogue API"
|
| 48 |
+
self.version = "1.0.0"
|
| 49 |
+
self.description = self._get_description()
|
| 50 |
+
self.docs_url = "/docs"
|
| 51 |
+
self.redoc_url = "/redoc"
|
| 52 |
+
|
| 53 |
+
def _get_description(self) -> str:
|
| 54 |
+
return """
|
| 55 |
+
语音对话系统的HTTP API接口
|
| 56 |
+
|
| 57 |
+
## 功能特性
|
| 58 |
+
|
| 59 |
+
* **TTS模型管理**: 查看、加载、删除TTS模型
|
| 60 |
+
* **模型状态监控**: 实时监控模型下载和加载状态
|
| 61 |
+
* **RESTful API**: 标准的REST接口设计
|
| 62 |
+
* **自动文档**: 自动生成的API文档和测试界面
|
| 63 |
+
|
| 64 |
+
## 使用方法
|
| 65 |
+
|
| 66 |
+
1. 查看所有可用的TTS模型: `GET /api/v1/tts/models`
|
| 67 |
+
2. 加载指定模型: `POST /api/v1/tts/models/load`
|
| 68 |
+
3. 查看模型状态: `GET /api/v1/tts/models/{model_id}/status`
|
| 69 |
+
4. 删除模型: `DELETE /api/v1/tts/models/{model_id}`
|
| 70 |
+
"""
|
| 71 |
+
|
| 72 |
+
def get_cors_config(self) -> dict:
|
| 73 |
+
"""获取CORS配置"""
|
| 74 |
+
return {
|
| 75 |
+
"allow_origins": ["*"], # 生产环境中应该设置具体的域名
|
| 76 |
+
"allow_credentials": True,
|
| 77 |
+
"allow_methods": ["*"],
|
| 78 |
+
"allow_headers": ["*"],
|
| 79 |
+
}
|
src/VoiceDialogue/api/core/lifespan.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import time
|
| 3 |
+
from contextlib import asynccontextmanager
|
| 4 |
+
|
| 5 |
+
from fastapi import FastAPI
|
| 6 |
+
|
| 7 |
+
from utils import get_system_language
|
| 8 |
+
from .config import TTSConfigInitializer
|
| 9 |
+
from .service_factories import get_service_definitions
|
| 10 |
+
from .service_manager import ServiceManager
|
| 11 |
+
|
| 12 |
+
logger = logging.getLogger(__name__)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class LifespanManager:
|
| 16 |
+
"""应用生命周期管理器"""
|
| 17 |
+
|
| 18 |
+
def __init__(self, app: FastAPI):
|
| 19 |
+
self.app = app
|
| 20 |
+
self.service_manager = ServiceManager()
|
| 21 |
+
|
| 22 |
+
async def startup(self):
|
| 23 |
+
"""应用启动逻辑"""
|
| 24 |
+
logger.info("正在启动VoiceDialogue API服务...")
|
| 25 |
+
startup_start_time = time.time()
|
| 26 |
+
|
| 27 |
+
try:
|
| 28 |
+
# 初始化系统语言
|
| 29 |
+
system_language = get_system_language()
|
| 30 |
+
logger.info(f"系统默认语言: {system_language}")
|
| 31 |
+
|
| 32 |
+
# 初始化TTS配置
|
| 33 |
+
tts_config = TTSConfigInitializer.initialize()
|
| 34 |
+
self._update_app_state(tts_config)
|
| 35 |
+
|
| 36 |
+
# 获取服务定义
|
| 37 |
+
service_definitions = get_service_definitions(system_language)
|
| 38 |
+
|
| 39 |
+
# 启动所有服务
|
| 40 |
+
await self._start_all_services(service_definitions)
|
| 41 |
+
|
| 42 |
+
# 更新应用状态
|
| 43 |
+
self._update_app_state({
|
| 44 |
+
"service_manager": self.service_manager,
|
| 45 |
+
"system_running": True,
|
| 46 |
+
"system_language": system_language
|
| 47 |
+
})
|
| 48 |
+
|
| 49 |
+
# 记录启动信息
|
| 50 |
+
startup_duration = time.time() - startup_start_time
|
| 51 |
+
service_status = self.service_manager.get_service_status()
|
| 52 |
+
|
| 53 |
+
logger.info(f"VoiceDialogue API服务启动完成")
|
| 54 |
+
logger.info(f"启动总耗时: {startup_duration:.2f}秒")
|
| 55 |
+
logger.info(f"启动的服务数量: {service_status['total_services']}")
|
| 56 |
+
|
| 57 |
+
if service_status['startup_errors']:
|
| 58 |
+
logger.warning(f"启动时发生 {service_status['startup_errors']} 个错误")
|
| 59 |
+
|
| 60 |
+
except Exception as e:
|
| 61 |
+
logger.error(f"服务启动失败: {e}", exc_info=True)
|
| 62 |
+
await self.shutdown()
|
| 63 |
+
raise
|
| 64 |
+
|
| 65 |
+
def _update_app_state(self, state_updates: dict):
|
| 66 |
+
"""更新应用状态"""
|
| 67 |
+
for key, value in state_updates.items():
|
| 68 |
+
setattr(self.app.state, key, value)
|
| 69 |
+
|
| 70 |
+
async def _start_all_services(self, service_definitions):
|
| 71 |
+
"""启动所有服务"""
|
| 72 |
+
for service_def in service_definitions:
|
| 73 |
+
success = self.service_manager.start_service(service_def)
|
| 74 |
+
if not success and service_def.required:
|
| 75 |
+
raise RuntimeError(f"必需服务 {service_def.name} 启动失败")
|
| 76 |
+
|
| 77 |
+
async def shutdown(self):
|
| 78 |
+
"""应用关闭逻辑"""
|
| 79 |
+
logger.info("正在关闭VoiceDialogue API服务...")
|
| 80 |
+
|
| 81 |
+
# 更新状态
|
| 82 |
+
setattr(self.app.state, "system_running", False)
|
| 83 |
+
|
| 84 |
+
# 停止所有服务
|
| 85 |
+
self.service_manager.stop_all_services()
|
| 86 |
+
|
| 87 |
+
logger.info("VoiceDialogue API服务已关闭")
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
@asynccontextmanager
|
| 91 |
+
async def lifespan(app: FastAPI):
|
| 92 |
+
"""FastAPI生命周期管理"""
|
| 93 |
+
# 创建生命周期管理器
|
| 94 |
+
lifespan_manager = LifespanManager(app)
|
| 95 |
+
|
| 96 |
+
try:
|
| 97 |
+
# 启动
|
| 98 |
+
await lifespan_manager.startup()
|
| 99 |
+
yield
|
| 100 |
+
finally:
|
| 101 |
+
# 关闭
|
| 102 |
+
await lifespan_manager.shutdown()
|
src/VoiceDialogue/api/core/service_factories.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Any
|
| 2 |
+
|
| 3 |
+
from services.audio import EchoCancellingAudioCapture, TTSAudioGenerator, AudioStreamPlayer
|
| 4 |
+
from services.audio.audio_generator import BaseTTSConfig
|
| 5 |
+
from services.core.constants import (
|
| 6 |
+
transcribed_text_queue, text_input_queue, audio_output_queue,
|
| 7 |
+
audio_frames_queue, user_voice_queue
|
| 8 |
+
)
|
| 9 |
+
from services.speech import SpeechStateMonitor, ASRWorker
|
| 10 |
+
from services.text.text_generator import LLMResponseGenerator
|
| 11 |
+
from .service_manager import ServiceDefinition
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class ServiceFactories:
|
| 15 |
+
"""服务工厂类,封装所有服务的创建逻辑"""
|
| 16 |
+
|
| 17 |
+
@staticmethod
|
| 18 |
+
def create_audio_capture() -> EchoCancellingAudioCapture:
|
| 19 |
+
"""创建音频捕获服务"""
|
| 20 |
+
return EchoCancellingAudioCapture(
|
| 21 |
+
audio_frames_queue=audio_frames_queue
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
@staticmethod
|
| 25 |
+
def create_speech_monitor() -> SpeechStateMonitor:
|
| 26 |
+
"""创建语音监控服务"""
|
| 27 |
+
return SpeechStateMonitor(
|
| 28 |
+
audio_frame_queue=audio_frames_queue,
|
| 29 |
+
user_voice_queue=user_voice_queue,
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
@staticmethod
|
| 33 |
+
def create_asr_worker(language: str) -> ASRWorker:
|
| 34 |
+
"""创建ASR服务"""
|
| 35 |
+
return ASRWorker(
|
| 36 |
+
user_voice_queue=user_voice_queue,
|
| 37 |
+
transcribed_text_queue=transcribed_text_queue,
|
| 38 |
+
language=language
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
@staticmethod
|
| 42 |
+
def create_llm_generator() -> LLMResponseGenerator:
|
| 43 |
+
"""创建LLM文本生成服务"""
|
| 44 |
+
return LLMResponseGenerator(
|
| 45 |
+
user_question_queue=transcribed_text_queue,
|
| 46 |
+
generated_answer_queue=text_input_queue
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
@staticmethod
|
| 50 |
+
def create_tts_audio_generator(tts_speaker_config: BaseTTSConfig) -> TTSAudioGenerator:
|
| 51 |
+
"""创建TTS音频生成服务"""
|
| 52 |
+
return TTSAudioGenerator(
|
| 53 |
+
text_input_queue=text_input_queue,
|
| 54 |
+
audio_output_queue=audio_output_queue,
|
| 55 |
+
tts_config=tts_speaker_config
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
@staticmethod
|
| 59 |
+
def create_audio_player() -> AudioStreamPlayer:
|
| 60 |
+
"""创建音频播放服务"""
|
| 61 |
+
return AudioStreamPlayer(audio_playing_queue=audio_output_queue)
|
| 62 |
+
|
| 63 |
+
@staticmethod
|
| 64 |
+
def create_tts_config_loader() -> Any:
|
| 65 |
+
"""创建TTS配置加载器的虚拟服务"""
|
| 66 |
+
|
| 67 |
+
class TTSConfigLoader:
|
| 68 |
+
def __init__(self):
|
| 69 |
+
self.is_ready = False
|
| 70 |
+
self._running = False
|
| 71 |
+
|
| 72 |
+
def start(self):
|
| 73 |
+
self._running = True
|
| 74 |
+
self.is_ready = True
|
| 75 |
+
|
| 76 |
+
def stop(self):
|
| 77 |
+
self._running = False
|
| 78 |
+
|
| 79 |
+
def is_alive(self):
|
| 80 |
+
return self._running
|
| 81 |
+
|
| 82 |
+
return TTSConfigLoader()
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def get_service_definitions(system_language: str) -> list:
|
| 86 |
+
"""获取服务定义配置"""
|
| 87 |
+
|
| 88 |
+
return [
|
| 89 |
+
ServiceDefinition(
|
| 90 |
+
name="tts_config_loader",
|
| 91 |
+
factory=ServiceFactories.create_tts_config_loader,
|
| 92 |
+
required=False,
|
| 93 |
+
startup_timeout=10
|
| 94 |
+
),
|
| 95 |
+
|
| 96 |
+
ServiceDefinition(
|
| 97 |
+
name="speech_monitor",
|
| 98 |
+
factory=ServiceFactories.create_speech_monitor,
|
| 99 |
+
dependencies=[],
|
| 100 |
+
health_check=lambda service: hasattr(service, 'is_ready') and service.is_ready
|
| 101 |
+
),
|
| 102 |
+
|
| 103 |
+
ServiceDefinition(
|
| 104 |
+
name="asr_worker",
|
| 105 |
+
factory=lambda: ServiceFactories.create_asr_worker(system_language),
|
| 106 |
+
dependencies=["speech_monitor"]
|
| 107 |
+
),
|
| 108 |
+
|
| 109 |
+
ServiceDefinition(
|
| 110 |
+
name="llm_generator",
|
| 111 |
+
factory=ServiceFactories.create_llm_generator,
|
| 112 |
+
dependencies=["asr_worker"]
|
| 113 |
+
),
|
| 114 |
+
|
| 115 |
+
ServiceDefinition(
|
| 116 |
+
name="audio_player",
|
| 117 |
+
factory=ServiceFactories.create_audio_player,
|
| 118 |
+
dependencies=["llm_generator"]
|
| 119 |
+
)
|
| 120 |
+
]
|
src/VoiceDialogue/api/core/service_manager.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import time
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
from typing import Dict, List, Callable, Any, Optional
|
| 5 |
+
|
| 6 |
+
logger = logging.getLogger(__name__)
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
@dataclass
|
| 10 |
+
class ServiceDefinition:
|
| 11 |
+
"""服务定义类,用于配置化管理服务"""
|
| 12 |
+
name: str
|
| 13 |
+
factory: Callable[[], Any]
|
| 14 |
+
dependencies: List[str] = None
|
| 15 |
+
required: bool = True
|
| 16 |
+
startup_timeout: int = 30 # 启动超时时间(秒)
|
| 17 |
+
health_check: Optional[Callable[[Any], bool]] = None
|
| 18 |
+
|
| 19 |
+
def __post_init__(self):
|
| 20 |
+
if self.dependencies is None:
|
| 21 |
+
self.dependencies = []
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class ServiceManager:
|
| 25 |
+
"""服务管理器,负责服务的统一启动、停止和监控"""
|
| 26 |
+
|
| 27 |
+
def __init__(self):
|
| 28 |
+
self.services: Dict[str, Any] = {}
|
| 29 |
+
self.startup_errors: Dict[str, str] = {}
|
| 30 |
+
self.startup_times: Dict[str, float] = {}
|
| 31 |
+
self._shutdown_hooks: List[Callable] = []
|
| 32 |
+
|
| 33 |
+
def add_shutdown_hook(self, hook: Callable):
|
| 34 |
+
"""添加关闭钩子函数"""
|
| 35 |
+
self._shutdown_hooks.append(hook)
|
| 36 |
+
|
| 37 |
+
def start_service(self, service_def: ServiceDefinition) -> bool:
|
| 38 |
+
"""启动单个服务"""
|
| 39 |
+
start_time = time.time()
|
| 40 |
+
try:
|
| 41 |
+
logger.info(f"正在启动服务: {service_def.name}")
|
| 42 |
+
|
| 43 |
+
# 检查依赖服务
|
| 44 |
+
if not self._check_dependencies(service_def.dependencies):
|
| 45 |
+
raise RuntimeError(f"服务 {service_def.name} 的依赖服务未就绪")
|
| 46 |
+
|
| 47 |
+
service = service_def.factory()
|
| 48 |
+
service.start()
|
| 49 |
+
|
| 50 |
+
# 等待服务就绪
|
| 51 |
+
if not self._wait_for_service_ready(service, service_def):
|
| 52 |
+
raise TimeoutError(f"服务 {service_def.name} 启动超时")
|
| 53 |
+
|
| 54 |
+
# 健康检查
|
| 55 |
+
if service_def.health_check and not service_def.health_check(service):
|
| 56 |
+
raise RuntimeError(f"服务 {service_def.name} 健康检查失败")
|
| 57 |
+
|
| 58 |
+
self.services[service_def.name] = service
|
| 59 |
+
self.startup_times[service_def.name] = time.time() - start_time
|
| 60 |
+
|
| 61 |
+
logger.info(f"服务 {service_def.name} 启动成功,耗时: {self.startup_times[service_def.name]:.2f}秒")
|
| 62 |
+
return True
|
| 63 |
+
|
| 64 |
+
except Exception as e:
|
| 65 |
+
error_msg = f"服务 {service_def.name} 启动失败: {e}"
|
| 66 |
+
logger.error(error_msg, exc_info=True)
|
| 67 |
+
self.startup_errors[service_def.name] = error_msg
|
| 68 |
+
|
| 69 |
+
if service_def.required:
|
| 70 |
+
raise RuntimeError(error_msg)
|
| 71 |
+
return False
|
| 72 |
+
|
| 73 |
+
def _check_dependencies(self, dependencies: List[str]) -> bool:
|
| 74 |
+
"""检查依赖服务是否已启动并就绪"""
|
| 75 |
+
for dep in dependencies:
|
| 76 |
+
if dep not in self.services:
|
| 77 |
+
logger.error(f"依赖服务 {dep} 未启动")
|
| 78 |
+
return False
|
| 79 |
+
if not self.services[dep].is_ready:
|
| 80 |
+
logger.error(f"依赖服务 {dep} 未就绪")
|
| 81 |
+
return False
|
| 82 |
+
return True
|
| 83 |
+
|
| 84 |
+
def _wait_for_service_ready(self, service: Any, service_def: ServiceDefinition) -> bool:
|
| 85 |
+
"""等待服务就绪"""
|
| 86 |
+
timeout = service_def.startup_timeout
|
| 87 |
+
start_time = time.time()
|
| 88 |
+
|
| 89 |
+
while not service.is_ready and (time.time() - start_time) < timeout:
|
| 90 |
+
time.sleep(0.1)
|
| 91 |
+
|
| 92 |
+
return service.is_ready
|
| 93 |
+
|
| 94 |
+
def stop_all_services(self):
|
| 95 |
+
"""停止所有服务"""
|
| 96 |
+
logger.info("正在停止所有服务...")
|
| 97 |
+
|
| 98 |
+
# 执行关闭钩子
|
| 99 |
+
for hook in self._shutdown_hooks:
|
| 100 |
+
try:
|
| 101 |
+
hook()
|
| 102 |
+
except Exception as e:
|
| 103 |
+
logger.error(f"执行关闭钩子时发生错误: {e}", exc_info=True)
|
| 104 |
+
|
| 105 |
+
# 按启动顺序的逆序停止服务
|
| 106 |
+
for service_name in reversed(list(self.services.keys())):
|
| 107 |
+
self._stop_single_service(service_name)
|
| 108 |
+
|
| 109 |
+
self.services.clear()
|
| 110 |
+
|
| 111 |
+
def _stop_single_service(self, service_name: str):
|
| 112 |
+
"""停止单个服务"""
|
| 113 |
+
try:
|
| 114 |
+
service = self.services[service_name]
|
| 115 |
+
logger.info(f"正在停止服务: {service_name}")
|
| 116 |
+
|
| 117 |
+
service.stop()
|
| 118 |
+
|
| 119 |
+
# 等待服务停止(最多等待5秒)
|
| 120 |
+
timeout = 5
|
| 121 |
+
start_time = time.time()
|
| 122 |
+
while service.is_alive() and (time.time() - start_time) < timeout:
|
| 123 |
+
time.sleep(0.1)
|
| 124 |
+
|
| 125 |
+
if service.is_alive():
|
| 126 |
+
logger.warning(f"服务 {service_name} 未能在超时时间内停止")
|
| 127 |
+
else:
|
| 128 |
+
logger.info(f"服务 {service_name} 已停止")
|
| 129 |
+
|
| 130 |
+
except Exception as e:
|
| 131 |
+
logger.error(f"停止服务 {service_name} 时发生错误: {e}", exc_info=True)
|
| 132 |
+
|
| 133 |
+
def get_service_status(self) -> dict:
|
| 134 |
+
"""获取所有服务状态"""
|
| 135 |
+
return {
|
| 136 |
+
"total_services": len(self.services),
|
| 137 |
+
"startup_errors": len(self.startup_errors),
|
| 138 |
+
"startup_times": self.startup_times.copy(),
|
| 139 |
+
"errors": self.startup_errors.copy(),
|
| 140 |
+
"services": {
|
| 141 |
+
name: {
|
| 142 |
+
"running": service.is_alive(),
|
| 143 |
+
"ready": service.is_ready
|
| 144 |
+
}
|
| 145 |
+
for name, service in self.services.items()
|
| 146 |
+
}
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
def get_service(self, name: str) -> Optional[Any]:
|
| 150 |
+
"""获取指定服务实例"""
|
| 151 |
+
return self.services.get(name)
|
| 152 |
+
|
| 153 |
+
def is_service_running(self, name: str) -> bool:
|
| 154 |
+
"""检查服务是否正在运行"""
|
| 155 |
+
service = self.services.get(name)
|
| 156 |
+
return service is not None and service.is_alive()
|
src/VoiceDialogue/services/audio/__init__.py
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .aec_audio_capture import EchoCancellingAudioCapture
|
| 2 |
+
from .audio_answer import TTSAudioGenerator
|
| 3 |
+
from .audio_player import AudioStreamPlayer
|
| 4 |
+
|
| 5 |
+
__all__ = (
|
| 6 |
+
"EchoCancellingAudioCapture",
|
| 7 |
+
"TTSAudioGenerator",
|
| 8 |
+
"AudioStreamPlayer",
|
| 9 |
+
)
|