feat(server-release): 从 main 同步切片层级结构修复与解析增强

- parsers/mineru_parser.py: MinerU V2 解析增强 + 切片层级结构修复
- parsers/heading_rules.py: 标题识别规则引擎增强
- knowledge/manager.py: 跨页表格合并与切片后处理优化

切片数从旧版 819 降至 437(与本地 VLM 解析的 448 接近)
This commit is contained in:
lacerate551
2026-06-20 22:00:57 +08:00
parent 8d60616124
commit 2c84cb8a5f
3 changed files with 437 additions and 63 deletions

View File

@@ -106,11 +106,12 @@ DEFAULT_HEADING_RULES: List[HeadingRule] = [
name="chinese_chapter",
),
# 2. 中文条款编号 -> h2
# 匹配:第一条、第三款 等
# 匹配:第一条、第三款 等(仅短标题,长正文段落不算标题)
HeadingRule(
pattern=re.compile(r'^第[一二三四五六七八九十百千万]+[条款]'),
level=2,
name="chinese_article",
max_length=30,
),
# 3. 数字三级标题 -> h3必须在二级之前匹配
# 匹配1.1.1 背景、2.3.4 方案 等
@@ -121,20 +122,23 @@ DEFAULT_HEADING_RULES: List[HeadingRule] = [
max_length=100,
),
# 4. 数字二级标题 -> h2必须在一级之前匹配
# 匹配1.1 背景、2.3 方案 等
# 匹配1.1 背景、2.3 方案、2.1运行调度(无空格)
# 使用负向前瞻排除三级标题(由 numeric_level3 处理)
HeadingRule(
pattern=re.compile(r'^\d+\.\d+[\.\s]'),
pattern=re.compile(r'^\d+\.\d+(?!\.\d)'),
level=2,
name="numeric_level2",
max_length=80,
),
# 5. 数字一级标题 -> h1
# 匹配1. 概述、2、背景 等
# 排除:以 ;。,、: 结尾的文本(这些是编号列表项/子条目,不是独立标题)
HeadingRule(
pattern=re.compile(r'^\d+[\.、\s]'),
level=1,
name="numeric_level1",
max_length=50,
exclude_pattern=re.compile(r'[;。,、::]$'),
),
# 6. 英文章节标题 -> h1
# 匹配Chapter 1、Section 2、Part 3 等
@@ -201,6 +205,22 @@ class HeadingRuleEngine:
import copy
self.rules = copy.deepcopy(DEFAULT_HEADING_RULES)
def _validate_level(self, level: int, text: str, rule_name=None):
"""各级别标题长度防护:超长文本不应作为标题,降为正文。
H1 > 40字, H2 > 60字, H3 > 50字 → 降为正文。
统一覆盖 v1 常规匹配、v2 style 匹配、bold_short_text 兜底所有返回路径。"""
text_len = len(text)
if level == 1 and text_len > 40:
logger.debug(f"标题识别: '{text[:30]}...' H1 但超长({text_len}字),降为正文")
return 0, None
if level == 2 and text_len > 60:
logger.debug(f"标题识别: '{text[:30]}...' H2 但超长({text_len}字),降为正文")
return 0, None
if level == 3 and text_len > 50:
logger.debug(f"标题识别: '{text[:30]}...' H3 但超长({text_len}字),降为正文")
return 0, None
return level, rule_name
def detect(self, text: str, style: Optional[List[str]] = None) -> Tuple[int, Optional[str]]:
"""
检测文本的标题级别
@@ -227,7 +247,7 @@ class HeadingRuleEngine:
level = rule.match(text)
if level > 0:
logger.debug(f"标题识别(v2 style): '{text[:30]}' -> h{level} (规则: {rule.name})")
return level, rule.name
return self._validate_level(level, text, rule.name)
# 再检查是否匹配中文章节/条款等高优先级规则
for rule in self.rules:
@@ -236,9 +256,16 @@ class HeadingRuleEngine:
level = rule.match(text)
if level > 0:
logger.debug(f"标题识别(v2 style): '{text[:30]}' -> h{level} (规则: {rule.name})")
return level, rule.name
return self._validate_level(level, text, rule.name)
# 最后兜底:加粗短文本 → h2
# 但需先检查所有规则的 exclude_pattern防止编号列表项被误判为标题
# 例如 "3.完全满足品规。指..." 虽有 bold 样式,但属于列表项而非标题
for rule in self.rules:
if rule.enabled and rule.exclude_pattern and rule.exclude_pattern.search(text):
logger.debug(f"标题识别(v2 style): '{text[:30]}'{rule.name} 的 exclude_pattern 排除")
return 0, None
# 否则作为加粗短文本 → h2与 bold_short_text 规则对齐,但不依赖 **...** 标记)
logger.debug(f"标题识别(v2 style): '{text[:30]}' -> h2 (规则: bold_short_text_via_style)")
return 2, 'bold_short_text'
@@ -247,7 +274,7 @@ class HeadingRuleEngine:
level = rule.match(text)
if level > 0:
logger.debug(f"标题识别: '{text[:30]}' -> h{level} (规则: {rule.name})")
return level, rule.name
return self._validate_level(level, text, rule.name)
return 0, None