From 0ea7b6195180d36931e4d26fa32695456ca14e5e Mon Sep 17 00:00:00 2001 From: liuwei Date: Fri, 24 Apr 2026 15:44:03 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=B8=B8=E9=A9=BB=E7=BE=A4?= =?UTF-8?q?=E9=95=BF=E6=9C=9F=E8=AE=B0=E5=BF=86=E4=B8=8E=E6=88=90=E5=91=98?= =?UTF-8?q?=E8=BD=BB=E7=94=BB=E5=83=8F=E8=BE=93=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/ai_auto_response/config.toml | 14 ++++-- .../context/context_builder.py | 50 +++++++++++++++++++ plugins/ai_auto_response/main.py | 21 +++++++- 3 files changed, 79 insertions(+), 6 deletions(-) diff --git a/plugins/ai_auto_response/config.toml b/plugins/ai_auto_response/config.toml index 2854ef8..45b70e0 100644 --- a/plugins/ai_auto_response/config.toml +++ b/plugins/ai_auto_response/config.toml @@ -72,8 +72,14 @@ default_char_limit = 30 default_total_limit = 30 [prompt_compact] -group_profile_max_chars = 220 -group_profile_max_lines = 6 +# 这里改成“常驻轻背景 + 相关增强”后,群长期摘要和成员轻画像都会稳定带给模型: +# 1. group_profile 放宽,让群长期摘要不会总被前面的模式/知识域说明挤掉; +# 2. member_profile_brief_* 新增为常驻轻画像额度,每次都给当前发言人一小段稳定画像; +# 3. 更重的成员记忆、群事实、向量记忆仍然保留独立额度,并继续走按需增强。 +group_profile_max_chars = 420 +group_profile_max_lines = 10 +member_profile_brief_max_chars = 260 +member_profile_brief_max_lines = 6 # 最近上下文现在要真实交给模型 30 条,因此这里同步放宽整体上下文裁剪阈值: # 1. recent_message_max_lines 提到 30,避免“窗口明明有 30,提示词里只留下 4 条”; # 2. context_max_lines/context_max_chars 一起抬高,避免最近消息刚拼进去又被整体截断; @@ -86,8 +92,8 @@ at_member_profile_max_chars = 160 at_member_profile_max_lines = 5 member_memory_max_chars = 180 member_memory_max_lines = 6 -memory_max_chars = 240 -memory_max_lines = 8 +memory_max_chars = 520 +memory_max_lines = 14 strict_memory_relevance = true [image] diff --git a/plugins/ai_auto_response/context/context_builder.py b/plugins/ai_auto_response/context/context_builder.py index 6bc77f6..6338dab 100644 --- a/plugins/ai_auto_response/context/context_builder.py +++ b/plugins/ai_auto_response/context/context_builder.py @@ -59,6 +59,7 @@ class ContextBuilder: "trigger_type": trigger.get("trigger_type", "none"), "reply_mode": reply_mode, "flow_state": flow_state, + "member_profile_brief_prompt": self._build_member_profile_brief_prompt(member_context or {}), "memory_prompt": self._build_member_memory_prompt(member_context, member_memory_focus or []), "at_member_profile_prompt": self._build_at_member_profile_prompt( member_context=member_context or {}, @@ -69,6 +70,7 @@ class ContextBuilder: "vector_memory_prompt": self._build_vector_memory_prompt(vector_memories), "social_memory_prompt": self._build_social_memory_prompt(social_memory or {}), "group_facts_prompt": self._build_group_facts_prompt(group_facts or {}), + "group_long_memory_prompt": self._build_group_long_memory_prompt(group_profile or {}), "group_profile_prompt": self._build_group_profile_prompt(group_profile or {}), "quote_prompt": self._build_quote_prompt(quote_context or {}), "image_prompt": self._build_image_prompt(image_context or {}), @@ -207,6 +209,35 @@ class ContextBuilder: text = re.sub(r"[^\u4e00-\u9fffA-Za-z0-9_]", "", text) return text[:8] + @staticmethod + def _build_member_profile_brief_prompt(member_context: Dict) -> str: + # 这份摘要是“常驻给模型看的轻画像”: + # 1. 不要求当前一定是 @ 或强定向,因为用户希望每次回答都能带上对这个人的基本认识; + # 2. 这里只保留少量稳定信息,避免画像太重把当前问题压住; + # 3. 更细的成员记忆、近期相关记忆,仍走后面的按需增强链路。 + if not member_context: + return "" + meta = member_context.get("meta", {}) or {} + summary = str(member_context.get("summary_text", "") or "").strip() + interaction_style = str(member_context.get("interaction_style", "") or "").strip() + response_hint = str(member_context.get("response_style_hint", "") or "").strip() + topics = ContextBuilder._stringify_items(member_context.get("topics_of_interest", []) or [], 3) + recent_focus = ContextBuilder._stringify_items(member_context.get("recent_focus", []) or [], 2) + skills = ContextBuilder._stringify_items(meta.get("skill_profile", []) or [], 2) + reply_prefs = ContextBuilder._stringify_items(meta.get("long_term_reply_preferences", []) or [], 2) + lines = [ + "当前发言人轻画像:", + f"成员摘要:{summary}" if summary else "", + f"互动风格:{interaction_style}" if interaction_style else "", + f"偏好回复方式:{response_hint}" if response_hint else "", + f"长期兴趣:{topics}" if topics else "", + f"近期关注:{recent_focus}" if recent_focus else "", + f"技能侧重点:{skills}" if skills else "", + f"回复偏好:{reply_prefs}" if reply_prefs else "", + "这些信息只用于帮助理解提问方式和回答切口,不要像在背档案。", + ] + return "\n".join([line for line in lines if line]) + @staticmethod def _build_member_memory_prompt(member_context: Dict, focus_lines: List[str] | None = None) -> str: if not member_context: @@ -320,6 +351,25 @@ class ContextBuilder: def _build_group_facts_prompt(group_facts: Dict) -> str: return str((group_facts or {}).get("prompt", "") or "").strip() + @staticmethod + def _build_group_long_memory_prompt(group_profile: Dict) -> str: + # 这份摘要是“群长期背景常驻层”: + # 1. 每次都给一小段,帮助模型知道这个群长期在聊什么、什么风格更合适; + # 2. 不把完整群画像整段塞进去,避免大量通用风格描述把 token 吃满; + # 3. 更细的群事实、群关系仍走相关性增强链路。 + if not group_profile: + return "" + summary = ContextBuilder._compact_group_summary(str(group_profile.get("group_memory_summary", "") or ""), max_chars=220, max_sentences=4) + focus = ", ".join(group_profile.get("knowledge_focus", [])[:4]) + memory_style = ContextBuilder._build_style_summary(group_profile.get("group_memory_style", {})) + lines = [ + "群长期背景:", + f"长期摘要:{summary}" if summary else "", + f"常聊方向:{focus}" if focus else "", + f"历史社交风格:{memory_style}" if memory_style else "", + ] + return "\n".join([line for line in lines if line]) + @staticmethod def _build_group_profile_prompt(group_profile: Dict) -> str: if not group_profile: diff --git a/plugins/ai_auto_response/main.py b/plugins/ai_auto_response/main.py index 4d7dc13..f710c1c 100644 --- a/plugins/ai_auto_response/main.py +++ b/plugins/ai_auto_response/main.py @@ -911,8 +911,12 @@ class AIAutoResponsePlugin(MessagePluginInterface): memory_hints=memory_hints, ) persona = self._compose_dify_persona_text(group_profile, context) + group_profile_parts = [ + self._string_block("群长期记忆(常驻)", context.get("group_long_memory_prompt", "")), + self._string_block("群当前画像", context.get("group_profile_prompt", "")), + ] group_profile_text = self._compact_text( - str(context.get("group_profile_prompt", "") or "").strip() or "当前群没有特殊画像。", + "\n\n".join([part for part in group_profile_parts if part]).strip() or "当前群没有特殊画像。", max_chars=int(self.prompt_compact_config.get("group_profile_max_chars", 220) or 220), max_lines=int(self.prompt_compact_config.get("group_profile_max_lines", 6) or 6), ) @@ -945,6 +949,14 @@ class AIAutoResponsePlugin(MessagePluginInterface): max_lines=int(self.prompt_compact_config.get("context_max_lines", 10) or 10), ) + # 成员画像拆成两层: + # 1. 常驻轻画像:每次都带,帮助模型理解这个人的提问方式、风格和切口; + # 2. 定向增强画像:只有明确 @ / 强定向 / followup 时再额外补,避免平时过度套人设。 + member_profile_brief_text = self._compact_text( + str(context.get("member_profile_brief_prompt", "") or ""), + max_chars=int(self.prompt_compact_config.get("member_profile_brief_max_chars", 260) or 260), + max_lines=int(self.prompt_compact_config.get("member_profile_brief_max_lines", 6) or 6), + ) at_member_profile_text = "" if bool(prompt_strategy.get("allow_member_memory")): at_member_profile_text = self._compact_text( @@ -962,6 +974,7 @@ class AIAutoResponsePlugin(MessagePluginInterface): member_memory_text = self._remove_overlap_lines(member_memory_text, at_member_profile_text) memory_parts = [ + self._string_block("当前发言人画像(常驻)", member_profile_brief_text), self._string_block("本次@发起者画像(优先)", at_member_profile_text), self._string_block("成员记忆", member_memory_text), self._string_block( @@ -1214,7 +1227,11 @@ class AIAutoResponsePlugin(MessagePluginInterface): } allow_member_memory = strong_directed or is_followup or returning_state in {"returning_member", "long_absent_member"} - allow_social_memory = is_question_like and strong_directed + # 群关系记忆继续按需开放,但问答模式下不再必须“强定向”才允许: + # 1. 用户希望回答能带上群里的长期背景和互动关系; + # 2. 关系记忆仍会经过相关性过滤,所以放宽入口不会直接把无关关系灌进去; + # 3. 这样技术问答里也更容易利用“谁经常和谁接话、谁常问哪类问题”的弱背景。 + allow_social_memory = is_question_like allow_group_facts = reply_mode == "qa_with_context" allow_vector_memory = reply_mode == "qa_with_context" or returning_state == "long_absent_member"