修复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
+42
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,11 +8566,27 @@ 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格式:包含大括号且是键值对
# ⭐ Task #1685: 跳过表格结构内容
if not _has_table_structure:
_json_match = re.search(r'\{[^}]+\}', text) _json_match = re.search(r'\{[^}]+\}', text)
if _json_match: if _json_match:
# 检查是否包含键值对(key: value 或 "key": "value") # 检查是否包含键值对(key: value 或 "key": "value")
@@ -8573,7 +8601,11 @@ class MessageMonitor:
else: else:
logger.debug(f"[ocr] 过滤器2结果: 检测到JSON配置文件格式,返回True: {text!r}") logger.debug(f"[ocr] 过滤器2结果: 检测到JSON配置文件格式,返回True: {text!r}")
return True return True
else:
logger.debug(f"[ocr] 过滤器2结果: 检测到表格结构,跳过JSON配置文件检测: {text[:80]!r}")
# 2. 检测key:value对格式:至少两个冒号分隔的键值对 # 2. 检测key:value对格式:至少两个冒号分隔的键值对
# ⭐ Task #1685: 跳过表格结构内容,避免误判表格中的"键:值"格式
if not _has_table_structure:
_colon_pattern = r'[\u4e00-\u9fff]{2,10}\s*[::]\s*\S+' _colon_pattern = r'[\u4e00-\u9fff]{2,10}\s*[::]\s*\S+'
_colon_matches = re.findall(_colon_pattern, text) _colon_matches = re.findall(_colon_pattern, text)
if len(_colon_matches) >= 2: if len(_colon_matches) >= 2:
@@ -8599,6 +8631,8 @@ class MessageMonitor:
# 纯配置文本,丢弃 # 纯配置文本,丢弃
logger.debug(f"[ocr] 过滤器2结果: 检测到多条配置文件格式({len(_colon_matches)}条key:value),返回True: {text!r}") logger.debug(f"[ocr] 过滤器2结果: 检测到多条配置文件格式({len(_colon_matches)}条key:value),返回True: {text!r}")
return True 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,6 +8659,8 @@ 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)
# ⭐ Task #1685: 跳过表格结构内容
if not _has_table_structure:
_eq_pattern = r'[\u4e00-\u9fff]{2,10}\s*=\s*\S+' _eq_pattern = r'[\u4e00-\u9fff]{2,10}\s*=\s*\S+'
_eq_matches = re.findall(_eq_pattern, text) _eq_matches = re.findall(_eq_pattern, text)
if len(_eq_matches) >= 2: if len(_eq_matches) >= 2:
@@ -8636,7 +8672,11 @@ class MessageMonitor:
else: else:
logger.debug(f"[ocr] 过滤器2结果: 检测到等号分隔的配置文件格式({len(_eq_matches)}条key=value),返回True: {text!r}") logger.debug(f"[ocr] 过滤器2结果: 检测到等号分隔的配置文件格式({len(_eq_matches)}条key=value),返回True: {text!r}")
return True return True
else:
logger.debug(f"[ocr] 过滤器2结果: 检测到表格结构,跳过等号配置文件检测: {text[:80]!r}")
# 4. 检测YAML/INI格式:行首为key,后跟冒号或等号 # 4. 检测YAML/INI格式:行首为key,后跟冒号或等号
# ⭐ Task #1685: 跳过表格结构内容
if not _has_table_structure:
_yaml_ini_pattern = r'^[\u4e00-\u9fff]{2,10}\s*[:=]\s*\S+' _yaml_ini_pattern = r'^[\u4e00-\u9fff]{2,10}\s*[:=]\s*\S+'
_yaml_ini_matches = re.findall(_yaml_ini_pattern, text, re.MULTILINE) _yaml_ini_matches = re.findall(_yaml_ini_pattern, text, re.MULTILINE)
if len(_yaml_ini_matches) >= 2: if len(_yaml_ini_matches) >= 2:
@@ -8648,6 +8688,8 @@ class MessageMonitor:
else: else:
logger.debug(f"[ocr] 过滤器2结果: 检测到YAML/INI配置文件格式({len(_yaml_ini_matches)}行),返回True: {text!r}") logger.debug(f"[ocr] 过滤器2结果: 检测到YAML/INI配置文件格式({len(_yaml_ini_matches)}行),返回True: {text!r}")
return True return True
else:
logger.debug(f"[ocr] 过滤器2结果: 检测到表格结构,跳过YAML/INI配置文件检测: {text[:80]!r}")
# ⭐ 闲聊消息检测(2026-08-22 新增) # ⭐ 闲聊消息检测(2026-08-22 新增)
# 特征:短文本 + 疑问词 + 无具体查询主题 # 特征:短文本 + 疑问词 + 无具体查询主题