RAG 数据准备不是“文本切块加 Embedding”,而是一条包含解析、结构恢复、清洗、切分、元数据、版本、权限和质量门禁的数据 Pipeline。输入结构已经损坏时,调整 Chunk Size 只是在更细粒度地索引噪声。

版面解析需要保留标题层级、段落、列表、表格、公式、页码与阅读顺序。普通数字文本型文档可以采用通用解析器;扫描件、复杂表格和多栏排版需要OCR或版面模型;低置信度页面应进入降级与人工复核。
Code
原始文档 → 页面与版面识别 → 结构化 Markdown → 去除重复页眉页脚 → 恢复标题树和表格 → Chunk Pipeline
解析质量门禁可以检查:正文是否为空、乱码比例、标题数量、页码连续性、表格保留率和解析器置信度。失败文档不应直接进入索引。
Code
from typing import Literalfrom pydantic import BaseModelclass Chunk(BaseModel): document_id: str chunk_id: str parent_id: str | None heading_path: list[str] page_start: int | None page_end: int | None content: str content_hash: str token_count: int chunk_type: Literal["section", "paragraph", "table"] source_uri: str document_version: str access_level: str embedding_version: str
元数据决定系统能否增量更新、权限过滤和引用来源。没有稳定documentid与chunkid,旧版本无法清理;没有页码和标题路径,答案无法溯源;没有访问级别,检索可能越权。
通用工程起点是“结构优先、固定长度兜底”:先按 H2/H3、段落和表格建立 Section,只有 Section 超长时才做 Sliding Window。
Code
MAX_TOKENS = 600OVERLAP_TOKENS = 80def split_section(section, tokenizer): if token_len(section.content, tokenizer) <= MAX_TOKENS: return [section] units = split_by_paragraph_then_sentence(section.content) return sliding_pack( units=units, max_tokens=MAX_TOKENS, overlap_tokens=OVERLAP_TOKENS, tokenizer=tokenizer, )600/80 只是实验初值。参数要结合文档结构、Embedding 上下文长度、检索指标和生成 Token 成本评估。Overlap 过小会丢边界,过大会制造重复证据并降低 ContextPrecision。
图:三类 Chunk 切分策略的边界与取舍

固定长度适合快速建立基线;结构感知能够保留标题、段落、列表和表格边界,通常应作为默认工程起点;语义切分则适合在评测集成熟后,用更高计算成本换取动态粒度。
标题与标题后的第一段。 表头、数据行、单位和脚注。 列表引导句与列表项。 定义、适用范围和例外条件。 公式与变量解释。 代码块与对应说明。
超长表格可以重复表头后按行组切分,并保持同一个table_id。代码和公式优先整体保留,必要时把解释作为 Parent、局部代码作为 Child。
Child Chunk 粒度小,适合精确召回;Parent Section 保留完整语义,适合作为模型上下文。检索时先命中 Child,再按parent_id扩展并去重。
Code
child_hits = vector_search(query, top_k=20)parent_ids = []seen = set()for hit in child_hits: if hit.parent_id not in seen: parent_ids.append(hit.parent_id) seen.add(hit.parent_id)contexts = load_parent_chunks(parent_ids[:8])
Parent 扩展后仍需 Context Builder:按相关性、来源权威性、时间和权限排序,去除内容重复,再依据 Token Budget 裁剪。
图:Parent-Child Retrieval 的检索与上下文组装时序

这条链路把“召回粒度”和“生成上下文粒度”解耦:Child 负责命中,Parent 负责补全语义;父段过长时不整段回填,而是围绕命中 Child 截取邻近窗口,再经过重排和 Token Budget 裁剪后交给模型。
Code
import hashlibdef normalized_hash(text: str) -> str: normalized = " ".join(text.split()) return hashlib.sha256(normalized.encode("utf-8")).hexdigest()评测集应包含问题、必要证据、正确来源和答案。比较固定长度、结构切分、Sliding Window、Parent-Child 四种策略,至少观察 Recall@K、ContextPrecision、证据完整率、平均 Chunk 数、平均 Token 和索引成本。
还要建立结构 Hard Case:跨页表格、脚注、长列表、嵌套标题、公式和代码。平均指标无法代表这些高价值文档是否可靠。
1. 解析质量优先于 Chunk 参数。 2. 结构感知切分优先,固定 Token 兜底。 3. Child 负责召回,Parent 负责完整上下文。 4. Chunk 必须带来源、版本、权限和内容 Hash。 5. 解析、切分、Embedding 和索引应支持独立增量更新。
RAG 数据准备仍然是数据工程:有模型、有主键、有版本、有质量、有权限、有增量和对账。
下一篇进入 Data Agent,说明结构化查询如何通过MDL、Schema Linking、SQLAST和数据库权限形成确定性约束链。
Data+AI项目获取/加入社群:
公众号主页私信【项目】
夜雨聆风