修复预分割配置碎片漏网导致用户消息被丢弃

新增_is_config_fragment检测bot配置面板碎片(≥2配置键或空值截断标签),
在_split_and_denoise_segments逐段循环接入拦截,并对合并结果做二次净化逐行
恢复用户消息,解决单key碎片合并后凑够阈值被整体判噪声丢弃的问题(如"近期公告")。
This commit is contained in:
youpaishidifu
2026-08-24 22:16:36 +08:00
parent 1b36d92909
commit 08ddc5a89a
+83 -1
View File
@@ -7420,6 +7420,54 @@ class MessageMonitor:
# 默认:短、无问号、无@、非群名片 → 视为 md 内容延续 # 默认:短、无问号、无@、非群名片 → 视为 md 内容延续
return True return True
def _is_config_fragment(self, text: str) -> bool:
"""检测段是否为 bot 配置面板碎片(即使 key:value 不完整也判为碎片)。
⭐ Task #1598 修复:预分割逐段调用 _is_noise_text 时,bot 配置单行碎片
因 key:value 不足 2 条(如"权限类型:"无值、或整块仅 1 条完整 key:value)
而漏网被保留;合并回整段后总 kv 数 ≥ 阈值触发整体丢弃,连带牺牲其中
的用户消息(如"近期公告")。
本方法在预分割阶段提前识别并丢弃这些碎片:
- 段内出现 ≥2 个 bot 配置键词 + 冒号(值可空,OCR 常把值拆到下一行)
- 或短段(≤30字)以单个 bot 配置键词 + 空冒号结尾(被截断的标签)
排除含疑问词或 @提及 的真实用户问句。
"""
if not text:
return False
_s = text.strip()
if not _s:
return False
# ── 排除真实用户问句:含疑问词或 @提及 → 不是配置碎片 ──
_has_question = any(q in text for q in ('?', '?', '吗', '呢', '多少', '怎么', '如何', '哪', '什么', '几'))
_has_at = '@' in text and re.search(r'@\w+', text)
if _has_question or _has_at:
return False
# ── bot 配置键词表(bot 渲染插件配置/帮助面板使用的标签)──
_config_keys = (
'图标', '标题', '指令', '命令', '权限类型', '权限', '格子', '玩家', '商人',
'类型', '价格', '数量', '描述', '别名', '冷却', '消耗', '需求', '条件',
'等级', '奖励', '惩罚', '效果', '属性', '状态',
)
_key_alts = '|'.join(re.escape(k) for k in _config_keys)
# 匹配 "key:"(值可空)——OCR 常把值拆到下一行或整块截断导致无值
_key_colon_re = re.compile(r'(?:' + _key_alts + r')\s*[::]\s*\S*')
_matches = _key_colon_re.findall(_s)
if len(_matches) >= 2:
logger.debug(f"[ocr] 预分割: 检测到bot配置碎片({len(_matches)}个配置键),丢弃: {_s[:60]!r}")
return True
# 短段(≤30字)以单个 bot 配置键 + 空冒号结尾 → 被截断的标签,判为碎片
if len(_s) <= 30:
_empty_val_re = re.compile(r'(?:' + _key_alts + r')\s*[::]\s*$')
if _empty_val_re.search(_s):
logger.debug(f"[ocr] 预分割: 检测到bot配置标签(空值,疑似截断),丢弃: {_s[:60]!r}")
return True
return False
def _split_and_denoise_segments(self, raw_text: str, platform: str) -> str: def _split_and_denoise_segments(self, raw_text: str, platform: str) -> str:
"""混合文本预分割 + 分段噪声隔离。 """混合文本预分割 + 分段噪声隔离。
@@ -7491,9 +7539,19 @@ class MessageMonitor:
if stripped_seg != stripped_seg_before_star: if stripped_seg != stripped_seg_before_star:
logger.debug(f"[{platform}] 预分割: 清理markdown加粗残留*: {stripped_seg_before_star[:60]!r} -> {stripped_seg[:60]!r}") 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 is_noise = self._is_noise_text(stripped_seg) if stripped_seg else True
_is_cfg_fragment = False
if not is_noise and stripped_seg:
# ⭐ Task #1596: 二次过滤——bot 配置面板碎片(kv<3 漏网)也要丢弃,
# 否则合并回整段后总 kv 数 ≥ 阈值触发整体丢弃,连带牺牲用户消息。
_is_cfg_fragment = self._is_config_fragment(stripped_seg)
if _is_cfg_fragment:
is_noise = True
if is_noise: if is_noise:
noise_count += 1 noise_count += 1
logger.debug(f"[{platform}] 预分割: 段[{i}] 含噪声特征,丢弃: {stripped_seg[:60]!r}" if stripped_seg else f"[{platform}] 预分割: 段[{i}] 剥离bot回复后为空,丢弃: {seg[:60]!r}") if _is_cfg_fragment:
logger.debug(f"[{platform}] 预分割: 段[{i}] 含bot配置碎片,丢弃: {stripped_seg[:60]!r}")
else:
logger.debug(f"[{platform}] 预分割: 段[{i}] 含噪声特征,丢弃: {stripped_seg[:60]!r}" if stripped_seg else f"[{platform}] 预分割: 段[{i}] 剥离bot回复后为空,丢弃: {seg[:60]!r}")
else: else:
clean_segments.append(stripped_seg) clean_segments.append(stripped_seg)
logger.debug(f"[{platform}] 预分割: 段[{i}] 干净,保留: {stripped_seg[:60]!r}") logger.debug(f"[{platform}] 预分割: 段[{i}] 干净,保留: {stripped_seg[:60]!r}")
@@ -7511,6 +7569,30 @@ class MessageMonitor:
# 有部分噪声被剥离,有部分干净段保留 # 有部分噪声被剥离,有部分干净段保留
result = '\n'.join(clean_segments) result = '\n'.join(clean_segments)
logger.info(f"[{platform}] 预分割: {len(segments)}段中剥离{noise_count}段噪声,保留{len(clean_segments)}段干净文本") logger.info(f"[{platform}] 预分割: {len(segments)}段中剥离{noise_count}段噪声,保留{len(clean_segments)}段干净文本")
# ⭐ Task #1597: 合并后二次净化双保险
# 若合并文本仍被 _is_noise_text 判为配置噪声(说明仍有 kv≥2 的配置行混入,
# 通常是多个单 key 碎片各自漏网、合并后才凑够阈值),逐行剥离 bot 配置
# 碎片行与噪声行,只保留用户消息行,避免连带丢弃用户真实消息。
if self._is_noise_text(result):
_merged_lines = result.split('\n')
_recovered = []
for _ln in _merged_lines:
_ln_s = _ln.strip()
if not _ln_s:
continue
if self._is_config_fragment(_ln_s):
logger.debug(f"[{platform}] 预分割二次净化: 剥离配置行: {_ln_s[:40]!r}")
continue
if self._is_noise_text(_ln_s):
logger.debug(f"[{platform}] 预分割二次净化: 剥离噪声行: {_ln_s[:40]!r}")
continue
_recovered.append(_ln_s)
if _recovered:
_recovered_text = '\n'.join(_recovered)
logger.info(f"[{platform}] 预分割二次净化: 从{len(_merged_lines)}行中恢复{len(_recovered)}行用户消息: {_recovered_text[:80]!r}")
return _recovered_text
return result return result
@staticmethod @staticmethod