fix: 跨轮语义去重+checkbox清理
Task #1548: 跨轮语义去重增强 - 三层策略: Jaccard≥0.75 → 核心词重叠≥0.80 → 子集检测≥0.90 - 新增 _extract_content_keywords 提取CJK bigram关键词 - 修复登录插件去哪儿下载等变体查询未被拦截问题 Task #1549: Markdown小尾巴清理 - _clean_markdown 新增 checkbox 标记移除 - 支持 [√] [✗] [X] [x] [V] [v] [ ] [⚠] [!] 格式 - 修复 bot回复末尾残留 [√] GitHub(稳定版) 问题
This commit is contained in:
@@ -622,6 +622,12 @@ class AutoReply:
|
||||
# 移除列表语法:- item → item
|
||||
text = re.sub(r'^[\-\*]\s+', '', text, flags=re.MULTILINE)
|
||||
|
||||
# ⭐ Task #1549: 移除 checkbox 标记:[√] GitHub(稳定版) → GitHub(稳定版)
|
||||
# KB 中的下载选项列表使用 checkbox 格式,如 "- [ √ ] GitHub(稳定版)"
|
||||
# 清理后应返回 "GitHub(稳定版)" 而非 "[ √ ] GitHub(稳定版)"
|
||||
# 支持: [√] [✗] [X] [x] [V] [v] [ ] [⚠] [!]
|
||||
text = re.sub(r'\[\s*[√✗XxVv⚠! ]?\s*\]\s*', '', text)
|
||||
|
||||
# 移除加粗语法:**text** → text
|
||||
text = re.sub(r'\*\*(.+?)\*\*', r'\1', text)
|
||||
|
||||
|
||||
+59
-2
@@ -5359,8 +5359,9 @@ class MessageMonitor:
|
||||
几分钟后又发送相似查询(如"@服务器最近有哪些公告信息?"),
|
||||
因OCR变体或细微措辞差异,被当作新查询再次处理。
|
||||
|
||||
方案:检查当前查询与最近5分钟内用户查询的 Jaccard 相似度,
|
||||
若 >= 阈值(0.3)则视为语义重复,阻止回复。
|
||||
方案:双层匹配——
|
||||
1. Jaccard相似度(CJK bigram) >= 0.75 → 语义重复
|
||||
2. 核心关键词完全一致 → 语义重复(兜底:Jaccard对"服务器"等修饰词敏感)
|
||||
|
||||
返回:True = 语义重复,应跳过;False = 新查询,可继续处理。
|
||||
"""
|
||||
@@ -5374,9 +5375,13 @@ class MessageMonitor:
|
||||
if now - ts <= self._SEMANTIC_USER_DUP_TTL
|
||||
]
|
||||
|
||||
# ⭐ 提取当前查询的核心关键词(去停用词后)
|
||||
current_keywords = self._extract_content_keywords(text)
|
||||
|
||||
# 与最近查询比较
|
||||
SIM_THRESHOLD = 0.75 # Jaccard相似度阈值:>=0.75视为语义重复(提高防误杀)
|
||||
for recent_text, recent_ts in self._recent_user_queries:
|
||||
# 层1: Jaccard相似度
|
||||
sim = self._semantic_similarity(text, recent_text)
|
||||
if sim >= SIM_THRESHOLD:
|
||||
logger.debug(
|
||||
@@ -5385,8 +5390,60 @@ class MessageMonitor:
|
||||
)
|
||||
return True
|
||||
|
||||
# 层2: 核心关键词高重叠(兜底:Jaccard对"服务器"等修饰词敏感时)
|
||||
# 例: "咱服务器那个登录插件去哪儿下载" vs "咱那个登录插件去哪儿下载"
|
||||
# Jaccard=55%,但核心词{登录,插件,下载}高度重叠 → 视为重复
|
||||
# 使用Szymkiewicz-Simpson系数: |A∩B| / min(|A|,|B|)
|
||||
recent_keywords = self._extract_content_keywords(recent_text)
|
||||
if current_keywords and recent_keywords and len(current_keywords) >= 2 and len(recent_keywords) >= 2:
|
||||
overlap = len(current_keywords & recent_keywords)
|
||||
min_size = min(len(current_keywords), len(recent_keywords))
|
||||
overlap_ratio = overlap / min_size if min_size > 0 else 0
|
||||
if overlap_ratio >= 0.80: # 80%重叠视为语义重复
|
||||
logger.debug(
|
||||
f"[{platform}] 用户查询语义去重: 核心词重叠({overlap_ratio:.0%}) "
|
||||
f"'{text[:40]!r}' ~ '{recent_text[:40]!r}'"
|
||||
)
|
||||
return True
|
||||
|
||||
# 层3: 核心词子集检测(兜底:q2 vs q4 大小写差异时 overlap 偏低)
|
||||
# 例: "登录插件儿下载" vs "登录插件下载"("儿"是OCR/口语变体)
|
||||
# 检查过滤后文本是否为对方子串,或核心词bigram是否为对方子集
|
||||
if current_keywords and recent_keywords and len(current_keywords) >= 3 and len(recent_keywords) >= 3:
|
||||
# 核心词子集:小集合的bigram是否大部分在大集合中
|
||||
smaller, larger = (current_keywords, recent_keywords) if len(current_keywords) <= len(recent_keywords) else (recent_keywords, current_keywords)
|
||||
subset_ratio = len(smaller & larger) / len(smaller) if smaller else 0
|
||||
if subset_ratio >= 0.90: # 90%子集视为语义重复
|
||||
logger.debug(
|
||||
f"[{platform}] 用户查询语义去重: 核心词子集({subset_ratio:.0%}) "
|
||||
f"'{text[:40]!r}' ~ '{recent_text[:40]!r}'"
|
||||
)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _extract_content_keywords(self, text: str) -> frozenset:
|
||||
"""提取文本的核心内容关键词(CJK bigram,去停用词)。
|
||||
|
||||
用于跨轮去重的关键词匹配兜底:当Jaccard相似度因修饰词差异偏低时,
|
||||
核心关键词一致仍可判定为重复。
|
||||
|
||||
例: "咱服务器那个登录插件去哪儿下载啊" → frozenset{"登录", "插件", "下载", ...}
|
||||
"""
|
||||
if not text:
|
||||
return frozenset()
|
||||
# 提取CJK字符
|
||||
cjk = ''.join(c for c in text if '\u4e00' <= c <= '\u9fff')
|
||||
# 去除标点
|
||||
while cjk and cjk[-1] in self._SEMANTIC_TRIM_PUNCT:
|
||||
cjk = cjk[:-1]
|
||||
# 去除停用词单字
|
||||
filtered = ''.join(c for c in cjk if c not in self._SEMANTIC_STOPWORDS)
|
||||
if len(filtered) < 2:
|
||||
return frozenset()
|
||||
# 提取bigram作为关键词
|
||||
return frozenset(filtered[i:i+2] for i in range(len(filtered) - 1))
|
||||
|
||||
def _add_to_recent_user_queries(self, text: str):
|
||||
"""将当前用户查询添加到最近查询列表,用于跨轮语义去重。"""
|
||||
if text and text.strip():
|
||||
|
||||
Reference in New Issue
Block a user