RAG 文档预处理:从原始文档到高质量知识库
很多团队把 80% 的精力花在调 Embedding 模型、优化 Prompt、换 LLM 上却忽略了决定 RAG 上限的关键环节——文档预处理。分块方式差再好的检索算法也召回不准文本没清洗Embedding 模型产生的向量就是带噪声的。本文以 TMC 差旅财务系统为业务背景系统梳理文档预处理的全流程技术。一、文档预处理在 RAG 中的位置RAG 系统的起点是原始文档终点是 LLM 生成的答案。文档预处理位于这条链路的入口它的输出质量直接决定后续检索和生成的成败任何一环出错都会被下游放大环节出错后果TMC 场景解析遗漏PDF 表格中的发票金额没提取到“INV042 结算金额是多少” → 查不到清洗不干净页眉第 2 页 / 机密混入 chunk污染语义检索时将这个 chunk 排在前面LLM 误以为文档是机密级分块不当PR 状态流转被切在 “Raised →” 和 “Approved → Paid” 之间用户搜PR 状态召回半个流程LLM 回答残缺元数据缺失新旧政策混在一起无时效性过滤2024 版和 2026 版《差旅制度》同时返回LLM 给出过时标准文档预处理的目标让后续的分块、向量化、检索在干净、结构化、富信息的数据上进行从源头保证知识库索引的质量。二、文档解析把五花八门的格式翻译成统一文本2.1 解析的核心挑战企业知识库从来不是单一格式——TMC 系统面对的文档来源包括格式典型文档解析难点.txt付款申请流程.txt无结构标记需靠内容推断层级.md系统模块职责.md标题层级清晰但代码块/表格需特殊处理.pdf电子版航空公司结算协议表格、多栏排版、嵌入字体.pdf扫描件酒店发票扫描件需要 OCR手写体识别.docx部门差旅报销制度 v3.2.docx样式标记混杂修订痕迹残留HTML公司 Wiki 差旅 FAQ导航栏、广告、页脚等噪声Excel月度差旅费用汇总表.xlsx行列结构在纯文本化后丢失2.2 示例一Spring AI 解析与清洗 PDFJava以《TMC 差旅住宿标准.pdf》为例这是一个包含表格和文本的政策 PDF目标是提取可读文本并清洗为干净的Document。第一步配置 PDF 解析器Componentpublic class PdfReaderStrategy implements DocumentReaderStrategy { Override public boolean supports(File file) { return file.getName().toLowerCase().endsWith(.pdf); } Override public ListDocument read(File file) throws IOException { // PDF 读取配置裁剪页眉页脚按段落切分 PdfDocumentReaderConfig config PdfDocumentReaderConfig.builder() .withPageTopMargin(50) // 裁剪顶部 50 单位去页眉 .withPageBottomMargin(50) // 裁剪底部 50 单位去页脚 .withPagesPerDocument(1) // 每页生成为一个 Document .withPageExtractedTextFormatter( new ExtractedTextFormatter.Builder() .withNumberOfTopTextLinesToDelete(0) .build()) .build(); Resource resource new FileSystemResource(file); return new PagePdfDocumentReader(resource, config).get(); }}第二步文本清洗解析出的原始文本通常带着 PDF 的换行碎片和空白残留——比如表格内容被拆成了逐行的散文字。清洗管线Servicepublic class DocumentCleanService { /** 完整的文档清洗管线 */ public ListDocument clean(ListDocument documents) { return documents.stream() .filter(doc - doc ! null doc.getText() ! null) .map(this::cleanSingleDocument) .filter(doc - !doc.getText().isBlank()) // 丢弃清洗后为空的文档 .collect(Collectors.toList()); } private Document cleanSingleDocument(Document doc) { String text doc.getText(); // 1. 空白规范化合并连续空格/制表符/换行统一为一个空格 text text.replaceAll(\\s, ).trim(); // 2. 移除 PDF 产生的乱码字符保留字母、数字、中文、常见标点 text text.replaceAll([^\\p{L}\\p{N}\\p{P}\\p{Z}\\n], ); // 3. 修复 PDF 断行导致的词语粘连 // PDF 行末换行常把差旅住\n宿标准变成两个词需合并 text text.replaceAll((?[\\u4e00-\\u9fff])\\s(?[\\u4e00-\\u9fff]), ); // 4. 按句号/换行拆分段落去除重复段落 String[] paragraphs text.split([。\\n]); SetString seen new LinkedHashSet(); for (String para : paragraphs) { String trimmed para.trim(); if (!trimmed.isEmpty() trimmed.length() 2) { seen.add(trimmed); } } text String.join(。\n, seen); return new Document(text, doc.getMetadata()); }}完整调用链路——从策略选择器到清洗完毕RestControllerRequestMapping(/rag)public class RagController { private final DocumentReaderStrategySelector selector; private final DocumentCleanService cleanService; public RagController(DocumentReaderStrategySelector selector, DocumentCleanService cleanService) { this.selector selector; this.cleanService cleanService; } GetMapping(/read) public ListDocument readAndCleanDocument(RequestParam(path) String path) { File file new File(path); if (!file.exists() || !file.isFile()) { throw new IllegalArgumentException(文件不存在: path); } try { // 解析 ListDocument rawDocs selector.read(file); // 清洗 return cleanService.clean(rawDocs); } catch (IOException e) { throw new RuntimeException(处理文件失败: e.getMessage(), e); } }}经过这条链路后《TMC 差旅住宿标准.pdf》的每一页变成一个干净、无噪声、无重复段落的Document对象后续可直接送入分块和向量化。2.3 示例二LlamaIndex / LangChain 解析与清洗 docxPython以《部门差旅报销制度 v3.2.docx》为例这是 Word 文档包含标题层级、表格和修订痕迹。目标是加载 → 解析 → 清洗 → 输出干净的Document列表。第一步加载 docxfrom docx import Document as DocxDocumentfrom langchain_core.documents import Documentdef load_tmc_docx(file_path: str) - list[Document]: 加载《部门差旅报销制度.docx》按段落提取文本 并标记表格为结构化元数据 docx DocxDocument(file_path) documents [] for i, para in enumerate(docx.paragraphs): text para.text.strip() if not text: continue # 提取段落样式作为结构元数据标题层级 metadata { source: file_path, style: para.style.name, # Heading 1 / Heading 2 / Normal paragraph_index: i, doc_type: 报销制度, version: v3.2, } documents.append(Document(page_contenttext, metadatametadata)) # 额外处理表格提取行列结构生成文字描述 for j, table in enumerate(docx.tables): table_text table_to_text(table) # 自定义行列转文字描述 documents.append(Document( page_contenttable_text, metadata{source: file_path, is_table: True, table_index: j} )) return documents第二步文本清洗docx 文档的噪声不同于 PDF——主要是修订痕迹、样式标记残留和多余的段落空白import redef clean_tmc_documents(documents: list[Document]) - list[Document]: 完整的 docx 清洗管线 cleaned [] for doc in documents: text doc.page_content # 1. 移除 Word 修订痕迹如[已删除: xxx]等标记 text re.sub(r\[已删除:.*?\], , text) text re.sub(r\[已插入:.*?\], , text) # 2. 空白规范化 text re.sub(r\s, , text).strip() # 3. 移除无意义的超短行页码、分隔符残片等 if len(text) 5: continue doc.page_content text cleaned.append(doc) # 4. 去重移除内容完全相同的文档 seen set() unique_docs [] for doc in cleaned: key doc.page_content[:100] # 用前100字符做近似去重 if key not in seen: seen.add(key) unique_docs.append(doc) return unique_docs完整调用# 加载 → 清洗一行完成raw_docs load_tmc_docx(部门差旅报销制度_v3.2.docx)clean_docs clean_tmc_documents(raw_docs)print(f解析 {len(raw_docs)} 个段落清洗后保留 {len(clean_docs)} 个有效文档)# → 解析 87 个段落清洗后保留 52 个有效文档# 过滤掉了35个空行、修订标记碎片和重复内容经过清洗后的Document列表保留了标题层级信息metadata.style后续结构化分块时可直接利用这些元数据按章节切分。2.4 TMC 场景的解析陷阱TMC 财务文档有大量非纯文本内容常规解析器容易丢信息•流程图/状态图PR 的 “Raised → Approved → Paid → Void” 流转在很多 PDF 里是图片。需要多模态模型如 GPT-4o做图文描述转换•表格数据《差旅住宿标准表》城市 × 级别 × 上限金额纯文本化后行列关系丢失检索时无法精确匹配技术部经理在北京的住宿上限•嵌套结构系统模块职责.txt中 AR 下分对账、CreditNote、收款管理、预付款管理四个子项扁平化解析会丢失层级归属三、文本清洗去噪、归一、统一3.1 通用清洗原则上述两个完整示例覆盖了主流清洗操作。总结通用清洗清单操作说明TMC 示例空白规范化合并连续空格/制表符/换行PR 审批 流程→PR 审批 流程乱码清理移除 PDF 复制产生的无效字符\xa0不间断空格→ 普通空格断行修复修复 PDF 行末换行导致的词语切断差旅住\n宿标准→差旅住宿标准段落去重移除重复段落多文档交叉引用常见5 份文档都含同一段供应商付款流程只保留一份修订痕迹清除Word 的[已删除: xxx]等标记报销制度 v3.1 → v3.2 的修订历史不应进入知识库短文本过滤丢弃孤立的页码、分隔符、机密等单词页尾第 3 页、TMC © 2025等 3-4 字的无效片段3.2 TMC 术语归一化TMC 系统中同一概念有多种表达清洗时需建立术语词典做统一替换避免语义相同、字面不同导致的漏召回统一术语变体表达PR付款申请“付款申请”、“Payment Request”、“付款申请书”、“PR 单”OverPayment超额支付“超额支付”、“多付”、“结算超额”、“超付”XOExchange Order“Exchange Order”、“外部订单”、“换开单”、“XO 单”Deposit预存款“预存款”、“预付款余额”、“客户余额”、“存底金”Refund退款“退款”、“退票退款”、“退回款项”、“返款”清洗时用正则做批量替换确保同一概念在知识库中表达一致embedding 向量在语义空间中聚集。四、文档分块RAG 最关键的技术决策分块是预处理中最影响检索质量的一步。分得太粗——一个 chunk 包含多个不相关主题检索精度低分得太细——关键上下文被切断LLM 看到的信息残缺。4.1 固定长度分块最基础的方式按 token 数固定切割相邻块之间保留 overlap。TMC 场景的问题付款申请流程.txt中 PR 的 3 个用途被切到不同 chunkChunk A: 1. 向供应商付款多数以 EXS 开头的 supplierCode 2. 员工报销supplierCode 以 E 开头Chunk B: 3. 退票产生的付款用户搜退票付款怎么处理Chunk B 被召回但缺少与前两条的并列语境LLM 无法确认退票产生的付款是不是一种独立的 PR 类型。4.2 递归字符分割按优先级逐级尝试切分段落(\n\n)→换行(\n)→句号(。)→分号()→空格→字符。一旦当前级别能切出合格的块就不再向下。from langchain.text_splitter import RecursiveCharacterTextSplittersplitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap50, separators[\n\n, \n, 。, , , , ])TMC 场景系统模块职责.txt天然按\n\n分段——AR 一段、AP 一段、GL 一段。递归分割器优先在段落边界切分每个模块独立成一个 chunk检索AR 的职责时精准命中。4.3 语义分块不再依赖字符数或固定分隔符而是根据语义边界切分。核心思路相邻句子的语义相似度高 → 属于同一块相似度陡降 → 切分点。方法原理适用场景Embedding 阈值计算相邻句子余弦相似度低于阈值则切分叙述性长文档LLM 判断让 LLM 直接标注切分位置复杂嵌套结构的政策文档Proposition ChunkingLLM 将文本拆为独立的事实陈述每个陈述为一个 chunk知识密集型文档TMC 场景预存款与XO核销.txt中“预存款来源”OverPayment 企业预付款和预存款用途差旅消费 抵扣 XO是两个独立的语义块。语义分块能自动识别主题切换将它们分为独立 chunk而不是机械地把两个主题塞进同一个固定长度的块里。4.4 结构化分块按标题/文档树利用原文档的标题层级#、##、###构建文档树在标题边界切分。每个 chunk 保留其所属的标题路径。系统模块职责.txt 的文档树├── TMC 财务系统模块H1│ ├── AR应收账款H2 → Chunk 1│ │ ├── 对账│ │ ├── 生成 CreditNote│ │ ├── 收款管理│ │ └── 预付款管理│ ├── AP应付账款H2 → Chunk 2│ │ ├── 供应商付款管理│ │ ├── 员工报销管理│ │ └── 付款申请PR管理│ └── GL总账H2 → Chunk 3│ └── ...按 H2 标题切分后AR、AP、GL 各为一个独立 chunk每个 chunk 内容自包含。用户问AR 和 AP 有什么区别时两个完整 chunk 分别被召回LLM 拿到各自定义再对比。4.5 语境增强分块头Contextual Chunk Headers, CCH即使按标题切分chunk 存入向量库后是独立存在的——丢失了我来自哪篇文档的上下文。CCH 在每个 chunk 前预置文档级和章节级上下文然后用增强后的文本做 embedding原始 Chunk - 供应商付款管理 - 员工报销管理 - 付款申请PR管理CCH 增强后 [文档系统模块职责 | 章节AP应付账款] - 供应商付款管理 - 员工报销管理 - 付款申请PR管理用户搜AP 管理哪些内容即使问题中没有系统模块职责字样chunk 也因携带上下文信息而被命中。CCH 是一种几乎零成本的检索质量提升手段建议对所有分块策略叠加使用。4.6 分块策略对比与选择策略优点缺点TMC 场景推荐固定长度实现简单token 数精确可控语义切断频繁不推荐用于生产递归分割尊重自然段落边界实现成本低对无结构文档效果差流程/制度类 TXT 文档语义分块跨段落合并相关主题计算成本高需额外 LLM 调用混合主题的长政策文档结构化分块标题层级完整保留依赖原文档有良好结构Markdown 格式的模块说明CCH 增强弥补任何分块法的上下文丢失增加少量 token 消耗所有场景建议叠加TMC 推荐组合结构化分块按 H2 标题切分 CCH 增强。流程类文档按章节切分每个 chunk 注入文档标题作为前缀兼顾语义完整和检索命中。五、元数据提取与增强分块只解决了大小问题元数据回答更关键的问题——“这个块在说什么、属于谁、什么时候有效”。5.1 元数据类型类型字段示例TMC 场景用途文档级文件名、来源、版本号付款申请流程.txt, v2.1检索后引用来源结构级章节标题、页码、层级路径# PR 流程 ## 供应商付款详细流程定位 chunk 在原文档中的位置时效性生效日期、截止日期effective_date: 2026-01-01过滤过期政策只检索现行有效的文档业务属性适用部门、单据类型department: 技术部,doc_type: PR精确过滤缩小检索范围语义级摘要、关键词、可回答问题keywords: [PR, 付款申请, 审批流程]增强检索匹配桥接查询-文档的语义鸿沟5.2 业务元数据注入解析后置增强实际生产中像PdfDocumentReaderConfig这类框架内置的 Reader 配置通常不提供任意元数据注入方法。企业的通用做法是解析后置增强——Reader 先提取内容随后在Document对象上直接追加业务元数据Spring AI 方案Document的metadata是MapString, Object解析完后直接 put 进去。GetMapping(/read)public ListDocument readAndEnrichDocuments(RequestParam(path) String path) { try { // 1. 解析 ListDocument documents selector.read(new File(path)); // 2. 后置增强直接操作 Document 的 metadata Map String docType detectDocType(path); // 根据文件名/内容推断单据类型 String department extractDepartment(path); String effectiveDate extractEffectiveDate(documents); String version extractVersion(path); for (Document doc : documents) { doc.getMetadata().put(doc_type, docType); doc.getMetadata().put(department, department); doc.getMetadata().put(effective_date, effectiveDate); doc.getMetadata().put(version, version); } // 3. 清洗 返回 return cleanService.clean(documents); } catch (IOException e) { throw new RuntimeException(处理文件失败: e.getMessage(), e); }}LlamaIndex 方案直接修改Document.metadata字典同样在加载后立即注入。from llama_index.core import SimpleDirectoryReaderdocuments SimpleDirectoryReader(data/tmc_policies).load_data()# 后置注入业务元数据for doc in documents: doc.metadata[doc_type] 差旅政策 doc.metadata[department] 技术部 doc.metadata[effective_date] 2026-01-01 doc.metadata[version] v3.2检索时按元数据过滤确保查得又准又对用户技术部住宿报销标准是多少 → 元数据过滤department技术部, doc_type政策, effective_date2026 → 只检索技术部现行有效的差旅政策文档 → 不会返回销售部政策也不会返回 2024 年已废止的旧版标准企业实践当知识库规模达到数百份文档时手动为每份文档写元数据不现实。通常的做法是按目录约定自动注入——例如约定data/{部门}/{单据类型}/{版本}/目录结构程序扫描时自动提取路径段作为元数据零手工维护。5.3 LLM 驱动的语义元数据增强用 LLM 为每个 chunk 自动生成摘要、关键词和典型问题是 2025-2026 年的主流做法# 对 TMC 知识库的每个 chunk让 LLM 生成语义元数据prompt 你是 TMC 差旅财务系统的知识管理助手。为以下文本块生成元数据文本{chunk_text}生成 JSON{ title: 标题20字以内, summary: 摘要50字以内, keywords: [关键词1, 关键词2, 关键词3], questions: [该文本能回答的典型问题1, 典型问题2], doc_type: PR/Invoice/Refund/Deposit/XO/通用}# 输出示例针对付款申请流程 chunk# {# title: PR 状态流转,# summary: PR从Raised到Approved再到Paid的完整状态变更流程,# keywords: [PR, 付款申请, 状态流转, Raised, Approved, Paid],# questions: [# PR 付款申请有哪些状态,# PR 审批通过后如何变为已付款# ],# doc_type: PR# }LLM 生成的典型问题本质上做了查询-文档的语义桥接——用户的真实问法往往和文档原文措辞不同“怎么批这个单子” vs “PR 审批流程”预生成的问题覆盖了多种问法大幅提升召回率。六、TMC 场景的完整预处理流水线以 TMC 差旅财务系统的 5 份知识库文档为例推荐以下端到端方案文档格式解析器分块策略特殊处理付款申请流程.txtMarkdownMarkdownDocumentReaderH2 标题分块 CCHPR 术语扩展三种 PR 用途分别生成摘要发票与结算流程.txtMarkdownMarkdownDocumentReaderH2 标题分块 CCHOverPayment→Deposit 资金链路关键词标注退款处理流程.txtMarkdownMarkdownDocumentReaderH2 标题分块 CCH三种 Refund 类型分别生成 chunk 摘要预存款与XO核销.txtMarkdownMarkdownDocumentReaderH2 标题分块 CCHXO 术语词典词条关联系统模块职责.txtMarkdownMarkdownDocumentReaderH2 标题分块 CCHAR/AP/GL/Settle 互相引用关系标注统一预处理流水线1. 文档解析 → SimpleDirectoryReader / Spring AI DocumentReader 加载全部文档2. 术语归一化 → 术语词典批量替换超额→OverPayment等3. 文本清洗 → 去空白、去特殊字符、段落去重4. 结构化分块 → 按 ## 标题切分chunk_size800, overlap1005. CCH 增强 → 每个 chunk 前缀加 [文档名 / 章节名]6. 业务元数据注入 → doc_type、department、effective_date、version7. LLM 语义元数据 → 生成标题、摘要、关键词、典型问题8. 向量化 → 将增强后的 chunk 元数据送入 Embedding 模型七、总结文档预处理是 RAG 系统的地基工程——地基不牢上层再精致的检索算法和 Prompt 工程也无法弥补。核心要点解析要完整不同格式走不同解析器策略模式优雅路由表格和图片需要特殊处理清洗要彻底去噪声、统术语、防重复——干净的输入产生准确的向量分块要聪明优先结构化分块 CCH 上下文增强不要默认用固定长度元数据要丰富业务属性支持精确过滤LLM 生成的语义元数据桥接查询-文档鸿沟从 Demo 到生产预处理策略需要根据实际文档特征持续调优没有万能配置对 TMC 差旅财务系统而言文档结构清晰、术语体系完整预处理成本不高但收益巨大——好的分块能让PR 状态流转一查即中好的元数据能确保只检索 2026 年现行有效的差旅政策。学AI大模型的正确顺序千万不要搞错了2026年AI风口已来各行各业的AI渗透肉眼可见超多公司要么转型做AI相关产品要么高薪挖AI技术人才机遇直接摆在眼前有往AI方向发展或者本身有后端编程基础的朋友直接冲AI大模型应用开发转岗超合适就算暂时不打算转岗了解大模型、RAG、Prompt、Agent这些热门概念能上手做简单项目也绝对是求职加分王给大家整理了超全最新的AI大模型应用开发学习清单和资料手把手帮你快速入门学习路线:✅大模型基础认知—大模型核心原理、发展历程、主流模型GPT、文心一言等特点解析✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑✅开发基础能力—Python进阶、API接口调用、大模型开发框架LangChain等实操✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经以上6大模块看似清晰好上手实则每个部分都有扎实的核心内容需要吃透我把大模型的学习全流程已经整理好了抓住AI时代风口轻松解锁职业新可能希望大家都能把握机遇实现薪资/职业跃迁这份完整版的大模型 AI 学习资料已经上传CSDN朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】