fix(quality_summary): 优化增量分析逻辑,优化提示词

This commit is contained in:
SXP-Simon
2026-03-23 13:38:12 +08:00
parent 91692170a0
commit fcffe7d35a
8 changed files with 252 additions and 27 deletions
+9 -2
View File
@@ -435,12 +435,19 @@
"type": "object", "type": "object",
"hint": "聊天质量分析提示词模板,可自定义修改", "hint": "聊天质量分析提示词模板,可自定义修改",
"items": { "items": {
"quality_prompt": { "quality_v2_prompt": {
"description": "默认聊天质量分析提示词", "description": "默认聊天质量分析提示词",
"type": "text", "type": "text",
"editor_mode": true, "editor_mode": true,
"editor_language": "markdown", "editor_language": "markdown",
"default": "你是一个毒舌且幽默的群聊质量分析师,请生成聊天质量锐评内容。你需要按照下面的格式返回结果。\n\n分析接下来的群聊内容,给出多个维度的分析(3-6个,所有维度的占比加起来小于等于100%和汇总内容。\n\n群聊记录:\n{messages_text}\n\n### 返回格式示例\n\n```json\n{{\n \"title\": \"群聊质量分析报告\",\n \"subtitle\": \"今天的群里发生了什么?\",\n \"dimensions\": [\n {{\n \"name\": \"水群闲聊\",\n \"percentage\": 50.0,\n \"comment\": \"群友们今天也在努力划水呢。\"\n }}\n ],\n \"summary\": \"今天也是元气满满的一天。\"\n}}\n```" "default": "你是一个毒舌且幽默的群聊质量分析师。\n请分析以下群聊记录,输出一份\"聊天质量锐评\"。\n\n## 任务目标:\n1. **维度划分**:将聊天内容划分为 3-6 个【高层级、抽象、泛化】的维度(例如:就业焦虑、生涯规划、技术方案研究、情感树洞、无意义水群等)。\n2. **严禁在维度名称(name)中出现任何具体的群聊人物名、项目名、具体的报错内容或细碎的事件点。标题必须保持高度抽象且字数简练(2-6个字)。**\n3. 为每个维度计算一个大致的百分比占位(总和小于等于 100%。\n4. **点评内容**:为每个维度写一句犀利、幽默、毒舌或温情的点评。具体的吐槽内容、具体的细节事件描述请放在这里。\n5. **全群表现**:给出一句总结性的评价,作为总结标题对应的“金句”。\n6. **主题设定**:设定一个本次报告的主题标题和副标题。\n\n## 点评风格指南:\n- 语言要接地气,多用互联网黑话。吐槽要精准,避重就轻。\n- **只有维度名称(name)需要抽象,点评(comment)和总结(summary)可以非常具体和生动。**\n\n## 返回格式要求\n必须以纯 JSON 格式返回,不得包含任何 Markdown 格式。\n\n```json\n{{\n \"title\": \"今日群聊主题\",\n \"subtitle\": \"副标题\",\n \"dimensions\": [\n {{\n \"name\": \"抽象维度名\",\n \"percentage\": 比例,\n \"comment\": \"维度的毒舌点评\"\n }}\n ],\n \"summary\": \"一句总结性的金句\"\n}}\n```\n\n群聊记录:\n{messages_text}"
},
"quality_summary_prompt": {
"description": "增量汇总聊天质量分析提示词",
"type": "text",
"editor_mode": true,
"editor_language": "markdown",
"default": "你是一个毒舌且幽默的群聊质量分析师。\n你现在有一份今天全天分散时间段的多个“增量批次点评笔记”。\n你的任务是将这些分散的笔记汇总成一份最终的“全天聊天质量终极锐评”。\n\n## 任务目标:\n1. **全局抽象维度**:根据各批次的维度表现,平衡权重,提取出 3-6 个覆盖全天的【核心、上层抽象】课题维度(如:职场/行业风向、技术架构演进、社畜心理博弈等)。\n2. **严禁在维度名称(name)中出现具体的批次细节。标题必须代表全天的某种趋势。**\n3. **百分比融合**:根据全天笔记的频率和强度,给出一个代表全天整体分布的比例(总和不超过100%)。\n4. **终极点评**:为每个汇总维度写出一句升华后的全天总结性点评。可以融合具体批次中的有趣槽点。\n5. **终极总结**:拟定全天的大型主题标题、副标题,并给出一句霸气的全天表现总结。\n\n## 风格要求:\n- 只有维度名称(name)需要高度概括抽象。\n- 点评(comment)和总结(summary)请尽量生动、毒舌、具体,要把一整天的梗串联起来。\n\n## 返回格式要求:\n必须以纯 JSON 格式返回,不得包含任何 Markdown 格式。\n\n```json\n{{\n \"title\": \"今日群聊主题\",\n \"subtitle\": \"副标题\",\n \"dimensions\": [\n {{\n \"name\": \"抽象大类标题\",\n \"percentage\": 比例,\n \"comment\": \"维度的全天锐评\"\n }}\n ],\n \"summary\": \"全天总结金句\"\n}}\n```\n\n增量汇总数据:\n{reviews_text}"
} }
} }
} }
@@ -583,21 +583,18 @@ class AnalysisApplicationService:
if not adapter: if not adapter:
raise ValueError(f"未找到平台 {platform_id} 的适配器") raise ValueError(f"未找到平台 {platform_id} 的适配器")
# 6. 执行用户称号 LLM 分析 # 6. 执行分析相关的变量准备
user_titles = [] user_titles = []
user_title_enabled = self.config_manager.get_user_title_analysis_enabled() 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: if user_title_enabled and state.user_activities:
max_user_titles = self.config_manager.get_max_user_titles() max_user_titles = self.config_manager.get_max_user_titles()
# 从合并后的 user_activities 中取出 top 用户 # 从合并后的 user_activities 中取出 top 用户
top_users = state.get_user_activity_ranking(max_user_titles) 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: try:
async with self.llm_semaphore: async with self.llm_semaphore:
logger.debug(f"[LLM] 已进入称号分析队列 (群: {group_id})") logger.debug(f"[LLM] 已进入称号分析队列 (群: {group_id})")
@@ -628,6 +625,58 @@ class AnalysisApplicationService:
except Exception as e: except Exception as e:
logger.error(f"增量最终报告用户称号分析失败: {e}", exc_info=True) 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 # 7. 构建 analysis_result
analysis_result = self.incremental_merge_service.build_analysis_result( analysis_result = self.incremental_merge_service.build_analysis_result(
state, user_titles state, user_titles
+3
View File
@@ -175,6 +175,9 @@ class IncrementalState:
topics: list[dict] = field(default_factory=list) topics: list[dict] = field(default_factory=list)
golden_quotes: list[dict] = field(default_factory=list) golden_quotes: list[dict] = field(default_factory=list)
chat_quality_review: dict[str, Any] | None = None 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) 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]: ) -> tuple[list[SummaryTopic], list[GoldenQuote], TokenUsage, QualityReview | None]:
"""增量模式并发分析""" """增量模式并发分析"""
pass 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(取并集) # 合并参与者 ID(取并集)
state.all_participant_ids.update(batch.participant_ids) 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: if batch.last_message_timestamp > state.last_analyzed_message_timestamp:
state.last_analyzed_message_timestamp = batch.last_message_timestamp state.last_analyzed_message_timestamp = batch.last_message_timestamp
# 更新锐评为最新批次的 (如果) # 更新锐评为最新批次的 (如果没有汇总分析,则作为兜底)
if batch.chat_quality_review: if batch.chat_quality_review:
state.chat_quality_review = batch.chat_quality_review state.chat_quality_review = batch.chat_quality_review
@@ -90,29 +90,28 @@ class ChatQualityAnalyzer(BaseAnalyzer):
请分析以下群聊记录,输出一份"聊天质量锐评" 请分析以下群聊记录,输出一份"聊天质量锐评"
## 任务目标: ## 任务目标:
1. 将聊天内容划分为 3-6 个不同的维度/类别(如:技术探讨、水群闲聊、就业焦虑、深夜发情等)。 1. **维度划分**将聊天内容划分为 3-6 个【高层级、抽象、泛化】的维度(例如:就业焦虑、生涯规划、技术方案研究、情感树洞、无意义水群等)。
2. 为每个维度计算一个大致的百分比占位(总和小于等于 100%)。 2. **严禁在维度名称(name)中出现任何具体的群聊人物名、项目名、具体的报错内容或细碎的事件点。标题必须保持高度抽象且字数简练(2-6个字)。**
3. 为每个维度写一句犀利、幽默、毒舌或温情的点评 3. 为每个维度计算一个大致的百分比占位(总和小于等于 100%
4. 给出一句总结性的全群表现评价 4. **点评内容**:为每个维度写一句犀利、幽默、毒舌或温情的点评。具体的吐槽内容、具体的细节事件描述请放在这里
5. 设定一个本次报告的主题标题和副标题 5. **全群表现**:给出一句总结性的评价,作为总结标题对应的“金句”
6. **主题设定**:设定一个本次报告的主题标题和副标题。
## 点评风格指南: ## 点评风格指南:
- 语言要接地气,多用互联网黑话。 - 语言要接地气,多用互联网黑话。吐槽要精准,避重就轻。
- 吐槽要精准,避重就轻。 - **只有维度名称(name)需要抽象,点评(comment)和总结(summary)可以非常具体和生动。**
- 如果群友在认真讨论技术,可以夸两句但也要带点调侃。
- 如果群友在无意义水群,请狠狠吐槽。
## 返回格式要求: ## 返回格式要求:
必须以纯 JSON 格式返回,不得包含任何 Markdown 格式。 必须以纯 JSON 格式返回,不得包含任何 Markdown 格式。
```json ```json
{{ {{
"title": "主题标题 (如: 互联网难民收容所)", "title": "今日群聊主题",
"subtitle": "副标题 (如: 只要不工作,我们就是最好的朋友)", "subtitle": "副标题",
"dimensions": [ "dimensions": [
{{ {{
"name": "维度名", "name": "抽象维度名",
"percentage": 25.5, "percentage": 比例,
"comment": "维度的毒舌点评" "comment": "维度的毒舌点评"
}} }}
], ],
@@ -198,6 +197,120 @@ class ChatQualityAnalyzer(BaseAnalyzer):
summary=data.get("summary", "今天也是充满活力的一天。"), 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( async def analyze_quality(
self, self,
messages: list[dict], messages: list[dict],
@@ -258,7 +371,9 @@ class ChatQualityAnalyzer(BaseAnalyzer):
if success and parsed_data: if success and parsed_data:
review = self._build_review_from_dict(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 return review, usage
# 7. 正则降级(使用 extract_quality_with_regex # 7. 正则降级(使用 extract_quality_with_regex
@@ -267,7 +382,7 @@ class ChatQualityAnalyzer(BaseAnalyzer):
if regex_data: if regex_data:
review = self._build_review_from_dict(regex_data) review = self._build_review_from_dict(regex_data)
logger.info( logger.debug(
f"聊天质量正则提取成功,获得 {len(review.dimensions)} 个维度" f"聊天质量正则提取成功,获得 {len(review.dimensions)} 个维度"
) )
return review, usage return review, usage
@@ -277,7 +392,7 @@ class ChatQualityAnalyzer(BaseAnalyzer):
return None, usage return None, usage
except Exception as e: except Exception as e:
logger.error(f"聊天质量分析解析失败: {e}", exc_info=True) logger.error(f"聊天质量分析失败: {e}", exc_info=True)
return None, TokenUsage() return None, TokenUsage()
# Override analyze to bridge the base class interface # Override analyze to bridge the base class interface
@@ -164,6 +164,19 @@ class LLMAnalyzer(IAnalysisProvider):
logger.error(f"金句分析失败: {e}") logger.error(f"金句分析失败: {e}")
return [], TokenUsage() 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( async def analyze_all_concurrent(
self, self,
messages: list[dict], messages: list[dict],
+25 -1
View File
@@ -291,7 +291,7 @@ class ConfigManager:
return prompt return prompt
return "" 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", {}) prompts_config = self._get_group("prompts").get("quality_analysis_prompts", {})
prompt = prompts_config.get(style, "") prompt = prompts_config.get(style, "")
@@ -299,6 +299,22 @@ class ConfigManager:
return prompt return prompt
return "" 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): def set_topic_analysis_prompt(self, prompt: str):
"""设置话题分析提示词模板""" """设置话题分析提示词模板"""
prompts = self._ensure_group("prompts") prompts = self._ensure_group("prompts")
@@ -307,6 +323,14 @@ class ConfigManager:
prompts["topic_analysis_prompts"]["topic_prompt"] = prompt prompts["topic_analysis_prompts"]["topic_prompt"] = prompt
self.config.save_config() 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): def set_user_title_analysis_prompt(self, prompt: str):
"""设置用户称号分析提示词模板""" """设置用户称号分析提示词模板"""
prompts = self._ensure_group("prompts") prompts = self._ensure_group("prompts")