From 08ddc5a89ab455ad6e86c3120a3c45f6243f00fe Mon Sep 17 00:00:00 2001 From: youpaishidifu Date: Mon, 24 Aug 2026 22:16:36 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=A2=84=E5=88=86=E5=89=B2?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=A2=8E=E7=89=87=E6=BC=8F=E7=BD=91=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E7=94=A8=E6=88=B7=E6=B6=88=E6=81=AF=E8=A2=AB=E4=B8=A2?= =?UTF-8?q?=E5=BC=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增_is_config_fragment检测bot配置面板碎片(≥2配置键或空值截断标签), 在_split_and_denoise_segments逐段循环接入拦截,并对合并结果做二次净化逐行 恢复用户消息,解决单key碎片合并后凑够阈值被整体判噪声丢弃的问题(如"近期公告")。 --- monitor.py | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/monitor.py b/monitor.py index 1558bb3..0035f90 100644 --- a/monitor.py +++ b/monitor.py @@ -7420,6 +7420,54 @@ class MessageMonitor: # 默认:短、无问号、无@、非群名片 → 视为 md 内容延续 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: """混合文本预分割 + 分段噪声隔离。 @@ -7491,9 +7539,19 @@ class MessageMonitor: 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 + _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: 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: clean_segments.append(stripped_seg) logger.debug(f"[{platform}] 预分割: 段[{i}] 干净,保留: {stripped_seg[:60]!r}") @@ -7511,6 +7569,30 @@ class MessageMonitor: # 有部分噪声被剥离,有部分干净段保留 result = '\n'.join(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 @staticmethod