用 LangChain4j 从零实现 RAG:基于 PDF 文档的智能问答系统
在大语言模型时代,让 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. 配置嵌入模型(将文本转换为向量)@Beanpublic OpenAiEmbeddingModel embeddingModel() {return OpenAiEmbeddingModel.withApiKey("你的OpenAI API密钥");}// 2. 配置向量存储(存储文档向量)@Beanpublic EmbeddingStore<TextSegment> embeddingStore() {// 实际生产环境可替换为Pinecone、Weaviate等向量数据库return new InMemoryEmbeddingStore<>();}// 3. 配置大语言模型(用于生成回答)@Beanpublic 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聊天助手@Resourceprivate ChatMemoryAssistant ragChatAssistant;// 注入向量存储@Resourceprivate 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();}}
}
文档处理流程解析:
- 文档加载:通过
FileInputStream
读取本地 PDF 文件(支持其他格式如 Word、TXT 等)。 - 文档解析:使用
ApacheTikaDocumentParser
解析文档内容(Tika 支持多种格式,无需单独处理 PDF 解析逻辑)。 - 文档分块:
EmbeddingStoreIngestor
自动将文档分割为适合嵌入的小片段(默认策略:每块 200 字符,重叠 0 字符,可自定义)。 - 嵌入存储:分块后的文本片段通过嵌入模型转换为向量,存入
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 会自动将检索到的文档内容作为上下文传入大模型,生成符合系统提示的回答。
运行与测试
准备工作:
- 替换代码中的 OpenAI API 密钥(可在 OpenAI 官网申请)。
- 将 PDF 文档放入
src/main/resources/static
目录(或修改代码中的文件路径)。
启动应用:运行 Spring Boot 主类,访问接口
http://localhost:8080/rag/query?query=你的问题
。示例:
- 若 PDF 中包含 "错误码 1001 表示参数无效" 的内容,查询
http://localhost:8080/rag/query?query=错误码A0001是什么意思
,会返回基于文档的准确回答。
- 若 PDF 中包含 "错误码 1001 表示参数无效" 的内容,查询
优化与扩展方向
替换向量存储:将
InMemoryEmbeddingStore
替换为生产级向量数据库(如 Pinecone、Milvus、Weaviate),支持大规模文档存储。自定义分块策略:默认分块可能不适合长文档,可自定义分块器:
DocumentSplitter splitter = new CharacterDocumentSplitter(500, // 块大小50, // 块重叠"\n" // 分隔符 ); EmbeddingStoreIngestor.builder().documentSplitter(splitter).embeddingStore(embeddingStore).build().ingest(document);
使用本地模型:若需隐私保护,可替换为本地嵌入模型(如 BGE)和大语言模型(如 Llama 3):
// 本地嵌入模型 EmbeddingModel embeddingModel = new BgeSmallEnV15QuantizedEmbeddingModel(); // 本地大语言模型 ChatModel chatModel = new Llama3ChatModel(...);
添加文档元数据:解析文档时可添加元数据(如作者、日期),检索时支持过滤:
Document document = Document.from(text, Map.of("source", "Java开发手册"));
总结
本文通过实际代码演示了如何用 LangChain4j 快速搭建 RAG 系统,核心流程包括:文档解析→分块→嵌入存储→检索→生成回答。LangChain4j 通过封装复杂的底层逻辑,让开发者只需关注业务需求,大幅降低了 RAG 的实现门槛。
无论是企业内部知识库问答、产品手册查询还是法律文档分析,这种架构都能快速适配,为 AI 应用注入 "读懂本地文档" 的能力。