修复OCR消息合并导致配置文件过滤器误伤用户消息的问题 (Task #1685)

1. 问题:OCR把多个消息拼接成一个整体,包含表格内容
   - 表格内容中的'服主: youpaishidifu'被正则表达式匹配为'键:值'格式
   - _is_noise_text检测到'多条配置文件格式(2条key:value)',返回True
   - 导致用户消息被丢弃,引起消息丢失

2. 修复:
   - 在_is_noise_text函数中添加表格内容特征检查
   - 如果文本包含表格结构特征('|'分隔符、多行表格),则跳过配置文件检测
   - 在_is_config_fragment函数中添加表格内容特征检查
   - 避免误判表格内容为配置碎片

3. 测试:
   - 验证表格内容不再被误判为配置文件格式
   - 验证纯配置文件仍能被正确检测
   - 验证用户消息不受影响
This commit is contained in:
youpaishidifu
2026-09-02 17:15:51 +08:00
parent aad8594b06
commit 3245e790c5
+103 -61
View File
@@ -8341,6 +8341,7 @@ class MessageMonitor:
- 段内出现 ≥2 个 bot 配置键词 + 冒号(值可空,OCR 常把值拆到下一行) - 段内出现 ≥2 个 bot 配置键词 + 冒号(值可空,OCR 常把值拆到下一行)
- 或短段(≤30字)以单个 bot 配置键词 + 空冒号结尾(被截断的标签) - 或短段(≤30字)以单个 bot 配置键词 + 空冒号结尾(被截断的标签)
排除含疑问词或 @提及 的真实用户问句。 排除含疑问词或 @提及 的真实用户问句。
⭐ Task #1685: 排除表格内容,避免误判表格中的"键:值"格式。
""" """
if not text: if not text:
return False return False
@@ -8354,6 +8355,17 @@ class MessageMonitor:
if _has_question or _has_at: if _has_question or _has_at:
return False return False
# ── Task #1685: 排除表格内容 ──
# 表格内容中的"|"分隔符是特征,不应被误判为配置碎片
_has_table_structure = '|' in text and text.count('|') >= 2
if _has_table_structure:
_lines = text.split('\n')
_table_lines = [line for line in _lines if '|' in line]
if len(_table_lines) >= 2:
# 包含多行表格结构,不是配置碎片
logger.debug(f"[ocr] 预分割: 检测到表格结构({len(_table_lines)}行),不是配置碎片: {_s[:60]!r}")
return False
# ── bot 配置键词表(bot 渲染插件配置/帮助面板使用的标签)── # ── bot 配置键词表(bot 渲染插件配置/帮助面板使用的标签)──
_config_keys = ( _config_keys = (
'图标', '标题', '指令', '命令', '权限类型', '权限', '格子', '玩家', '商人', '图标', '标题', '指令', '命令', '权限类型', '权限', '格子', '玩家', '商人',
@@ -8554,51 +8566,73 @@ class MessageMonitor:
logger.debug(f"[ocr] 过滤器2结果: 空文本,返回False") logger.debug(f"[ocr] 过滤器2结果: 空文本,返回False")
return False return False
# ⭐ Task #1685: 表格内容特征检查(在配置文件检测之前)
# OCR可能把多个消息拼接成一个整体,包含表格内容
# 表格内容中的"键:值"格式不应被误判为配置文件
# 特征:包含"|"分隔符(Markdown表格格式)
_has_table_structure = '|' in text and text.count('|') >= 2
if _has_table_structure:
# 检查是否为表格结构:包含多行"|"分隔的内容
_lines = text.split('\n')
_table_lines = [line for line in _lines if '|' in line]
if len(_table_lines) >= 2:
# 包含多行表格结构,跳过配置文件检测
logger.debug(f"[ocr] 过滤器2结果: 检测到表格结构({len(_table_lines)}行),跳过配置文件检测: {text[:80]!r}")
# 继续后续其他噪声检测,但不进行配置文件格式检测
# ⭐ Task #1592: 配置文件格式检测(最粗暴方法) # ⭐ Task #1592: 配置文件格式检测(最粗暴方法)
# 检测所有配置文件格式:key:value 对、JSON 对象等 # 检测所有配置文件格式:key:value 对、JSON 对象等
# 特征:无问号/疑问词,有明显的配置结构 # 特征:无问号/疑问词,有明显的配置结构
# 直接丢弃,不进入后续KB搜索 # 直接丢弃,不进入后续KB搜索
# 1. 检测JSON格式:包含大括号且是键值对 # 1. 检测JSON格式:包含大括号且是键值对
_json_match = re.search(r'\{[^}]+\}', text) # ⭐ Task #1685: 跳过表格结构内容
if _json_match: if not _has_table_structure:
# 检查是否包含键值对(key: value 或 "key": "value") _json_match = re.search(r'\{[^}]+\}', text)
_json_content = _json_match.group(0) if _json_match:
_kv_pattern = re.search(r'["\']?\w+["\']?\s*[::]\s*["\']?[^"\']+["\']?', _json_content) # 检查是否包含键值对(key: value 或 "key": "value")
if _kv_pattern: _json_content = _json_match.group(0)
# ⭐ Task #1595: 检查用户消息特征 _kv_pattern = re.search(r'["\']?\w+["\']?\s*[::]\s*["\']?[^"\']+["\']?', _json_content)
_has_user_msg_feature = '@' in text and bool(re.search(r'@\w+', text)) if _kv_pattern:
_has_user_msg_feature |= any(q in text for q in ['?', '?', '吗', '呢', '多少', '怎么', '如何', '哪', '什么', '几']) # ⭐ Task #1595: 检查用户消息特征
if _has_user_msg_feature: _has_user_msg_feature = '@' in text and bool(re.search(r'@\w+', text))
logger.debug(f"[ocr] 过滤器2结果: 检测到JSON但含用户消息特征,继续检查: {text[:80]!r}") _has_user_msg_feature |= any(q in text for q in ['?', '?', '吗', '呢', '多少', '怎么', '如何', '哪', '什么', '几'])
else: if _has_user_msg_feature:
logger.debug(f"[ocr] 过滤器2结果: 检测到JSON配置文件格式,返回True: {text!r}") logger.debug(f"[ocr] 过滤器2结果: 检测到JSON但含用户消息特征,继续检查: {text[:80]!r}")
return True else:
logger.debug(f"[ocr] 过滤器2结果: 检测到JSON配置文件格式,返回True: {text!r}")
return True
else:
logger.debug(f"[ocr] 过滤器2结果: 检测到表格结构,跳过JSON配置文件检测: {text[:80]!r}")
# 2. 检测key:value对格式:至少两个冒号分隔的键值对 # 2. 检测key:value对格式:至少两个冒号分隔的键值对
_colon_pattern = r'[\u4e00-\u9fff]{2,10}\s*[::]\s*\S+' # ⭐ Task #1685: 跳过表格结构内容,避免误判表格中的"键:值"格式
_colon_matches = re.findall(_colon_pattern, text) if not _has_table_structure:
if len(_colon_matches) >= 2: _colon_pattern = r'[\u4e00-\u9fff]{2,10}\s*[::]\s*\S+'
# ⭐ Task #1595 修复:配置面板+用户消息混排场景 _colon_matches = re.findall(_colon_pattern, text)
# OCR可能把"配置面板 + 用户消息 + UI时间戳"拼成一行 if len(_colon_matches) >= 2:
# 例:"图标:potato标题:自杀...近期公告游戏专用<>有人@利17:36:25" # ⭐ Task #1595 修复:配置面板+用户消息混排场景
# 如果直接丢弃整段,会误杀其中的用户消息 # OCR可能把"配置面板 + 用户消息 + UI时间戳"拼成一行
# 检查是否有用户消息特征(@提及、疑问词、明确提问) # 例:"图标:potato标题:自杀...近期公告游戏专用<>有人@利17:36:25"
_has_user_msg_feature = False # 如果直接丢弃整段,会误杀其中的用户消息
# 特征1: @提及(用户@他人或@机器人) # 检查是否有用户消息特征(@提及、疑问词、明确提问)
if '@' in text and re.search(r'@\w+', text): _has_user_msg_feature = False
_has_user_msg_feature = True # 特征1: @提及(用户@他人或@机器人)
# 特征2: 疑问词(吗呢多少怎么如何哪什么几) if '@' in text and re.search(r'@\w+', text):
if any(q in text for q in ['?', '?', '吗', '呢', '多少', '怎么', '如何', '哪', '什么', '几']): _has_user_msg_feature = True
_has_user_msg_feature = True # 特征2: 疑问词(吗呢多少怎么如何哪什么几)
# 特征3: 明确的查询意图词(查/问/看/有/在哪/如何/怎么) if any(q in text for q in ['?', '?', '吗', '呢', '多少', '怎么', '如何', '哪', '什么', '几']):
if re.search(r'[查问看].*(?:插件|功能|命令|模式|系统|公告|信息)', text): _has_user_msg_feature = True
_has_user_msg_feature = True # 特征3: 明确的查询意图词(查/问/看/有/在哪/如何/怎么)
if _has_user_msg_feature: if re.search(r'[查问看].*(?:插件|功能|命令|模式|系统|公告|信息)', text):
# 包含用户消息特征,不丢弃,继续后续检查 _has_user_msg_feature = True
logger.debug(f"[ocr] 过滤器2结果: 检测到多条key:value但含用户消息特征(@提及/疑问词),继续检查: {text[:80]!r}") if _has_user_msg_feature:
else: # 包含用户消息特征,不丢弃,继续后续检查
# 纯配置文本,丢弃 logger.debug(f"[ocr] 过滤器2结果: 检测到多条key:value但含用户消息特征(@提及/疑问词),继续检查: {text[:80]!r}")
logger.debug(f"[ocr] 过滤器2结果: 检测到多条配置文件格式({len(_colon_matches)}条key:value),返回True: {text!r}") else:
return True # 纯配置文本,丢弃
logger.debug(f"[ocr] 过滤器2结果: 检测到多条配置文件格式({len(_colon_matches)}条key:value),返回True: {text!r}")
return True
else:
logger.debug(f"[ocr] 过滤器2结果: 检测到表格结构,跳过key:value配置文件检测: {text[:80]!r}")
elif len(_colon_matches) == 1: elif len(_colon_matches) == 1:
# 单条key:value,检查是否为完整的一条(无多余字符) # 单条key:value,检查是否为完整的一条(无多余字符)
_colon_match = re.match(r'^[\u4e00-\u9fff]{2,10}\s*[::]\s*\S+$', text) _colon_match = re.match(r'^[\u4e00-\u9fff]{2,10}\s*[::]\s*\S+$', text)
@@ -8625,29 +8659,37 @@ class MessageMonitor:
logger.debug(f"[ocr] 过滤器2结果: 检测到单条配置文件格式(key: value),返回True: {text!r}") logger.debug(f"[ocr] 过滤器2结果: 检测到单条配置文件格式(key: value),返回True: {text!r}")
return True return True
# 3. 检测其他配置文件格式:等号分隔的键值对(如key=value) # 3. 检测其他配置文件格式:等号分隔的键值对(如key=value)
_eq_pattern = r'[\u4e00-\u9fff]{2,10}\s*=\s*\S+' # ⭐ Task #1685: 跳过表格结构内容
_eq_matches = re.findall(_eq_pattern, text) if not _has_table_structure:
if len(_eq_matches) >= 2: _eq_pattern = r'[\u4e00-\u9fff]{2,10}\s*=\s*\S+'
# ⭐ Task #1595: 检查用户消息特征 _eq_matches = re.findall(_eq_pattern, text)
_has_user_msg_feature = '@' in text and bool(re.search(r'@\w+', text)) if len(_eq_matches) >= 2:
_has_user_msg_feature |= any(q in text for q in ['?', '?', '吗', '呢', '多少', '怎么', '如何', '哪', '什么', '几']) # ⭐ Task #1595: 检查用户消息特征
if _has_user_msg_feature: _has_user_msg_feature = '@' in text and bool(re.search(r'@\w+', text))
logger.debug(f"[ocr] 过滤器2结果: 检测到等号配置但含用户消息特征,继续检查: {text[:80]!r}") _has_user_msg_feature |= any(q in text for q in ['?', '?', '吗', '呢', '多少', '怎么', '如何', '哪', '什么', '几'])
else: if _has_user_msg_feature:
logger.debug(f"[ocr] 过滤器2结果: 检测到等号分隔的配置文件格式({len(_eq_matches)}条key=value),返回True: {text!r}") logger.debug(f"[ocr] 过滤器2结果: 检测到等号配置但含用户消息特征,继续检查: {text[:80]!r}")
return True else:
logger.debug(f"[ocr] 过滤器2结果: 检测到等号分隔的配置文件格式({len(_eq_matches)}条key=value),返回True: {text!r}")
return True
else:
logger.debug(f"[ocr] 过滤器2结果: 检测到表格结构,跳过等号配置文件检测: {text[:80]!r}")
# 4. 检测YAML/INI格式:行首为key,后跟冒号或等号 # 4. 检测YAML/INI格式:行首为key,后跟冒号或等号
_yaml_ini_pattern = r'^[\u4e00-\u9fff]{2,10}\s*[:=]\s*\S+' # ⭐ Task #1685: 跳过表格结构内容
_yaml_ini_matches = re.findall(_yaml_ini_pattern, text, re.MULTILINE) if not _has_table_structure:
if len(_yaml_ini_matches) >= 2: _yaml_ini_pattern = r'^[\u4e00-\u9fff]{2,10}\s*[:=]\s*\S+'
# ⭐ Task #1595: 检查用户消息特征 _yaml_ini_matches = re.findall(_yaml_ini_pattern, text, re.MULTILINE)
_has_user_msg_feature = '@' in text and bool(re.search(r'@\w+', text)) if len(_yaml_ini_matches) >= 2:
_has_user_msg_feature |= any(q in text for q in ['?', '?', '吗', '呢', '多少', '怎么', '如何', '哪', '什么', '几']) # ⭐ Task #1595: 检查用户消息特征
if _has_user_msg_feature: _has_user_msg_feature = '@' in text and bool(re.search(r'@\w+', text))
logger.debug(f"[ocr] 过滤器2结果: 检测到YAML/INI但含用户消息特征,继续检查: {text[:80]!r}") _has_user_msg_feature |= any(q in text for q in ['?', '?', '吗', '呢', '多少', '怎么', '如何', '哪', '什么', '几'])
else: if _has_user_msg_feature:
logger.debug(f"[ocr] 过滤器2结果: 检测到YAML/INI配置文件格式({len(_yaml_ini_matches)}行),返回True: {text!r}") logger.debug(f"[ocr] 过滤器2结果: 检测到YAML/INI但含用户消息特征,继续检查: {text[:80]!r}")
return True else:
logger.debug(f"[ocr] 过滤器2结果: 检测到YAML/INI配置文件格式({len(_yaml_ini_matches)}行),返回True: {text!r}")
return True
else:
logger.debug(f"[ocr] 过滤器2结果: 检测到表格结构,跳过YAML/INI配置文件检测: {text[:80]!r}")
# ⭐ 闲聊消息检测(2026-08-22 新增) # ⭐ 闲聊消息检测(2026-08-22 新增)
# 特征:短文本 + 疑问词 + 无具体查询主题 # 特征:短文本 + 疑问词 + 无具体查询主题