Langchain4j(5)RAG之多格式文档加载(PDF / Word / TXT / 批量文件夹)

概念

官方标准 RAG 架构:

复制代码
1. Document Loading   文档加载
2. Parsing            格式解析
3. Text Cleaning      文本清洗(官方推荐)
4. Splitting          文档分块
5. Embedding          向量生成
6. Storage            向量存储
7. Retrieval          检索
8. Generation         回答生成

支持的文档类型

格式 加载器 特性
PDF ApachePdfDocumentLoader, PdfBoxDocumentLoader 文本、表格、元数据
Word ApacheTikaDocumentLoader .doc, .docx
Excel ApacheTikaDocumentLoader .xls, .xlsx
HTML HtmlDocumentLoader 网页解析
Markdown MarkdownDocumentLoader .md 文件
TXT TextDocumentLoader 纯文本

DEMO

1.准备文件



2.添加依赖

用于读取 PDF + Word

复制代码
<!-- 读取 PDF -->
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
</dependency>

<!-- 读取 Word DOCX -->
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-document-parser-apache-poi</artifactId>
</dependency>

完整版的 pom

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- 使用 Spring Boot 父项目,自动管理版本 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/>
    </parent>

    <groupId>org.example</groupId>
    <artifactId>langchain4j-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>17</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <langchain4j.version>1.3.0</langchain4j.version>
    </properties>

    <dependencies>
        <!-- 1. Spring Boot 核心依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!-- 2. LangChain4j 核心依赖 -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j</artifactId>
        </dependency>

        <!-- 3. OpenAI 适配依赖 -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-open-ai</artifactId>
        </dependency>

        <!-- 4. 流式依赖       -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-reactor</artifactId>
        </dependency>
        <!-- 5.读取 PDF -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
        </dependency>

        <!-- 6. 读取 Word DOCX -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-document-parser-apache-poi</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>dev.langchain4j</groupId>
                <artifactId>langchain4j-bom</artifactId>
                <version>${langchain4j.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <!-- Spring Boot 打包插件 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3.实现

复制代码
package org.deepseek.demo11;

import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentParser;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

public class RAGMultiDocDemo {

    interface Assistant {
        @SystemMessage("你必须严格根据文档内容回答,不知道就说未找到,绝对不能编造。")
        String ask(String question);
    }

    public static void main(String[] args) {
        try {
            // 1. 模型配置
            ChatModel chatModel = OpenAiChatModel.builder()
                    .apiKey("sk-xxxxxxxxxxxxxx")
                    .baseUrl("https://api.deepseek.com")
                    .modelName("deepseek-chat")
                    .temperature(0.0)
                    .build();

            EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
                    .apiKey("sk-xxxxxxxxxxxxxxxx")
                    .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                    .modelName("text-embedding-v1")
                    .build();

            // 2. 批量加载 所有 PDF / Word / TXT
            String folderPath = "D:\\Project\\public\\langchain4j\\demo\\docs";
            List<Document> documents = new ArrayList<>();
            ApachePdfBoxDocumentParser pdfParser = new ApachePdfBoxDocumentParser();

            Files.walk(Paths.get(folderPath))
                    .filter(Files::isRegularFile)
                    .forEach(path -> {
                        try {
                            String fileName = path.getFileName().toString().toLowerCase();

                            // 【PDF 解析】使用你提供的 parse(InputStream)
                            if (fileName.endsWith(".pdf")) {
                                documents.add(pdfParser.parse(Files.newInputStream(path)));
                                System.out.println("✅ 加载PDF:" + fileName);
                            }

                            // Word / TXT 直接读取文本
                            else if (fileName.endsWith(".docx") || fileName.endsWith(".doc") || fileName.endsWith(".txt")) {
                                String text = Files.readString(path);
                                documents.add(Document.from(text));
                                System.out.println("✅ 加载文档:" + fileName);
                            }

                        } catch (Exception e) {
                            System.err.println("❌ 加载失败:" + path);
                        }
                    });

            // 3. 文档分块(短文本不分块,保证关键信息完整)
            List<TextSegment> segments = new ArrayList<>();
            for (Document doc : documents) {
                String cleanText = doc.text()
                        .replace("\n", " ")
                        .replace("\r", " ")
                        .trim();

                if (cleanText.length() < 300) {
                    segments.add(TextSegment.from(cleanText));
                } else {
                    segments.addAll(DocumentSplitters.recursive(512, 128).split(Document.from(cleanText)));
                }
            }

            // 4. 向量入库(分批,避开通义限制)
            EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
            int batchSize = 20;
            for (int i = 0; i < segments.size(); i += batchSize) {
                int end = Math.min(i + batchSize, segments.size());
                var batch = segments.subList(i, end);
                embeddingStore.addAll(embeddingModel.embedAll(batch).content(), batch);
            }

            // 5. 检索器
            ContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
                    .embeddingStore(embeddingStore)
                    .embeddingModel(embeddingModel)
                    .maxResults(10)
                    .build();

            // 6. AI 助手
            Assistant assistant = AiServices.builder(Assistant.class)
                    .chatModel(chatModel)
                    .contentRetriever(retriever)
                    .build();

            // 7. 测试提问
            System.out.println("------------------------------------------------");
            System.out.println("创始人: " + assistant.ask("公司创始人是谁?"));
            System.out.println("成立时间: " + assistant.ask("公司成立时间是?"));
            System.out.println("公司名称: " + assistant.ask("公司名称是?"));

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

知识点

RAG 核心原理

  1. RAG(检索增强生成)
    • 先检索外部知识库,再结合检索内容生成回答,解决大模型幻觉、知识滞后问题
    • 流程:用户问题 → 向量检索 → 上下文注入 → 大模型回答
  2. 向量嵌入(Embedding)
    • 将文本转为高维向量,用于语义相似度计算
    • 相同语义文本向量距离更近,实现语义检索
  3. 向量检索
    • 问题向量化后与向量库匹配,召回最相关文本片段
    • 控制召回数量,平衡检索精度与效率
  4. Prompt 工程
    • 系统提示词约束回答行为,禁止编造内容
    • 温度系数 temperature=0.0 保证回答严谨、无发散

LangChain4j 核心组件

  1. 文档模型

    • Document:统一封装 PDF、Word、TXT 等文档内容
    • TextSegment:- 文档分块后的最小处理单元,是向量生成、向量存储、检索的核心对象,包含文本内容、元数据等信息,与向量一一对应。
  2. 文档解析器

    • ApachePdfBoxDocumentParser:基于Apache PDFBox开源库实现,专门用于解析PDF文档,支持复杂PDF(如带图片、表格的PDF,但表格文本可能无法完全解析)
    • 通用文本处理:TXT、DOCX、DOC等文本文件无需专用解析器,可直接通过文件读取方法获取文本内容,再封装为Document对象。
  3. 文档分块器

    • 核心类与方法:DocumentSplitters.recursive(maxSegmentSize, overlapSize),属于递归分块器,是项目中使用的核心分块工具。

    • 官方职责:将长文档切分为多个小片段TextSegment,适配模型的上下文窗口,避免因文本过长超出模型输入限制。

    • 参数详情:

      • 512:每个片段的最大长度(单位:字符),需根据向量模型的输入限制调整。

      • 128:相邻片段的重叠长度(单位:字符),核心作用是保证语义连续性,避免一句话被拆分导致语义断裂。

    • 实践建议:长文本必须切分;短文本(<200字符)建议不切分;数字、时间、关键词等短关键信息需保持完整,避免切分后丢失核心信息。

  4. 模型相关

    • ChatModel:对话大模型接口,负责最终的问答生成,项目中使用OpenAiChatModel实现类(兼容DeepSeek接口),调用DeepSeek Chat模型。
    • EmbeddingModel:向量模型接口,负责将文本片段转为高维向量,项目中使用OpenAiEmbeddingModel实现类(兼容阿里云接口),调用阿里云text-embedding-v1模型。
  5. 向量存储与检索

    • EmbeddingStore:向量存储抽象接口,用于存储"向量-文本片段"的映射关系,本例使用 InMemoryEmbeddingStore 内存向量库,轻量、无需部署,适合演示和小规模测试;生产环境可替换为ElasticSearch、PGVector等。
    • EmbeddingStoreContentRetriever:内容检索器,RAG检索端核心组件,负责将用户问题向量化、在向量库中检索相关片段,无需手动实现检索逻辑,简化开发。
    • 向量检索官方行为:根据用户问题生成向量,在向量库中进行相似度检索,返回最相关的Top-K片段(项目中K=10)。
  6. AI 服务封装

    • AiServices:简化 AI 服务构建,绑定模型与检索器
    • @SystemMessage:注解式定义系统提示词

工程实践与优化

  1. 批处理优化
    • 向量生成分批调用(项目中batchSize=20),避免单次请求超出向量模型的接口频率/数量限制,提升接口调用的稳定性。
  2. 文本分块策略
    • 差异化分块:短文本直接保留,长文本自动切块,既保证短文本的语义完整性,又避免长文本超出模型输入限制。
    • 关键注意:短文本(<15字)语义向量表达较弱,检索相似度偏低,建议不切分,并适当提高maxResults,结合关键词检索提升精度。
  3. 低耦合设计
    • 组件可替换:模型(对话模型、向量模型)、向量库、文档解析器均可灵活替换,无需修改整体代码结构,便于后续扩展和迭代。
    • 模块化设计:将RAG流程拆分为模型初始化、文档加载、分块、向量生成、检索、问答等独立模块,职责清晰,便于维护和调试。
  4. 鲁棒性处理
    • 文件遍历容错:通过try-catch捕获单个文件的加载、解析异常,确保单个文件处理失败不影响整体流程的执行。
    • 格式兼容:针对不同格式的文档(PDF、Word、TXT)采用差异化的解析方案,提升项目的兼容性。

加载文件:

复制代码
遍历文件夹 → 判断后缀 → 选择解析器 → 解析 → 加入列表

Files.walk(文件夹)
    .filter(是文件)
    .forEach(文件 -> {
        if (pdf)  用 PDF 解析器
        if (docx) 用 Word 解析器
        if (txt)  用 纯文本解析器
    })

FileSystemDocumentLoader

FileSystemDocumentLoader 支持自动加载 PDF / Word / TXT

PDF 自动加载必须满足两个条件:

  1. 引入 langchain4j-document-parser-apache-pdfbox 依赖
  2. 解析器通过 SPI 自动注册

手动加载 PDF 是兼容写法,适用于所有版本

批量加载标准实现

复制代码
1. 遍历目标目录,获取所有文件;
2. 根据文件后缀判断文件类型;

3. 获取系统中注册的对应DocumentParser;

4. 调用parser.parse(inputStream)解析单个文件,生成Document对象;

5. 收集所有Document对象到列表,完成批量加载。

核心总结:批量加载 = 文件遍历 + 循环调用单个解析器,DocumentParser仅负责单个文件解析,不具备批量处理能力。

PDF 解析与文本清洗

PPDF 格式内部按行 / 块存储,解析后天然包含大量换行、空格、分段

这些符号会降低向量相似度,导致检索匹配失败

属于PDF 格式特性,非解析器错误,

必须进行文本清洗

  1. 去除换行符 \n
  2. 去除回车符 \r
  3. 多个空格合并为单个空格
  4. 去除首尾空白

DocumentParser

DocumentParser 是单个文件解析器

不提供批量能力、不提供遍历能力

职责:将 InputStream → 解析为 1 个 Document

文档分块(Document Splitting)

  1. DocumentSplitter 官方职责
    官方定义:
    将长文档切分为多个小片段 TextSegment,适配模型上下文窗口。
  2. recursive(512, 128) 官方行为
    512:最大片段长度
    128:片段重叠长度(保证语义连续性)
    实践:
    长文本必须切分
    短文本(<200)建议不切分
    数字、时间、关键词等短关键信息应保持完整

Embedding & Retrieval 规则

  1. 向量检索官方行为

    根据用户问题生成向量

    在向量库中做相似度检索

    返回最相关的 Top-K 片段

  2. 对短文本的检索说明

    短文本(<15 字)语义向量表达较弱

    检索相似度偏低

    建议:

    不切分

    适当提高 maxResults

    结合关键词检索

流程图

复制代码
开始
  ↓
【初始化模型】
  ├─ 对话模型:DeepSeek Chat
  └─ 向量模型:阿里云 text-embedding-v1
  ↓
【遍历指定文件夹】
  ↓
判断文件类型
  ├─ .pdf → Apache PdfBox 解析 → 生成 Document
  ├─ .doc/.docx/.txt → 直接读取文本 → 生成 Document
  └─ 其他文件 → 跳过
  ↓
【文档清洗】
  去换行、去空格、统一格式
  ↓
【智能分块】
  ├─ 文本长度 < 300 → 直接作为片段
  └─ 文本长度 ≥ 300 → 按 512 字符分块,重叠 128
  ↓
【批量生成向量】
  调用向量模型 → 得到文本片段向量
  ↓
【存入内存向量库】
  InMemoryEmbeddingStore
  ↓
【构建检索器】
  EmbeddingStoreContentRetriever
  ↓
【创建 AI 助手】
  绑定对话模型 + 检索器
  ↓
【用户提问】
  例:创始人、成立时间、公司名称
  ↓
【RAG 检索】
  问题向量化 → 向量库匹配 → 返回最相关10条片段
  ↓
【AI 生成回答】
  仅依据检索内容作答,不编造
  ↓
输出答案
  ↓
结束
相关推荐
Seven972 小时前
【从0到1构建一个ClaudeAgent】内存管理-上下文压缩
java
迷藏4942 小时前
**基于Python与OpenCV的光场显示图像处理技术实践**在现代显示技术发展中,**光场显示(Light
java·图像处理·python·opencv
Godson_beginner2 小时前
Aspose.PDF for Java(实现PDF转Word无水印无页数限制)
java·spring·pdf·文档转换
Lsk_Smion2 小时前
Sability安卓(三)_基础开发知识扫盲,开学XML......
android·java·android studio·安卓
indexsunny2 小时前
互联网大厂Java求职面试实战:从Spring Boot到Kafka的技术问答解析
java·spring boot·spring cloud·kafka·flyway·hikaricp·microservices
juniperhan2 小时前
Flink 系列第9篇:Flink 重启策略详解
java·大数据·数据仓库·flink
星晨雪海2 小时前
优惠券秒杀的核心业务逻辑
java·前端·数据库
阿飞不想努力2 小时前
文件上传原理与实操
java·spring boot·vue·文件上传
Cx330❀2 小时前
线程进阶实战:资源划分与线程控制核心指南
java·大数据·linux·运维·服务器·开发语言·搜索引擎