""" 异步会议服务 - 处理会议总结生成的异步任务 采用FastAPI BackgroundTasks模式 """ import uuid import time from datetime import datetime from typing import Optional, Dict, Any, List import redis from app.core.config import REDIS_CONFIG from app.core.database import get_db_connection from app.services.llm_service import LLMService class AsyncMeetingService: """异步会议服务类 - 处理会议相关的异步任务""" def __init__(self): # 确保redis客户端自动解码响应,代码更简洁 if 'decode_responses' not in REDIS_CONFIG: REDIS_CONFIG['decode_responses'] = True self.redis_client = redis.Redis(**REDIS_CONFIG) self.llm_service = LLMService() # 复用现有的同步LLM服务 def start_summary_generation(self, meeting_id: int, user_prompt: str = "") -> str: """ 创建异步总结任务,任务的执行将由外部(如API层的BackgroundTasks)触发。 Args: meeting_id: 会议ID user_prompt: 用户额外提示词 Returns: str: 任务ID """ try: task_id = str(uuid.uuid4()) # 在数据库中创建任务记录 self._save_task_to_db(task_id, meeting_id, user_prompt) # 将任务详情存入Redis,用于快速查询状态 current_time = datetime.now().isoformat() task_data = { 'task_id': task_id, 'meeting_id': str(meeting_id), 'user_prompt': user_prompt, 'status': 'pending', 'progress': '0', 'created_at': current_time, 'updated_at': current_time } self.redis_client.hset(f"llm_task:{task_id}", mapping=task_data) self.redis_client.expire(f"llm_task:{task_id}", 86400) print(f"Meeting summary task created: {task_id} for meeting: {meeting_id}") return task_id except Exception as e: print(f"Error starting summary generation: {e}") raise e def _process_task(self, task_id: str): """ 处理单个异步任务的函数,设计为由BackgroundTasks调用。 """ print(f"Background task started for meeting summary task: {task_id}") try: # 从Redis获取任务数据 task_data = self.redis_client.hgetall(f"llm_task:{task_id}") if not task_data: print(f"Error: Task {task_id} not found in Redis for processing.") return meeting_id = int(task_data['meeting_id']) user_prompt = task_data.get('user_prompt', '') # 1. 更新状态为processing self._update_task_status_in_redis(task_id, 'processing', 10, message="任务已开始...") # 2. 获取会议转录内容 self._update_task_status_in_redis(task_id, 'processing', 30, message="获取会议转录内容...") transcript_text = self._get_meeting_transcript(meeting_id) if not transcript_text: raise Exception("无法获取会议转录内容") # 3. 构建提示词 self._update_task_status_in_redis(task_id, 'processing', 40, message="准备AI提示词...") full_prompt = self._build_prompt(transcript_text, user_prompt) # 4. 调用LLM API self._update_task_status_in_redis(task_id, 'processing', 50, message="AI正在分析会议内容...") summary_content = self.llm_service._call_llm_api(full_prompt) if not summary_content: raise Exception("LLM API调用失败或返回空内容") # 5. 保存结果到主表 self._update_task_status_in_redis(task_id, 'processing', 95, message="保存总结结果...") self._save_summary_to_db(meeting_id, summary_content, user_prompt) # 6. 任务完成 self._update_task_in_db(task_id, 'completed', 100, result=summary_content) self._update_task_status_in_redis(task_id, 'completed', 100, result=summary_content) print(f"Task {task_id} completed successfully") except Exception as e: error_msg = str(e) print(f"Task {task_id} failed: {error_msg}") # 更新失败状态 self._update_task_in_db(task_id, 'failed', 0, error_message=error_msg) self._update_task_status_in_redis(task_id, 'failed', 0, error_message=error_msg) # --- 会议相关方法 --- def _get_meeting_transcript(self, meeting_id: int) -> str: """从数据库获取会议转录内容""" try: with get_db_connection() as connection: cursor = connection.cursor() query = """ SELECT speaker_tag, start_time_ms, end_time_ms, text_content FROM transcript_segments WHERE meeting_id = %s ORDER BY start_time_ms """ cursor.execute(query, (meeting_id,)) segments = cursor.fetchall() if not segments: return "" # 组装转录文本 transcript_lines = [] for speaker_tag, start_time, end_time, text in segments: # 将毫秒转换为分:秒格式 start_min = start_time // 60000 start_sec = (start_time % 60000) // 1000 transcript_lines.append(f"[{start_min:02d}:{start_sec:02d}] 说话人{speaker_tag}: {text}") return "\n".join(transcript_lines) except Exception as e: print(f"获取会议转录内容错误: {e}") return "" def _build_prompt(self, transcript_text: str, user_prompt: str) -> str: """ 构建完整的提示词 使用数据库中配置的MEETING_TASK提示词模板 """ # 从数据库获取会议任务的提示词模板 system_prompt = self.llm_service.get_task_prompt('MEETING_TASK') prompt = f"{system_prompt}\n\n" if user_prompt: prompt += f"用户额外要求:{user_prompt}\n\n" prompt += f"会议转录内容:\n{transcript_text}\n\n请根据以上内容生成会议总结:" return prompt def _save_summary_to_db(self, meeting_id: int, summary_content: str, user_prompt: str) -> Optional[int]: """保存总结到数据库 - 更新meetings表的summary、user_prompt和updated_at字段""" try: with get_db_connection() as connection: cursor = connection.cursor() # 更新meetings表的summary、user_prompt和updated_at字段 update_query = """ UPDATE meetings SET summary = %s, user_prompt = %s, updated_at = NOW() WHERE meeting_id = %s """ cursor.execute(update_query, (summary_content, user_prompt, meeting_id)) connection.commit() print(f"成功保存会议总结到meetings表,meeting_id: {meeting_id}") return meeting_id except Exception as e: print(f"保存总结到数据库错误: {e}") return None # --- 状态查询和数据库操作方法 --- def get_task_status(self, task_id: str) -> Dict[str, Any]: """获取任务状态""" try: task_data = self.redis_client.hgetall(f"llm_task:{task_id}") if not task_data: task_data = self._get_task_from_db(task_id) if not task_data: return {'task_id': task_id, 'status': 'not_found', 'error_message': 'Task not found'} return { 'task_id': task_id, 'status': task_data.get('status', 'unknown'), 'progress': int(task_data.get('progress', 0)), 'meeting_id': int(task_data.get('meeting_id', 0)), 'created_at': task_data.get('created_at'), 'updated_at': task_data.get('updated_at'), 'result': task_data.get('result'), 'error_message': task_data.get('error_message') } except Exception as e: print(f"Error getting task status: {e}") return {'task_id': task_id, 'status': 'error', 'error_message': str(e)} def get_meeting_llm_tasks(self, meeting_id: int) -> List[Dict[str, Any]]: """获取会议的所有LLM任务""" try: with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) query = "SELECT task_id, status, progress, user_prompt, created_at, completed_at, error_message FROM llm_tasks WHERE meeting_id = %s ORDER BY created_at DESC" cursor.execute(query, (meeting_id,)) tasks = cursor.fetchall() for task in tasks: if task.get('created_at'): task['created_at'] = task['created_at'].isoformat() if task.get('completed_at'): task['completed_at'] = task['completed_at'].isoformat() return tasks except Exception as e: print(f"Error getting meeting LLM tasks: {e}") return [] def _update_task_status_in_redis(self, task_id: str, status: str, progress: int, message: str = None, result: str = None, error_message: str = None): """更新Redis中的任务状态""" try: update_data = { 'status': status, 'progress': str(progress), 'updated_at': datetime.now().isoformat() } if message: update_data['message'] = message if result: update_data['result'] = result if error_message: update_data['error_message'] = error_message self.redis_client.hset(f"llm_task:{task_id}", mapping=update_data) except Exception as e: print(f"Error updating task status in Redis: {e}") def _save_task_to_db(self, task_id: str, meeting_id: int, user_prompt: str): """保存任务到数据库""" try: with get_db_connection() as connection: cursor = connection.cursor() insert_query = "INSERT INTO llm_tasks (task_id, meeting_id, user_prompt, status, progress, created_at) VALUES (%s, %s, %s, 'pending', 0, NOW())" cursor.execute(insert_query, (task_id, meeting_id, user_prompt)) connection.commit() except Exception as e: print(f"Error saving task to database: {e}") raise def _update_task_in_db(self, task_id: str, status: str, progress: int, result: str = None, error_message: str = None): """更新数据库中的任务状态""" try: with get_db_connection() as connection: cursor = connection.cursor() params = [status, progress, error_message, task_id] if status == 'completed': query = "UPDATE llm_tasks SET status = %s, progress = %s, error_message = %s, result = %s, completed_at = NOW() WHERE task_id = %s" params.insert(2, result) else: query = "UPDATE llm_tasks SET status = %s, progress = %s, error_message = %s WHERE task_id = %s" cursor.execute(query, tuple(params)) connection.commit() except Exception as e: print(f"Error updating task in database: {e}") def _get_task_from_db(self, task_id: str) -> Optional[Dict[str, str]]: """从数据库获取任务信息""" try: with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) query = "SELECT * FROM llm_tasks WHERE task_id = %s" cursor.execute(query, (task_id,)) task = cursor.fetchone() if task: # 确保所有字段都是字符串,以匹配Redis的行为 return {k: v.isoformat() if isinstance(v, datetime) else str(v) for k, v in task.items()} return None except Exception as e: print(f"Error getting task from database: {e}") return None # 创建全局实例 async_meeting_service = AsyncMeetingService()