[fix] user_title 获取逻辑

This commit is contained in:
SXP-Simon
2025-11-04 20:35:26 +08:00
parent 29c025c98b
commit 284389b5d2
4 changed files with 50 additions and 22 deletions
+23 -12
View File
@@ -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:
+6 -4
View File
@@ -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
)
+8
View File
@@ -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
+13 -6
View File
@@ -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
return None