Files
AIclinicalresearch/docs/02-通用能力层/02-文档处理引擎/03-PDF表格提取引擎设计方案.md
HaHafeng dc6b292308 docs(asl): Complete Tool 3 extraction workbench V2.0 development plan (v1.5)
ASL Tool 3 Development Plan:
- Architecture blueprint v1.5 (6 rounds of architecture review, 13 red lines)
- M1/M2/M3 sprint checklists (Skeleton Pipeline / HITL Workbench / Dynamic Template Engine)
- Code patterns cookbook (9 chapters: Fan-out, Prompt engineering, ACL, SSE dual-track, etc.)
- Key patterns: Fan-out with Last Child Wins, Optimistic Locking, teamConcurrency throttling
- PKB ACL integration (anti-corruption layer), MinerU Cache-Aside, NOTIFY/LISTEN cross-pod SSE
- Data consistency snapshot for long-running extraction tasks

Platform capability:
- Add distributed Fan-out task pattern development guide (7 patterns + 10 anti-patterns)
- Add system-level async architecture risk analysis blueprint
- Add PDF table extraction engine design and usage guide (MinerU integration)
- Add table extraction source code (TableExtractionManager + MinerU engine)

Documentation updates:
- Update ASL module status with Tool 3 V2.0 plan readiness
- Update system status document (v6.2) with latest milestones
- Add V2.0 product requirements, prototypes, and data dictionary specs
- Add architecture review documents (4 rounds of review feedback)
- Add test PDF files for extraction validation

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 22:49:16 +08:00

585 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PDF 表格提取引擎设计方案
> **文档版本**: v1.0
> **创建日期**: 2026-02-23
> **最后更新**: 2026-02-23
> **文档目的**: 定义 PDF 表格提取引擎的统一架构,为系统综述/Meta 分析等场景提供精确的结构化表格数据
> **核心原则**: 引擎对使用者透明 — 提交 PDF返回结构化表格无需关心底层实现
> **当前状态**: MinerU Cloud API (VLM) 已接入并完成测试,其他引擎待逐步评测
---
## 1. 业务背景
### 1.1 核心需求
ASL 智能文献模块的**全文复筛**环节,需要从医学 PDF 文献中精确提取数据表格:
- **系统综述 (Systematic Review)**: 基线特征表、结局指标表、不良事件表
- **Meta 分析**: 效应值、置信区间、样本量等关键数值
- **数据核验**: 数值必须与原文 100% 一致,不容许任何精度损失
### 1.2 为什么独立建设
当前文档处理引擎基于 `pymupdf4llm`,定位是 **PDF → Markdown 全文文本转换**,在表格提取场景中存在严重缺陷:
| 问题 | 实测数据 |
|------|----------|
| 8 篇 PDF 仅 1 篇输出结构化表格 | 表格检出率 12.5% |
| 其余 7 篇表格退化为纯文本 | 行列结构完全丢失 |
| 不支持合并单元格 | 医学表格大量使用 rowspan/colspan |
**结论:全文文本提取和结构化表格提取是两个不同的能力,需要分别建设。**
---
## 2. 引擎架构设计
### 2.1 核心理念
> **使用者不需要关心底层用了什么技术,只需要:提交 PDF → 获取结构化表格。**
底层引擎可以是 MinerU、Qwen-VL、PaddleOCR、Docling 或任意其他方案,通过统一接口抽象,实现热切换和渐进升级。
### 2.2 统一架构
```
┌─────────────────────────────────────────────────────────────┐
│ 业务层 (使用者) │
│ ASL 全文复筛 / 系统综述数据提取 / Meta 分析 │
│ │
│ const tables = await tableEngine.extract(pdfBuffer); │
│ // 只关心输入 PDF 和输出 tables不关心底层引擎 │
└───────────────────────────┬─────────────────────────────────┘
┌───────────────────────────▼─────────────────────────────────┐
│ PDF 表格提取引擎 (统一抽象层) │
│ │
│ interface TableExtractionEngine { │
│ extract(pdf: Buffer): Promise<ExtractedTable[]> │
│ extractFromUrl(url: string): Promise<ExtractedTable[]> │
│ } │
│ │
│ 统一输出ExtractedTable[] │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ { title, headers, rows, mergedCells, footnotes, │ │
│ │ pageNumber, confidence, rawHtml } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────┤
│ 引擎适配器 (可插拔) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ MinerU │ │ Qwen3-VL │ │ PaddleOCR-VL │ │
│ │ Cloud API │ │ 多模态 LLM │ │ 百度 OCR │ │
│ │ (VLM) │ │ │ │ │ │
│ │ ✅ 已接入 │ │ 📋 待评测 │ │ 📋 待评测 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Qwen-OCR + │ │ Docling │ │ DeepSeek │ │
│ │ Qwen-Long │ │ (IBM) │ │ LLM │ │
│ │ │ │ │ │ │ │
│ │ 📋 待评测 │ │ 📋 待评测 │ │ ✅ 已测试 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 2.3 统一输出格式
无论底层使用哪个引擎,输出都遵循统一的 `ExtractedTable` 结构:
```typescript
interface ExtractedTable {
/** 表格标题 (如 "Table 1 Baseline characteristics") */
title: string;
/** 表头行 */
headers: string[];
/** 数据行 (二维数组) */
rows: string[][];
/** 合并单元格信息 */
mergedCells?: MergedCell[];
/** 脚注 */
footnotes?: string[];
/** 所在 PDF 页码 */
pageNumber?: number;
/** 引擎自信度 (0-1) */
confidence?: number;
/** 原始 HTML (供前端渲染或调试) */
rawHtml?: string;
/** 原始 Markdown (备选格式) */
rawMarkdown?: string;
}
interface MergedCell {
row: number;
col: number;
rowSpan: number;
colSpan: number;
}
```
---
## 3. 候选引擎全景
### 3.1 引擎候选清单
| 引擎 | 类型 | 特点 | 成本 | 状态 |
|------|------|------|------|------|
| **MinerU Cloud API** | VLM 云端 | 表格结构最完整rowspan/colspan 支持 | 2000 页/天免费 | ✅ 已接入 |
| **Qwen3-VL** | 多模态 LLM | 多模态理解最强,复杂表格语义识别好 | 按 token 计费 | 📋 待评测 |
| **Qwen-OCR + Qwen-Long** | OCR + LLM 组合 | 成本最低、功能最全的组合方案 | 极低 | 📋 待评测 |
| **百度 PaddleOCR-VL 1.5** | VL OCR | 医学场景案例多,准确率高,免费额度最多 | 官方免费额度多 | 📋 待评测 |
| **Docling (IBM)** | 本地部署 | MIT 开源TableFormer 模型,可完全离线 | 免费 (本地部署) | 📋 待评测 |
| **DeepSeek LLM** | 文本 LLM | 从原始文本重构表格Markdown 输出 | ~0.14 元/万 token | ✅ 已测试 |
### 3.2 推荐分类
**最佳性价比组合:**
1. **Qwen-OCR + Qwen-Long** — 成本最低,功能最全
2. **百度 PaddleOCR-VL** — 官方免费额度最多,技术最成熟
**医学文献表格提取最佳选择:**
1. **Qwen3-VL** — 多模态理解最强,支持复杂表格
2. **百度 PaddleOCR-VL 1.5** — 医学场景案例多,准确率高
**数据合规 / 离线场景:**
1. **Docling (IBM)** — MIT 开源,完全本地部署
### 3.3 评测计划
按优先级逐步评测,使用同一组 8 篇医学 PDF 文献作为基准:
| 阶段 | 引擎 | 优先级 | 评测重点 |
|------|------|--------|----------|
| ✅ 已完成 | MinerU Cloud API | — | 作为 baseline |
| ✅ 已完成 | DeepSeek LLM | — | 文本 LLM 方案的上限 |
| P1 待测 | Qwen3-VL | 高 | 多模态 vs MinerU VLM 的表格精度 |
| P1 待测 | PaddleOCR-VL 1.5 | 高 | 免费额度 + 医学场景准确率 |
| P2 待测 | Qwen-OCR + Qwen-Long | 中 | 验证最低成本方案的可行性 |
| P2 待测 | Docling | 中 | 离线方案,评估部署成本 |
---
## 4. 已完成测试MinerU vs pymupdf4llm vs DeepSeek
### 4.1 测试概要
- **测试对象**: 8 篇真实医学 PDF 文献(含 1 篇中文),涵盖 RCT、队列研究
- **测试方法**: pymupdf4llm (本地) / MinerU Cloud API (VLM) / DeepSeek LLM (deepseek-chat)
### 4.2 核心结果
| 指标 | pymupdf4llm | MinerU API (VLM) | DeepSeek LLM |
|------|-------------|------------------|--------------|
| 结构化表格检出 | 3 个 (12.5%) | **28 个 (100%)** | 24 个 (85%) |
| 输出格式 | 纯文本 | **HTML `<table>`** | Markdown `\|..\|` |
| 合并单元格 | ❌ | **✅ rowspan/colspan** | ⚠️ 文字描述 |
| 数值精度 | ✅ 原始 | **✅ 100% 保真** | ⚠️ 可能翻译 |
| 总耗时 (8 篇) | 16.1s | ~50s | 234.6s |
| 综合评分 | 2.7/5 | **4.6/5** | 3.4/5 |
### 4.3 逐文件对比
| # | 文件 | pymupdf4llm | MinerU API | DeepSeek LLM |
|---|------|-------------|------------|--------------|
| 1 | S2589537025 (EClinMed) | 0 表格 | **1 HTML** | 1 MD |
| 2 | Dongen 2003 | 0 结构化 | **4 HTML** | 3 MD |
| 3 | Ginkgo+Donepezil | 0 结构化 | **3 HTML** | 3 MD |
| 4 | Ginkgo Community | 0 结构化 | **6 HTML** | 6 MD |
| 5 | Ginkgo NPS | 3 MD | **3 HTML** | 3 MD |
| 6 | Herrschaft 2012 | 0 结构化 | **3 HTML** | 3 MD |
| 7 | Ihl 2011 | 0 结构化 | **3 HTML** | 3 MD |
| 8 | NIRS 队列研究 (中文) | 0 结构化 | **5 HTML** | 2 MD |
### 4.4 质量深度分析 (Herrschaft 2012 — Table 1)
原始表格: 5 列、18 行,"Type of dementia" 合并 3 行。
| 特征 | pymupdf4llm | MinerU API | DeepSeek LLM |
|------|-------------|------------|--------------|
| 列数正确 | ❌ 无结构 | **✅ 5 列** | ✅ 4 列 |
| 行数完整 | ✅ 数据在 | **✅ 18 行** | ✅ 18 行 |
| 合并单元格 | ❌ | **✅ rowspan=3** | ⚠️ 加粗标注 |
| 数值保真 | ✅ | **✅ 含 ±** | ⚠️ 翻译行名 |
### 4.5 综合评分
| 维度 | pymupdf4llm | MinerU API | DeepSeek LLM |
|------|:-----------:|:----------:|:------------:|
| 表格检测率 | 1/5 | **5/5** | 4/5 |
| 结构保真度 | 1/5 | **5/5** | 4/5 |
| 数值精度 | 5/5 | **5/5** | 4/5 |
| 速度 | 5/5 | 3/5 | 2/5 |
| 合并单元格 | 1/5 | **5/5** | 3/5 |
| 中文支持 | 3/5 | **5/5** | 4/5 |
| 成本 | 5/5 | 4/5 | 3/5 |
| **综合** | **2.7** | **4.6** | **3.4** |
---
## 5. 技术实现设计
### 5.1 接口抽象
```typescript
// common/document/tableExtraction/types.ts
/** 统一引擎接口 — 所有适配器必须实现 */
interface ITableExtractionEngine {
readonly name: string;
extract(pdf: Buffer, options?: ExtractionOptions): Promise<ExtractionResult>;
}
interface ExtractionOptions {
language?: 'ch' | 'en' | 'auto';
/** 指定页码范围,如 "1-5,8" */
pageRanges?: string;
/** 是否启用公式识别 */
enableFormula?: boolean;
}
interface ExtractionResult {
tables: ExtractedTable[];
/** 引擎名称 */
engine: string;
/** 处理耗时 (ms) */
duration: number;
/** PDF 总页数 */
pageCount: number;
/** 原始 Markdown 全文 (可选) */
fullMarkdown?: string;
}
```
### 5.2 引擎管理器
```typescript
// common/document/tableExtraction/engineManager.ts
class TableExtractionEngineManager {
private engines: Map<string, ITableExtractionEngine> = new Map();
private defaultEngine: string = 'mineru';
/** 注册引擎适配器 */
register(engine: ITableExtractionEngine): void {
this.engines.set(engine.name, engine);
}
/** 设置默认引擎 */
setDefault(name: string): void {
this.defaultEngine = name;
}
/** 提取表格 — 使用者唯一入口 */
async extract(
pdf: Buffer,
options?: ExtractionOptions & { engine?: string }
): Promise<ExtractionResult> {
const engineName = options?.engine || this.defaultEngine;
const engine = this.engines.get(engineName);
if (!engine) throw new Error(`Engine not found: ${engineName}`);
return engine.extract(pdf, options);
}
}
```
### 5.3 MinerU 适配器 (第一个实现)
```typescript
// common/document/tableExtraction/engines/mineruEngine.ts
class MinerUEngine implements ITableExtractionEngine {
readonly name = 'mineru';
async extract(pdf: Buffer, options?: ExtractionOptions): Promise<ExtractionResult> {
// 1. 请求上传 URL
// 2. 上传 PDF
// 3. 轮询等待解析完成
// 4. 下载结果 ZIP
// 5. 解析 HTML 表格 → ExtractedTable[]
// ...
}
}
```
### 5.4 未来适配器 (预留接口)
```typescript
// 后续逐步实现
class Qwen3VLEngine implements ITableExtractionEngine { ... }
class PaddleOCRVLEngine implements ITableExtractionEngine { ... }
class QwenOCRLongEngine implements ITableExtractionEngine { ... }
class DoclingEngine implements ITableExtractionEngine { ... }
```
### 5.5 文件规划
```
backend/src/common/document/tableExtraction/
├── types.ts # 统一类型定义
├── engineManager.ts # 引擎管理器 (统一入口)
├── htmlTableParser.ts # HTML <table> → ExtractedTable 转换
├── engines/
│ ├── mineruEngine.ts # MinerU Cloud API 适配器 ✅ 首个实现
│ ├── qwen3vlEngine.ts # Qwen3-VL 适配器 (待实现)
│ ├── paddleOcrEngine.ts # PaddleOCR-VL 适配器 (待实现)
│ ├── qwenOcrLongEngine.ts # Qwen-OCR + Qwen-Long 适配器 (待实现)
│ ├── doclingEngine.ts # Docling 适配器 (待实现)
│ └── deepseekEngine.ts # DeepSeek LLM 适配器 (已测试,可选)
└── index.ts # 导出统一入口
```
---
## 6. 使用方式
### 6.1 业务层调用 (使用者视角)
```typescript
import { getTableExtractionEngine } from '@/common/document/tableExtraction';
// 使用者不需要知道底层是 MinerU 还是 Qwen-VL
const engine = getTableExtractionEngine();
const result = await engine.extract(pdfBuffer, { language: 'auto' });
for (const table of result.tables) {
console.log(`${table.title}: ${table.rows.length}× ${table.headers.length}`);
// 直接使用结构化数据
}
```
### 6.2 管理员切换引擎
```bash
# backend/.env — 切换默认引擎
TABLE_EXTRACTION_ENGINE=mineru # 当前默认
# TABLE_EXTRACTION_ENGINE=qwen3vl # 未来切换
# TABLE_EXTRACTION_ENGINE=paddle # 未来切换
# MinerU 配置
MINERU_API_TOKEN=your_token
MINERU_API_BASE=https://mineru.net/api/v4
MINERU_MODEL_VERSION=vlm
```
### 6.3 场景决策矩阵
| 场景 | 推荐引擎 | 说明 |
|------|----------|------|
| ASL 标题摘要初筛 | pymupdf4llm (文本引擎) | 不需要表格,只需全文文本 |
| ASL 全文复筛 — 表格提取 | **PDF 表格提取引擎** | 自动选择最优引擎 |
| 系统综述数据提取 | **PDF 表格提取引擎** | 需要精确数值表格 |
| Meta 分析效应值识别 | 表格引擎 + LLM 语义理解 | 提取 → 理解两步走 |
| PKB 知识库入库 | pymupdf4llm (文本引擎) | 只需 Markdown 文本 |
---
## 7. MinerU Cloud API 接入指南 (当前默认引擎)
### 7.1 API 概览
| 项目 | 说明 |
|------|------|
| 服务商 | OpenDataLab (上海人工智能实验室) |
| API 地址 | `https://mineru.net/api/v4` |
| 认证方式 | Bearer Token |
| 模型版本 | `vlm` (视觉语言模型,推荐) |
| 免费额度 | 2000 页/天 |
| 文件限制 | 单文件 ≤ 200MB≤ 600 页 |
### 7.2 核心流程
```
PDF 文件
Step 1: POST /file-urls/batch → 获取预签名上传 URL + batch_id
Step 2: PUT {pre-signed URL} → 上传 PDF 文件
Step 3: 云端 VLM 模型自动解析 → 识别表格/文本/图片
Step 4: GET /extract-results/batch/{batch_id} → 轮询状态
Step 5: 下载结果 ZIP → 含 .md (内嵌 HTML 表格) + .json + images
```
### 7.3 代码示例
```python
import requests, time, zipfile, io
TOKEN = "your_token"
API = "https://mineru.net/api/v4"
headers = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
# Step 1: 请求上传 URL
resp = requests.post(f"{API}/file-urls/batch", headers=headers, json={
"files": [{"name": "paper.pdf", "data_id": "paper1"}],
"enable_table": True,
"model_version": "vlm",
})
batch_id = resp.json()["data"]["batch_id"]
upload_url = resp.json()["data"]["file_urls"][0]
# Step 2: 上传文件
with open("paper.pdf", "rb") as f:
requests.put(upload_url, data=f)
# Step 3-4: 轮询等待
while True:
time.sleep(10)
r = requests.get(f"{API}/extract-results/batch/{batch_id}", headers=headers)
results = r.json()["data"]["extract_result"]
if all(x["state"] in ("done", "failed") for x in results):
break
# Step 5: 下载解析
for result in results:
if result["state"] == "done":
zr = requests.get(result["full_zip_url"])
with zipfile.ZipFile(io.BytesIO(zr.content)) as zf:
for name in zf.namelist():
if name.endswith('.md'):
md = zf.read(name).decode('utf-8')
# md 中包含 HTML <table> 格式的表格
```
### 7.4 输出格式
MinerU 的表格以 HTML `<table>` 嵌入 Markdown 中,完整保留合并单元格:
```html
<table>
<tr><td rowspan="3">Type of dementia</td><td>Probable AD</td><td>107 (54)</td></tr>
<tr><td>Possible AD with CVD</td><td>73 (36)</td></tr>
<tr><td>Probable VaD</td><td>20 (10)</td></tr>
</table>
```
---
## 8. 成本估算
### 8.1 MinerU (当前)
| 场景 | 文献数 | 平均页数 | 总页数 | 天数 | 费用 |
|------|--------|----------|--------|------|------|
| 小型综述 | 20 篇 | 10 页 | 200 页 | 1 天 | 免费 |
| 中型综述 | 100 篇 | 10 页 | 1000 页 | 1 天 | 免费 |
| 大型综述 | 500 篇 | 10 页 | 5000 页 | 3 天 | 免费 |
### 8.2 各引擎预估成本对比
| 引擎 | 免费额度 | 超出后单价 | 500 篇 (5000 页) 预估 |
|------|----------|-----------|----------------------|
| MinerU | 2000 页/天 | 待确认 | 免费 (分 3 天) |
| Qwen-OCR + Qwen-Long | 按 token | ~0.004 元/千 token | 约 10-20 元 |
| PaddleOCR-VL | 官方免费额度多 | 极低 | 接近免费 |
| Qwen3-VL | 按 token | ~0.02 元/千 token | 约 50-100 元 |
| Docling | 本地部署 | 仅算力成本 | 免费 |
| DeepSeek LLM | 按 token | ~0.14 元/万 token | 约 30-50 元 |
---
## 9. 测试脚本
### 9.1 已有脚本
| 脚本 | 路径 | 功能 |
|------|------|------|
| 三方对比测试 | `extraction_service/test_pdf_table_extraction.py` | pymupdf4llm / MinerU / DeepSeek 完整对比 |
| 结果分析 | `extraction_service/analyze_table_results.py` | 从提取结果生成对比报告 |
### 9.2 运行方法
```bash
cd AIclinicalresearch
# 运行全部三个方法
python extraction_service/test_pdf_table_extraction.py
# 单独运行某个方法
python extraction_service/test_pdf_table_extraction.py pymupdf
python extraction_service/test_pdf_table_extraction.py mineru
python extraction_service/test_pdf_table_extraction.py deepseek
# 生成对比报告
python extraction_service/analyze_table_results.py
```
### 9.3 测试输出
```
extraction_service/test_output/pdf_table_extraction/
├── pymupdf4llm/ # pymupdf4llm 提取结果
├── mineru/ # MinerU 提取结果
├── deepseek/ # DeepSeek 提取结果
├── raw_results.json # 原始测试数据
└── comparison_report.md # 综合对比报告
```
### 9.4 后续评测扩展
新引擎的评测脚本将遵循同样的结构,添加到 `test_pdf_table_extraction.py` 中:
```bash
python extraction_service/test_pdf_table_extraction.py qwen3vl
python extraction_service/test_pdf_table_extraction.py paddle
python extraction_service/test_pdf_table_extraction.py qwenocr
```
---
## 10. 路线图
### Phase 1: 基础框架 + MinerU (当前)
- [x] MinerU Cloud API 对比测试
- [x] DeepSeek LLM 对比测试
- [ ] 实现统一接口 `ITableExtractionEngine`
- [ ] 实现 `MinerUEngine` 适配器
- [ ] 实现 `engineManager` 引擎管理器
- [ ] ASL 全文复筛集成
### Phase 2: 多引擎评测
- [ ] Qwen3-VL 评测 + 适配器
- [ ] PaddleOCR-VL 1.5 评测 + 适配器
- [ ] 同一基准集横向对比报告
- [ ] 确定最优引擎组合策略
### Phase 3: 性价比优化
- [ ] Qwen-OCR + Qwen-Long 评测 (最低成本方案)
- [ ] Docling 本地部署评测 (离线方案)
- [ ] 引擎路由策略 (按文档复杂度自动选择引擎)
### Phase 4: 生产加固
- [ ] 提取结果缓存 (避免重复解析)
- [ ] 批量提取队列 (pg-boss 异步任务)
- [ ] 质量监控 (空表格/异常值检测)
- [ ] 引擎降级策略 (主引擎不可用时自动切换)
---
## 11. 相关文档
- [文档处理引擎 README](./README.md) — 引擎总览 (含全文文本提取)
- [文档处理引擎设计方案 V1](./01-文档处理引擎设计方案.md) — pymupdf4llm 全文文本架构
- [文档处理引擎使用指南](./02-文档处理引擎使用指南.md) — 现有 API 调用指南
- [MinerU 官方文档](https://mineru.net/doc/docs/index_en/) — MinerU Cloud API 在线文档
- [对比测试报告](../../../extraction_service/test_output/pdf_table_extraction/comparison_report.md) — 完整测试数据
---
**维护人**: 技术架构师
**设计原则**: 引擎对使用者透明,底层可热切换,以测试数据驱动选型