mirror of
https://github.com/Nezumi-2711/astrbot_plugin_qq_group_daily_analysis.git
synced 2026-09-22 20:01:04 +00:00
fix(quality_summary): 优化增量分析逻辑,优化提示词
This commit is contained in:
@@ -583,21 +583,18 @@ class AnalysisApplicationService:
|
||||
if not adapter:
|
||||
raise ValueError(f"未找到平台 {platform_id} 的适配器")
|
||||
|
||||
# 6. 执行用户称号 LLM 分析
|
||||
# 6. 执行分析相关的变量准备
|
||||
user_titles = []
|
||||
user_title_enabled = self.config_manager.get_user_title_analysis_enabled()
|
||||
unified_msg_origin = (
|
||||
f"{platform_id}:GroupMessage:{group_id}" if platform_id else group_id
|
||||
)
|
||||
|
||||
if user_title_enabled and state.user_activities:
|
||||
max_user_titles = self.config_manager.get_max_user_titles()
|
||||
# 从合并后的 user_activities 中取出 top 用户
|
||||
top_users = state.get_user_activity_ranking(max_user_titles)
|
||||
|
||||
unified_msg_origin = (
|
||||
f"{platform_id}:GroupMessage:{group_id}"
|
||||
if platform_id
|
||||
else group_id
|
||||
)
|
||||
|
||||
try:
|
||||
async with self.llm_semaphore:
|
||||
logger.debug(f"[LLM] 已进入称号分析队列 (群: {group_id})")
|
||||
@@ -628,6 +625,58 @@ class AnalysisApplicationService:
|
||||
except Exception as e:
|
||||
logger.error(f"增量最终报告用户称号分析失败: {e}", exc_info=True)
|
||||
|
||||
# 6.5 执行聊天质量汇总分析 (如果有多个批次的质量报告)
|
||||
if (
|
||||
self.config_manager.get_chat_quality_analysis_enabled()
|
||||
and state.all_quality_reviews
|
||||
):
|
||||
try:
|
||||
async with self.llm_semaphore:
|
||||
logger.debug(
|
||||
f"[LLM] 已进入聊天质量汇总分析队列 (群: {group_id})"
|
||||
)
|
||||
(
|
||||
summarized_review,
|
||||
quality_token_usage,
|
||||
) = await self.llm_analyzer.summarize_quality_reviews(
|
||||
batch_reviews=state.all_quality_reviews,
|
||||
umo=unified_msg_origin,
|
||||
)
|
||||
if summarized_review:
|
||||
# 更新 state 中的 review 为汇总后的结果
|
||||
# 这里我们需要将 QualityReview 对象存回 dict 或直接在后续处理中使用
|
||||
# build_analysis_result 会使用 state.chat_quality_review
|
||||
state.chat_quality_review = {
|
||||
"title": summarized_review.title,
|
||||
"subtitle": summarized_review.subtitle,
|
||||
"dimensions": [
|
||||
{
|
||||
"name": d.name,
|
||||
"percentage": d.percentage,
|
||||
"comment": d.comment,
|
||||
"color": d.color,
|
||||
}
|
||||
for d in summarized_review.dimensions
|
||||
],
|
||||
"summary": summarized_review.summary,
|
||||
}
|
||||
|
||||
# 累加 Token
|
||||
state.total_token_usage["prompt_tokens"] = (
|
||||
state.total_token_usage.get("prompt_tokens", 0)
|
||||
+ quality_token_usage.prompt_tokens
|
||||
)
|
||||
state.total_token_usage["completion_tokens"] = (
|
||||
state.total_token_usage.get("completion_tokens", 0)
|
||||
+ quality_token_usage.completion_tokens
|
||||
)
|
||||
state.total_token_usage["total_tokens"] = (
|
||||
state.total_token_usage.get("total_tokens", 0)
|
||||
+ quality_token_usage.total_tokens
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"增量最终报告聊天质量汇总失败: {e}", exc_info=True)
|
||||
|
||||
# 7. 构建 analysis_result
|
||||
analysis_result = self.incremental_merge_service.build_analysis_result(
|
||||
state, user_titles
|
||||
|
||||
@@ -175,6 +175,9 @@ class IncrementalState:
|
||||
topics: list[dict] = field(default_factory=list)
|
||||
golden_quotes: list[dict] = field(default_factory=list)
|
||||
chat_quality_review: dict[str, Any] | None = None
|
||||
all_quality_reviews: list[dict] = field(
|
||||
default_factory=list
|
||||
) # 存储所有批次的质量锐评,用于最终报告时的汇总分析
|
||||
|
||||
# 合并后的统计数据(按小时)
|
||||
hourly_message_counts: dict[str, int] = field(default_factory=dict)
|
||||
|
||||
@@ -85,3 +85,13 @@ class IAnalysisProvider(ABC):
|
||||
) -> tuple[list[SummaryTopic], list[GoldenQuote], TokenUsage, QualityReview | None]:
|
||||
"""增量模式并发分析"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def summarize_quality_reviews(
|
||||
self,
|
||||
batch_reviews: list[dict],
|
||||
umo: str | None = None,
|
||||
session_id: str | None = None,
|
||||
) -> tuple[QualityReview | None, TokenUsage]:
|
||||
"""汇总多个聊天质量报告(增量模式使用)"""
|
||||
pass
|
||||
|
||||
@@ -174,10 +174,14 @@ class IncrementalMergeService:
|
||||
# 合并参与者 ID(取并集)
|
||||
state.all_participant_ids.update(batch.participant_ids)
|
||||
|
||||
# 收集所有批次的质量锐评(用于最终汇总)
|
||||
if batch.chat_quality_review:
|
||||
state.all_quality_reviews.append(batch.chat_quality_review)
|
||||
|
||||
# 记录最后分析消息时间戳(取最大值)
|
||||
if batch.last_message_timestamp > state.last_analyzed_message_timestamp:
|
||||
state.last_analyzed_message_timestamp = batch.last_message_timestamp
|
||||
# 更新锐评为最新批次的 (如果有)
|
||||
# 更新锐评为最新批次的 (如果没有汇总分析,则作为兜底)
|
||||
if batch.chat_quality_review:
|
||||
state.chat_quality_review = batch.chat_quality_review
|
||||
|
||||
|
||||
@@ -90,29 +90,28 @@ class ChatQualityAnalyzer(BaseAnalyzer):
|
||||
请分析以下群聊记录,输出一份"聊天质量锐评"。
|
||||
|
||||
## 任务目标:
|
||||
1. 将聊天内容划分为 3-6 个不同的维度/类别(如:技术探讨、水群闲聊、就业焦虑、深夜发情等)。
|
||||
2. 为每个维度计算一个大致的百分比占位(总和小于等于 100%)。
|
||||
3. 为每个维度写一句犀利、幽默、毒舌或温情的点评。
|
||||
4. 给出一句总结性的全群表现评价。
|
||||
5. 设定一个本次报告的主题标题和副标题。
|
||||
1. **维度划分**:将聊天内容划分为 3-6 个【高层级、抽象、泛化】的维度(例如:就业焦虑、生涯规划、技术方案研究、情感树洞、无意义水群等)。
|
||||
2. **严禁在维度名称(name)中出现任何具体的群聊人物名、项目名、具体的报错内容或细碎的事件点。标题必须保持高度抽象且字数简练(2-6个字)。**
|
||||
3. 为每个维度计算一个大致的百分比占位(总和小于等于 100%)。
|
||||
4. **点评内容**:为每个维度写一句犀利、幽默、毒舌或温情的点评。具体的吐槽内容、具体的细节事件描述请放在这里。
|
||||
5. **全群表现**:给出一句总结性的评价,作为总结标题对应的“金句”。
|
||||
6. **主题设定**:设定一个本次报告的主题标题和副标题。
|
||||
|
||||
## 点评风格指南:
|
||||
- 语言要接地气,多用互联网黑话。
|
||||
- 吐槽要精准,避重就轻。
|
||||
- 如果群友在认真讨论技术,可以夸两句但也要带点调侃。
|
||||
- 如果群友在无意义水群,请狠狠吐槽。
|
||||
- 语言要接地气,多用互联网黑话。吐槽要精准,避重就轻。
|
||||
- **只有维度名称(name)需要抽象,点评(comment)和总结(summary)可以非常具体和生动。**
|
||||
|
||||
## 返回格式要求:
|
||||
必须以纯 JSON 格式返回,不得包含任何 Markdown 格式。
|
||||
|
||||
```json
|
||||
{{
|
||||
"title": "主题标题 (如: 互联网难民收容所)",
|
||||
"subtitle": "副标题 (如: 只要不工作,我们就是最好的朋友)",
|
||||
"title": "今日群聊主题",
|
||||
"subtitle": "副标题",
|
||||
"dimensions": [
|
||||
{{
|
||||
"name": "维度名称",
|
||||
"percentage": 25.5,
|
||||
"name": "抽象维度名",
|
||||
"percentage": 比例,
|
||||
"comment": "维度的毒舌点评"
|
||||
}}
|
||||
],
|
||||
@@ -198,6 +197,120 @@ class ChatQualityAnalyzer(BaseAnalyzer):
|
||||
summary=data.get("summary", "今天也是充满活力的一天。"),
|
||||
)
|
||||
|
||||
async def summarize_batch_reviews(
|
||||
self,
|
||||
batch_reviews: list[dict],
|
||||
umo: str | None = None,
|
||||
session_id: str | None = None,
|
||||
) -> tuple[QualityReview | None, TokenUsage]:
|
||||
"""
|
||||
汇总多个增量批次的质量报告,生成最终的每日全天总评。
|
||||
"""
|
||||
if not batch_reviews:
|
||||
return None, TokenUsage()
|
||||
|
||||
if len(batch_reviews) == 1:
|
||||
return self._build_review_from_dict(batch_reviews[0]), TokenUsage()
|
||||
|
||||
try:
|
||||
# 构建汇总用的提示词
|
||||
reviews_text = ""
|
||||
for i, rev in enumerate(batch_reviews):
|
||||
title = rev.get("title", "未命名")
|
||||
summary = rev.get("summary", "")
|
||||
dims = ", ".join(
|
||||
[
|
||||
f"{d.get('name')}({d.get('percentage')}%)"
|
||||
for d in rev.get("dimensions", [])
|
||||
]
|
||||
)
|
||||
reviews_text += f"\n批次 {i + 1} [{title}]:\n- 维度表现: {dims}\n- 核心摘要: {summary}\n"
|
||||
|
||||
prompt_template = self.config_manager.get_quality_summary_prompt()
|
||||
|
||||
if not prompt_template:
|
||||
prompt_template = """你是一个毒舌且幽默的群聊质量分析师。
|
||||
你现在有一份今天全天分散时间段的多个“增量批次点评笔记”。
|
||||
你的任务是将这些分散的笔记汇总成一份最终的“全天聊天质量终极锐评”。
|
||||
|
||||
## 任务目标:
|
||||
1. **全局抽象维度**:根据各批次的维度表现,平衡权重,提取出 3-6 个覆盖全天的【核心、上层抽象】课题维度(如:职场/行业风向、技术架构演进、社畜心理博弈等)。
|
||||
2. **严禁在维度名称(name)中出现具体的批次细节。标题必须代表全天的某种趋势。**
|
||||
3. **百分比融合**:根据全天笔记的频率和强度,给出一个代表全天整体分布的比例(总和不超过100%)。
|
||||
4. **终极点评**:为每个汇总维度写出一句升华后的全天总结性点评。可以融合具体批次中的有趣槽点。
|
||||
5. **终极总结**:拟定全天的大型主题标题、副标题,并给出一句霸气的全天表现总结。
|
||||
|
||||
## 风格要求:
|
||||
- 只有维度名称(name)需要高度概括抽象。
|
||||
- 点评(comment)和总结(summary)请尽量生动、毒舌、具体,要把一整天的梗串联起来。
|
||||
|
||||
## 返回格式要求:
|
||||
必须以纯 JSON 格式返回,不得包含任何 Markdown 格式。
|
||||
|
||||
```json
|
||||
{{
|
||||
"title": "今日群聊主题",
|
||||
"subtitle": "副标题",
|
||||
"dimensions": [
|
||||
{{
|
||||
"name": "抽象大类标题",
|
||||
"percentage": 比例,
|
||||
"comment": "维度的全天锐评"
|
||||
}}
|
||||
],
|
||||
"summary": "全天总结金句"
|
||||
}}
|
||||
```
|
||||
"""
|
||||
prompt = prompt_template.format(reviews_text=reviews_text)
|
||||
|
||||
# 调用 LLM 进行汇总
|
||||
system_prompt = await self._build_system_prompt(umo)
|
||||
|
||||
response = await call_provider_with_retry(
|
||||
self.context,
|
||||
self.config_manager,
|
||||
prompt=prompt,
|
||||
max_tokens=self.get_max_tokens(),
|
||||
temperature=0.7,
|
||||
umo=umo,
|
||||
provider_id_key=self.get_provider_id_key(),
|
||||
system_prompt=system_prompt,
|
||||
)
|
||||
|
||||
if response is None:
|
||||
return None, TokenUsage()
|
||||
|
||||
token_usage_dict = extract_token_usage(response)
|
||||
usage = TokenUsage(
|
||||
prompt_tokens=token_usage_dict["prompt_tokens"],
|
||||
completion_tokens=token_usage_dict["completion_tokens"],
|
||||
total_tokens=token_usage_dict["total_tokens"],
|
||||
)
|
||||
|
||||
result_text = extract_response_text(response)
|
||||
if not result_text:
|
||||
return None, usage
|
||||
|
||||
success, parsed_data, error_msg = parse_json_object_response(
|
||||
result_text, "汇总质量分析"
|
||||
)
|
||||
|
||||
if success and parsed_data:
|
||||
review = self._build_review_from_dict(parsed_data)
|
||||
logger.info(
|
||||
f"聊天质量汇总分析成功,解析到 {len(review.dimensions)} 个汇总维度"
|
||||
)
|
||||
return review, usage
|
||||
|
||||
# 降级:如果汇总失败,返回最新的一个
|
||||
logger.warning(f"聊天质量汇总分析失败,降级使用最新批次: {error_msg}")
|
||||
return self._build_review_from_dict(batch_reviews[-1]), usage
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"聊天质量汇总分析异常: {e}", exc_info=True)
|
||||
return self._build_review_from_dict(batch_reviews[-1]), TokenUsage()
|
||||
|
||||
async def analyze_quality(
|
||||
self,
|
||||
messages: list[dict],
|
||||
@@ -258,7 +371,9 @@ class ChatQualityAnalyzer(BaseAnalyzer):
|
||||
|
||||
if success and parsed_data:
|
||||
review = self._build_review_from_dict(parsed_data)
|
||||
logger.info(f"聊天质量分析成功,解析到 {len(review.dimensions)} 个维度")
|
||||
logger.debug(
|
||||
f"聊天质量分析成功,解析到 {len(review.dimensions)} 个维度"
|
||||
)
|
||||
return review, usage
|
||||
|
||||
# 7. 正则降级(使用 extract_quality_with_regex)
|
||||
@@ -267,7 +382,7 @@ class ChatQualityAnalyzer(BaseAnalyzer):
|
||||
|
||||
if regex_data:
|
||||
review = self._build_review_from_dict(regex_data)
|
||||
logger.info(
|
||||
logger.debug(
|
||||
f"聊天质量正则提取成功,获得 {len(review.dimensions)} 个维度"
|
||||
)
|
||||
return review, usage
|
||||
@@ -277,7 +392,7 @@ class ChatQualityAnalyzer(BaseAnalyzer):
|
||||
return None, usage
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"聊天质量分析解析失败: {e}", exc_info=True)
|
||||
logger.error(f"聊天质量分析失败: {e}", exc_info=True)
|
||||
return None, TokenUsage()
|
||||
|
||||
# Override analyze to bridge the base class interface
|
||||
|
||||
@@ -164,6 +164,19 @@ class LLMAnalyzer(IAnalysisProvider):
|
||||
logger.error(f"金句分析失败: {e}")
|
||||
return [], TokenUsage()
|
||||
|
||||
async def summarize_quality_reviews(
|
||||
self,
|
||||
batch_reviews: list[dict],
|
||||
umo: str | None = None,
|
||||
session_id: str | None = None,
|
||||
) -> tuple[QualityReview | None, TokenUsage]:
|
||||
"""
|
||||
汇总多个质量分析报告(增量模式使用)
|
||||
"""
|
||||
return await self.chat_quality_analyzer.summarize_batch_reviews(
|
||||
batch_reviews, umo, session_id
|
||||
)
|
||||
|
||||
async def analyze_all_concurrent(
|
||||
self,
|
||||
messages: list[dict],
|
||||
|
||||
@@ -291,7 +291,7 @@ class ConfigManager:
|
||||
return prompt
|
||||
return ""
|
||||
|
||||
def get_quality_analysis_prompt(self, style: str = "quality_prompt") -> str:
|
||||
def get_quality_analysis_prompt(self, style: str = "quality_v2_prompt") -> str:
|
||||
"""获取聊天质量分析提示词模板"""
|
||||
prompts_config = self._get_group("prompts").get("quality_analysis_prompts", {})
|
||||
prompt = prompts_config.get(style, "")
|
||||
@@ -299,6 +299,22 @@ class ConfigManager:
|
||||
return prompt
|
||||
return ""
|
||||
|
||||
def set_quality_analysis_prompt(self, prompt: str):
|
||||
"""设置聊天质量分析提示词模板"""
|
||||
prompts = self._ensure_group("prompts")
|
||||
if "quality_analysis_prompts" not in prompts:
|
||||
prompts["quality_analysis_prompts"] = {}
|
||||
prompts["quality_analysis_prompts"]["quality_v2_prompt"] = prompt
|
||||
self.config.save_config()
|
||||
|
||||
def get_quality_summary_prompt(self, style: str = "quality_summary_prompt") -> str:
|
||||
"""获取聊天质量汇总分析提示词模板"""
|
||||
prompts_config = self._get_group("prompts").get("quality_analysis_prompts", {})
|
||||
prompt = prompts_config.get(style, "")
|
||||
if prompt:
|
||||
return prompt
|
||||
return ""
|
||||
|
||||
def set_topic_analysis_prompt(self, prompt: str):
|
||||
"""设置话题分析提示词模板"""
|
||||
prompts = self._ensure_group("prompts")
|
||||
@@ -307,6 +323,14 @@ class ConfigManager:
|
||||
prompts["topic_analysis_prompts"]["topic_prompt"] = prompt
|
||||
self.config.save_config()
|
||||
|
||||
def set_quality_summary_prompt(self, prompt: str):
|
||||
"""设置聊天质量汇总分析提示词模板"""
|
||||
prompts = self._ensure_group("prompts")
|
||||
if "quality_analysis_prompts" not in prompts:
|
||||
prompts["quality_analysis_prompts"] = {}
|
||||
prompts["quality_analysis_prompts"]["quality_summary_prompt"] = prompt
|
||||
self.config.save_config()
|
||||
|
||||
def set_user_title_analysis_prompt(self, prompt: str):
|
||||
"""设置用户称号分析提示词模板"""
|
||||
prompts = self._ensure_group("prompts")
|
||||
|
||||
Reference in New Issue
Block a user