AI Day 19
AI Day 19: 生产级RAG(1):文档解析与Chunking工程 — 数据质量决定RAG上限
文档解析与Chunking工程是生产级RAG系统的"数据地基"——将非结构化文档(PDF/Word/HTML/扫描件)精确转化为结构化、可检索的知识片段(Chunks)。解析质量和Chunking策略决定了RAG系统的理论上限:检索再精准、模型再强大,如果源数据就是错的或碎片化的,最终输出必然低质。这是一个80%工程量花在数据处理上的典型领域。
2026-04-20
DocumentChunkingPDFUnstructured.ioLlamaParseDoclingSemanticTableOCRDataFinancialRAG
日期:2026-04-20
阶段:第二阶段 — 工程实践 (Day 16-30)
标签:Document Parsing Chunking PDF Extraction Unstructured.io LlamaParse Docling Semantic Chunking Table Extraction OCR Data Pipeline Financial Documents RAG Quality
学习路径树
AI/LLM 深度技术学习 50天计划
├── 第一阶段:模型基础 (Day 1-15) ✅
│ ├── Day 1: Transformer架构与LLM基础 ✅
│ ├── Day 2: 模型量化与本地部署 ✅
│ ├── Day 3: 训练过程深度:Pre-training / SFT / RLHF / DPO ✅
│ ├── Day 4: Prompt Engineering与上下文学习(ICL)原理 ✅
│ ├── Day 5: RAG架构:检索增强生成全链路 ✅
│ ├── Day 6: 向量数据库与Embedding模型 ✅
│ ├── Day 7: Fine-tuning实战:LoRA / QLoRA / Adapter ✅
│ ├── Day 8: 推理优化:vLLM / TensorRT-LLM / SGLang ✅
│ ├── Day 9: 长上下文技术:RoPE扩展 / Ring Attention ✅
│ ├── Day 10: 多模态模型:Vision-Language架构 ✅
│ ├── Day 11: Reasoning模型:CoT / o1 / R1 / Extended Thinking ✅
│ ├── Day 12: Agent框架:ReAct / Tool Use / Planning ✅
│ ├── Day 13: MCP协议与Tool生态 ✅
│ ├── Day 14: 模型评估:Benchmark / Arena / 安全评估 ✅
│ └── Day 15: 阶段复习与架构总结 ✅
│
├── 第二阶段:工程实践 (Day 16-30)
│ ├── Day 16: LLM应用架构设计 — API Gateway/路由/缓存 ✅
│ ├── Day 17: LLM安全与Guardrails — 生产环境防护体系 ✅
│ ├── Day 18: LLM可观测性 — 日志/Tracing/指标/告警 ✅
│ ├── Day 19: 生产级RAG(1) — 文档解析与Chunking工程 ← 你在这里
│ ├── Day 20: 生产级RAG(2) — Retrieval优化与Reranking
│ ├── Day 21: 生产级RAG(3) — 评估框架与持续迭代
│ ├── Day 22-25: Agent系统工程化(状态管理/错误恢复/成本控制)
│ └── Day 26-30: 多模型编排/部署策略/端到端项目
│
├── 第三阶段:金融零售AI应用 (Day 31-42)
│ ├── Day 31-35: 金融AI(风控模型/智能投顾/合规/反欺诈)
│ ├── Day 36-40: 零售AI(推荐系统/智能客服/供应链预测/营销)
│ └── Day 41-42: CeFi x DeFi x AI融合架构
│
└── 第四阶段:面试冲刺 (Day 43-50)
├── Day 43-46: 系统设计面试(LLM平台/RAG/Agent/推荐)
├── Day 47-49: 产品/架构面试模拟
└── Day 50: 总结与作品集
核心概念
一句话定义
文档解析与Chunking工程是生产级RAG系统的"数据地基"——将非结构化文档(PDF/Word/HTML/扫描件)精确转化为结构化、可检索的知识片段(Chunks)。解析质量和Chunking策略决定了RAG系统的理论上限:检索再精准、模型再强大,如果源数据就是错的或碎片化的,最终输出必然低质。这是一个80%工程量花在数据处理上的典型领域。
金融类比
RAG数据处理 = 银行数据治理(Data Governance) —— 垃圾进,垃圾出
银行数据治理: RAG文档处理:
├── 数据采集层 ├── 文档采集层
│ ├── 多源头接入(核心系统/外部数据) │ ├── 多格式接入(PDF/Word/HTML/图片)
│ ├── 数据格式标准化(ISO 8583) │ ├── 格式标准化(统一Markdown/JSON)
│ ├── 数据质量校验(完整性/一致性) │ ├── 解析质量检查(文字准确率/结构保留)
│ └── 异常数据处理(缺失值/重复) │ └── 异常处理(乱码/截断/表格错位)
│ │
├── 数据加工层 ├── Chunking加工层
│ ├── 数据清洗(去噪/脱敏) │ ├── 文本清洗(去页眉页脚/水印)
│ ├── 数据分类(客户/交易/产品) │ ├── 文档分段(按语义/结构/固定大小)
│ ├── 数据关联(360度客户视图) │ ├── Metadata标注(来源/日期/类别)
│ └── 数据分层(ODS/DW/DM) │ └── 层次组织(Parent-Child/索引)
│ │
├── 数据质量管理 ├── 数据质量Gate
│ ├── 质量规则(准确性/及时性/完整性) │ ├── 质量规则(字符准确率>98%/结构完整)
│ ├── 质量报表(数据健康度Dashboard) │ ├── 质量报表(解析成功率/Chunk质量分布)
│ ├── 质量闭环(发现→修复→验证) │ ├── 质量闭环(检测→修复→重新索引)
│ └── 数据血缘(Lineage追踪) │ └── 文档血缘(Chunk→原文溯源)
│ │
└── 核心原则: 数据质量决定分析上限 └── 核心原则: 解析质量决定RAG上限
→ 风控模型再好,喂垃圾数据也不行 → Embedding再好,Chunk是碎片也没用
→ 银行花50%预算在数据治理 → RAG工程80%精力在数据处理
→ "Data is the new oil" → "Parsing is the new bottleneck"
为什么Day 5学过RAG还要再学3天
| Day 5学的 | Day 19-21补充的 |
|---|---|
| RAG架构全貌(Naive RAG/Advanced RAG/Modular RAG) | 深入每个环节的工程细节 |
| Chunking概念(知道有这回事) | 7种Chunking策略对比、实验数据、最佳实践 |
| Embedding选型(了解有哪些) | 解析质量如何影响Embedding效果 |
| Reranking概念(知道要做) | Day 20深入:Cross-Encoder/ColBERT/LLM-as-Reranker |
| 评估指标(听说过RAGAS) | Day 21深入:评估框架搭建、持续迭代流程 |
Day 5是"知道RAG是什么",Day 19-21是"能把RAG做到生产级"。差距就像"知道银行要做风控"和"能设计一套实时风控引擎"。
知识点1: 文档解析——为什么这是RAG最难的部分
文档格式挑战全景
真实世界的文档 = 一团混乱的非结构化信息
格式挑战:
├── PDF (地狱级)
│ ├── 原生PDF: 有文字层,但排版信息丢失(多栏/脚注/页眉混在一起)
│ ├── 扫描PDF: 纯图片,需要OCR才能提取文字
│ ├── 混合PDF: 部分页面原生、部分扫描(金融报表常见)
│ ├── 加密PDF: 需要密码或权限才能读取
│ └── 表格PDF: 表格线是画出来的,不是真正的表格结构
│
├── Word/DOCX
│ ├── 样式不规范(用加粗代替标题、手动空格代替缩进)
│ ├── 嵌入对象(图表/公式/OLE对象)
│ ├── 修订模式(Track Changes)残留
│ └── 兼容性问题(WPS/LibreOffice vs MS Office)
│
├── HTML/Web页面
│ ├── 动态渲染(JavaScript生成内容)
│ ├── 广告/导航/侧边栏噪音
│ ├── 分页加载(需要翻页或滚动)
│ └── 结构不标准(div乱嵌套、table做布局)
│
├── PPT/Slides
│ ├── 信息密度极低(一页就几个关键词)
│ ├── 图表/SmartArt无法直接提取文字含义
│ ├── Speaker Notes可能包含关键信息
│ └── 动画/渐进显示增加解析复杂度
│
├── 图片/扫描件
│ ├── OCR准确率受扫描质量影响(倾斜/模糊/低分辨率)
│ ├── 手写体识别困难
│ ├── 印章/水印干扰
│ └── 多语言混排(中英文混合文档)
│
└── 特殊格式
├── Excel (结构化但Embedding不友好)
├── Email (.eml/.msg,含附件嵌套)
├── Markdown/RST (相对友好)
└── 代码文件 (需要语义理解)
为什么PDF解析是"地狱"
PDF的设计初衷是精确还原打印效果,而不是结构化存储信息。这导致:
PDF内部数据结构(简化):
{
"page": 1,
"objects": [
{"type": "text", "x": 72, "y": 700, "content": "第一章", "font": "SimHei-Bold", "size": 18},
{"type": "text", "x": 72, "y": 680, "content": "1.1 背景介绍", "font": "SimHei", "size": 14},
{"type": "text", "x": 72, "y": 650, "content": "本报告旨在...", "font": "SimSun", "size": 12},
{"type": "text", "x": 300, "y": 650, "content": "数据来源:...", "font": "SimSun", "size": 12},
{"type": "line", "x1": 72, "y1": 500, "x2": 520, "y2": 500}, // 这是表格线?分隔线?装饰?
{"type": "image", "x": 72, "y": 300, "width": 448, "height": 180} // 图表?照片?扫描文字?
]
}
核心困难:
- 没有语义标签 — PDF不知道"第一章"是标题,只知道它是18号加粗字
- 多栏布局 — 两列文字的阅读顺序靠坐标推断,经常出错
- 表格没有结构 — 表格线是画出来的线段,需要几何推理才能还原行列
- 脚注/页眉混入正文 — 页面底部的脚注和正文内容在同一层
- 跨页内容 — 一个段落/表格跨两页,中间被页码/页眉打断
扫描件 vs 原生PDF
| 维度 | 原生PDF(Digital-born) | 扫描PDF(Scanned) |
|---|---|---|
| 文字提取 | 直接提取,准确率~100% | 需OCR,准确率85-98% |
| 表格识别 | 需要几何推理 | OCR+几何推理,更难 |
| 处理速度 | 快(毫秒级/页) | 慢(秒级/页) |
| 中文处理 | 字体嵌入则OK | OCR中文准确率较低 |
| 金融场景 | 电子生成的报表/合同 | 历史档案/签章扫描件/银行回单 |
| 推荐工具 | PyMuPDF/pdfplumber | Tesseract+Layout分析 或 VLM |
知识点2: 2025-2026文档解析工具链
工具全景对比
| 工具 | 类型 | 核心优势 | 主要局限 | 适用场景 | 价格(2026) |
|---|---|---|---|---|---|
| Unstructured.io | 开源+商业 | 格式覆盖最全(25+格式)、Pipeline完善 | 表格精度一般、大文档慢 | 通用企业场景 | 开源免费 / API $0.01/页 |
| LlamaParse | 商业API | 基于LLM的智能解析、表格强 | 依赖API、成本较高 | 复杂PDF/表格密集 | 1000页免费/天 → $3/1000页 |
| Docling (IBM) | 开源 | 学术论文/技术文档强、布局分析好 | 非英语支持弱 | 技术/学术文档 | 免费开源 |
| Marker | 开源 | PDF→Markdown转换质量高、速度快 | 只支持PDF/图片 | PDF批量转换 | 免费开源 |
| PyMuPDF (fitz) | 开源库 | 速度极快、底层控制力强 | 需要自己处理布局逻辑 | 原生PDF文字提取 | 免费开源 |
| pdfplumber | 开源库 | 表格提取精准、API简洁 | 只支持原生PDF | PDF表格提取 | 免费开源 |
| Azure Doc Intelligence | 商业API | 企业级、预训练模型多(发票/收据/身份证) | 锁定Azure生态 | 企业合规场景 | $1.5/1000页 |
| Google Document AI | 商业API | OCR强、多语言、自定义模型训练 | 锁定GCP | 多语言/扫描件 | $1.5/1000页 |
| 多模态VLM解析 | LLM API | 理解能力最强、能处理任何格式 | 成本高、速度慢、幻觉风险 | 复杂/非标文档 | ~$0.03-0.10/页 |
各工具深度分析
Unstructured.io — 当前事实标准
定位: RAG数据处理的"瑞士军刀"
核心能力:
├── 支持格式: PDF/DOCX/PPTX/HTML/EML/CSV/TSV/TXT/MD/RST/XML/EPUB...
├── 解析策略:
│ ├── fast: 基于规则的快速提取(适合原生PDF)
│ ├── hi_res: Layout分析模型+OCR(适合复杂布局)
│ ├── ocr_only: 纯OCR(适合扫描件)
│ └── auto: 自动判断使用哪种策略
├── 元素类型:
│ ├── Title / NarrativeText / ListItem
│ ├── Table / FigureCaption / Image
│ ├── Header / Footer / PageBreak
│ └── Formula / Address / EmailAddress
├── Chunking内置:
│ ├── by_title: 按标题分段
│ ├── by_page: 按页分段
│ ├── by_similarity: 按语义相似度分段
│ └── basic: 固定大小分段
└── 集成:
├── LangChain / LlamaIndex 原生支持
├── 输出格式: JSON / CSV / Markdown
└── 部署: 本地Docker / Serverless API
关键代码示例:
from unstructured.partition.pdf import partition_pdf
from unstructured.chunking.title import chunk_by_title
# Step 1: 解析PDF
elements = partition_pdf(
filename="financial_report.pdf",
strategy="hi_res", # 高精度模式
infer_table_structure=True, # 推断表格结构
languages=["chi_sim", "eng"],# 中英文OCR
extract_images_in_pdf=True, # 提取图片
)
# Step 2: 按标题Chunking
chunks = chunk_by_title(
elements,
max_characters=1500, # 每个Chunk最大字符数
new_after_n_chars=1000, # 超过此长度开始寻找分割点
overlap=200, # 重叠字符数
combine_text_under_n_chars=200, # 太短的段落合并
)
# Step 3: 查看结果
for chunk in chunks:
print(f"Type: {chunk.category}")
print(f"Text: {chunk.text[:100]}...")
print(f"Metadata: {chunk.metadata.to_dict()}")
LlamaParse — LLM驱动的智能解析
定位: "用AI理解文档"而非"用规则提取文字"
核心差异:
传统解析: PDF → OCR/规则提取 → 原始文本 → 后处理
LlamaParse: PDF → 多模态LLM理解 → 结构化Markdown → 直接可用
优势:
├── 表格理解: 能理解复杂合并单元格、嵌套表格
├── 图表描述: 自动生成图表的文字描述
├── 上下文保留: 理解"同上"/"见表1"等指代关系
├── 格式清洁: 输出干净的Markdown,无需后处理
└── 指令定制: 可以通过Prompt指导解析行为
局限:
├── 依赖API: 无法本地部署(截至2026Q1)
├── 成本: 大规模处理成本显著
├── 延迟: 每页2-5秒(vs传统工具100ms/页)
├── 幻觉: LLM可能"补全"不存在的内容
└── 数据隐私: 文档需要上传到云端
多模态VLM解析 — 2025-2026的新范式
核心思路: 直接把PDF页面截图喂给GPT-4o/Claude,让VLM"看"文档
工作流程:
PDF页面 → 转为图片(300DPI) → 发送给VLM → 返回结构化文本
Prompt模板:
"请将这张文档图片转换为结构化Markdown。
保留所有表格(用Markdown表格格式),
保留标题层级,
图表请用文字描述其内容和关键数据点。"
优势:
├── 理解力最强: 能处理任何人类能读懂的文档
├── 零配置: 不需要针对格式写规则
├── 表格/图表: 直接"看懂"复杂布局
└── 多语言: 天然支持任何语言
成本估算(2026):
GPT-4o: ~$0.03/页 (低分辨率) / ~$0.08/页 (高分辨率)
Claude: ~$0.04/页 (标准) / ~$0.10/页 (高分辨率)
Gemini: ~$0.01/页 (价格最低)
适用场景:
├── 非标文档(每种格式不同): 合同/法律文件/手写笔记
├── 小批量高价值: 100页以内的关键文档
├── 质量>成本: 金融合规/法律审计场景
└── 兜底策略: 传统工具解析失败时的Fallback
2026生产环境推荐组合
推荐架构: 分层解析策略(Tiered Parsing)
Tier 1 — 快速通道 (80%文档):
原生PDF/DOCX/HTML → Unstructured.io (fast模式) → 标准Chunks
成本: ~$0.001/页, 速度: 100ms/页
Tier 2 — 精确通道 (15%文档):
复杂PDF/表格密集 → Unstructured.io (hi_res) + pdfplumber → 结构化Chunks
成本: ~$0.01/页, 速度: 1-2s/页
Tier 3 — 智能通道 (5%文档):
扫描件/非标格式/解析失败 → VLM(GPT-4o/Claude) → 高质量Chunks
成本: ~$0.05/页, 速度: 3-5s/页
路由逻辑:
if 文档是原生PDF/DOCX/HTML:
→ Tier 1
elif 包含复杂表格 or Tier 1质量分<阈值:
→ Tier 2
elif 扫描件 or Tier 2失败:
→ Tier 3
else:
→ 人工审核队列
知识点3: Chunking工程深度
为什么Chunking如此关键
Chunking的本质: 决定"检索单元"的粒度和质量
类比: 图书馆整理书架
├── 粒度太粗(整本书作为一个单元):
│ → 搜"利率计算公式",返回整本《金融数学》—— 信息淹没
│
├── 粒度太细(每句话作为一个单元):
│ → 搜"利率计算公式",返回"其中r为年利率" —— 缺乏上下文
│
└── 粒度合适(按章节/主题组织):
→ 搜"利率计算公式",返回完整的利率计算章节 —— 既精准又完整
Chunking直接影响:
1. 检索精度: Chunk太大 → 语义稀释 → 召回不精准
2. 检索召回: Chunk太小 → 上下文丢失 → 无法回答复杂问题
3. LLM生成质量: 喂给LLM的Context质量 = Chunk质量
4. Token成本: Chunk越大 → 每次检索消耗的Token越多
7种Chunking策略详解
策略1: Fixed-Size Chunking (固定大小切分)
原理: 按固定字符数/Token数切分,到达阈值就切
优点: 实现最简单、速度最快、行为可预测
缺点: 完全无视语义边界,可能把一句话/一个段落切成两半
参数:
chunk_size: 每个Chunk的大小 (通常500-2000字符 / 128-512 tokens)
chunk_overlap: 相邻Chunk的重叠部分 (通常10-20% of chunk_size)
示例:
原文: "利率风险是银行面临的主要风险之一。|切割点|当市场利率上升时..."
适用: 快速原型、对质量要求不高的场景
不适用: 任何生产环境(通常作为Baseline)
策略2: Recursive Character Splitting (递归字符切分)
原理: 按优先级尝试不同分隔符切分,尽量在自然边界切割
分隔符优先级(默认):
["\n\n", "\n", " ", ""]
→ 先尝试按段落切 → 段落太大则按行切 → 行太长则按词切
LangChain实现:
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", "。", "!", "?", ";", " ", ""]
)
中文优化: 添加中文句号/分号作为分隔符
优点: 比Fixed好很多,尊重基本文本结构
缺点: 仍然不理解语义,只是在"语法边界"切割
适用: 大多数场景的默认选择,性价比最高
策略3: Semantic Chunking (语义切分)
原理: 用Embedding计算相邻句子的语义相似度,在语义跳变处切割
工作流程:
1. 将文档按句子拆分
2. 计算每个句子的Embedding向量
3. 计算相邻句子的余弦相似度
4. 相似度低于阈值的位置 = 语义边界 = 切割点
示例:
句子1: "银行的核心业务是存贷款" ─┐ 相似度0.85 → 不切
句子2: "贷款利率由央行基准利率决定" ─┘
句子3: "区块链技术正在改变金融行业" ← 与上文相似度0.32 → 切!
关键参数:
breakpoint_threshold: 语义跳变阈值 (percentile方式: 通常取5th-10th percentile)
embedding_model: 用于计算语义的Embedding模型
buffer_size: 计算相似度时的滑动窗口大小
LlamaIndex实现:
from llama_index.core.node_parser import SemanticSplitterNodeParser
splitter = SemanticSplitterNodeParser(
buffer_size=1,
breakpoint_percentile_threshold=95,
embed_model=embed_model,
)
优点: Chunk内语义一致性高,检索质量显著提升
缺点: 需要计算Embedding(慢/有成本),Chunk大小不可控
适用: 对检索质量要求高、文档语义跳跃大的场景
策略4: Document-Structure Chunking (基于文档结构)
原理: 利用文档自身结构(标题/章节/列表)作为切割依据
工作流程:
1. 解析文档结构(识别H1/H2/H3标题层级)
2. 按标题层级切分(每个H2下的内容 = 一个Chunk)
3. 保留层级关系作为Metadata
示例(金融报表):
H1: 年度报告 ← 不切(太粗)
├── H2: 公司概况 ← Chunk 1
├── H2: 财务摘要 ← Chunk 2
│ ├── H3: 收入分析 ← Chunk 2a (如果H2太大则细切)
│ └── H3: 利润分析 ← Chunk 2b
└── H2: 风险因素 ← Chunk 3
Metadata附加:
{
"chunk_text": "收入分析的具体内容...",
"title_path": "年度报告 > 财务摘要 > 收入分析",
"heading_level": 3,
"document_name": "2025年报.pdf",
"page_numbers": [12, 13]
}
优点: 语义最自然(文档作者已经做好了分段)
缺点: 依赖文档有良好结构(很多文档结构混乱)
适用: 技术文档/报告/法规等结构化程度高的文档
策略5: Sliding Window Chunking (滑动窗口)
原理: 固定窗口大小,按固定步长滑动,生成大量重叠Chunk
参数:
window_size: 窗口大小 (如1000字符)
step_size: 步长 (如200字符)
→ 重叠率 = 1 - step/window = 80%
效果:
文档: [A B C D E F G H I J K L M N O P Q R S T]
Chunk1: [A B C D E F G H I J]
Chunk2: [C D E F G H I J K L] ← 与Chunk1重叠80%
Chunk3: [E F G H I J K L M N]
...
优点: 信息完全不丢失(每个位置被多个Chunk覆盖)
缺点: Chunk数量爆炸(是原文的window/step倍),存储和检索成本高
适用: 关键文档/法律合同(宁可多花钱也不能漏信息)
策略6: Parent-Child Chunking (父子层级)
原理: 创建两层Chunk——小的Child Chunk用于精确检索,大的Parent Chunk用于提供上下文
工作流程:
1. 先按较大粒度创建Parent Chunks (如2000字符)
2. 每个Parent内再切分为多个Child Chunks (如500字符)
3. 检索时: 用Child Chunk匹配查询
4. 返回时: 返回对应的Parent Chunk给LLM
类比:
图书馆 → 先用索引卡片(Child)找到相关位置 → 再把整本参考书(Parent)拿给读者
示例:
Parent Chunk (2000字符): "第三章 风险管理框架 ... [完整章节内容]"
├── Child 1 (500字符): "3.1 市场风险度量方法 VaR..."
├── Child 2 (500字符): "3.2 信用风险评估模型..."
├── Child 3 (500字符): "3.3 操作风险分类..."
└── Child 4 (500字符): "3.4 流动性风险管理..."
查询: "VaR计算方法" → 匹配Child 1 → 返回Parent给LLM
LlamaIndex实现:
from llama_index.core.node_parser import SentenceWindowNodeParser
node_parser = SentenceWindowNodeParser.from_defaults(
window_size=3, # 每个句子窗口包含前后3个句子
original_text_metadata_key="window",
)
优点: 检索精度高(小Chunk) + 上下文充分(大Parent)
缺点: 存储翻倍、实现复杂度增加
适用: 生产级RAG系统的首选策略之一
策略7: Agentic Chunking (Agent驱动)
原理: 用LLM作为Agent来决定如何切分——让AI"理解"后决定哪里该切
工作流程:
1. LLM逐句/逐段阅读文档
2. 判断当前内容和已有Chunk是否属于同一主题
3. 如果是 → 追加到当前Chunk
4. 如果不是 → 结束当前Chunk,开始新Chunk
5. (可选) LLM为每个Chunk生成摘要作为Metadata
Prompt示例:
"你是一个文档分段专家。请判断以下新段落是否与当前Chunk属于同一主题:
当前Chunk主题: {current_chunk_summary}
新段落: {new_paragraph}
回答YES(属于同一主题,继续追加)或NO(不同主题,开始新Chunk)。"
优点: 语义理解最深、分段质量最高
缺点: 成本极高(每个切割点都要调用LLM)、速度极慢、不确定性
适用: 少量高价值文档(金融合规文件/法律合同)、离线预处理
Chunk Size实验数据
基于多个公开基准测试的经验数据(2025-2026):
Chunk Size vs 检索质量 (Recall@10):
┌─────────────┬──────────────┬──────────────┬──────────────┐
│ Chunk Size │ 通用文档 │ 技术文档 │ 金融报表 │
├─────────────┼──────────────┼──────────────┼──────────────┤
│ 128 tokens │ 0.62 (太碎片) │ 0.58 │ 0.55 │
│ 256 tokens │ 0.71 │ 0.68 │ 0.65 │
│ 512 tokens │ 0.78 ★最佳 │ 0.76 ★最佳 │ 0.72 │
│ 1024 tokens │ 0.75 │ 0.74 │ 0.78 ★最佳 │
│ 2048 tokens │ 0.68 (太稀释) │ 0.70 │ 0.75 │
└─────────────┴──────────────┴──────────────┴──────────────┘
关键发现:
├── 通用文档: 512 tokens(约300-400中文字)效果最好
├── 技术文档: 512 tokens,但需配合结构化切分
├── 金融报表: 偏大(1024 tokens),因为表格/数据需要完整上下文
├── 法律合同: 偏大(1024-2048),条款不能切断
└── 对话/FAQ: 偏小(256 tokens),QA对天然就是好的Chunk
经验法则:
最优Chunk Size ≈ 一个完整"知识点"的平均长度
→ 需要在你的特定数据上做实验,没有万能参数
Overlap设计
Overlap的作用: 防止关键信息恰好落在切割边界
Overlap比例推荐:
├── Fixed/Recursive: 10-20% of chunk_size
├── Semantic: 通常不需要(语义完整)
├── Parent-Child: 通过父Chunk天然覆盖
└── Sliding Window: 通过步长控制,通常50-80%
Overlap过小的问题:
"...利率上调50个基点。|切断|这导致银行净息差扩大..."
→ Chunk 1只知道"上调50基点",不知道影响
→ Chunk 2只知道"净息差扩大",不知道原因
Overlap过大的问题:
→ Chunk数量膨胀
→ 存储成本增加
→ 检索时同一内容出现多次(需要去重)
Metadata附加策略
Metadata = Chunk的"身份证" —— 检索时的重要信号
必须附加的Metadata:
├── source: 来源文件路径/URL
├── page_number: 原文页码(用于溯源)
├── chunk_index: 在文档中的位置序号
├── created_at: 解析时间
└── document_type: 文档类型(报告/合同/法规)
推荐附加的Metadata:
├── title_path: 标题层级路径("年报>财务>收入分析")
├── section_type: 内容类型(正文/表格/列表/脚注)
├── language: 语言(zh/en/mixed)
├── confidence: 解析置信度(OCR场景)
├── summary: LLM生成的Chunk摘要(用于Hybrid Search)
└── entities: 提取的命名实体(公司名/金额/日期)
金融场景特有Metadata:
├── fiscal_period: 财务期间(2025Q3/FY2025)
├── company: 公司名称/股票代码
├── document_class: 文档分类(10-K/招股书/审计报告)
├── currency: 涉及币种
└── regulatory_ref: 关联法规编号
Metadata用于:
1. 过滤检索范围(只搜"2025年报告")
2. 结果排序(优先最新文档)
3. 溯源(用户点"查看来源"跳转原文)
4. 权限控制(按文档密级/部门过滤)
知识点4: 表格与结构化数据处理
表格是RAG的"老大难"
为什么表格难处理:
├── 检测难: PDF中表格由线段/文字组成,需要几何推理判断这是不是表格
├── 提取难: 合并单元格/嵌套表格/跨页表格破坏结构
├── 表示难: 表格转文字后丢失行列关系
└── 检索难: 表格内容的Embedding效果差(数字+短文本)
金融场景尤其严重:
银行年报 → 50%以上内容是表格
监管报表 → 几乎全是表格
合同条款 → 费率表/还款计划表
交易记录 → 流水/对账单
表格处理流程
Step 1: 表格检测(Table Detection)
方法:
├── 规则: 检测PDF中的线段组合(有线表格)
├── 模型: DETR/TableTransformer(无线表格也能检测)
└── VLM: 直接问LLM"这页有表格吗?在哪里?"
Step 2: 表格提取(Table Extraction)
方法:
├── pdfplumber: 最精准的开源表格提取(原生PDF)
├── Camelot: 基于OpenCV的表格提取
├── img2table: 图片中的表格提取
└── VLM: GPT-4o/Claude直接"看"表格
Step 3: 表格表示(Table Representation)
方案对比:
├── Table-as-Markdown:
│ | 项目 | 2024 | 2025 |
│ |------|------|------|
│ | 收入 | 100M | 120M |
│ 优点: Embedding效果较好、LLM理解力强
│ 缺点: 复杂表格表达力不足
│
├── Table-as-JSON:
│ {"headers":["项目","2024","2025"],"rows":[["收入","100M","120M"]]}
│ 优点: 结构精确、可编程处理
│ 缺点: Embedding效果差
│
├── Table-as-NaturalLanguage:
│ "2024年收入为100M,2025年收入增长至120M..."
│ 优点: Embedding效果最好、语义最完整
│ 缺点: 丢失精确数值关系
│
└── 推荐: Markdown存储 + NL描述作为辅助Metadata
嵌套表格/跨页表格
嵌套表格(Nested Table):
银行产品费率表常见: 表格内嵌套子表格
┌─────────────────────────────────┐
│ 产品类别 │ 子产品 │ 费率 │
├──────────┼────────┼──────────────┤
│ 贷款 │ 房贷 │ LPR+30bp │
│ ├────────┼──────────────┤
│ │ 消费贷 │ 5.8%-12.6% │
├──────────┼────────┼──────────────┤
│ 存款 │ 活期 │ 0.2% │
│ ├────────┼──────────────┤
│ │ 定期 │ 1.5%-2.65% │
└─────────────────────────────────┘
处理策略: 拆分为扁平化表格 + 保留层级信息作为前缀
→ "贷款-房贷: LPR+30bp"
→ "贷款-消费贷: 5.8%-12.6%"
→ "存款-活期: 0.2%"
跨页表格(Cross-Page Table):
问题: 表格从第5页开始,第6页继续,中间被页眉/页脚打断
处理策略:
1. 检测: 页面底部有未闭合的表格 → 标记为"待续"
2. 合并: 下一页顶部的表格行与上一页合并
3. 验证: 检查列数是否一致、数据类型是否匹配
工具支持:
├── pdfplumber: 可以手动实现跨页合并
├── Unstructured: 部分支持(不完美)
├── LlamaParse: 较好支持(LLM理解上下文)
└── VLM: 发送相邻两页图片,让LLM合并
知识点5: 多语言与金融文档
中英文混合处理
金融文档中英混合的挑战:
"本行2025年净利息收入(Net Interest Income, NII)达到RMB 523.6亿元,
同比增长8.2%。Non-Performing Loan(NPL) ratio为1.38%,
较上年末下降12bps。"
问题:
├── 分词: 中文需要分词、英文按空格分,混合时边界模糊
├── Embedding: 不同模型对中英混合的表示能力差异大
├── Chunking: 中文句号"。"vs英文句号".",分隔符需要同时考虑
└── OCR: 中英混合区域的识别准确率下降
推荐方案:
├── Embedding模型选型:
│ ├── 优选: BGE-M3 / Cohere multilingual-v3 / OpenAI text-embedding-3-large
│ ├── 次选: E5-mistral (英文强中文一般)
│ └── 避免: 纯英文模型(all-MiniLM等)
│
├── Chunking分隔符配置:
│ separators = [
│ "\n\n", # 段落分隔(通用)
│ "\n", # 换行(通用)
│ "。", # 中文句号
│ "!", "?", # 中文感叹/问号
│ ";", # 中文分号
│ ". ", # 英文句号+空格
│ "! ", "? ", # 英文感叹/问号
│ "; ", # 英文分号
│ " ", # 空格
│ "", # 逐字符(最后手段)
│ ]
│
└── OCR配置:
languages=["chi_sim", "chi_tra", "eng"] # 简体+繁体+英文
金融文档特殊处理
金融报表(年报/10-K/招股书):
├── 特点: 高度结构化、表格密集、数字精确、术语密集
├── 处理:
│ ├── 表格: 必须精确提取,一个小数点错误可能导致严重后果
│ ├── 脚注: 不能丢弃!脚注往往包含关键会计政策说明
│ ├── 图表: 需要描述性文字(VLM生成)
│ └── MD&A: 管理层讨论部分是最有价值的文字段
└── Chunking: 按报表章节结构切分,每个财务科目+注释保持完整
合同/法律文件:
├── 特点: 条款编号严格、交叉引用多("见第3.2(b)条")
├── 处理:
│ ├── 条款编号保留: "第3.2(b)条"必须出现在Chunk中
│ ├── 定义条款: 单独提取为参考Chunk(很多术语在合同开头定义)
│ ├── 交叉引用: Metadata中标注引用关系
│ └── 签章页: 通常无需索引
└── Chunking: 按条款编号切分,不切断完整条款
监管文件(巴塞尔/MiCA/SEC规则):
├── 特点: 条文编号、大量"应当"/"必须"/"不得"
├── 处理:
│ ├── 条文编号作为Metadata: {"regulation": "Basel III", "article": "4.2.1"}
│ ├── 生效日期: 标注每条规定的生效/过渡期
│ └── 影响范围: 标注适用机构类型
└── Chunking: 按条文编号切分,保留上下文(条文间有依赖)
OCR + LLM后处理
扫描件处理流程(2026最佳实践):
Step 1: 预处理
├── 倾斜校正(Deskew)
├── 二值化(Binarization)
├── 去噪(Denoise)
└── 分辨率增强(Super-Resolution,如果<200DPI)
Step 2: OCR引擎
├── Tesseract 5.x: 开源、支持中文、需要训练数据
├── PaddleOCR: 百度开源、中文最强、支持版面分析
├── EasyOCR: 多语言、轻量级
└── 商业: Azure/Google/AWS (准确率最高、有SLA)
Step 3: LLM后处理(关键创新)
将OCR原始输出喂给LLM进行纠错和结构化:
Prompt: "以下是一份金融文档的OCR结果,可能存在识别错误。
请修正明显的OCR错误(如: O和0混淆、l和1混淆),
恢复正确的段落结构,保持所有数字精确。
原始OCR: {ocr_text}"
效果:
├── OCR原始: "净利润达到52l.6亿元,同比增长8.2o%"
└── LLM纠正: "净利润达到521.6亿元,同比增长8.2%"
Step 4: 质量验证
├── 字符置信度 < 阈值的区域标记为"低置信"
├── 数字区域交叉校验(总数=各项之和)
└── 低置信区域回退到VLM重新解析
知识点6: 数据Pipeline设计
完整ETL流程
文档RAG的ETL Pipeline:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 1. 采集 │→ │ 2. 解析 │→ │ 3. 切分 │→ │ 4. 增强 │→ │ 5. 索引 │→ │ 6. 维护 │
│ Ingest │ │ Parse │ │ Chunk │ │ Enrich │ │ Index │ │ Maintain │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
详细流程:
1. 采集(Ingest)
├── 数据源: 文件系统/S3/SharePoint/Confluence/邮件
├── 格式检测: MIME type识别 + Magic number验证
├── 去重: 文件Hash去重(避免重复处理)
├── 元数据提取: 文件名/大小/修改时间/作者
└── 队列: 推入处理队列(按优先级排序)
2. 解析(Parse)
├── 路由: 根据格式选择解析策略(Tier 1/2/3)
├── 文字提取: 获取原始文本
├── 结构识别: 标题/段落/列表/表格/图片
├── 表格处理: 检测 → 提取 → 转Markdown
├── 图片处理: OCR/VLM描述
└── 清洗: 去页眉页脚/水印/重复空行
3. 切分(Chunk)
├── 策略选择: 根据文档类型选择Chunking策略
├── 参数配置: chunk_size/overlap/分隔符
├── 执行切分: 生成Chunk列表
├── Metadata附加: 来源/页码/标题路径/类型
└── 质量过滤: 过滤太短(<50字)/太长(>3000字)/乱码Chunk
4. 增强(Enrich)
├── Embedding生成: 每个Chunk → 向量
├── 摘要生成: (可选) LLM为每个Chunk生成摘要
├── 实体提取: (可选) 提取命名实体
├── 关键词提取: (可选) 用于BM25混合检索
└── 分类标注: (可选) 自动分类(财务/法律/技术)
5. 索引(Index)
├── 向量写入: 写入向量数据库(Pinecone/Weaviate/Qdrant)
├── 全文写入: 写入Elasticsearch/OpenSearch(BM25)
├── Metadata写入: 写入关系数据库(用于过滤)
├── 原文存储: 原始文档存S3/MinIO(用于溯源)
└── 索引验证: 抽样查询验证索引正确性
6. 维护(Maintain)
├── 增量更新: 新文档 → 检测 → 处理 → 追加索引
├── 版本管理: 文档更新时 → 旧Chunk标记过期 → 新Chunk替换
├── 过期清理: 定期清理过期/删除的文档Chunk
├── 重新索引: Embedding模型升级时全量重新Embedding
└── 质量监控: 持续监控检索命中率/用户反馈
增量更新策略
增量更新是生产环境的核心需求(不能每次全量重建):
方案1: 文件级增量
├── 检测: 文件Hash/修改时间变化
├── 处理: 整个文件重新解析 → 删除旧Chunks → 插入新Chunks
├── 优点: 实现简单
└── 缺点: 一个字改动就重处理整个文件
方案2: Chunk级增量(推荐)
├── 检测: 对比新旧文件的Chunk内容Hash
├── 处理:
│ ├── 新增Chunk → 解析+Embedding+索引
│ ├── 删除Chunk → 从索引中移除
│ ├── 修改Chunk → 更新Embedding+索引
│ └── 未变Chunk → 不处理
├── 优点: 最小化处理量
└── 缺点: 实现复杂(需要Chunk级对齐)
方案3: Append-Only + TTL
├── 策略: 新版本Chunk直接追加,旧版本标记过期时间
├── 检索: 优先返回最新版本
├── 清理: 定期GC过期Chunks
├── 优点: 无删除操作,安全性高
└── 缺点: 存储膨胀
金融场景推荐:
日常文档 → 方案2(Chunk级增量)
监管文件 → 方案3(Append-Only,保留历史版本用于审计)
年报/季报 → 方案1(低频更新,全量重处理可接受)
质量检查Gate
质量Gate: 每个阶段的质量检查关卡(不合格则不进入下一阶段)
Gate 1: 解析质量检查
├── 字符准确率: OCR置信度 > 95%(金融场景要求>98%)
├── 结构完整性: 标题层级连续、表格行列数合理
├── 内容完整性: 提取字符数 vs 预期字符数(基于页数估算)
├── 编码正确性: 无乱码/特殊字符异常
└── 不通过: 回退到更高Tier重新解析 或 进入人工审核队列
Gate 2: Chunk质量检查
├── 长度分布: 检查是否存在异常短/长的Chunk
├── 语义完整性: 抽样检查Chunk是否是完整语义单元
├── Metadata完整性: 必填字段不能为空
├── 重复检测: MinHash/SimHash去除近似重复Chunk
└── 不通过: 调整Chunking参数重新切分
Gate 3: Embedding质量检查
├── 维度验证: 向量维度正确
├── 异常检测: 检测全零向量/异常值
├── 语义验证: 抽样对比"已知相关Chunk对"的余弦相似度
└── 不通过: 检查Embedding模型是否正常
Gate 4: 索引质量检查
├── 查询验证: 用预设测试查询验证检索结果
├── 召回验证: 已知答案应该能被检索到
├── 延迟验证: 查询响应时间在SLA内
└── 不通过: 检查索引配置/数据分布
质量Dashboard指标:
├── 解析成功率: 成功解析文档数 / 总文档数
├── Chunk质量分布: 优/良/差的比例
├── 平均Chunk长度: 监控是否偏移
├── 检索命中率: 测试查询的命中率趋势
└── 用户反馈率: "答案不相关"的反馈频率
今日思考
思考1: "数据工程师"才是RAG系统最重要的角色
一个常见误区: 团队把最优秀的人放在"模型选型"和"Prompt优化"上,
让初级工程师负责"数据处理"。结果呢?
模型: GPT-4o (最强)
Prompt: 精心调优
数据: PDF解析一堆乱码,表格全部丢失,Chunk随机切割
→ 输出质量: 垃圾
正确的资源分配:
数据处理(解析+Chunking): 60%的工程精力
检索优化(Embedding+Reranking): 25%
生成优化(Prompt+模型选型): 15%
这和我10年金融系统经验完全一致:
银行数据仓库项目中,ETL团队永远是最大的团队,
因为数据质量问题永远是最多的问题。
报表错了?80%概率是数据源头就错了。
思考2: VLM解析是"降维打击",但成本曲线决定了分层策略的必要
2025年: GPT-4o直接"看"PDF的效果已经超过大多数传统解析工具
2026年: 多模态模型价格持续下降,但距离"全量VLM解析"仍有距离
成本对比(处理10万页文档):
传统解析(Unstructured fast): ~$100
传统解析(Unstructured hi_res): ~$1,000
VLM解析(GPT-4o): ~$5,000-10,000
对于银行/金融机构:
每年要处理的文档: 数百万页
全用VLM: 每年$50,000-100,000 (仅解析成本)
分层策略: 每年$5,000-10,000 (80%走传统,20%走VLM)
结论: 分层解析策略不是"落后",而是工程上的最优解。
就像银行不会对每笔交易都做最高级别审查——
小额走快速通道,大额走人工审核。
思考3: Chunking没有银弹——必须在自己的数据上做实验
我见过太多团队陷入"参数调优陷阱":
"用512还是1024?" "Overlap用10%还是20%?" "用Semantic还是Recursive?"
现实是: 没有通用最优解,因为:
1. 不同文档类型的最优参数不同(法律合同 vs 技术文档)
2. 不同查询类型的最优参数不同(事实查询 vs 分析查询)
3. 不同Embedding模型的最优参数不同(ada-002 vs BGE-M3)
4. 甚至同一类文档,不同来源的最优参数也不同
正确做法:
1. 建立评估基准(Golden Set): 100组<问题, 正确答案, 来源文档>
2. 用3-5种策略分别处理
3. 跑自动化评估(RAGAS/DeepEval)
4. 选择在你的场景下最优的策略
5. 持续监控线上效果,定期迭代
这就是工程与研究的区别:
研究: 找到理论最优解
工程: 在约束条件(成本/延迟/数据特性)下找到实际最优解
面试表达
Q: 你如何设计一个生产级RAG系统的数据处理Pipeline?
结构化回答(2分钟版):
1. 分层解析架构:
"我会设计三层解析策略:
- Tier 1(80%文档): 用Unstructured.io快速模式处理标准格式
- Tier 2(15%文档): 用hi_res模式+pdfplumber处理复杂表格
- Tier 3(5%文档): 用GPT-4o/Claude多模态解析处理扫描件和非标文档
路由逻辑基于文档格式和Tier 1的质量评分自动决策。"
2. Chunking策略:
"默认使用Recursive Character Splitting + 中文分隔符优化。
对于结构化文档(年报/法规)使用Document-Structure切分。
在检索质量要求高的场景使用Parent-Child策略。
关键是: 在自己的数据上做AB实验,没有万能参数。"
3. 质量保障:
"每个阶段设置Quality Gate:
- 解析后: 检查字符准确率、结构完整性
- Chunking后: 检查长度分布、语义完整性
- 索引后: 用Golden Set测试检索召回
不合格的数据不进入下一阶段。"
4. 持续运维:
"Chunk级增量更新避免全量重建。
监控检索命中率和用户反馈,持续迭代解析和Chunking策略。"
追问准备:
Q: 表格怎么处理?
→ "Markdown存储 + 自然语言描述作为辅助Metadata。跨页表格通过上下文检测合并。"
Q: 中英混合文档怎么办?
→ "Embedding选BGE-M3/Cohere multilingual,Chunking分隔符同时覆盖中英文标点。"
Q: 如何评估解析质量?
→ "对比OCR输出与原文的字符准确率,表格提取用单元格级别的F1 Score。"
Q: Chunking策略怎么选?为什么?
"选择Chunking策略的核心考虑:
1. 看文档类型:
- 结构良好(有标题层级): Document-Structure优先
- 结构混乱(纯文本/聊天): Recursive Character作为Baseline
- 高价值少量文档: 可以用Semantic甚至Agentic
2. 看查询模式:
- 精确事实查询('利率是多少'): 小Chunk(256-512 tokens)
- 分析型查询('风险因素有哪些'): 大Chunk(1024-2048 tokens)
- 混合查询: Parent-Child策略(小Child检索 + 大Parent返回)
3. 看成本约束:
- Semantic Chunking需要为每个句子计算Embedding
- Agentic Chunking需要为每个切割点调LLM
- 生产环境通常用Recursive + 结构化的组合
4. 最终靠实验:
建Golden Set,跑评估,选在自己数据上表现最好的。"
实操练习建议
1. 用Unstructured.io解析一份金融PDF:
pip install unstructured[all-docs]
→ 下载一份上市公司年报PDF
→ 分别用fast和hi_res模式解析,对比结果
→ 重点关注表格提取的准确性
2. 对比3种Chunking策略:
→ 对同一文档分别用Fixed(512)/Recursive(512)/Semantic切分
→ 人工检查10个Chunk的语义完整性
→ 用同样的测试查询对比检索效果
3. 用VLM解析复杂文档:
→ 将一页包含表格的PDF转为图片
→ 发送给GPT-4o/Claude,要求转为结构化Markdown
→ 对比传统工具的提取结果
4. 搭建简单的Quality Gate:
→ 写一个脚本检查Chunk长度分布
→ 检测异常短/长Chunk并标记
→ 计算Chunk去重后的独立信息比例
资源推荐
| 资源 | 说明 | 链接 |
|---|---|---|
| Unstructured.io文档 | 最全的文档解析开源框架 | unstructured.io/docs |
| LlamaParse文档 | LLM驱动的解析API | docs.cloud.llamaindex.ai |
| Docling (IBM) | 开源文档理解框架 | github.com/DS4SD/docling |
| Greg Kamradt - Chunking视频 | 5种Chunking策略对比实验 | YouTube搜索 |
| Pinecone Chunking指南 | 实操导向的Chunking教程 | pinecone.io/learn/chunking-strategies |
| LlamaIndex文档 | Node Parser完整文档 | docs.llamaindex.ai |
| RAGAS文档 | RAG评估框架(Day 21深入) | docs.ragas.io |
| pdfplumber | Python PDF表格提取库 | github.com/jsvine/pdfplumber |
明日预告
Day 20: 生产级RAG(2) — Retrieval优化与Reranking
预习问题:
1. 为什么Embedding检索(向量搜索)不够,还需要Reranking?
2. Cross-Encoder和Bi-Encoder有什么区别?为什么Reranker用Cross-Encoder?
3. Hybrid Search(向量+BM25)比纯向量搜索好多少?在什么场景下?
连接点:
Day 19(解析+Chunking) → Day 20(检索+Reranking)
→ 今天解决"数据怎么准备",明天解决"数据怎么找到"
→ Chunk质量决定了检索的上限,Reranking决定了检索的下限
→ 一个完整的RAG检索链路: Query → Embedding → ANN → Rerank → Top-K → LLM