From 284389b5d2ba23270e81007875068e5c37ac6636 Mon Sep 17 00:00:00 2001 From: SXP-Simon Date: Tue, 4 Nov 2025 18:32:38 +0800 Subject: [PATCH] =?UTF-8?q?[fix]=20user=5Ftitle=20=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/analysis/analyzers/user_title_analyzer.py | 35 ++++++++++++------- src/analysis/llm_analyzer.py | 10 +++--- src/analysis/statistics.py | 8 +++++ src/utils/helpers.py | 19 ++++++---- 4 files changed, 50 insertions(+), 22 deletions(-) diff --git a/src/analysis/analyzers/user_title_analyzer.py b/src/analysis/analyzers/user_title_analyzer.py index 3b5ac73..68c3537 100644 --- a/src/analysis/analyzers/user_title_analyzer.py +++ b/src/analysis/analyzers/user_title_analyzer.py @@ -136,13 +136,14 @@ class UserTitleAnalyzer(BaseAnalyzer): logger.error(f"创建用户称号对象失败: {e}") return [] - def prepare_user_data(self, messages: List[Dict], user_analysis: Dict) -> Dict: + def prepare_user_data(self, messages: List[Dict], user_analysis: Dict, top_users: List[Dict] = None) -> Dict: """ 准备用户数据 Args: messages: 群聊消息列表 user_analysis: 用户分析统计 + top_users: 活跃用户列表(从get_top_users获取) Returns: 准备好的用户数据字典 @@ -150,8 +151,19 @@ class UserTitleAnalyzer(BaseAnalyzer): try: user_summaries = [] + # 如果提供了top_users列表,只分析这些活跃用户 + if top_users: + logger.info(f"使用get_top_users筛选出的 {len(top_users)} 个活跃用户进行称号分析") + target_user_ids = {str(user['user_id']) for user in top_users} + else: + # 兼容旧逻辑:如果没有提供top_users,则使用所有消息数>=5的用户 + logger.info("未提供活跃用户列表,使用消息数>=5的用户") + target_user_ids = {user_id for user_id, stats in user_analysis.items() + if stats["message_count"] >= 5} + for user_id, stats in user_analysis.items(): - if stats["message_count"] < 5: # 过滤活跃度太低的用户 + # 只处理活跃用户 + if user_id not in target_user_ids: continue # 分析用户特征 @@ -164,18 +176,16 @@ class UserTitleAnalyzer(BaseAnalyzer): "qq": int(user_id), "message_count": stats["message_count"], "avg_chars": round(avg_chars, 1), - "emoji_ratio": round(stats["emoji_count"] / stats["message_count"], 2), - "night_ratio": round(night_messages / stats["message_count"], 2), - "reply_ratio": round(stats["reply_count"] / stats["message_count"], 2) + "emoji_ratio": round(stats["emoji_count"] / stats["message_count"], 2) if stats["message_count"] > 0 else 0, + "night_ratio": round(night_messages / stats["message_count"], 2) if stats["message_count"] > 0 else 0, + "reply_ratio": round(stats["reply_count"] / stats["message_count"], 2) if stats["message_count"] > 0 else 0 }) if not user_summaries: return {"user_summaries": []} - # 按消息数量排序,取前N名 - max_user_titles = self.get_max_count() + # 按消息数量排序 user_summaries.sort(key=lambda x: x["message_count"], reverse=True) - user_summaries = user_summaries[:max_user_titles] return {"user_summaries": user_summaries} @@ -183,7 +193,7 @@ class UserTitleAnalyzer(BaseAnalyzer): logger.error(f"准备用户数据失败: {e}") return {"user_summaries": []} - async def analyze_user_titles(self, messages: List[Dict], user_analysis: Dict, umo: str = None) -> Tuple[List[UserTitle], TokenUsage]: + async def analyze_user_titles(self, messages: List[Dict], user_analysis: Dict, umo: str = None, top_users: List[Dict] = None) -> Tuple[List[UserTitle], TokenUsage]: """ 分析用户称号 @@ -191,19 +201,20 @@ class UserTitleAnalyzer(BaseAnalyzer): messages: 群聊消息列表 user_analysis: 用户分析统计 umo: 模型唯一标识符 + top_users: 活跃用户列表(从get_top_users获取,可选) Returns: (用户称号列表, Token使用统计) """ try: - # 准备用户数据 - user_data = self.prepare_user_data(messages, user_analysis) + # 准备用户数据,传入活跃用户列表 + user_data = self.prepare_user_data(messages, user_analysis, top_users) if not user_data["user_summaries"]: logger.info("没有符合条件的用户,返回空结果") return [], TokenUsage() - logger.info(f"开始分析 {len(user_data['user_summaries'])} 个用户的称号") + logger.info(f"开始分析 {len(user_data['user_summaries'])} 个活跃用户的称号") return await self.analyze(user_data, umo) except Exception as e: diff --git a/src/analysis/llm_analyzer.py b/src/analysis/llm_analyzer.py index 973aa05..ebd11f9 100644 --- a/src/analysis/llm_analyzer.py +++ b/src/analysis/llm_analyzer.py @@ -57,7 +57,7 @@ class LLMAnalyzer: logger.error(f"话题分析失败: {e}") return [], TokenUsage() - async def analyze_user_titles(self, messages: List[Dict], user_analysis: Dict, umo: str = None) -> Tuple[List[UserTitle], TokenUsage]: + async def analyze_user_titles(self, messages: List[Dict], user_analysis: Dict, umo: str = None, top_users: List[Dict] = None) -> Tuple[List[UserTitle], TokenUsage]: """ 使用LLM分析用户称号 保持原有接口,委托给专门的UserTitleAnalyzer处理 @@ -66,13 +66,14 @@ class LLMAnalyzer: messages: 群聊消息列表 user_analysis: 用户分析统计 umo: 模型唯一标识符 + top_users: 活跃用户列表(可选) Returns: (用户称号列表, Token使用统计) """ try: logger.info("开始用户称号分析") - return await self.user_title_analyzer.analyze_user_titles(messages, user_analysis, umo) + return await self.user_title_analyzer.analyze_user_titles(messages, user_analysis, umo, top_users) except Exception as e: logger.error(f"用户称号分析失败: {e}") return [], TokenUsage() @@ -96,7 +97,7 @@ class LLMAnalyzer: logger.error(f"金句分析失败: {e}") return [], TokenUsage() - async def analyze_all_concurrent(self, messages: List[Dict], user_analysis: Dict, umo: str = None) -> Tuple[List[SummaryTopic], List[UserTitle], List[GoldenQuote], TokenUsage]: + async def analyze_all_concurrent(self, messages: List[Dict], user_analysis: Dict, umo: str = None, top_users: List[Dict] = None) -> Tuple[List[SummaryTopic], List[UserTitle], List[GoldenQuote], TokenUsage]: """ 并发执行所有分析任务(话题、用户称号、金句) @@ -104,6 +105,7 @@ class LLMAnalyzer: messages: 群聊消息列表 user_analysis: 用户分析统计 umo: 模型唯一标识符 + top_users: 活跃用户列表(可选) Returns: (话题列表, 用户称号列表, 金句列表, 总Token使用统计) @@ -114,7 +116,7 @@ class LLMAnalyzer: # 并发执行三个分析任务 results = await asyncio.gather( self.topic_analyzer.analyze_topics(messages, umo), - self.user_title_analyzer.analyze_user_titles(messages, user_analysis, umo), + self.user_title_analyzer.analyze_user_titles(messages, user_analysis, umo, top_users), self.golden_quote_analyzer.analyze_golden_quotes(messages, umo), return_exceptions=True ) diff --git a/src/analysis/statistics.py b/src/analysis/statistics.py index c1bbb1a..4d60b9c 100644 --- a/src/analysis/statistics.py +++ b/src/analysis/statistics.py @@ -17,6 +17,9 @@ class UserAnalyzer: def analyze_users(self, messages: List[Dict]) -> Dict[str, Dict]: """分析用户活跃度""" + # 获取机器人QQ号用于过滤 + bot_qq_id = self.config_manager.get_bot_qq_id() + user_stats = defaultdict(lambda: { "message_count": 0, "char_count": 0, @@ -29,6 +32,11 @@ class UserAnalyzer: for msg in messages: sender = msg.get("sender", {}) user_id = str(sender.get("user_id", "")) + + # 跳过机器人自己的消息,避免进入统计 + if bot_qq_id and user_id == str(bot_qq_id): + continue + nickname = InfoUtils.get_user_nickname(self.config_manager, sender) user_stats[user_id]["message_count"] += 1 diff --git a/src/utils/helpers.py b/src/utils/helpers.py index 339d263..b538f77 100644 --- a/src/utils/helpers.py +++ b/src/utils/helpers.py @@ -8,7 +8,7 @@ from ...src.models.data_models import GroupStatistics, SummaryTopic, UserTitle, from ...src.core.message_handler import MessageHandler from ...src.analysis.llm_analyzer import LLMAnalyzer from ...src.analysis.statistics import UserAnalyzer - +from astrbot.api import logger class MessageAnalyzer: """消息分析器 - 整合所有分析功能""" @@ -36,6 +36,11 @@ class MessageAnalyzer: # 用户分析 user_analysis = self.user_analyzer.analyze_users(messages) + + # 获取活跃用户列表 - 使用get_top_users方法,limit从配置中读取 + max_user_titles = self.config_manager.get_max_user_titles() + top_users = self.user_analyzer.get_top_users(user_analysis, limit=max_user_titles) + logger.info(f"获取到 {len(top_users)} 个活跃用户用于称号分析(配置上限: {max_user_titles})") # LLM分析 - 使用并发方式 topics = [] @@ -50,9 +55,9 @@ class MessageAnalyzer: # 如果三个分析都启用,使用并发执行 if topic_enabled and user_title_enabled and golden_quote_enabled: - # 并发执行所有三个分析任务 + # 并发执行所有三个分析任务,传入活跃用户列表 topics, user_titles, golden_quotes, total_token_usage = await self.llm_analyzer.analyze_all_concurrent( - messages, user_analysis, umo=unified_msg_origin + messages, user_analysis, umo=unified_msg_origin, top_users=top_users ) else: # 如果只启用部分分析,则按需执行 @@ -63,7 +68,10 @@ class MessageAnalyzer: total_token_usage.total_tokens += topic_tokens.total_tokens if user_title_enabled: - user_titles, title_tokens = await self.llm_analyzer.analyze_user_titles(messages, user_analysis, umo=unified_msg_origin) + # 传入活跃用户列表 + user_titles, title_tokens = await self.llm_analyzer.analyze_user_titles( + messages, user_analysis, umo=unified_msg_origin, top_users=top_users + ) total_token_usage.prompt_tokens += title_tokens.prompt_tokens total_token_usage.completion_tokens += title_tokens.completion_tokens total_token_usage.total_tokens += title_tokens.total_tokens @@ -86,6 +94,5 @@ class MessageAnalyzer: } except Exception as e: - from astrbot.api import logger logger.error(f"消息分析失败: {e}") - return None \ No newline at end of file + return None