fix(bm25): 修复 BM25 索引覆盖 bug + 综合评测集 v2 + 检索增强

核心修复:
- knowledge/base.py: BM25Index.add_documents 从覆盖改为追加+去重,
  修复只有最后上传文件的 chunks 保留在 BM25 中的严重 bug
  (影响: 2.docx/3.docx/PDF 的 755 个 chunk 在 BM25 中完全缺失)

检索增强 (延续上次会话):
- core/engine.py: section cluster boost + lexical match exemption
- api/chat_routes.py: lexical/cluster rescue 层 + SSE 事件
- core/mmr.py: MMR 去重改进

评测体系:
- tests/eval_dataset_v2.json: 62 题综合评测集 (9 种题型×4 文档)
- scripts/eval_e2e.py: 推理模型 LLM 评分兼容 + 新数据集格式支持
- scripts/validate_eval_dataset.py: 数据集验证工具

其他:
- parsers/mineru_parser.py: 解析器改进
This commit is contained in:
lacerate551
2026-06-17 17:31:43 +08:00
parent edaef7ad60
commit 8c92657490
8 changed files with 1322 additions and 72 deletions

View File

@@ -137,8 +137,8 @@ def parse_with_mineru_online(
file_path: str,
api_token: str = None,
api_url: str = None,
model_version: str = "vlm",
timeout: int = 300
model_version: str = None,
timeout: int = None
) -> Dict[str, Any]:
"""
使用 MinerU 在线 API 解析文档
@@ -157,8 +157,8 @@ def parse_with_mineru_online(
file_path: 文档文件路径
api_token: API Token默认从 config 读取)
api_url: API 地址
model_version: 模型版本 (vlm / pipeline / MinerU-HTML)
timeout: 请求超时(秒)
model_version: 模型版本 (vlm / pipeline / MinerU-HTML),默认从 config 读取
timeout: 轮询超时(秒),默认从 config 读取
Returns:
解析结果(与 parse_with_mineru 格式相同)
@@ -167,10 +167,12 @@ def parse_with_mineru_online(
import time
import zipfile
import io
from config import MINERU_API_TOKEN, MINERU_API_URL
from config import MINERU_API_TOKEN, MINERU_API_URL, MINERU_MODEL_VERSION, MINERU_ONLINE_TIMEOUT
token = api_token or MINERU_API_TOKEN
url = api_url or MINERU_API_URL
model_version = model_version or MINERU_MODEL_VERSION
timeout = timeout or MINERU_ONLINE_TIMEOUT
if not token:
raise RuntimeError("MinerU 在线 API Token 未配置,请在 config.py 中设置 MINERU_API_TOKEN")
@@ -241,9 +243,15 @@ def parse_with_mineru_online(
result_resp.raise_for_status()
result = result_resp.json()
# 检查 API 层面的错误码,快速失败而非静默等到超时
api_code = result.get("code")
if api_code and api_code != 0:
api_msg = result.get("msg", "未知错误")
raise RuntimeError(f"MinerU API 错误 (code={api_code}): {api_msg}")
extract_results = result.get("data", {}).get("extract_result", [])
if not extract_results:
logger.debug(f"等待解析结果... ({waited}s)")
logger.debug(f"等待解析结果... ({waited}s/{max_wait}s)")
continue
# 取第一个文件的结果
@@ -276,11 +284,16 @@ def parse_with_mineru_online(
if progress:
extracted = progress.get("extracted_pages", 0)
total = progress.get("total_pages", 0)
logger.info(f"解析进度: {extracted}/{total} 页 ({waited}s)")
logger.info(f"解析进度: {extracted}/{total} 页 ({waited}s/{max_wait}s, model={model_version})")
else:
logger.debug(f"状态: {state}, 等待中... ({waited}s)")
logger.debug(f"状态: {state}, 等待中... ({waited}s/{max_wait}s)")
raise RuntimeError("MinerU 在线解析超时")
raise RuntimeError(
f"MinerU 在线解析超时 ({max_wait}s)"
f",当前 model_version={model_version}"
f",可尝试: 1) 设置 MINERU_MODEL_VERSION=pipeline 加速"
f" 2) 增大 MINERU_ONLINE_TIMEOUT"
)
except Exception as e:
raise RuntimeError(f"MinerU 在线 API 调用失败: {e}")
@@ -737,7 +750,7 @@ def parse_with_mineru(
lang: str = "ch",
enable_table: bool = True,
enable_formula: bool = True,
backend: str = "pipeline",
backend: str = None,
start_page: int = 0,
end_page: int = 99999
) -> Dict[str, Any]:
@@ -770,6 +783,14 @@ def parse_with_mineru(
if not file_path.exists():
raise FileNotFoundError(f"文件不存在: {file_path}")
# 从 config 读取默认 backend
if backend is None:
try:
from config import MINERU_LOCAL_BACKEND
backend = MINERU_LOCAL_BACKEND
except ImportError:
backend = 'pipeline'
# 检查文件大小
file_size = file_path.stat().st_size
if file_size > MAX_PDF_SIZE:
@@ -814,7 +835,6 @@ def parse_with_mineru(
cmd = [
str(mineru_exe),
"--",
"-p", str(file_path),
"-o", str(output_dir),
"-m", "auto",
@@ -859,7 +879,7 @@ def parse_with_mineru(
logger.error(f"MinerU 解析失败: {e}")
raise
finally:
清理临时目录
# 清理临时目录
if cleanup_output and os.path.exists(output_dir):
shutil.rmtree(output_dir, ignore_errors=True)
@@ -1688,7 +1708,7 @@ def parse_with_mineru_persistent(
lang: str = "ch",
enable_table: bool = True,
enable_formula: bool = True,
backend: str = "pipeline",
backend: str = None,
start_page: int = 0,
end_page: int = 99999,
cleanup_after_image_move: bool = True
@@ -1727,6 +1747,14 @@ def parse_with_mineru_persistent(
if not file_path.exists():
raise FileNotFoundError(f"文件不存在: {file_path}")
# 从 config 读取默认 backend
if backend is None:
try:
from config import MINERU_LOCAL_BACKEND
backend = MINERU_LOCAL_BACKEND
except ImportError:
backend = 'pipeline'
# 检查文件大小
file_size = file_path.stat().st_size
if file_size > MAX_PDF_SIZE: