用 LangChain4j 从零实现 RAG:基于 PDF 文档的智能问答系统

发布于:2025-07-13 ⋅ 阅读:(19) ⋅ 点赞:(0)

在大语言模型时代,让 AI 能够理解并基于本地文档回答问题的 RAG(检索增强生成)技术成为热门。本文将结合实际代码,详细介绍如何使用 LangChain4j 框架快速实现一个基于 PDF 文档的 RAG 系统,让 AI 能够 "读懂" 你的本地文档并精准回答相关问题。

什么是 RAG?为什么选择 LangChain4j?

RAG(Retrieval-Augmented Generation,检索增强生成)是一种将外部知识检索与大语言模型生成相结合的技术。它解决了大语言模型 "知识过时" 和 "幻觉生成" 的问题,通过在生成回答前检索相关文档内容,让 AI 基于真实可信的来源生成答案。

LangChain4j 是 Java 生态中优秀的大语言模型应用开发框架,它提供了简洁的 API 封装,简化了 RAG 流程中文档处理、嵌入生成、向量存储、检索匹配等核心环节的实现,让开发者可以用最少的代码搭建生产级 RAG 系统。

实战:用 LangChain4j 实现 PDF 文档问答

环境准备:核心依赖

首先需要在pom.xml中引入 LangChain4j 的核心依赖,包括框架核心、内存向量存储、文档解析器和大语言模型集成(这里以 OpenAI 为例):

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-open-ai</artifactId>
            <version>1.1.0</version>
        </dependency>

        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j</artifactId>
            <version>1.1.0</version>
        </dependency>

        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-reactor</artifactId>
            <version>1.1.0-beta7</version>
        </dependency>

        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-easy-rag</artifactId>
            <version>1.1.0-beta7</version>
        </dependency>

核心组件配置:搭建 RAG 基础框架

我们需要配置三个核心组件:向量存储(用于存储文档嵌入)、嵌入模型(将文本转换为向量)、RAG 聊天助手(整合检索与生成)。以下是 Spring 环境下的配置类实现:

import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.InMemoryEmbeddingStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RAGConfig {

    // 1. 配置嵌入模型(将文本转换为向量)
    @Bean
    public OpenAiEmbeddingModel embeddingModel() {
        return OpenAiEmbeddingModel.withApiKey("你的OpenAI API密钥");
    }

    // 2. 配置向量存储(存储文档向量)
    @Bean
    public EmbeddingStore<TextSegment> embeddingStore() {
        // 实际生产环境可替换为Pinecone、Weaviate等向量数据库
        return new InMemoryEmbeddingStore<>();
    }

    // 3. 配置大语言模型(用于生成回答)
    @Bean
    public ChatModel chatModel() {
        return OpenAiChatModel.withApiKey("你的OpenAI API密钥")
                .modelName("gpt-4o-mini") // 可替换为gpt-4等模型
                .temperature(0.7); // 控制回答随机性,0表示更确定
    }

    // 4. 配置RAG聊天助手(整合检索与生成)
    @Bean(name = "ragChatAssistant")
    public ChatMemoryAssistant ragChatAssistant(
            EmbeddingStore<TextSegment> embeddingStore,
            ChatModel chatModel) {
        
        // 创建内容检索器(从向量存储中检索相关文档)
        EmbeddingStoreContentRetriever contentRetriever = EmbeddingStoreContentRetriever.from(
                embeddingStore,
                embeddingModel(), // 用于将查询转换为向量
                3 // 每次检索返回3个最相关的文档片段
        );

        // 构建RAG聊天助手
        return AiServices.builder(ChatMemoryAssistant.class)
                .chatModel(chatModel) // 生成回答的大模型
                .contentRetriever(contentRetriever) // 检索相关文档
                .chatMemory(MessageWindowChatMemory.withMaxMessages(50)) // 保留对话历史
                .build();
    }
}

关键组件说明

  • EmbeddingStore:存储文档的向量表示,这里使用内存存储(适合演示),生产环境建议使用分布式向量数据库。
  • EmbeddingModel:将文本(文档片段和用户查询)转换为向量,实现语义匹配。
  • ChatModel:大语言模型,基于检索到的文档生成自然语言回答。
  • ChatMemoryAssistant:LangChain4j 提供的接口式助手,自动处理检索 - 生成流程,支持对话记忆。

文档处理:解析 PDF 并存入向量存储

接下来需要实现文档的加载、解析和嵌入存储。以下是控制器中处理 PDF 文档并提供问答接口的代码:

import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.parser.apache.tika.ApacheTikaDocumentParser;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

@RestController
@Slf4j
public class RAGController {

    // 注入RAG聊天助手
    @Resource
    private ChatMemoryAssistant ragChatAssistant;

    // 注入向量存储
    @Resource
    private EmbeddingStore<TextSegment> embeddingStore;

    /**
     * 加载PDF文档并执行RAG问答
     */
    @GetMapping("/rag/query")
    public String ragQuery(@RequestParam String query) {
        try {
            // 1. 读取本地PDF文件(这里以"Java开发错误码.pdf"为例)
            FileInputStream fileInputStream = new FileInputStream(
                "src/main/resources/static/Java开发错误码.pdf"
            );

            // 2. 使用Apache Tika解析PDF文档(支持多种格式:PDF/Word/Excel等)
            Document document = new ApacheTikaDocumentParser().parse(fileInputStream);

            // 3. 将文档分块并存入向量存储
            // 自动分块(默认按字符长度分块,可自定义分块策略)
            EmbeddingStoreIngestor.ingest(document, embeddingStore);
            log.info("文档解析完成,已存入向量存储");

            // 4. 调用RAG助手生成回答(自动检索相关文档)
            String answer = ragChatAssistant.chat(query);
            log.info("RAG回答:{}", answer);

            return answer;

        } catch (FileNotFoundException e) {
            log.error("文档未找到", e);
            return "文档不存在,请检查路径";
        } catch (Exception e) {
            log.error("RAG处理失败", e);
            return "处理失败:" + e.getMessage();
        }
    }
}

文档处理流程解析

  1. 文档加载:通过FileInputStream读取本地 PDF 文件(支持其他格式如 Word、TXT 等)。
  2. 文档解析:使用ApacheTikaDocumentParser解析文档内容(Tika 支持多种格式,无需单独处理 PDF 解析逻辑)。
  3. 文档分块EmbeddingStoreIngestor自动将文档分割为适合嵌入的小片段(默认策略:每块 200 字符,重叠 0 字符,可自定义)。
  4. 嵌入存储:分块后的文本片段通过嵌入模型转换为向量,存入EmbeddingStore

定义聊天助手接口

最后需要定义ChatMemoryAssistant接口,LangChain4j 会通过动态代理自动实现接口逻辑:

import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;

public interface ChatMemoryAssistant {

    // 系统提示:告诉AI如何使用检索到的文档回答问题
    @SystemMessage("""
        你是一个基于文档的问答助手。请根据提供的文档内容回答用户问题。
        如果文档中没有相关信息,请明确说明"文档中未找到相关内容",不要编造答案。
        回答要简洁明了,基于文档事实。
        """)
    String chat(@UserMessage String query);
}

接口说明

  • @SystemMessage:定义 AI 的角色和行为准则,这里要求 AI 严格基于检索到的文档回答。
  • @UserMessage:标记用户输入的查询参数。
  • LangChain4j 会自动将检索到的文档内容作为上下文传入大模型,生成符合系统提示的回答。

运行与测试

  1. 准备工作

    • 替换代码中的 OpenAI API 密钥(可在 OpenAI 官网申请)。
    • 将 PDF 文档放入src/main/resources/static目录(或修改代码中的文件路径)。
  2. 启动应用:运行 Spring Boot 主类,访问接口http://localhost:8080/rag/query?query=你的问题

  3. 示例

    • 若 PDF 中包含 "错误码 1001 表示参数无效" 的内容,查询http://localhost:8080/rag/query?query=错误码A0001是什么意思,会返回基于文档的准确回答。

优化与扩展方向

  1. 替换向量存储:将InMemoryEmbeddingStore替换为生产级向量数据库(如 Pinecone、Milvus、Weaviate),支持大规模文档存储。

  2. 自定义分块策略:默认分块可能不适合长文档,可自定义分块器:

    DocumentSplitter splitter = new CharacterDocumentSplitter(
        500, // 块大小
        50,  // 块重叠
        "\n"  // 分隔符
    );
    EmbeddingStoreIngestor.builder()
        .documentSplitter(splitter)
        .embeddingStore(embeddingStore)
        .build()
        .ingest(document);
    
  3. 使用本地模型:若需隐私保护,可替换为本地嵌入模型(如 BGE)和大语言模型(如 Llama 3):

    // 本地嵌入模型
    EmbeddingModel embeddingModel = new BgeSmallEnV15QuantizedEmbeddingModel();
    // 本地大语言模型
    ChatModel chatModel = new Llama3ChatModel(...);
    
  4. 添加文档元数据:解析文档时可添加元数据(如作者、日期),检索时支持过滤:

    Document document = Document.from(text, Map.of("source", "Java开发手册"));
    

总结

本文通过实际代码演示了如何用 LangChain4j 快速搭建 RAG 系统,核心流程包括:文档解析→分块→嵌入存储→检索→生成回答。LangChain4j 通过封装复杂的底层逻辑,让开发者只需关注业务需求,大幅降低了 RAG 的实现门槛。

无论是企业内部知识库问答、产品手册查询还是法律文档分析,这种架构都能快速适配,为 AI 应用注入 "读懂本地文档" 的能力。


网站公告

今日签到

点亮在社区的每一天
去签到