From 5a81ffea202be2ccc08d4a9f05108476cc6143d7 Mon Sep 17 00:00:00 2001 From: SXP-Simon Date: Tue, 10 Feb 2026 21:25:25 +0800 Subject: [PATCH] =?UTF-8?q?refactor(=E8=AF=9D=E9=A2=98=E5=88=86=E6=9E=90):?= =?UTF-8?q?=20=E9=87=87=E7=94=A8=20ID-Only=20Prompt=20=E7=AD=96=E7=95=A5?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=A4=B4=E5=83=8F=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TopicAnalyzer: 仅向 LLM 发送 [ID] 而非昵称,减少 Token 并消除歧义 - _conf_schema.json: 更新默认 Prompt,强制要求 LLM 输出 ID 引用 - Logic: 后端负责将 ID 映射回昵称,实现精确的头像关联 --- _conf_schema.json | 14 ++++-- .../analysis/analyzers/topic_analyzer.py | 46 +++++++++++-------- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/_conf_schema.json b/_conf_schema.json index 6aa4a98..749bc71 100644 --- a/_conf_schema.json +++ b/_conf_schema.json @@ -7,7 +7,11 @@ "group_list_mode": { "description": "群聊权限模式", "type": "string", - "options": ["whitelist", "blacklist", "none"], + "options": [ + "whitelist", + "blacklist", + "none" + ], "default": "none", "hint": "whitelist: 仅允许列表内群;blacklist: 拒绝列表内群;none: 不限制" }, @@ -87,7 +91,9 @@ "auto_analysis_time": { "type": "list", "description": "自动分析时间列表", - "default": ["23:00"], + "default": [ + "23:00" + ], "hint": "每日自动分析的时间点列表,格式HH:MM(例如 23:00 ),支持多个时间点,增量分析也基于此时间点进行分析和报告生成", "items": { "type": "string" @@ -316,7 +322,7 @@ "type": "text", "editor_mode": true, "editor_language": "markdown", - "default": "你是一个帮我进行群聊信息总结的助手,生成总结内容时,你需要严格遵守下面的几个准则:\n\n请分析接下来提供的群聊记录,提取出最多 **{max_topics}** 个主要话题。\n\n## 对于每个话题,请提供:\n\n1. **话题名称**(突出主题内容,尽量简明扼要)\n2. **主要参与者**(最多5人)\n3. **话题详细描述**(包含关键信息和结论)\n\n## 注意事项:\n\n- 对于比较有价值的点,稍微用一两句话详细讲讲,比如不要生成 \"Nolan 和 SOV 讨论了 galgame 中关于性符号的衍生情况\" 这种宽泛的内容,而是生成更加具体的讨论内容,让其他人只看这个消息就能知道讨论中有价值的、有营养的信息。\n- 对于其中的部分信息,你需要特意提到主题施加的主体是谁,是哪个群友做了什么事情,而不要直接生成和群友没有关系的语句。\n- 对于每一条总结,尽量讲清楚前因后果,以及话题的结论:是什么、为什么、怎么做。如果用户没有讲到细节,则可以不用这么做。\\n- **用户引用**:在话题详情描述中,如果提到了具体用户,请使用 `[用户ID]` 的格式来指代(例如 `[123456]`)。不要只写昵称。我们会自动渲染头像。\\n\\n## 群聊记录:\n\n{messages_text}\n\n---\n\n## 重要:必须返回标准 JSON 格式\n\n严格遵守以下规则:\n\n1. 只使用英文双引号 `\"` ,不要使用中文引号 `\"` `\"`\n2. 字符串内容中的引号必须转义为 `\\\"`\n3. 多个对象之间用逗号分隔\n4. 数组元素之间用逗号分隔\n5. 不要在 JSON 外添加任何文字说明\n6. 描述内容避免使用特殊符号,用普通文字表达\n\n### 返回格式示例:\n\n```json\n[\n {{\n \"topic\": \"话题名称\",\n \"contributors\": [\"用户1\", \"用户2\"],\n \"detail\": \"话题描述内容\"\n }},\n {{\n \"topic\": \"另一个话题\",\n \"contributors\": [\"用户3\", \"用户4\"],\n \"detail\": \"另一个话题的描述\"\n }}\n]\n```\n\n**注意**:返回的内容必须是纯 JSON,不要包含 markdown 代码块标记或其他格式。" + "default": "你是一个帮我进行群聊信息总结的助手,生成总结内容时,你需要严格遵守下面的几个准则:\n\n请分析接下来提供的群聊记录,提取出最多 **{max_topics}** 个主要话题。根据实际聊天内容提取所有最有意义的话题。\n\n## 对于每个话题,请提供:\n\n1. **话题名称**(突出主题内容,尽量简明扼要,控制在 10 字以内)\n2. **主要参与者的用户ID**(最多 5 人,按参与度排序)\n3. **话题详细描述**(包含关键信息和结论)\n\n## 注意事项:\n\n- 对于比较有价值的点,稍微用一两句话详细讲讲,让读者能了解讨论的深度\n- 对于其中的部分信息,你需要特意提到主题施加的主体是谁,即明确指出\"谁做了什么\"\n- **用户引用**:在话题详情描述中,如果提到了具体用户,请使用 `[用户ID]` 的格式来指代(例如 `[123456]`)。不要只写昵称。我们会自动渲染头像。\n- 对于每一条总结,尽量讲清楚前因后果,不要只列出结论\n- 如果某个话题有明确的结论或共识,请在描述中体现\n- 忽略无意义的闲聊、灌水、单纯的表情回复等\n- 优先选择讨论深度较深、参与人数较多的话题\n- 如果消息太少或没有明确话题,可以返回空数组 []\n\n群聊记录格式: [HH:MM] [用户ID]: 消息内容\n\n群聊记录:\n{messages_text}\n\n---\n\n## 重要:必须返回标准 JSON 格式\n\n严格遵守以下规则:\n\n1. 只使用英文双引号 `\"` ,不要使用中文引号 `“` `”`\n2. 字符串内容中的引号必须转义为 `\\\"`\n3. 多个对象之间用逗号分隔\n4. 数组元素之间用逗号分隔\n5. 不要在 JSON 外添加任何文字说明\n6. 描述内容避免使用特殊符号,用普通文字表达\n\n### 返回格式示例:\n\n```json\n[\n {{\n \"topic\": \"话题名称\",\n \"contributors\": [\"123456789\", \"987654321\"],\n \"detail\": \"话题的详细描述,包含讨论内容、关键信息和结论。注意:在描述中提及用户时,使用 [用户ID] 格式,例如 [123456789]。\"\n }},\n {{\n \"topic\": \"另一个话题\",\n \"contributors\": [\"111222333\", \"444555666\"],\n \"detail\": \"另一个话题的详细描述...\"\n }}\n]\n```\n\n**注意**:返回的内容必须是纯 JSON,不要包含 markdown 代码块标记或其他格式。" } } }, @@ -350,4 +356,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/infrastructure/analysis/analyzers/topic_analyzer.py b/src/infrastructure/analysis/analyzers/topic_analyzer.py index ead87b1..6eff13a 100644 --- a/src/infrastructure/analysis/analyzers/topic_analyzer.py +++ b/src/infrastructure/analysis/analyzers/topic_analyzer.py @@ -148,9 +148,10 @@ class TopicAnalyzer(BaseAnalyzer): return "" # 构建消息文本 + # 使用用户提供的 ID-Only 格式: [HH:MM] [用户ID]: 消息内容 messages_text = "\n".join( [ - f"[{msg['time']}] [{msg['user_id']}] {msg['sender']}: {msg['content']}" + f"[{msg['time']}] [{msg['user_id']}]: {msg['content']}" for msg in text_messages ] ) @@ -338,32 +339,41 @@ class TopicAnalyzer(BaseAnalyzer): logger.debug(f"第一条文本消息类型: {type(text_messages[0])}") logger.debug(f"第一条文本消息内容: {text_messages[0]}") - # 建立昵称到ID的映射表 - nickname_to_id = {} + # 建立 ID 到昵称的映射表 + id_to_nickname = {} for msg in text_messages: sender = msg.get("sender") user_id = msg.get("user_id") if sender and user_id: - nickname_to_id[sender] = user_id + id_to_nickname[user_id] = sender # 直接传入原始消息,让 build_prompt 方法处理 topics, usage = await self.analyze(messages, umo, session_id) - # 回填贡献者 ID + # 后处理:contributors 此时包含的是 ID,需要映射回昵称 for topic in topics: - ids = set() - for contributor in topic.contributors: - # 尝试精确匹配 - if contributor in nickname_to_id: - ids.add(nickname_to_id[contributor]) - else: - # 尝试模糊匹配(LLM可能会简化名字) - for nick, uid in nickname_to_id.items(): - if contributor in nick or nick in contributor: - ids.add(uid) - # 找到一个匹配即可,避免过度匹配 - break - topic.contributor_ids = list(ids) + raw_ids = topic.contributors # LLM 返回的是 ID 列表 + + # 填充 contributor_ids + # 过滤掉非数字的脏数据 (LLM 偶尔会发疯) + valid_ids = [str(uid).strip() for uid in raw_ids if str(uid).strip().isdigit()] + topic.contributor_ids = valid_ids + + # 映射回昵称用于显示 + resolved_names = [] + for uid in valid_ids: + # 尝试从当前批次消息映射 + name = id_to_nickname.get(uid) + if not name: + # 尝试去全局配置里找 (e.g. 机器人自己) + bot_ids = self.config_manager.get_bot_self_ids() + if uid in bot_ids: + name = "Bot" + else: + name = uid # Fallback to ID + resolved_names.append(name) + + topic.contributors = resolved_names return topics, usage