mirror of
https://github.com/Nezumi-2711/astrbot_plugin_qq_group_daily_analysis.git
synced 2026-09-22 13:38:43 +00:00
[fix] max_tokens 参数处理 以及 过滤机器人自己的消息
Merge pull request #35 from SXP-Simon/fix/user-title
This commit is contained in:
@@ -117,6 +117,24 @@
|
||||
"default": 2,
|
||||
"hint": "重试之间的基准等待时间(秒),实际等待时间为基值乘以尝试次数。"
|
||||
},
|
||||
"topic_max_tokens": {
|
||||
"type": "int",
|
||||
"description": "(兼容部分提供商)话题分析最大 Token 数",
|
||||
"default": 12288,
|
||||
"hint": "(兼容部分提供商,实测大部分模型调整后没有明显效果)话题分析时 LLM 能生成的最大 token 数量。当分析内容较多或者分析提示词复杂时,建议适当调大此值以保证输出质量。"
|
||||
},
|
||||
"golden_quote_max_tokens": {
|
||||
"type": "int",
|
||||
"description": "(兼容部分提供商)金句分析最大 Token 数",
|
||||
"default": 4096,
|
||||
"hint": "(兼容部分提供商,实测大部分模型调整后没有明显效果)金句分析时 LLM 能生成的最大 token 数量。当分析内容较多或者分析提示词复杂时,建议适当调大此值以保证输出质量。"
|
||||
},
|
||||
"user_title_max_tokens": {
|
||||
"type": "int",
|
||||
"description": "(兼容部分提供商)用户称号分析最大 Token 数",
|
||||
"default": 4096,
|
||||
"hint": "(兼容部分提供商,实测大部分模型调整后没有明显效果)用户称号分析时 LLM 能生成的最大 token 数量。当分析内容较多或者分析提示词复杂时,建议适当调大此值以保证输出质量。"
|
||||
},
|
||||
"custom_api_key": {
|
||||
"type": "string",
|
||||
"description": "自定义 LLM 服务 API Key (选填)",
|
||||
|
||||
@@ -29,7 +29,7 @@ class GoldenQuoteAnalyzer(BaseAnalyzer):
|
||||
|
||||
def get_max_tokens(self) -> int:
|
||||
"""获取最大token数"""
|
||||
return 1500
|
||||
return self.config_manager.get_golden_quote_max_tokens()
|
||||
|
||||
def get_temperature(self) -> float:
|
||||
"""获取温度参数"""
|
||||
|
||||
@@ -29,7 +29,7 @@ class TopicAnalyzer(BaseAnalyzer):
|
||||
|
||||
def get_max_tokens(self) -> int:
|
||||
"""获取最大token数"""
|
||||
return 10000
|
||||
return self.config_manager.get_topic_max_tokens()
|
||||
|
||||
def get_temperature(self) -> float:
|
||||
"""获取温度参数"""
|
||||
|
||||
@@ -26,7 +26,7 @@ class UserTitleAnalyzer(BaseAnalyzer):
|
||||
|
||||
def get_max_tokens(self) -> int:
|
||||
"""获取最大token数"""
|
||||
return 1500
|
||||
return self.config_manager.get_user_title_max_tokens()
|
||||
|
||||
def get_temperature(self) -> float:
|
||||
"""获取温度参数"""
|
||||
@@ -136,22 +136,42 @@ 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:
|
||||
准备好的用户数据字典
|
||||
"""
|
||||
try:
|
||||
# 获取机器人QQ号用于过滤
|
||||
bot_qq_id = self.config_manager.get_bot_qq_id()
|
||||
|
||||
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 bot_qq_id and str(user_id) == str(bot_qq_id):
|
||||
logger.debug(f"过滤掉机器人QQ号: {user_id}")
|
||||
continue
|
||||
|
||||
# 只处理活跃用户
|
||||
if user_id not in target_user_ids:
|
||||
continue
|
||||
|
||||
# 分析用户特征
|
||||
@@ -164,18 +184,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 +201,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 +209,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:
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
@@ -69,8 +77,15 @@ class UserAnalyzer:
|
||||
|
||||
def get_top_users(self, user_analysis: Dict[str, Dict], limit: int = 10) -> List[Dict]:
|
||||
"""获取最活跃的用户"""
|
||||
# 获取机器人QQ号用于过滤
|
||||
bot_qq_id = self.config_manager.get_bot_qq_id()
|
||||
|
||||
users = []
|
||||
for user_id, stats in user_analysis.items():
|
||||
# 过滤机器人自己
|
||||
if bot_qq_id and str(user_id) == str(bot_qq_id):
|
||||
continue
|
||||
|
||||
users.append({
|
||||
"user_id": user_id,
|
||||
"nickname": stats["nickname"],
|
||||
|
||||
@@ -38,7 +38,7 @@ async def call_provider_with_retry(context, config_manager, prompt: str, max_tok
|
||||
for attempt in range(1, retries + 1):
|
||||
try:
|
||||
if custom_api_key and custom_api_base and custom_model:
|
||||
logger.info(f"使用自定义LLM提供商: {custom_api_base} model={custom_model}")
|
||||
logger.info(f"使用自定义LLM提供商: {custom_api_base} model={custom_model}, max_tokens={max_tokens}, temperature={temperature}")
|
||||
logger.debug(f"自定义LLM提供商 prompt 长度: {len(prompt) if prompt else 0}")
|
||||
logger.debug(f"自定义LLM提供商 prompt 前100字符: {prompt[:100] if prompt else 'None'}...")
|
||||
|
||||
@@ -102,7 +102,7 @@ async def call_provider_with_retry(context, config_manager, prompt: str, max_tok
|
||||
if not provider or provider_id == 'unknown':
|
||||
logger.warning(f"获取的提供商不正确 (Provider ID: {provider_id})")
|
||||
|
||||
logger.info(f"使用LLM provider: {provider}")
|
||||
logger.info(f"使用LLM provider: {provider}, max_tokens={max_tokens}, temperature={temperature}")
|
||||
if not provider:
|
||||
logger.error("provider 为空,无法调用 text_chat,直接返回 None")
|
||||
return None
|
||||
|
||||
@@ -87,6 +87,18 @@ class ConfigManager:
|
||||
"""获取LLM请求重试退避基值(秒),实际退避会乘以尝试次数"""
|
||||
return self.config.get("llm_backoff", 2)
|
||||
|
||||
def get_topic_max_tokens(self) -> int:
|
||||
"""获取话题分析最大token数"""
|
||||
return self.config.get("topic_max_tokens", 12288)
|
||||
|
||||
def get_golden_quote_max_tokens(self) -> int:
|
||||
"""获取金句分析最大token数"""
|
||||
return self.config.get("golden_quote_max_tokens", 4096)
|
||||
|
||||
def get_user_title_max_tokens(self) -> int:
|
||||
"""获取用户称号分析最大token数"""
|
||||
return self.config.get("user_title_max_tokens", 4096)
|
||||
|
||||
def get_custom_api_key(self) -> str:
|
||||
"""获取自定义 LLM 服务的 API Key"""
|
||||
return self.config.get("custom_api_key", "")
|
||||
|
||||
+13
-6
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user