fix: 修复中部孤立*导致用户查询被_is_noise_text误杀的问题

问题:OCR把bot的**小姐姐**识别成小姐姐*,单个*粘在昵称后,
导致'小姐姐*, 关于我们'(len≤10)被_is_noise_text判为残片丢弃,用户查询丢失。

根因:
1. _is_noise_text的逗号分段放行逻辑在elif分支,len≤10时永远不走逗号分段
2. 过滤管道无清理中部孤立*的机制(_strip_markdown_head只剥开头,_strip_markdown_tail只匹配**)

修复:
1. _is_noise_text:在len≤10分支的return True前增加逗号分段放行检查
   - 短文本含逗号时,检查逗号后半部分是否含有效查询词(疑问词/动词/主语)
   - 后半部分有效→不丢弃,保留用户真实查询
2. 过滤管道增加孤立*清理步骤(正则: ([\u4e00-\u9fff])\*(?!\*))
   - 清除中文字符旁的单个*(bot markdown加粗残留),不匹配**,不匹配英文场景
   - 覆盖4个调用点:for循环管道、尾部过滤、逐行过滤、去重后过滤
3. 预分割噪声检测也增加*清理

commit: 4个过滤管道+预分割+2个辅助方法,共73行变更
This commit is contained in:
youpaishidifu
2026-08-21 11:18:51 +08:00
parent 3e1e8cb700
commit fcc2e51777
+73 -6
View File
@@ -3510,6 +3510,14 @@ class MessageMonitor:
if not _msg_bot:
logger.debug(f"[ocr] stage[{i}] [filter/bot] bot旧回复整体剥离后为空,跳过: {buf.strip()[:60]!r}")
else:
# ⭐ 2026-08-21 清理 bot markdown 加粗残留:中文字符旁的孤立 *
# OCR 把 bot 的 **小姐姐** 识别成 小姐姐*,单个 * 粘在昵称后,
# 导致 _is_noise_text 误判为 OCR 残片丢弃整条消息。
# 模式:中文*(*后非*)→ 删除 *;不匹配 **,不匹配英文场景(100*2)
_msg_bot_before_star = _msg_bot
_msg_bot = re.sub(r'([\u4e00-\u9fff])\*(?!\*)', r'\1', _msg_bot)
if _msg_bot != _msg_bot_before_star:
logger.info(f"[ocr] stage[{i}] [filter/star] 清理markdown加粗残留*: {_msg_bot_before_star[:60]!r} -> {_msg_bot[:60]!r}")
# ⭐ 2026-08-19 新增:先剥【头部】md(bot md 列表项被 OCR 拼到用户消息前面,
# 如 "- 关于我们" / "-关于我们")。_strip_markdown_tail 只剥尾部,
# 对头部 md 无能为力(有空格→整段丢弃丢消息;无空格→漏检污染查库)。
@@ -3584,6 +3592,11 @@ class MessageMonitor:
if not _msg_bot:
logger.debug(f"[ocr] stage[{i}] [filter/bot] 尾部bot旧回复整体剥离后为空,跳过: {buf.strip()[:60]!r}")
else:
# ⭐ 2026-08-21 清理 bot markdown 加粗残留(同 for 循环块)
_msg_bot_before_star = _msg_bot
_msg_bot = re.sub(r'([\u4e00-\u9fff])\*(?!\*)', r'\1', _msg_bot)
if _msg_bot != _msg_bot_before_star:
logger.info(f"[ocr] stage[{i}] [filter/star] 清理markdown加粗残留*(尾部): {_msg_bot_before_star[:60]!r} -> {_msg_bot[:60]!r}")
# ⭐ 问句保护:若文本含问号且首段无问句,先分离尾部问句
# 例: "php直登,是|,游戏里输,Jweb拿授权token,否|,那那个登录插件应该又去哪儿下载呢?"
# _strip_markdown_tail 在首个 | 处切→只留"php直登,是"→问句丢失
@@ -3944,6 +3957,12 @@ class MessageMonitor:
logger.debug(f"[{platform}] OCR 过滤: YAML行 {line!r}")
continue
# ⭐ 2026-08-21 清理 bot markdown 加粗残留(逐行过滤管道)
line_before_star = line
line = re.sub(r'([\u4e00-\u9fff])\*(?!\*)', r'\1', line)
if line != line_before_star:
logger.debug(f"[{platform}] OCR 过滤: 清理markdown加粗残留* line={line_before_star!r} -> {line!r}")
# —— md 尾巴剥离:末尾 markdown 内容切掉,保留用户原始提问 ——
# ⭐ 2026-08-11:OCR 把 bot md 回复残片拼在用户消息尾部
# (如 "群管理员...(头衔UI)...# #来测试# # @C418minecraf")。
@@ -5859,6 +5878,11 @@ class MessageMonitor:
if not text or not text.strip():
return False
t = text.strip()
# ⭐ 2026-08-21 清理 bot markdown 加粗残留
t_before_star = t
t = re.sub(r'([\u4e00-\u9fff])\*(?!\*)', r'\1', t)
if t != t_before_star:
logger.debug(f"[ocr] _is_question_message: 清理markdown加粗残留*: {t_before_star[:60]!r} -> {t[:60]!r}")
if not t.endswith(('?', '?')):
return False
if not re.search(r'[\u4e00-\u9fff]', t):
@@ -6122,6 +6146,11 @@ class MessageMonitor:
if not timestamps:
# ⭐ 无可见时间戳:检查噪声文本
logger.debug(f"[ocr] 无可见时间戳")
# ⭐ 2026-08-21 清理 bot markdown 加粗残留(无时间戳分支)
text_before_star = text
text = re.sub(r'([\u4e00-\u9fff])\*(?!\*)', r'\1', text)
if text != text_before_star:
logger.info(f"[ocr] 清理markdown加粗残留*(无时间戳): {text_before_star[:60]!r} -> {text[:60]!r}")
if self._is_noise_text(text):
logger.debug(f"[ocr] 过滤器1结果: 无时间戳且为噪声文本,丢弃: {text[:80]!r}")
return False
@@ -6968,6 +6997,12 @@ class MessageMonitor:
for i, seg in enumerate(segments):
# ⭐ 2026-08-13: 先剥离bot旧回复,再检测噪声,防止混合文本被误判为噪声
stripped_seg = self._strip_bot_reply_fragments(seg)
# ⭐ 2026-08-21 清理 bot markdown 加粗残留(同 filter 管道)
if stripped_seg:
stripped_seg_before_star = stripped_seg
stripped_seg = re.sub(r'([\u4e00-\u9fff])\*(?!\*)', r'\1', stripped_seg)
if stripped_seg != stripped_seg_before_star:
logger.debug(f"[{platform}] 预分割: 清理markdown加粗残留*: {stripped_seg_before_star[:60]!r} -> {stripped_seg[:60]!r}")
is_noise = self._is_noise_text(stripped_seg) if stripped_seg else True
if is_noise:
noise_count += 1
@@ -7115,12 +7150,39 @@ class MessageMonitor:
_has_subject = any(s in text for s in ('我', '你', '他', '我们', '你们', '他们', '玩家', '用户', '管理员'))
# 无疑问词 + 无动词 或 无疑问词 + 无主语 → 可能是残片
if not _has_question_word and (not _has_verb or not _has_subject):
# 额外防御:检查是否是常见用户问句模式
_common_query = any(text.startswith(q) for q in ('怎么', '如何', '什么', '哪里', '哪个', '几', '多少', '为什么', '在哪', '如何'))
_ends_with_question = text.rstrip().endswith(('?', '?', '!', '!'))
if not _common_query and not _ends_with_question:
logger.debug(f"[ocr] 过滤器2结果: 检测到OCR残片(无疑问词+无动词/主语),返回True: {text!r}")
return True
# ⭐ 修复(2026-08-21):短文本逗号分段放行
# 原 elif 分支只在 len>10 时运行,短文本永远不走逗号分段。
# 例:'小姐姐*, 关于我们'(len=10) → _has_verb=False → 误判残片→丢消息
# 修复:len<=10 分支内也检查逗号分段,后半部分含有效查询→放行
if ',' in text or ',' in text:
_parts = re.split(r'[,,]', text)
if len(_parts) >= 2:
_second_part = _parts[-1].strip()
_has_q2 = any(w in _second_part for w in ('吗', '呢', '多少', '怎么', '如何', '哪', '什么', '几', '?', '?'))
_has_v2 = any(v in _second_part for v in ('怎么', '如何', '进行', '设置', '配置', '操作', '使用', '打开', '下载', '安装', '登录', '进入', '查看', '查询', '问', '找', '要', '可以', '能', '会', '请', '帮', '给', '有'))
_has_s2 = any(s in _second_part for s in ('我', '你', '他', '我们', '你们', '他们', '玩家', '用户', '管理员'))
if len(_second_part) >= 3 and (_has_q2 or _has_v2 or _has_s2):
logger.debug(f"[ocr] 过滤器2结果: 短文本逗号分段放行(后半含有效查询): {text!r} -> {_second_part!r}")
# 不return True,继续后续检查
else:
_common_query = any(text.startswith(q) for q in ('怎么', '如何', '什么', '哪里', '哪个', '几', '多少', '为什么', '在哪', '如何'))
_ends_with_question = text.rstrip().endswith(('?', '?', '!', '!'))
if not _common_query and not _ends_with_question:
logger.debug(f"[ocr] 过滤器2结果: 检测到OCR残片(无疑问词+无动词/主语),返回True: {text!r}")
return True
else:
_common_query = any(text.startswith(q) for q in ('怎么', '如何', '什么', '哪里', '哪个', '几', '多少', '为什么', '在哪', '如何'))
_ends_with_question = text.rstrip().endswith(('?', '?', '!', '!'))
if not _common_query and not _ends_with_question:
logger.debug(f"[ocr] 过滤器2结果: 检测到OCR残片(无疑问词+无动词/主语),返回True: {text!r}")
return True
else:
# 额外防御:检查是否是常见用户问句模式
_common_query = any(text.startswith(q) for q in ('怎么', '如何', '什么', '哪里', '哪个', '几', '多少', '为什么', '在哪', '如何'))
_ends_with_question = text.rstrip().endswith(('?', '?', '!', '!'))
if not _common_query and not _ends_with_question:
logger.debug(f"[ocr] 过滤器2结果: 检测到OCR残片(无疑问词+无动词/主语),返回True: {text!r}")
return True
elif ',' in text or ',' in text:
# 逗号分隔的混合文本:检查后半部分是否为正常查询
# 例:'个桓温,关于我们' → 前半是残片,后半是正常查询
@@ -8442,6 +8504,11 @@ class MessageMonitor:
continue
if self._is_system_message(ts):
continue
# ⭐ 2026-08-21 清理 bot markdown 加粗残留(去重后过滤管道)
ts_before_star = ts
ts = re.sub(r'([\u4e00-\u9fff])\*(?!\*)', r'\1', ts)
if ts != ts_before_star:
logger.info(f"[{platform}] 清理markdown加粗残留*: {ts_before_star[:60]!r} -> {ts[:60]!r}")
# Markdown 文档过滤:bot 回复以 md 格式为主(#标题、[链接](url)、-列表项),
# 用户日常对话不用 md 语法。拦截 md 格式文本避免 bot 旧回复被重新处理。
# ⭐ 2026-08-11 先剥 md 尾巴再判:md 在末尾→切小尾巴留用户提问;