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:
@@ -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
@@ -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
@@ -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, "")
|
||||
|
||||
Reference in New Issue
Block a user