目录

前言

一、为什么需要RAG?

二、RAG全称?

RAG 的全称解释

三、自顶向下解释之:RAG总体流程

3.1 文档解析

3.1.1 什么是文档解析?

3.1.2 解析 vs 分片 (Parsing vs Chunking)

3.1.3 解析的三大挑战

3.2 SpringAI中的文件解析

3.2.1 核心接口:DocumentReader

3.2.2 资源抽象:Resource

3.2.3 常见解析器实现

3.2.3.1 文本文件解析 (TextReader)

3.2.3.2 PDF 文件解析 (PdfReader)

3.2.3.3 通用解析器 (TikaDocumentReader)

3.3 痛点解决方案

3.3.1 痛点一:扫描版 PDF (图片型)

方案 A: 本地 OCR (Tesseract)

方案 B: 云端 OCR (推荐)

3.3.2 痛点二:表格解析 (Table Parsing)

3.3.3 痛点三:多栏排版 (Multi-column Layout)

3.3.4 痛点四:页眉页脚与页码 (Noise)

3.4 元数据

3.4.1 必提取的元数据字段

3.4.2 Spring AI 中注入元数据

4.3 从文件内容提取元数据 

3.5 解析总体流程

总结


前言

大家好,这里是程序员阿亮

众所周知,RAG是我们给LLM提供额外信息的一门技术,通过RAG我们可以提前封装好一系列的数据,如企业内部文档、额外的数据,让我们的Prompt在发送给LLM之前,检索出相关的数据,一起发送给LLM,去降低LLM响应的幻觉,提高准确率。

那么实际开发中我们RAG要考虑哪些部分呢,今天我来给大家深入研究一波~

本次博客会以SpringAI为例子来解释,实际上不管是langchain4j、SpringAI他们的内容都是差不多的,了解其中一个框架去学其他框架也是很快的!

一、为什么需要RAG?

所谓大模型,我们可以认为是一个大脑,但是,这个大脑其实并不是无所不知的,我们需要给这个大脑去提供一些它不知道的知识,这就是为什么我们需要RAG。

把 LLM 比作"大脑",把知识库比作"外部记忆"
那么传统 LLM:  只靠大脑里的记忆回答问题
如果加上RAG,就可以 先查外部记忆,再结合大脑回答问题 ,这样可以很明显降低我们大模型的幻觉,提高大模型的知识能力。

二、RAG全称?

RAG 的全称解释

英文

中文

含义

Retrieval

检索

从知识库中查找相关信息

Augmented

增强

用查找到的信息增强输入

Generation

生成

LLM 基于增强后的信息生成答案

三、自顶向下解释之:RAG总体流程

上面的图涵盖的RAG的总体流程,那么接下来我就带大家详解一波RAG的每个流程

3.1 文档解析

在 RAG 流程中,文档解析 (Parsing) 是最容易被忽视,但决定生死的一环。 "Garbage In, Garbage Out" (垃圾进,垃圾出) 是 RAG 的第一定律。如果你的解析器把表格弄乱了、把页眉页脚当成了正文、或者把扫描版 PDF 当成了空文件,那么后续的 Embedding 和检索再先进也无济于事。

3.1.1 什么是文档解析?

将非结构化或半结构化的原始文件(PDF, Word, HTML 等),转换为 RAG 系统可处理的 纯文本 (Text) + 元数据 (Metadata) 的过程。

以SpringAI为例子,我们文档解析会把二进制文件如pdf、word等转换为一个Document对象,对象的字段有metadata、content,其中metadata就是我们内容的元数据。

比如说content的source(来源)、page等。

3.1.2 解析 vs 分片 (Parsing vs Chunking)

这是两个最容易混淆的概念,必须区分开:

环节

英文

输入

输出

目的

解析

Parsing

原始文件 (PDF/Word)

完整文本 + 元数据

把文件变成文字

分片

Chunking

完整文本

文本片段 (Chunks)

把长文切成小块以便检索

顺序:先 解析 (Parsing) -> 后 分片 (Chunking)。 

3.1.3 解析的三大挑战

  1. 格式多样性:PDF 有文字版和扫描版,Word 有样式,HTML 有标签。
  2. 布局复杂性:多栏排版(如论文)、表格、页眉页脚、页码。
  3. 内容噪声:乱码、特殊符号、无关的版权声明。

3.2 SpringAI中的文件解析

浅浅讲解一下SpringAI处理文档的API

Spring AI 定义了统一的接口 DocumentReader,屏蔽了不同文件类型的差异。

3.2.1 核心接口:DocumentReader

public interface DocumentReader {
    /**
     * 读取文档并返回 Document 列表
     * 通常一个文件会被解析为一个或多个 Document 对象
     */
    List<Document> get();
}
  • 返回类型 List<Document>:为什么是列表?因为一个 PDF 可能有 100 页,解析器可能按页返回 100 个 Document,也可能合并为 1 个。
  • Document:Spring AI 的标准数据载体。
public class Document {
    private String id;           // 唯一 ID
    private String content;      // 核心:解析出的文本
    private Map<String, Object> metadata; // 核心:来源、页码等
}

3.2.2 资源抽象:Resource

Spring AI 使用 Spring 的 Resource 接口来定位文件,支持 classpath, file, url 等。

// 从类路径加载
Resource resource = new ClassPathResource("documents/manual.pdf");

// 从文件系统加载
Resource resource = new FileSystemResource("/data/docs/manual.pdf");

// 从 URL 加载
Resource resource = new UrlResource("https://example.com/manual.pdf");

3.2.3 常见解析器实现

3.2.3.1 文本文件解析 (TextReader)

最简单,直接读取纯文本。

@Bean
public DocumentReader textReader(ResourceLoader loader) {
    return new TextReader(loader.getResource("classpath:/data/notes.txt"));
}
3.2.3.2 PDF 文件解析 (PdfReader)

注意:Spring AI 自带的 PdfReader 通常基于简单的 PDF 库(如 Apache PDFBox),只能处理文字版 PDF

@Bean
public DocumentReader pdfReader(ResourceLoader loader) {
    return new PdfReader(loader.getResource("classpath:/data/manual.pdf"));
}

局限性

  • 无法处理扫描版 PDF(图片型)。
  • 表格内容可能会乱序(按流式读取,而非行列)。
  • 多栏排版可能会错行。
3.2.3.3 通用解析器 (TikaDocumentReader)

基于 Apache Tika,支持 1000+ 种格式(Word, PPT, Excel, PDF 等)。

@Bean
public DocumentReader tikaReader(ResourceLoader loader) {
    return new TikaDocumentReader(loader.getResource("classpath:/data/report.docx"));
}

优点:格式支持广,开箱即用。 缺点:对复杂布局(表格、多栏)的处理依然一般。

3.3 痛点解决方案

企业文档远比你想象的复杂。以下是真实场景中的“坑”及填坑方案。

3.3.1 痛点一:扫描版 PDF (图片型)

现象:解析后 content 为空,或者全是乱码。 原因:文件本质是图片,没有文字层。 解决方案OCR (光学字符识别)

方案 A: 本地 OCR (Tesseract)
  • 优点:免费,数据不出域。
  • 缺点:配置麻烦,中文识别率一般,速度慢。
  • 集成:Spring AI 目前无内置 Tesseract Reader,需自定义实现 DocumentReader 调用 Tesseract CLI 或 Java 封装。
方案 B: 云端 OCR (推荐)
  • 服务商:Azure Document Intelligence, AWS Textract, Google Cloud Vision, 阿里云 OCR。
  • 优点:识别率极高,能还原表格结构。
public class OcrDocumentReader implements DocumentReader {
    private final Resource resource;
    private final CloudOcrClient ocrClient;
    
    public List<Document> get() {
        // 1. 上传图片
        // 2. 调用 OCR API
        // 3. 获取识别后的文本和布局信息
        String text = ocrClient.recognize(resource);
        Document doc = new Document(text);
        doc.getMetadata().put("type", "scanned_pdf");
        return List.of(doc);
    }
}

3.3.2 痛点二:表格解析 (Table Parsing)

现象:表格被解析成一行乱序文字。

  • 原表:

    姓名

    年龄

    张三

    18

  • 解析后:姓名 张三 年龄 18 (语义丢失,LLM 无法理解对应关系)

解决方案

  1. Markdown 化:将表格转换为 Markdown 格式。
    • | 姓名 | 年龄 |\n| ---- | ---- |\n| 张三 | 18 |
    • LLM 对 Markdown 表格理解很好。
  2. HTML 化:保留 <table> 标签。
  3. 专用工具
    • Camelot (Python): 专门提取 PDF 表格。
    • Table Transformer (AI 模型): 识别表格结构。
    • Azure Document Intelligence: 云端最强,直接返回结构化 JSON。

3.3.3 痛点三:多栏排版 (Multi-column Layout)

现象:论文或报纸,左右两栏。解析器按从左到右、从上到下读取,导致句子交叉。

  • 原文:
    | 左边栏内容 A | 右边栏内容 B |
    | 左边栏内容 C | 右边栏内容 D |
    
  • 错误解析:A B C D
  • 正确解析:A C (左栏) + B D (右栏)

解决方案布局分析 (Layout Analysis)

  • 使用 Nougat (Meta 开源模型) 或 Marker
  • 使用云端 API (Azure/AWS) 的布局分析功能。
  • Spring AI 中需自定义解析器集成这些工具。

3.3.4 痛点四:页眉页脚与页码 (Noise)

现象:每一页底部都有 "© 2024 Company Name - Page 1"。 后果:这些噪声会被向量化,干扰检索(搜“页码”会搜出所有文档)。

解决方案正则清洗 (Regex Cleaning)。 在解析后,分片前,增加一个DocumentTransformer 进行清洗。去降低我们的噪声。

@Bean
public DocumentTransformer noiseCleaner() {
    return document -> {
        String content = document.getContent();
        // 移除页码,如 "Page 1 of 10"
        content = content.replaceAll("(?m)^Page\\s+\\d+\\s+of\\s+\\d+$", "");
        // 移除页脚版权
        content = content.replaceAll("©\\s+\\d{4}.*", "");
        // 移除过多空行
        content = content.replaceAll("\\n{3,}", "\n\n");
        
        document.setContent(content);
        return document;
    };
}

3.4 元数据

元数据是 RAG 实现权限控制精准过滤的关键。解析时必须尽可能多地提取信息。

3.4.1 必提取的元数据字段

字段名

类型

用途

示例

source

String

溯源,告诉用户答案来自哪

"employee_handbook.pdf"

file_type

String

过滤文件类型

"pdf", "docx"

page_number

Integer

定位具体位置

5

section_title

String

语义增强,知道属于哪一章

"第三章:考勤制度"

created_date

Date

时间过滤,只用最新文档

"2023-10-01"

department

String

权限隔离核心

"HR", "Finance"

3.4.2 Spring AI 中注入元数据

解析器通常只能提取基础元数据(如文件名)。业务元数据(如部门)需要手动注入。

@Service
public class MetadataEnricher {

    public List<Document> enrich(List<Document> docs, String department) {
        for (Document doc : docs) {
            // 1. 基础元数据 (解析器通常已自动填充)
            // doc.getMetadata().get("source") 
            
            // 2. 业务元数据 (手动注入)
            doc.getMetadata().put("department", department);
            doc.getMetadata().put("security_level", "internal");
            
            // 3. 时间戳
            doc.getMetadata().put("ingestion_time", System.currentTimeMillis());
        }
        return docs;
    }
}

4.3 从文件内容提取元数据 

有些元数据在文件内容里,比如“文档版本号”。 方案:使用 LLM 在解析阶段提取元数据。

// 伪代码:解析后先过一遍 LLM 提取 Meta
public Document extractMeta(Document doc) {
    String prompt = "从以下文本中提取版本号和发布日期,返回 JSON: " + doc.getContent().substring(0, 1000);
    String metaJson = llm.call(prompt);
    // 解析 JSON 并放入 doc.getMetadata()
    return doc;
}

3.5 解析总体流程


总结

到这里,我们学习了RAG的总体流程与第一个流程:文档解析的学习,为避免文章过长,我打算分多篇进行讲解

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐