Files
RAG-CUT/README.md
T
陈辅元andCursor 26461569c8 Stop tracking local agent/task docs and refresh README.
Exclude AGENTS.md, CLAUDE.md, IMPACT_ANALYSIS.md, and TASK_SUMMARY.md from the remote; output/ was already ignored.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-16 13:29:01 +08:00

443 lines
16 KiB
Markdown
Raw 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.
# RAG-cut
自研 AI 助手知识库 Demo —— 以**高质量文档切片**为核心,召回为辅。
重建文档 ingestion / 切片流水线,针对现有 AFE 知识库的两类问题:
1. **解析不足**:Word / PDF / Excel 中的图片、表格无法有效提取
2. **切分不佳**:同一逻辑段落被硬切到不同 chunk,图片丢失或错位
| 项 | 说明 |
| --- | --- |
| 仓库 | `http://192.168.3.110:3000/AFE_RAG/RAG-CUT.git` |
| 需求文档 | [`docx/自研搭建AI助手知识库.pdf`](docx/自研搭建AI助手知识库.pdf) |
| 样例文档 | `自研RAG代表文档/` |
---
## 功能概览
| 能力 | 说明 |
| --- | --- |
| 多格式解析 | pdf / doc / docx / ppt / pptx / ppsx / xlsx / csv / md / txt / html / json / xml / log / 常见图片 |
| PDF 双引擎 | MinerU(优先)+ PyMuPDF 回退;统一噪声过滤(页眉页脚、页码、**目录 TOC**) |
| 四种切分模式 | `default` · `delimiter` · `parent_child` · `by_row`(Demo 默认选结构感知) |
| 语义切分 | PDF / Word 在 `default` 下走 `heading_layout_multimodal`;超长章节按子标题/长度拆分(不产父片) |
| 父子标识符 | 独立模式 `parent_child`:子片检索、父片上下文 |
| 表格切分 | `by_row`:自动识别表头 / 说明行;Q&A 表可按行 1 片 |
| Demo UI | 原文预览 · 切片列表 · Markdown 预览 · **历史切片回看** · BM25 召回测试 |
| CLI / API | `scripts/chunk_cli.py` · `/api/chunk` · `/api/results` · `/api/recall` |
未支持:`.wps`(可先转为 docx/pdf)。独立图片暂不做 OCR。
---
## 快速开始
### 1. 克隆与依赖
```bash
git clone http://192.168.3.110:3000/AFE_RAG/RAG-CUT.git
cd RAG-CUT
pip install -r requirements.txt
```
另需本机工具(按文档类型):
| 场景 | 依赖 |
| --- | --- |
| `.doc` / `.ppt` 等 | **LibreOffice**(`soffice`)或 Windows 上的 **Microsoft Word/PowerPoint** |
| PDF 高精度解析 | MinerU(`requirements.txt` 已含 `mineru[all]`) |
| MinerU 图内文字回填 | 本机 Tesseract(可选,见下方环境变量) |
### 2. 启动 Demo
```bash
cd backend
python run.py
```
浏览器打开:**http://127.0.0.1:8000**(后端同时托管前端静态页)。
前后端分离开发时另开终端:
```bash
cd frontend
python run.py # http://127.0.0.1:5173 ,API 默认指向 :8000
```
可选参数:`--host 0.0.0.0 --port 8000 --no-reload`(后端)· `--port 5173`(前端)
### 3. 运行测试
```bash
cd backend
python -m pytest tests/ -q
```
### 环境变量(PDF)
| 变量 | 默认 | 说明 |
| --- | --- | --- |
| `RAG_CUT_PDF_ENGINE` | `auto` | `auto` 优先 MinerU,失败回退 PyMuPDF;可强制 `mineru` / `pymupdf` |
| `RAG_CUT_MINERU_CMD` | — | MinerU 可执行路径(不在 PATH 时) |
| `RAG_CUT_MINERU_API_URL` | — | 复用已启动的 MinerU API,避免每次切分重复加载模型 |
| `RAG_CUT_MINERU_TIMEOUT` | `540` | 单次 MinerU 解析超时(秒) |
| `RAG_CUT_MINERU_OCR` | `1` | MinerU 图片无 OCR 时用 Tesseract 回填;`0` 关闭以加快切分 |
详见 [`docx/mineru-integration.md`](docx/mineru-integration.md)。
### 运行栈
| 类别 | 选型 |
| --- | --- |
| Python | 3.12+ |
| Web | FastAPI + Uvicorn |
| PDF | MinerU / PyMuPDF + pdfplumber |
| Excel | openpyxl |
| 召回 | 内置 BM25(无向量库) |
---
## 技术架构
```
上传文件
│
▼
格式路由 (registry)
│
▼
解析器 (parsers) ──► 有序 Block 流(heading / paragraph / table / image …)
│ PDF:MinerU(优先)或 PyMuPDF → 噪声过滤 → 跨页表合并
▼
版面增强 (layout_meta) ──► 标题层级、图片/表格上下文、order_index
│
▼
目录过滤 (filter_toc_blocks) ──► 全格式剔除「目录 / Contents」区
│
▼
切分策略 (split_policy / pdf_strategy)
│
▼
切分器 (splitters) ──► Block 分组(含父子切片关联)
│
▼
渲染器 (renderer) ──► Markdown Chunk + embedding_text / retrieval 标记
```
**核心设计**:先解析为不可拆分的 Block,再按结构切分。`image` / `table` 为原子块,不会被拆到不同 chunk。
### 目录结构
```
RAG-cut/
├── backend/
│ ├── rag_cut/
│ │ ├── models.py / pipeline.py / renderer.py / layout_meta.py
│ │ ├── split_policy.py / retrieval.py
│ │ ├── parsers/ # word / ppt / pdf / xlsx / text / image + MinerU
│ │ │ └── pdf/ # PyMuPDF 流水线、噪声过滤、跨页表合并
│ │ └── splitters/ # default / heading / pdf_semantic / by_row / delimiter / parent_child
│ ├── api/main.py # FastAPI + 静态前端
│ ├── tests/
│ └── run.py
├── frontend/ # Demo(index.html + css + js)
├── scripts/ # chunk_cli · run_all_samples
├── storage/ # uploads / assets / results(运行时,已 gitignore)
├── docx/ # 需求 PDF、MinerU / 腾讯云切分说明
├── 自研RAG代表文档/ # 样例语料
└── requirements.txt
```
---
## 各格式处理
统一流水线:**解析 → Block →(目录过滤)→ 切分 → Markdown Chunk**。
| 扩展名 | 解析器 | 要点 | 推荐切分 |
| --- | --- | --- | --- |
| `.pdf` | `PdfParser` | MinerU / PyMuPDF → 噪声过滤 → 跨页表合并 | `default`(语义,`max≈2600`) |
| `.doc` `.docx` | `WordParser` | 转 PDF 后复用 PDF 流水线 | 同上 |
| `.ppt` `.pptx` `.ppsx` | `PptParser` | 转 PDF;页码 ≈ 幻灯片序号 | `default` |
| `.xlsx` `.xls` `.csv` | `SpreadsheetParser` | 检测 preamble / 表头 / 数据起始行 | **`by_row`**(Demo 需手动选) |
| `.md` `.html` | `TextParser` | 按标题 / 段落拆块 | `default` / `delimiter` / `parent_child` |
| `.json` | `TextParser` | 顶层 key / 数组元素 → `code` 块 | `default` |
| `.txt` `.xml` `.log` | `TextParser` | 按空行分段 | `default` |
| 图片 | `ImageParser` | 整图 1 个 Block | 整图 1 片 |
### 统一产出
| 项目 | 说明 |
| --- | --- |
| Block 类型 | `heading` / `paragraph` / `table` / `image` / `list` / `code` |
| Chunk | Markdown + `meta`(`heading` / `pages` / `embedding_text` / `retrieval` / 可选 `parent_chunk_id`) |
| 图片 | `storage/assets/{doc_id}/`;JSON 内为 `assets/{doc_id}/…` |
| 结果缓存 | `storage/results/{doc_id}.json`(历史回看 / 召回) |
| 中间产物 | `_conversion/`(Office→PDF)、`_mineru/`(MinerU 输出) |
### Word / PPT 转 PDF
优先 LibreOffice(`soffice --headless`),Windows 可回退 Office COM。转换失败时 API 返回 500 并附带各后端错误信息。
### Excel / CSV
`detect_spreadsheet_layout` 识别常见模板(说明行 + 表头 + 数据区);Q&A 评测表(列名含 query/answer 等)适合 `rows_per_chunk=1`。
> `.xls` 依赖 openpyxl,老二进制格式可能失败,建议先转 xlsx。
---
## PDF / Word 语义切分
`.pdf` / `.doc` / `.docx` 在 `mode=default` 时走 `splitters/pdf_semantic.py`(策略名:`heading_layout_multimodal`)。
Word 先转 PDF,再与 PDF 共用规则。自动策略下默认 `max_chunk_size=2600`、`overlap=120`;Demo 默认表单约为 `2200` / `120`。
### 流水线
```
PDF/Word
│
├─ 解析:MinerU(优先)或 PyMuPDF → Block 流
├─ 噪声过滤:页眉/页脚/页码、文档目录、装饰小图
├─ 跨页表格合并
├─ 版面增强:标题层级、图片/表格上下文
├─ pdf_strategy:打标签(操作手册 vs 年报)
└─ heading_layout_multimodal → Chunk
```
### 核心规则:`heading_layout_multimodal`
目标:同一逻辑小节(标题 + 正文 + 同节截图/表格)尽量落在同一 chunk。
| 步骤 | 行为 |
| --- | --- |
| 1. 再过滤噪声 | 跳过页码、running header、目录区、过小装饰图 |
| 2. 识别标题 | 解析器已标 `heading`,或正则/字号启发式补识别 |
| 3. 维护标题路径 | 标题栈记录章节层级 |
| 4. 按标题开新片 | 同级或更高级标题时 flush,开新 chunk |
| 5. 图文同节 | 正文、image、table 跟在当前标题路径下 |
| 6. 超长二次切 | 超过 `max_chunk_size` 时按子标题/长度拆分;**不单独保留父片** |
可识别标题形态示例:`1.` / `1.2.3` / `A.` / `Chapter 2` / `一、` / `(一)`;操作步骤 `Step N …` 作正文保留。封面 Logo 等无语义组标 `retrieval=false`。
### 文档类型标签:`pdf_strategy`
写入 `split_config.pdf_chunk_strategy`(Demo 策略面板可显示):
| 标签 | 典型特征 | 说明 |
| --- | --- | --- |
| `pdf_feature_step_screenshot` | 步骤标题多、操作词多、截图密度高 | 操作手册型 |
| `pdf_outline_report` | 「年报/财务报表」等词,或文件名含 annual/report/年报 | 年报/报告型 |
当前两类切分算法相同,标签用于结果标注与后续策略分叉预留。
---
## 切分模式(全格式)
后端 **4** 种模式(`SplitMode`):`default` / `delimiter` / `parent_child` / `by_row`。
| 模式 | 设计意图 | 行为 |
| --- | --- | --- |
| `default` | 非表格正文 | PDF/Word 走语义切分;其他按标题/页/长度打包;`image`/`table` 不拆 |
| `delimiter` | 正文含可匹配标识符 | 按自定义标识符切开,再按长度二次切 |
| `parent_child` | 层级标识符 | 父标识符切父片,子标识符切子片;检索用子片 |
| `by_row` | 表格 | 每片 = 表头 + N 行 Markdown |
**Demo**:始终显式传 `mode`(默认 `default`)。切 Excel/CSV 时请选手动「按行切分」。
**自动策略**(`choose_split_config`):仅当 API/Python **不传** `mode`/`config` 时生效——表格类选 `by_row`,PDF/PPT 等选 `default` 并覆盖 `max_chunk_size`。CLI 默认带 `--mode default`,不会走该自动选型。
### 父子标识符(`mode=parent_child`)
1. 按 `parent_delimiter` 切父片;超长再按长度二次切
2. 再按 `child_delimiter`(可选)切子片
3. 父片:`is_section_parent=true`,`retrieval=false`
4. 子片:`is_sub_chunk=true`,`retrieval=true`,`parent_chunk_id` 指向父片
| 参数 | 说明 |
| --- | --- |
| `parent_delimiter` | 必填;不写入切片正文 |
| `child_delimiter` | 可选;缺省则仅按子级最大长度拆 |
| `max_chunk_size` | 父级最大长度 |
| `child_max_size` | 子级最大长度(≤ 父级,硬上限 1500) |
| `overlap` | 超长二次切分重叠 |
### `default` 决策树(摘要)
```
.pdf / .doc / .docx → heading_layout_multimodal
单 table 且含 rows → 仅「不传 mode」时自动 by_row;显式 default 则走通用切分
有足够标题 → 标题大纲切;超长按子标题/长度拆(不产父片)
有 page、标题不足 → 按页;单页超限再按长度
否则 → 按 max_chunk_size 打包
```
### SplitConfig
| 参数 | 默认 | 说明 |
| --- | --- | --- |
| `mode` | `default` | 见上表 |
| `delimiter` | — | `delimiter` 模式必填 |
| `parent_delimiter` / `child_delimiter` | — | `parent_child`:父必填,子可选 |
| `max_chunk_size` | 1500 | 自动策略常覆盖为 1800–2600 |
| `child_max_size` | 512 | `parent_child` 子级上限 |
| `overlap` | 150 | 超长二次切分重叠 |
| `header_row_start` / `header_row_end` | 1 | 表头行(1-based) |
| `start_row` | 2 | 数据起始行 |
| `rows_per_chunk` | 1 | 每片数据行数 |
可解析扩展名(21 种):`.pdf` `.doc` `.docx` `.ppt` `.pptx` `.ppsx` `.xlsx` `.xls` `.csv` `.md` `.txt` `.html` `.htm` `.json` `.xml` `.log` `.jpg` `.jpeg` `.png` `.bmp` `.gif`。需求中的 `.wps` 尚未接入。
---
## Demo 前端
三栏:**原文** · **切片列表** · **切片预览**;左侧配置切分模式与参数;下方可测召回。
| 区域 | 说明 |
| --- | --- |
| 上传 | 拖拽 / 选择文件 |
| 切分模式 | 默认(结构感知)/ 通用标识符 / 父子标识符 / 按行 |
| 策略面板 | 切分后回显 mode / max_size / overlap / PDF 策略 |
| 历史切片 | 列出 `storage/results` 中已切文档,可回看 / 删除 |
| 原文 | PDF iframe / docx(mammoth)/ 文本 / 图片 |
| 切片列表 | 类型标签、父子标记、字符数 |
| 召回测试 | query + top_k;`retrieval=false` 的片不进候选 |
| API 面板 | 可折叠请求/响应 JSON |
---
## CLI / API
### CLI
```bash
python scripts/chunk_cli.py "path/to/file.pdf" --preview 3
python scripts/chunk_cli.py "path/to/file.xlsx" --mode by_row --rows-per-chunk 5 -o storage/result.json
python scripts/chunk_cli.py "readme.md" --mode delimiter --delimiter "##"
python scripts/chunk_cli.py "readme.md" --mode parent_child \
--parent-delimiter "##" --child-delimiter "###" \
--max-chunk-size 2000 --child-max-size 512
```
批量样例(若有 `data/`):`python scripts/run_all_samples.py`
### API
```bash
# 健康检查
curl http://127.0.0.1:8000/health
# 切分(不传 mode → 服务端自动策略)
curl -X POST http://127.0.0.1:8000/api/chunk -F "file=@./your.pdf"
# 切分(显式参数,与 Demo 一致)
curl -X POST http://127.0.0.1:8000/api/chunk \
-F "file=@./your.xlsx" \
-F "mode=by_row" \
-F "rows_per_chunk=5"
# 历史结果
curl http://127.0.0.1:8000/api/results
# 召回(先切分拿到 doc_id)
curl -X POST http://127.0.0.1:8000/api/recall \
-H "Content-Type: application/json" \
-d '{"doc_id":"abcdef123456","query":"如何修改交易密码","top_k":5}'
```
主要端点:`GET /health` · `POST /api/chunk` · `GET/DELETE /api/results…` · `POST /api/recall`。
### Python
```python
from pathlib import Path
from rag_cut import chunk_document, SplitConfig, SplitMode
from rag_cut.retrieval import recall_chunks
# 不传 config → 自动策略
result = chunk_document(Path("doc.pdf"))
result = chunk_document(
Path("doc.docx"),
config=SplitConfig(mode=SplitMode.DEFAULT, max_chunk_size=2600, overlap=120),
)
hits, n = recall_chunks("login password", result.chunks, top_k=5)
```
将 `backend/` 加入 `PYTHONPATH`,或通过 `scripts/chunk_cli.py` 调用。
### 输出字段(节选)
```json
{
"filename": "guide.docx",
"doc_id": "61c2a152cd5e",
"split_mode": "default",
"split_config": {
"mode": "default",
"max_chunk_size": 2600,
"pdf_chunk_strategy": "pdf_feature_step_screenshot",
"chunk_strategy": "heading_layout_multimodal"
},
"block_count": 269,
"chunk_count": 48,
"chunks": [
{
"index": 0,
"content": "# …\n\n![image3](assets/61c2a152cd5e/image3.png)",
"char_count": 431,
"block_types": ["heading", "paragraph", "image"],
"meta": {
"heading": "1 Getting Started",
"pages": [3],
"embedding_text": "…",
"retrieval": true
}
}
],
"assets_dir": "storage/assets/61c2a152cd5e"
}
```
---
## 期望效果对照
| 需求 | 状态 |
| --- | --- |
| 图片保留原文位置 | ✅ PDF / Word / PPT(转 PDF) |
| 表格提取为 Markdown | ✅ PDF / Excel / CSV |
| Word 同标题内容同 chunk | ✅ 语义切分 |
| PDF 多栏阅读顺序 | ✅ 双栏检测 + 行合并 |
| 页眉页脚 / 页码噪声过滤 | ✅ MinerU 类型跳过 + 位置启发式 |
| 文档目录不进切片 | ✅ 全格式 `filter_toc_blocks` |
| 手册截图文字不混入正文 | ✅ 大图区域过滤 |
| 父子切片(超长章节自动) | ❌ 已从默认切分移除 |
| 父子标识符切分 | ✅ `parent_child` |
| 召回入参/出参可检视 | ✅ `/api/recall` + Demo |
| 历史切片回看 | ✅ `/api/results` + Demo |
| wps | ⏳ 未实现 |
---
## 后续计划
- [ ] PDF OCR / 视觉描述增强
- [ ] 向量召回(Embedding)
- [ ] wps 格式支持
---
## 参考
- [`docx/自研搭建AI助手知识库.pdf`](docx/自研搭建AI助手知识库.pdf) — 需求
- [`docx/mineru-integration.md`](docx/mineru-integration.md) — MinerU 接入
- [`docx/tencent-cloud-document-splitting-settings.md`](docx/tencent-cloud-document-splitting-settings.md) — 腾讯云切分参考
- [`CLAUDE.md`](CLAUDE.md) / [`AGENTS.md`](AGENTS.md) — AI 协作说明