diff --git a/src/chat/utils/utils_video.py b/src/chat/utils/utils_video.py index 6ecd599af..bbe489336 100644 --- a/src/chat/utils/utils_video.py +++ b/src/chat/utils/utils_video.py @@ -207,6 +207,9 @@ class VideoAnalyzer: """检查视频是否已经分析过""" try: async with get_db_session() as session: + if not session: + logger.warning("无法获取数据库会话,跳过视频存在性检查。") + return None # 明确刷新会话以确保看到其他事务的最新提交 await session.expire_all() stmt = select(Videos).where(Videos.video_hash == video_hash) @@ -227,6 +230,9 @@ class VideoAnalyzer: try: async with get_db_session() as session: + if not session: + logger.warning("无法获取数据库会话,跳过视频结果存储。") + return None # 只根据video_hash查找 stmt = select(Videos).where(Videos.video_hash == video_hash) result = await session.execute(stmt) @@ -540,11 +546,14 @@ class VideoAnalyzer: # logger.info(f"✅ 多帧消息构建完成,包含{len(frames)}张图片") # 获取模型信息和客户端 - model_info, api_provider, client = self.video_llm._select_model() + selection_result = self.video_llm._model_selector.select_best_available_model(set(), "response") + if not selection_result: + raise RuntimeError("无法为视频分析选择可用模型。") + model_info, api_provider, client = selection_result # logger.info(f"使用模型: {model_info.name} 进行多帧分析") # 直接执行多图片请求 - api_response = await self.video_llm._execute_request( + api_response = await self.video_llm._executor.execute_request( api_provider=api_provider, client=client, request_type=RequestType.RESPONSE, diff --git a/src/chat/utils/utils_video_legacy.py b/src/chat/utils/utils_video_legacy.py index 4d8e06681..77ca88142 100644 --- a/src/chat/utils/utils_video_legacy.py +++ b/src/chat/utils/utils_video_legacy.py @@ -461,11 +461,14 @@ class LegacyVideoAnalyzer: # logger.info(f"✅ 多帧消息构建完成,包含{len(frames)}张图片") # 获取模型信息和客户端 - model_info, api_provider, client = self.video_llm._select_model() + selection_result = self.video_llm._model_selector.select_best_available_model(set(), "response") + if not selection_result: + raise RuntimeError("无法为视频分析选择可用模型 (legacy)。") + model_info, api_provider, client = selection_result # logger.info(f"使用模型: {model_info.name} 进行多帧分析") # 直接执行多图片请求 - api_response = await self.video_llm._execute_request( + api_response = await self.video_llm._executor.execute_request( api_provider=api_provider, client=client, request_type=RequestType.RESPONSE, diff --git a/src/common/database/sqlalchemy_models.py b/src/common/database/sqlalchemy_models.py index cf74eedb5..64c1fd66a 100644 --- a/src/common/database/sqlalchemy_models.py +++ b/src/common/database/sqlalchemy_models.py @@ -759,30 +759,38 @@ async def initialize_database(): @asynccontextmanager -async def get_db_session() -> AsyncGenerator[AsyncSession, None]: - """异步数据库会话上下文管理器""" +async def get_db_session() -> AsyncGenerator[Optional[AsyncSession], None]: + """ + 异步数据库会话上下文管理器。 + 在初始化失败时会yield None,调用方需要检查会话是否为None。 + """ session: Optional[AsyncSession] = None + SessionLocal = None try: - engine, SessionLocal = await initialize_database() + _, SessionLocal = await initialize_database() if not SessionLocal: - raise RuntimeError("Database session not initialized") - session = SessionLocal() + logger.error("数据库会话工厂 (_SessionLocal) 未初始化。") + yield None + return + except Exception as e: + logger.error(f"数据库初始化失败,无法创建会话: {e}") + yield None + return + try: + session = SessionLocal() # 对于 SQLite,在会话开始时设置 PRAGMA from src.config.config import global_config if global_config.database.database_type == "sqlite": - try: - await session.execute(text("PRAGMA busy_timeout = 60000")) - await session.execute(text("PRAGMA foreign_keys = ON")) - except Exception as e: - logger.warning(f"[SQLite] 设置会话 PRAGMA 失败: {e}") + await session.execute(text("PRAGMA busy_timeout = 60000")) + await session.execute(text("PRAGMA foreign_keys = ON")) yield session except Exception as e: - logger.error(f"数据库会话错误: {e}") + logger.error(f"数据库会话期间发生错误: {e}") if session: await session.rollback() - raise + raise # 将会话期间的错误重新抛出给调用者 finally: if session: await session.close() diff --git a/src/plugins/built_in/affinity_flow_chatter/plan_executor.py b/src/plugins/built_in/affinity_flow_chatter/plan_executor.py index 821d52bd6..cf14c221e 100644 --- a/src/plugins/built_in/affinity_flow_chatter/plan_executor.py +++ b/src/plugins/built_in/affinity_flow_chatter/plan_executor.py @@ -111,17 +111,47 @@ class ChatterPlanExecutor: } async def _execute_reply_actions(self, reply_actions: List[ActionPlannerInfo], plan: Plan) -> Dict[str, any]: - """串行执行所有回复动作""" + """串行执行所有回复动作,增加去重逻辑,避免对同一消息多次回复""" results = [] - total_actions = len(reply_actions) - if total_actions > 1: + + # --- 新增去重逻辑 --- + unique_actions = [] + replied_message_ids = set() + for action_info in reply_actions: + target_message = action_info.action_message + message_id = None + if target_message: + # 兼容 Pydantic 对象和字典两种情况 + if hasattr(target_message, "message_id"): + message_id = getattr(target_message, "message_id", None) + elif isinstance(target_message, dict): + message_id = target_message.get("message_id") + + if message_id: + if message_id not in replied_message_ids: + unique_actions.append(action_info) + replied_message_ids.add(message_id) + else: + logger.warning( + f"[多重回复] 检测到对消息ID '{message_id}' 的重复回复,已过滤。" + f" (动作: {action_info.action_type}, 原因: {action_info.reasoning})" + ) + else: + # 如果没有message_id,无法去重,直接添加 + unique_actions.append(action_info) + # --- 去重逻辑结束 --- + + total_actions = len(unique_actions) + if len(reply_actions) > total_actions: + logger.info(f"[多重回复] 原始回复任务 {len(reply_actions)} 个,去重后剩余 {total_actions} 个。") + elif total_actions > 1: logger.info(f"[多重回复] 开始执行 {total_actions} 个回复任务。") - for i, action_info in enumerate(reply_actions): + for i, action_info in enumerate(unique_actions): is_last_action = i == total_actions - 1 if total_actions > 1: logger.info(f"[多重回复] 正在执行第 {i+1}/{total_actions} 个回复...") - + # 传递 clear_unread 参数 result = await self._execute_single_reply_action(action_info, plan, clear_unread=is_last_action) results.append(result) diff --git a/src/plugins/built_in/maizone_refactored/services/qzone_service.py b/src/plugins/built_in/maizone_refactored/services/qzone_service.py index 40c0d424a..2022461f7 100644 --- a/src/plugins/built_in/maizone_refactored/services/qzone_service.py +++ b/src/plugins/built_in/maizone_refactored/services/qzone_service.py @@ -740,28 +740,42 @@ class QZoneService: feeds_list = [] my_name = json_data.get("logininfo", {}).get("name", "") for msg in json_data.get("msglist", []): + # 只有在处理好友说说时,才检查是否已评论并跳过 + commentlist = msg.get("commentlist") + # 只有在处理好友说说时,才检查是否已评论并跳过 if not is_monitoring_own_feeds: - is_commented = any( - c.get("name") == my_name for c in msg.get("commentlist", []) if isinstance(c, dict) - ) + is_commented = False + if isinstance(commentlist, list): + is_commented = any( + c.get("name") == my_name for c in commentlist if isinstance(c, dict) + ) if is_commented: continue - images = [pic["url1"] for pic in msg.get("pictotal", []) if "url1" in pic] + # --- 安全地处理图片列表 --- + images = [] + pictotal = msg.get("pictotal") + if isinstance(pictotal, list): + images = [ + pic["url1"] for pic in pictotal if isinstance(pic, dict) and "url1" in pic + ] + # --- 安全地处理评论列表 --- comments = [] - if "commentlist" in msg: - for c in msg["commentlist"]: - comments.append( - { - "qq_account": c.get("uin"), - "nickname": c.get("name"), - "content": c.get("content"), - "comment_tid": c.get("tid"), - "parent_tid": c.get("parent_tid"), # API直接返回了父ID - } - ) + if isinstance(commentlist, list): + for c in commentlist: + # 确保评论条目也是字典 + if isinstance(c, dict): + comments.append( + { + "qq_account": c.get("uin"), + "nickname": c.get("name"), + "content": c.get("content"), + "comment_tid": c.get("tid"), + "parent_tid": c.get("parent_tid"), + } + ) feeds_list.append( {