修复群聊近期话题查询仍走短期记忆的问题

- 为@机器人提问补充群聊近期话题/总结类意图识别
- 这类问题强制升级为 qa_with_context,打开群事实与向量记忆
- 放宽群聊话题回顾型问题的记忆相关性门槛,避免长期记忆被二次过滤
This commit is contained in:
liuwei
2026-04-27 10:35:23 +08:00
parent 55723519aa
commit 7d2ad5b3d8
4 changed files with 73 additions and 3 deletions

View File

@@ -53,6 +53,8 @@ class ContextBuilder:
"speaker_name_clean": self._clean_display_name(sender_name),
"is_at": bool(trigger.get("is_at", False)),
"is_directed": bool(trigger.get("is_directed", False)),
# 这类标记会被后面的 prompt 策略层消费,用来决定要不要放开群级记忆。
"is_group_memory_query": bool(trigger.get("is_group_memory_query", False)),
"recent_message_items": self._build_recent_message_items(selected_messages),
"recent_messages": recent_lines,
"recent_summary": "",

View File

@@ -9,6 +9,12 @@ class ResponsePlanner:
# 而是一句短短的回怼或挡回去,所以这里强制走 social_short。
if trigger.get("is_directed_abuse"):
return "social_short"
# “群里最近都聊什么”这类问题,本质是在问群记忆摘要:
# 1. 如果继续走 qa_fast就只会优先依赖最近现场消息
# 2. 这里直接抬到 qa_with_context后面才会打开群事实/向量记忆等补充层;
# 3. 不依赖 flow_state是因为这类问题和当前场子热不热关系不大。
if trigger.get("is_question") and trigger.get("is_group_memory_query"):
return "qa_with_context"
if trigger.get("is_question"):
return "qa_with_context" if flow_state in {"engaged", "deep_engaged"} else "qa_fast"
if trigger.get("is_followup"):

View File

@@ -11,6 +11,16 @@ QUESTION_PATTERNS = [
r"\?$", r"$", r"怎么", r"如何", r"咋弄", r"为啥", r"为什么",
r"有人知道", r"谁知道", r"能不能", r"可以吗", r"报错", r"怎么解决",
]
GROUP_MEMORY_QUERY_PATTERNS = [
# 这组模式专门识别“回顾群里最近在聊什么”的提问:
# 1. 这类问题本质上不是要 bot 现场接一句,而是要它调动群级记忆来做概括;
# 2. 之前它会被当成普通问句,落进 qa_fast结果只看最近 30 条现场消息;
# 3. 单独打标后,后续策略层才能有依据切到 qa_with_context。
r"(最近|这两天|这几天|近期).*(聊|讨论|在说|在聊|话题|主题|重点|近况)",
r"(都|最近).*(聊什么|说什么|讨论什么|在聊啥|在说啥|啥话题)",
r"(群里|大家|他们).*(最近|这两天|这几天).*(聊|讨论|话题|重点)",
r"(总结|概括).*(最近|这两天|这几天|近期).*(聊天|讨论|话题|内容)",
]
SOCIAL_PATTERNS = [r"小牛", r"在吗", r"出来", r"帮忙看", r"看看"]
LIGHT_SOCIAL_PATTERNS = [
r"哈哈", r"笑死", r"绷不住", r"离谱", r"逆天", r"牛逼", r"卧槽", r"我去",
@@ -34,6 +44,9 @@ class TriggerResult:
is_directed_abuse: bool = False
is_followup: bool = False
is_social_call: bool = False
# 这类问题是在问“群里最近都在聊啥”,
# 需要优先动用群事实/长期摘要/向量召回,而不是只看短期现场窗口。
is_group_memory_query: bool = False
is_returning_member: bool = False
should_respond: bool = False
topic: str = ""
@@ -94,6 +107,11 @@ class TriggerRouter:
result.is_directed = True
result.reasons.append("question_named_bot")
result.reasons.append("question")
if self._is_group_memory_query(content):
# “最近都聊什么”这类提问单独记下来,供后面的回复模式和记忆策略升级。
# 这里不直接改 should_respond是为了继续遵守“必须是定向提问才回答”的总原则。
result.is_group_memory_query = True
result.reasons.append("group_memory_query")
if is_directed_abuse(
content,
directed=bool(message.get("is_at")) or named_to_bot or conversation_hints.get("quote_targets_bot", False),
@@ -186,6 +204,12 @@ class TriggerRouter:
return keyword
return ""
def _is_group_memory_query(self, content: str) -> bool:
text = str(content or "").strip()
if not text:
return False
return any(re.search(pattern, text, flags=re.IGNORECASE) for pattern in GROUP_MEMORY_QUERY_PATTERNS)
def _has_bot_name(self, content_lower: str) -> bool:
text = str(content_lower or "").strip().lower()
if not text:

View File

@@ -1196,6 +1196,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
trigger_type = str(context.get("trigger_type", "none") or "none")
is_at = bool(context.get("is_at", False))
is_directed = bool(context.get("is_directed", False))
is_group_memory_query = bool(context.get("is_group_memory_query", False))
is_followup = bool(memory_hints.get("is_followup", False))
returning_state = str(memory_hints.get("returning_member_state", "") or "").strip()
strong_directed = is_at or is_directed or trigger_type in {"at_trigger", "quote_followup_trigger"}
@@ -1231,9 +1232,15 @@ class AIAutoResponsePlugin(MessagePluginInterface):
# 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"
allow_social_memory = is_question_like or is_group_memory_query
# “最近都聊什么”这类问题,本身就是在问群级记忆,
# 所以哪怕当前只是普通问答入口,也要把群事实和向量层放开。
allow_group_facts = reply_mode == "qa_with_context" or is_group_memory_query
allow_vector_memory = (
reply_mode == "qa_with_context"
or returning_state == "long_absent_member"
or is_group_memory_query
)
return {
"target_reply_chars": target_reply_chars_map.get(reply_mode, 10),
@@ -1292,6 +1299,13 @@ class AIAutoResponsePlugin(MessagePluginInterface):
@staticmethod
def _is_text_relevant(content: str, memory_text: str) -> bool:
# 对“最近都聊什么”这类群聊回顾型问题做一个显式兜底:
# 1. 这类问题天然缺少技术关键词,严格按词重叠时经常会得到 0 命中;
# 2. 但它问的恰恰就是“群级记忆摘要”,不应该再被相关性门槛二次挡掉;
# 3. 因此只要当前问题像群聊话题回顾,而记忆文本也明显是群摘要/群事实,就直接放行。
if AIAutoResponsePlugin._looks_like_group_memory_query(content):
if AIAutoResponsePlugin._looks_like_group_memory_text(memory_text):
return True
content_tokens = AIAutoResponsePlugin._extract_relevance_tokens(content)
memory_tokens = AIAutoResponsePlugin._extract_relevance_tokens(memory_text)
if not content_tokens or not memory_tokens:
@@ -1299,6 +1313,30 @@ class AIAutoResponsePlugin(MessagePluginInterface):
overlap = content_tokens & memory_tokens
return len(overlap) >= 1
@staticmethod
def _looks_like_group_memory_query(content: str) -> bool:
text = str(content or "").strip()
if not text:
return False
patterns = [
r"(最近|这两天|这几天|近期).*(聊|讨论|在说|在聊|话题|主题|重点|近况)",
r"(都|最近).*(聊什么|说什么|讨论什么|在聊啥|在说啥|啥话题)",
r"(群里|大家|他们).*(最近|这两天|这几天).*(聊|讨论|话题|重点)",
r"(总结|概括).*(最近|这两天|这几天|近期).*(聊天|讨论|话题|内容)",
]
return any(re.search(pattern, text, flags=re.IGNORECASE) for pattern in patterns)
@staticmethod
def _looks_like_group_memory_text(memory_text: str) -> bool:
text = str(memory_text or "").strip()
if not text:
return False
markers = [
"群长期背景", "长期摘要", "稳定主题", "近期重点", "未决问题",
"群事实", "最近沉淀", "最近长期反复出现", "下面是群", "相关话题",
]
return any(marker in text for marker in markers)
@staticmethod
def _extract_relevance_tokens(text: str) -> set[str]:
raw = str(text or "").lower()