LangChain4j RAG 检索增强生成

概述
RAG(Retrieval-Augmented Generation,检索增强生成)是一种让 LLM 能够访问外部知识库的技术。通过 RAG,AI 可以回答关于你自己文档的问题,而不仅限于其训练数据。
为什么需要 RAG?
LLM 的局限性
java
// LLM 不知道你公司的内部政策
String answer = model.generate("我们公司的年假政策是什么?");
// 输出: 抱歉,我不知道贵公司的具体年假政策...
使用 RAG 后
java
// RAG 可以从公司文档中检索相关信息
String answer = assistant.chat("我们公司的年假政策是什么?");
// 输出: 根据公司政策文档,员工每年享有15天带薪年假...
RAG 工作原理
1. 文档加载 → 2. 文档分割 → 3. 向量化(Embedding) → 4. 存储到向量数据库
↓
用户提问 → 问题向量化 → 相似度搜索 → 检索相关文档片段 → 组合上下文 → LLM 生成回答
Easy RAG(简单模式)
最快速的 RAG 实现
代码示例 (参考:rag-examples/src/main/java/_1_easy/Easy_RAG_Example.java)
java
package dev.langchain4j.example;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import java.nio.file.Path;
import java.util.List;
import static dev.langchain4j.data.document.loader.FileSystemDocumentLoader.loadDocuments;
import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI;
public class EasyRagExample {
interface Assistant {
String chat(String message);
}
public static void main(String[] args) {
// 1. 创建 ChatModel
ChatLanguageModel chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(GPT_4_O_MINI)
.build();
// 2. 加载文档(支持 txt, pdf, doc 等)
List<Document> documents = loadDocuments(
Path.of("src/main/resources/documents"),
"*.txt" // 加载所有 txt 文件
);
System.out.println("加载了 " + documents.size() + " 个文档");
// 3. 创建内存向量存储
InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
// 4. 将文档摄取到向量存储(自动分割、向量化、存储)
EmbeddingStoreIngestor.ingest(documents, embeddingStore);
// 5. 创建内容检索器
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.from(embeddingStore);
// 6. 创建带 RAG 的 Assistant
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.contentRetriever(contentRetriever) // 关键:注入检索器
.build();
// 7. 开始对话
System.out.println("\n=== RAG 对话示例 ===");
String answer1 = assistant.chat("文档中提到了什么内容?");
System.out.println("Q: 文档中提到了什么内容?");
System.out.println("A: " + answer1);
String answer2 = assistant.chat("能详细说说吗?");
System.out.println("\nQ: 能详细说说吗?");
System.out.println("A: " + answer2);
}
}
特点:
- ✅ 代码简洁,几行即可实现
- ✅ 自动处理文档分割、向量化
- ✅ 适合快速原型开发
- ❌ 缺乏精细控制
Naive RAG(基础模式)
完全手动控制的 RAG
代码示例 (参考:rag-examples/src/main/java/_2_naive/Naive_RAG_Example.java)
java
package dev.langchain4j.example;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentParser;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.parser.TextDocumentParser;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.embedding.onnx.bgesmallenv15q.BgeSmallEnV15QuantizedEmbeddingModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import java.nio.file.Path;
import java.util.List;
import static dev.langchain4j.data.document.loader.FileSystemDocumentLoader.loadDocument;
import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI;
public class NaiveRagExample {
interface Assistant {
String chat(String message);
}
public static void main(String[] args) {
Assistant assistant = createAssistant("src/main/resources/company-policy.txt");
// 测试问答
String answer = assistant.chat("我们公司的年假政策是什么?");
System.out.println(answer);
}
private static Assistant createAssistant(String documentPath) {
// 1. 创建 ChatModel
ChatLanguageModel chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(GPT_4_O_MINI)
.build();
// 2. 加载文档
DocumentParser parser = new TextDocumentParser();
Document document = loadDocument(Path.of(documentPath), parser);
System.out.println("文档加载完成,字符数: " + document.text().length());
// 3. 分割文档为小片段
DocumentSplitter splitter = DocumentSplitters.recursive(
300, // 每个片段最大 300 字符
0 // 片段间无重叠
);
List<TextSegment> segments = splitter.split(document);
System.out.println("文档分割为 " + segments.size() + " 个片段");
// 4. 创建 Embedding 模型(本地模型,无需 API)
EmbeddingModel embeddingModel = new BgeSmallEnV15QuantizedEmbeddingModel();
// 5. 将所有片段向量化
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
System.out.println("生成了 " + embeddings.size() + " 个向量");
// 6. 存储到向量数据库
EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
embeddingStore.addAll(embeddings, segments);
// 7. 创建内容检索器
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(2) // 每次检索最相关的 2 个片段
.minScore(0.6) // 最低相似度阈值 0.6
.build();
// 8. 创建 Assistant
return AiServices.builder(Assistant.class)
.chatModel(chatModel)
.contentRetriever(contentRetriever)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
}
}
关键步骤详解:
- 文档分割策略:
java
// 递归分割器(推荐)
DocumentSplitter splitter = DocumentSplitters.recursive(300, 0);
// 按段落分割
DocumentSplitter splitter = DocumentSplitters.recursive(500, 50, "\n\n");
// 按句子分割
DocumentSplitter splitter = DocumentSplitters.recursive(200, 20, "\\.\\s+");
- Embedding 模型选择:
java
// 本地模型(免费,无需 API)
EmbeddingModel embeddingModel = new BgeSmallEnV15QuantizedEmbeddingModel();
// OpenAI Embedding(需要 API Key)
EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("text-embedding-3-small")
.build();
- 检索参数调优:
java
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(3) // 返回前 3 个最相关片段
.minScore(0.7) // 相似度阈值(0-1)
.build();
支持的文档格式
加载不同格式的文档
java
import dev.langchain4j.data.document.parser.*;
// 文本文件
DocumentParser txtParser = new TextDocumentParser();
Document txtDoc = loadDocument(Path.of("doc.txt"), txtParser);
// PDF 文件
DocumentParser pdfParser = new ApachePdfBoxDocumentParser();
Document pdfDoc = loadDocument(Path.of("doc.pdf"), pdfParser);
// Word 文档
DocumentParser docParser = new ApachePoiDocumentParser();
Document wordDoc = loadDocument(Path.of("doc.docx"), docParser);
// Excel 表格
Document excelDoc = loadDocument(Path.of("data.xlsx"), docParser);
批量加载文档
java
// 加载目录下所有文件
List<Document> documents = loadDocuments(
Path.of("documents/"),
"**/*.{txt,pdf,docx}" // glob 模式
);
// 递归加载子目录
List<Document> documents = loadDocumentsRecursively(
Path.of("knowledge-base/")
);
高级 RAG 技术
1. 查询压缩(Query Compression)
代码示例 (参考:rag-examples/src/main/java/_3_advanced/_01_Advanced_RAG_with_Query_Compression_Example.java)
java
import dev.langchain4j.rag.DefaultRetrievalAugmentor;
import dev.langchain4j.rag.query.transformer.CompressingQueryTransformer;
// 创建查询压缩器
QueryTransformer queryTransformer = new CompressingQueryTransformer(chatModel);
// 创建检索增强器
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.queryTransformer(queryTransformer) // 添加查询压缩
.contentRetriever(contentRetriever)
.build();
// 使用检索增强器而不是直接使用检索器
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.retrievalAugmentor(retrievalAugmentor) // 注意:这里用 retrievalAugmentor
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
// 现在可以处理复杂的多轮对话
assistant.chat("我想了解保险政策");
assistant.chat("那事故赔偿呢?"); // 查询压缩器会将其扩展为完整查询
工作原理:
- 用户:那事故赔偿呢?
- 压缩器将其扩展为:关于保险政策中的事故赔偿是什么?
- 用扩展后的查询进行检索
2. 查询路由(Query Routing)
根据查询类型路由到不同的检索器:
java
import dev.langchain4j.rag.query.router.QueryRouter;
// 定义多个检索器
ContentRetriever technicalDocsRetriever = ...;
ContentRetriever hrDocsRetriever = ...;
ContentRetriever legalDocsRetriever = ...;
// 创建路由器
QueryRouter router = query -> {
String text = query.text().toLowerCase();
if (text.contains("技术") || text.contains("代码")) {
return technicalDocsRetriever;
} else if (text.contains("人事") || text.contains("薪资")) {
return hrDocsRetriever;
} else if (text.contains("合同") || text.contains("法律")) {
return legalDocsRetriever;
}
return technicalDocsRetriever; // 默认
};
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.queryRouter(router)
.build();
3. 重排序(Re-Ranking)
使用专门的重排序模型提高检索精度:
代码示例 (参考:rag-examples/src/main/java/_3_advanced/_03_Advanced_RAG_with_ReRanking_Example.java)
java
import dev.langchain4j.model.cohere.CohereScoringModel;
import dev.langchain4j.rag.content.aggregator.ReRankingContentAggregator;
// 创建 Cohere 重排序模型
ScoringModel scoringModel = CohereScoringModel.builder()
.apiKey(System.getenv("COHERE_API_KEY"))
.modelName("rerank-multilingual-v3.0")
.build();
// 创建内容聚合器
ContentAggregator contentAggregator = ReRankingContentAggregator.builder()
.scoringModel(scoringModel)
.minScore(0.8) // 重排序后的最低分数
.build();
// 构建检索增强器
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.contentRetriever(contentRetriever)
.contentAggregator(contentAggregator) // 添加重排序
.build();
工作流程:
- 初始检索:获取前 10 个候选片段
- 重排序:使用专门模型重新评分
- 筛选:只保留分数 > 0.8 的片段
4. 元数据过滤
为文档添加元数据,实现精确过滤:
代码示例 (参考:rag-examples/src/main/java/_3_advanced/_04_Advanced_RAG_with_Metadata_Example.java)
java
import dev.langchain4j.data.document.Metadata;
import dev.langchain4j.store.embedding.filter.Filter;
// 添加元数据
Document doc1 = Document.from(
"公司技术文档内容...",
Metadata.from("category", "技术", "department", "研发部", "year", 2024)
);
Document doc2 = Document.from(
"人事政策内容...",
Metadata.from("category", "人事", "department", "人力资源", "year", 2024)
);
// 摄取文档(保留元数据)
embeddingStore.addAll(embeddings, segments);
// 创建过滤器
Filter filter = Filter.metadataKey("category").isEqualTo("技术");
// 使用过滤器检索
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.filter(filter) // 只检索技术类文档
.maxResults(3)
.build();
动态过滤:
java
// 根据用户 ID 动态过滤
Function<Query, Filter> dynamicFilter = query -> {
String userId = query.metadata().chatMemoryId().toString();
return Filter.metadataKey("userId").isEqualTo(userId);
};
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.dynamicFilter(dynamicFilter)
.maxResults(3)
.build();
5. 返回来源引用
让 AI 告诉你答案来自哪些文档:
java
import dev.langchain4j.rag.content.Content;
interface AssistantWithSources {
Result<String> chat(String message);
}
class Result<T> {
T content;
List<Content> sources; // 来源
}
// 使用
AssistantWithSources assistant = AiServices.builder(AssistantWithSources.class)
.chatModel(chatModel)
.contentRetriever(contentRetriever)
.build();
Result<String> result = assistant.chat("年假政策是什么?");
System.out.println("答案: " + result.content);
System.out.println("来源: ");
for (Content source : result.sources) {
System.out.println(" - " + source.textSegment().metadata().getString("file_name"));
}
向量数据库集成
使用持久化向量数据库
LangChain4j 支持 15+ 向量数据库:
java
// Chroma
import dev.langchain4j.store.embedding.chroma.ChromaEmbeddingStore;
EmbeddingStore<TextSegment> chromaStore = ChromaEmbeddingStore.builder()
.baseUrl("http://localhost:8000")
.collectionName("my-collection")
.build();
// Pinecone
import dev.langchain4j.store.embedding.pinecone.PineconeEmbeddingStore;
EmbeddingStore<TextSegment> pineconeStore = PineconeEmbeddingStore.builder()
.apiKey(System.getenv("PINECONE_API_KEY"))
.environment("us-east-1-aws")
.index("my-index")
.build();
// PostgreSQL (pgvector)
import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore;
EmbeddingStore<TextSegment> pgStore = PgVectorEmbeddingStore.builder()
.host("localhost")
.port(5432)
.database("vectordb")
.user("postgres")
.password("password")
.table("embeddings")
.dimension(384) // embedding 维度
.build();
// Elasticsearch
import dev.langchain4j.store.embedding.elasticsearch.ElasticsearchEmbeddingStore;
EmbeddingStore<TextSegment> esStore = ElasticsearchEmbeddingStore.builder()
.serverUrl("http://localhost:9200")
.indexName("embeddings")
.build();
RAG 性能优化
1. 分块策略优化
java
// 小块:适合精确查询
DocumentSplitter smallChunks = DocumentSplitters.recursive(200, 20);
// 大块:适合需要更多上下文
DocumentSplitter largeChunks = DocumentSplitters.recursive(500, 50);
// 重叠分块:避免信息在边界丢失
DocumentSplitter overlapping = DocumentSplitters.recursive(300, 50);
2. 缓存检索结果
java
public class CachedRetriever implements ContentRetriever {
private final ContentRetriever delegate;
private final Map<String, List<Content>> cache = new ConcurrentHashMap<>();
@Override
public List<Content> retrieve(Query query) {
return cache.computeIfAbsent(
query.text(),
k -> delegate.retrieve(query)
);
}
}
3. 异步检索
java
CompletableFuture<List<Content>> future = CompletableFuture.supplyAsync(() ->
contentRetriever.retrieve(query)
);
最佳实践
1. 文档预处理
java
// 清理文档
String cleanText = document.text()
.replaceAll("\\s+", " ") // 规范化空白
.replaceAll("[^\\p{L}\\p{N}\\p{P}\\p{Z}]", "") // 移除特殊字符
.trim();
Document cleanedDoc = Document.from(cleanText, document.metadata());
2. 合理设置检索参数
java
// 对于短文档
ContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
.maxResults(1) // 少量结果
.minScore(0.8) // 高相似度
.build();
// 对于长文档或复杂问题
ContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
.maxResults(5) // 更多结果
.minScore(0.6) // 较低相似度
.build();
3. 监控检索质量
java
List<Content> contents = contentRetriever.retrieve(query);
for (Content content : contents) {
System.out.println("片段: " + content.textSegment().text());
System.out.println("相似度: " + content.score());
System.out.println("元数据: " + content.textSegment().metadata());
System.out.println("---");
}
常见问题
Q1: 如何选择 Embedding 模型?
A:
- 本地模型 (免费):
BgeSmallEnV15QuantizedEmbeddingModel - OpenAI (质量高):
text-embedding-3-small或text-embedding-3-large - 多语言 : Cohere 或
multilingual-e5
Q2: 分块大小如何选择?
A:
- 小块(100-300字符): 精确检索,适合问答
- 中块(300-500字符): 平衡选择(推荐)
- 大块(500-1000字符): 需要更多上下文
Q3: 如何处理多语言文档?
A: 使用多语言 Embedding 模型:
java
// 使用多语言模型
EmbeddingModel model = new MultilingualE5SmallEmbeddingModel();
// 或 OpenAI(自动支持多语言)
EmbeddingModel model = OpenAiEmbeddingModel.builder()
.apiKey(apiKey)
.modelName("text-embedding-3-small")
.build();
下一步学习
- 08-向量嵌入与向量数据库 - 深入理解 Embedding
- 09-智能体工作流 - 结合 RAG 构建智能体
- 10-框架集成 - 在生产环境部署 RAG
参考资料
- Easy RAG:
rag-examples/src/main/java/_1_easy/Easy_RAG_Example.java - Naive RAG:
rag-examples/src/main/java/_2_naive/Naive_RAG_Example.java - Advanced RAG:
rag-examples/src/main/java/_3_advanced/ - 官方文档: https://docs.langchain4j.dev/tutorials/rag