首页>AI>正文

RAG数据预处理链路重构与解析分割实操

Feng 26 阅读 AI

RAG 的本质是让大模型先查资料再作答。训练数据是冻结的,模型自己不知道昨天发生了什么,也分不清企业内网文档里的字段含义。把外部知识库接进生成链路,知识截止、幻觉、私有数据外流这三个问题就能同时压下去,代价远低于全量微调。

离线建库与在线问答是两条独立链路

建库在离线侧完成:文档清洗、分块、向量化、写进向量库并建索引。问答在在线侧完成:问题转向量、语义检索召回 Top-K 片段、拼进 Prompt、模型基于参考资料作答。两条链路解耦,意味着更新知识只需重建索引,不必碰模型权重。

组件选型上,嵌入模型决定检索精度的上限,BGE、E5、text-embedding-ada-002 都是常见选项;向量库负责存储与近邻查询,Milvus、Pinecone、Weaviate、FAISS 各有取舍;检索环节单靠向量不够,BM25 加向量的混合召回再叠一层 Rerank 重排序,相关性会明显好转。生成侧用 GPT、Llama、通义千问都行,重排序可用 CrossEncoder 或 BGE-Reranker。

解析器选型看格式,不看喜好

LangChain4j 把各种二进制文件统一解析成 Document 对象,文本内容加元数据(路径、格式、创建时间)一起带走。选哪个实现类,取决于输入文件的格式分布。

纯文本走 TextDocumentParser,零额外依赖。PDF 用 ApachePdfBoxDocumentParser,Office 全家桶(DOC/DOCX/PPT/PPTX/XLS/XLSX)交给 ApachePoiDocumentParser。格式混杂、来源不可控的场景直接上 ApacheTikaDocumentParser,代价是依赖最重、速度最慢。

PDF 解析有个原文没提的坑:PDFBox 默认按内容流顺序抽取文本,双栏排版或图文混排的 PDF 抽出来经常是左右栏交错、句子断裂。根因是内容流顺序不等于阅读顺序。处理办法是启用按位置排序抽取,或在解析后做一次行级清洗,把页眉页脚、页码这类高频重复行过滤掉。这类噪声不清,后面分块再合理也救不回来。

版本差异也要注意。LangChain4j 从 0.x 升到 1.x 后,部分解析器和分割器的包路径有调整,HuggingFaceTokenizer 需要单独引入 langchain4j-embeddings 相关模块,不再随核心包附带。照抄旧教程的 import 大概率编译不过,先对齐依赖版本再写代码。

依赖按需引入:

<dependency>
 <groupId>dev.langchain4j</groupId>
 <artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
</dependency>
<dependency>
 <groupId>dev.langchain4j</groupId>
 <artifactId>langchain4j-document-parser-apache-poi</artifactId>
</dependency>

加载单个文件用 FileSystemDocumentLoader.loadDocument,批量加载目录用 loadDocuments,需要递归子目录就换 loadDocumentsRecursively。按后缀过滤时传一个 PathMatcher,比如 glob:*.txt,避免把目录里的图片、压缩包一起塞进解析器。

分块的核心是粒度,不是长度数字

解析出来的文档动辄几万字,整篇向量化既超上下文窗口,检索精度也会崩。分割器把长文本切成 TextSegment,片段之间保留重叠,防止一句话被拦腰截断后语义丢失。单个段落超长时,分割器会自动降级到子分割器继续拆。

分割粒度按文档类型定:结构化手册、论文用 DocumentByParagraphSplitter;日志、代码、表格类文本用 DocumentByLineSplitter;文学和对话内容用 DocumentBySentenceSplitter;结构不明确的通用场景直接上 DocumentSplitters.recursive(),按段落、行、句子、单词逐级递归,这是默认推荐。

长度单位优先用 Token 而非字符。字符数简单但忽略语义,”人工智能”四个字和四个 token 完全不是一回事。HuggingFaceTokenizer 能给出贴近模型实际处理的 token 计数,用它定义片段长度更稳。

DocumentByParagraphSplitter splitter =
 new DocumentByParagraphSplitter(500, 50, new HuggingFaceTokenizer());
List<TextSegment> segments = splitter.split(document);

500 token 上限、50 token 重叠是常见起点,重叠比例控制在 10% 到 20%。分割完可以直接交给 EmbeddingStoreIngestor 一步完成向量化和入库,省掉手动串联。

参数调得对不对,用召回率说话

300 到 500 token 是经验值,不是定律。片段切得太碎,单块信息不完整,召回时容易命中一堆半截内容;切得太大,一块里混进多个主题,向量被稀释,相关性打分失真。

验证方法不复杂:从知识库里挑 20 条有明确答案的问题,人工标注每条的标准出处片段,跑检索统计 Top-3 命中率。命中率低于预期就先调片段长度和重叠量,再考虑换嵌入模型。这套小规模评测集建一次能反复用,比凭感觉调参靠谱得多。

RAG 和微调不是二选一

知识频繁变动、数据敏感、需要溯源引用的场景,RAG 是首选,更新向量库即可,私有数据不出内网。通用能力增强、输出风格对齐这类需求,微调更合适,但知识更新慢、成本高、数据要上传训练。两者可以叠加:微调管风格和指令遵循,RAG 管事实和时效。

实现场景已经很清晰:企业知识库问答、基于产品文档的智能客服、法律医疗的专业问答、带参考资料的内容创作,以及检索图片视频音频的多模态 RAG。演进方向上,混合检索、多轮对话 RAG、GraphRAG 结合知识图谱,以及检索与生成的联合训练,都在把这条链路往端到端优化推。

预处理是整条链路的地基。解析漏了内容,后面检索再准也答不全;分块不合理,召回质量从源头就输了。先把解析器和分割器这两步做扎实,再谈检索策略和生成效果。

ID:7283

Feng
这位作者很神秘,还没有填写简介。