fix: 跨轮语义去重+KB核心名词加权,修复公告信息匹配到基本信息

1. monitor.py: 新增跨轮语义去重(5分钟TTL, Jaccard≥30%)
   - _recent_user_queries列表 + _is_semantically_duplicate()
   - _poll候选构建阶段检查,回复后写入记录

2. auto_reply.py: non_core_kw新增通用信息类名词
   - '信息','内容','详情','情况','介绍'不应作为核心词
   - 原因:公告信息核心词应是{公告}而非{公告,信息}

3. knowledge_base.py: core_nouns新增公告/更新
   - 查询含核心名词时,section含该名词→2倍加分,不含→0.3倍惩罚
   - 修复近期公告(2.93)被基本信息(6.56)压过的问题
This commit is contained in:
youpaishidifu
2026-08-17 12:09:23 +08:00
parent 0fe4f018ba
commit 174b606721
3 changed files with 56 additions and 2 deletions
+4
View File
@@ -273,6 +273,10 @@ class AutoReply:
# "下载"是动作而非查询对象,真正核心的是被查询的插件名
# 例:"登录插件去哪儿下载"的核心词应是{'登录'},而非{'下载'}
'下载', '下到', '哪下', '在哪下', '怎么下',
# 2026-08-17 新增:通用信息类名词不应作为核心词
# "信息"/"内容"/"详情"等太泛,无法区分文档主题
# 例:"服务器最近有哪些公告信息"的核心词应是{'公告'},而非{'公告','信息'}
'信息', '内容', '详情', '情况', '介绍',
}
core_kw = q_mean - non_core_kw
doc_text = heading_clean + " " + title.lower() + " " + body_clean[:800]
+2 -2
View File
@@ -620,7 +620,7 @@ class KnowledgeBase:
# 不在此处penalty——此处penalty会被后续loop的bonus覆盖
# ⭐ 核心名词匹配奖励:查询中的关键名词在 section 中出现 → 巨幅奖励
# 防止"PHP管理员端的默认密码"匹配到不含"密码"的"全局配置" section
core_nouns = {'密码', '用户管理', '领地', '商品', 'CDK', '工单', '债券', '在线'}
core_nouns = {'密码', '用户管理', '领地', '商品', 'CDK', '工单', '债券', '在线', '公告', '更新'}
query_nouns = set(jieba.cut(query_stripped)) & core_nouns
if query_nouns:
# 查询包含核心名词,检查 section 是否包含这些名词
@@ -891,7 +891,7 @@ class KnowledgeBase:
if _download_match:
bonus += 8.0
# ⭐ body 包含查询核心名词(如"密码")→ 巨幅 bonus
core_nouns = {'密码', '用户管理', '领地', '商品', 'CDK', '工单', '债券', '在线'}
core_nouns = {'密码', '用户管理', '领地', '商品', 'CDK', '工单', '债券', '在线', '公告', '更新'}
body_noun_hits = sum(1 for n in core_nouns if n in body and n in query_stripped)
if body_noun_hits >= 1:
bonus += 15.0 # body 包含核心名词 → 优先返回
+50
View File
@@ -520,6 +520,8 @@ class MessageMonitor:
self._replied_texts: set = set()
# 最近已处理的消息(用于5分钟内OCR重复识别去重)
self._recent_processed_messages = [] # list of (text, timestamp)
# 最近处理过的用户查询(用于语义去重,5分钟内相似查询跳过)
self._recent_user_queries = [] # list of (text, timestamp)
# 语义指纹缓存(同屏OCR碎片30秒去重)
self._semantic_fingerprint_cache = [] # list of (fingerprint, timestamp)
# 上次回复时间戳(冷却期内跳过该平台 poll)
@@ -5189,6 +5191,7 @@ class MessageMonitor:
# —— 2026-08-16: 语义指纹去重 ——
_SEMANTIC_DUP_TTL = 30.0 # 同一指纹30秒内视为重复
_SEMANTIC_USER_DUP_TTL = 300.0 # 用户查询跨轮语义去重:5分钟内相似查询跳过
_SEMANTIC_STOPWORDS = frozenset((
'那', '那个', '这个', '这个', '这些', '那些', '那么',
'问', '问一下', '问一嘴', '打听一下',
@@ -5279,6 +5282,46 @@ class MessageMonitor:
self._semantic_fingerprint_cache.append((text, now))
return result
def _is_semantically_duplicate(self, text: str, platform: str) -> bool:
"""跨轮语义去重:5分钟内语义相似度>=阈值的用户查询跳过。
典型场景:用户发送"服务器最近有哪些公告信息?",机器人回复后,
几分钟后又发送相似查询(如"@服务器最近有哪些公告信息?"),
因OCR变体或细微措辞差异,被当作新查询再次处理。
方案:检查当前查询与最近5分钟内用户查询的 Jaccard 相似度,
若 >= 阈值(0.3)则视为语义重复,阻止回复。
返回:True = 语义重复,应跳过;False = 新查询,可继续处理。
"""
if not text or not text.strip():
return False
now = time.time()
# 清理过期条目
self._recent_user_queries = [
(t, ts) for t, ts in self._recent_user_queries
if now - ts <= self._SEMANTIC_USER_DUP_TTL
]
# 与最近查询比较
SIM_THRESHOLD = 0.3
for recent_text, recent_ts in self._recent_user_queries:
sim = self._semantic_similarity(text, recent_text)
if sim >= SIM_THRESHOLD:
logger.debug(
f"[{platform}] 用户查询语义去重: 跳过相似({sim:.0%}) "
f"'{text[:40]!r}' ~ '{recent_text[:40]!r}'"
)
return True
return False
def _add_to_recent_user_queries(self, text: str):
"""将当前用户查询添加到最近查询列表,用于跨轮语义去重。"""
if text and text.strip():
self._recent_user_queries.append((text.strip(), time.time()))
def _split_by_nick_sep(self, text: str) -> list:
"""将OCR合并的多条消息按昵称分隔符拆分,提取所有消息段。
@@ -8042,6 +8085,11 @@ class MessageMonitor:
if is_duplicate:
self._last_new_msg_time[platform] = time.time()
continue
# ⭐ 跨轮语义去重:5分钟内相似用户查询跳过
if self._is_semantically_duplicate(cleaned_item, platform):
logger.info(f"[{platform}] 用户查询语义去重: 5分钟内相似查询,跳过: {cleaned_item[:60]}")
self._last_new_msg_time[platform] = time.time()
continue
# ⭐ 添加到候选列表(含质量分)
score = self._score_message_quality(cleaned_item)
_candidates.append((score, item, cleaned_item))
@@ -8066,6 +8114,8 @@ class MessageMonitor:
# ⭐ 添加到最近处理列表
self._recent_processed_messages.append((_best_cleaned, now))
# ⭐ 添加到最近用户查询列表(用于跨轮语义去重)
self._add_to_recent_user_queries(_best_cleaned)
self._last_new_msg_time[platform] = time.time()
self._last_msg[platform] = _best_cleaned
engine_tag = self._last_chosen_engine.get(platform, "")