LangChain4j RAG 核心:Document、Embedding 与向量存储抽象
前言
RAG(Retrieval-Augmented Generation,检索增强生成)是当前 AI 应用的核心技术,它通过检索外部知识库来增强 LLM 的能力。LangChain4j 提供了完整的 RAG 栈,从文档加载、嵌入生成到向量存储,形成一条完整的数据链路。
本文将深入剖析 LangChain4j RAG 的核心抽象:Document 模型、Embedding 机制、向量存储适配。
一、Document 模型
1.1 Document 核心结构
java
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentType;
import dev.langchain4j.data.segment.TextSegment;
// 创建文档
Document document = Document.from(
"LangChain4j 是一个用于 Java 应用程序的大语言模型集成框架...",
Metadata.from("title", "LangChain4j 介绍")
.add("author", "张三")
.add("category", "AI")
.add("created_at", "2024-08-28")
);
System.out.println(document.text()); // 文档正文
System.out.println(document.metadata()); // 元数据
1.2 文档加载器
LangChain4j 支持多种文档加载器:
java
import dev.langchain4j.data.document.DocumentLoader;
import dev.langchain4j.data.document.DocumentType;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
// 1. 加载单个文件
Document pdfDocument = FileSystemDocumentLoader.loadDocument(
"docs/tech-guide.pdf",
DocumentType.PDF
);
// 2. 加载整个目录
List<Document> documents = FileSystemDocumentLoader.loadDocuments(
"docs/",
DocumentType.PDF,
DocumentType.TXT,
DocumentType.MD
);
// 3. 文件过滤(支持 glob 模式)
List<Document> filteredDocs = FileSystemDocumentLoader.loadDocuments(
"docs/",
path -> path.toString().endsWith(".pdf") // 只加载 PDF
);
1.3 文档分割策略
java
import dev.langchain4j.data.document.splitter.DocumentSplitters;
// 1. 按字符分割
DocumentByCharacterSplitter charSplitter = DocumentByCharacterSplitter.builder()
.chunkSize(500) // 每块 500 字符
.chunkOverlap(50) // 重叠 50 字符
.build();
List<TextSegment> charSegments = charSplitter.split(document);
// 2. 按段落分割
DocumentByParagraphSplitter paraSplitter = DocumentByParagraphSplitter.builder()
.maxCharactersPerSegment(1000)
.maxSegmentsPerDocument(100)
.build();
List<TextSegment> paraSegments = paraSplitter.split(document);
// 3. 按句子分割
DocumentByLineSplitter lineSplitter = DocumentByLineSplitter.builder()
.maxSegmentSize(200)
.overlapSize(20)
.build();
List<TextSegment> lineSegments = lineSplitter.split(document);
1.4 TextSegment 模型
java
import dev.langchain4j.data.segment.TextSegment;
// 创建 TextSegment
TextSegment segment = TextSegment.from(
"LangChain4j 支持多种 LLM 提供商",
Metadata.from("source", "tech-guide.pdf")
.add("page", "10")
.add("chapter", "1")
);
System.out.println(segment.text()); // 文本内容
System.out.println(segment.metadata()); // 元数据
二、Embedding 机制
2.1 嵌入模型创建
java
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
// OpenAI 嵌入模型
EmbeddingModel openaiEmbeddingModel = OpenAiEmbeddingModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("text-embedding-3-small")
.build();
// 本地嵌入模型(量化)
import dev.langchain4j.model.embedding.BgeSmallEnV15QuantizedEmbeddingModel;
EmbeddingModel localEmbeddingModel = new BgeSmallEnV15QuantizedEmbeddingModel();
// 生成嵌入
TextSegment segment = TextSegment.from("LangChain4j 是 Java AI 框架");
Response<Embedding> response = localEmbeddingModel.embed(segment);
Embedding embedding = response.content();
System.out.println("向量维度: " + embedding.dimension()); // 384
System.out.println("向量数据: " + Arrays.toString(embedding.vector()));
2.2 批量嵌入
java
List<TextSegment> segments = List.of(
TextSegment.from("LangChain4j 是 Java AI 框架"),
TextSegment.from("支持多种 LLM 提供商"),
TextSegment.from("提供 RAG 能力")
);
// 批量嵌入(性能优化)
Response<List<Embedding>> response = embeddingModel.embedAll(segments);
List<Embedding> embeddings = response.content();
for (int i = 0; i < embeddings.size(); i++) {
System.out.printf("Segment %d: 维度 %d%n", i, embeddings.get(i).dimension());
}
2.3 双模态嵌入(1.4.0 新特性)
java
// 1.4.0 支持图文混合嵌入
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModelName;
EmbeddingModel multimodalModel = OpenAiEmbeddingModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(OpenAiEmbeddingModelName.TEXT_EMBEDDING_3_LARGE)
.build();
// 文本嵌入
Embedding textEmbedding = multimodalModel.embed(
TextContent.from("这是一张猫的图片")
).content();
// 图像嵌入(1.4.0 新增)
Embedding imageEmbedding = multimodalModel.embed(
ImageContent.from("https://example.com/cat.jpg")
).content();
// 计算相似度
float similarity = CosineSimilarity.between(textEmbedding, imageEmbedding);
System.out.println("图文相似度: " + similarity);
三、向量存储抽象
3.1 EmbeddingStore 接口
LangChain4j 提供统一的向量存储接口:
java
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
// 1. 创建向量存储
EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
// 2. 添加嵌入
Embedding embedding = embeddingModel.embed(segment).content();
String id = embeddingStore.add(embedding, segment);
// 3. 批量添加
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
List<String> ids = embeddingStore.addAll(embeddings, segments);
// 4. 检索
Embedding queryEmbedding = embeddingModel.embed("什么是 LangChain4j?").content();
List<EmbeddingMatch<TextSegment>> matches = embeddingStore.findRelevant(
queryEmbedding, 5 // 返回 Top 5
);
for (EmbeddingMatch<TextSegment> match : matches) {
System.out.printf("相似度: %.4f, 文本: %s%n",
match.score(), match.embedded().text());
}
3.2 Redis 向量存储
java
import dev.langchain4j.store.embedding.redis.RedisEmbeddingStore;
import redis.clients.jedis.JedisPooled;
// 配置 Redis
JedisPooled jedis = new JedisPooled(
"localhost", 6379, "password"
);
// 创建 Redis 向量存储
EmbeddingStore<TextSegment> redisStore = RedisEmbeddingStore.builder()
.jedis(jedis)
.dimension(384) // 向量维度
.build();
// 使用方式与 InMemoryEmbeddingStore 一致
redisStore.add(embedding, segment);
List<EmbeddingMatch<TextSegment>> matches = redisStore.findRelevant(queryEmbedding, 5);
3.3 PgVector 向量存储
java
import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore;
import org.postgresql.ds.PGSimpleDataSource;
// 配置 PostgreSQL
PGSimpleDataSource dataSource = new PGSimpleDataSource();
dataSource.setUrl("jdbc:postgresql://localhost:5432/rag");
dataSource.setUser("postgres");
dataSource.setPassword("password");
// 创建 PgVector 向量存储
EmbeddingStore<TextSegment> pgVectorStore = PgVectorEmbeddingStore.builder()
.dataSource(dataSource)
.dimension(384)
.table("embeddings") // 表名
.createTable(true) // 自动创建表
.build();
// 使用
pgVectorStore.add(embedding, segment);
3.4 向量存储适配差异
| 特性 | InMemory | Redis | PgVector | Milvus |
|---|---|---|---|---|
| 持久化 | ❌ | ✅ | ✅ | ✅ |
| 分布式 | ❌ | ✅ | ❌(需额外配置) | ✅ |
| 性能 | 低 | 中 | 中 | 高 |
| 成本 | 免费 | 中 | 中 | 高 |
| 复杂度 | 简单 | 简单 | 简单 | 复杂 |
| 适用场景 | 开发测试 | 中小规模 | 中小规模 | 大规模 |
四、MetadataFilter 检索过滤
4.1 基础过滤
java
import dev.langchain4j.store.embedding.filter.Filter;
// 1. 等值过滤
Filter categoryFilter = Metadata.metadataKey("category").isEqualTo("AI");
// 2. 大于/小于
Filter dateFilter = Metadata.metadataKey("created_at")
.isGreaterThan("2024-01-01");
// 3. 包含
Filter tagsFilter = Metadata.metadataKey("tags")
.contains("Java");
// 4. 组合过滤
Filter combinedFilter = categoryFilter
.and(dateFilter)
.or(tagsFilter);
// 应用过滤
List<EmbeddingMatch<TextSegment>> matches = embeddingStore.findRelevant(
queryEmbedding,
5, // 最大结果数
0.7, // 最小相似度
combinedFilter // 过滤器
);
4.2 高级过滤示例
java
// 场景 1:按作者过滤
Filter authorFilter = Metadata.metadataKey("author").isEqualTo("张三");
// 场景 2:按时间范围
Filter timeRangeFilter = Metadata.metadataKey("created_at")
.isGreaterThan("2024-01-01")
.and(
Metadata.metadataKey("created_at")
.isLessThan("2024-12-31")
);
// 场景 3:按多个条件
Filter complexFilter = Metadata.metadataKey("category")
.isIn(List.of("AI", "ML", "DL"))
.and(
Metadata.metadataKey("page")
.isGreaterThan(10)
);
五、RAG 完整流程
5.1 流程图
┌─────────────────────────────────────────────────────────────┐
│ RAG 完整流程 │
└─────────────────────────────────────────────────────────────┘
┌─────────┐ ┌────────────┐ ┌──────────────┐ ┌─────────┐
│ 文档源 │ -> │ 文档加载器 │ -> │ 文档分割器 │ -> │TextSegment│
│ PDF/TXT │ │ FileSystem │ -> │ Splitter │ -> │ 列表 │
└─────────┘ └────────────┘ └──────────────┘ └────┬────┘
│
▼
┌──────────────┐
│ 元数据 │
│ Metadata │
└──────────────┘
│
▼
┌─────────┐ ┌────────────┐ ┌──────────────┐ ┌─────────┐
│ 文本嵌入 │ -> │ Embedding │ -> │ 向量存储 │ -> │向量数据库│
│ 模型 │ -> │ Model │ -> │ EmbeddingStore│ -> │Redis/PgV│
└─────────┘ └────────────┘ └──────────────┘ └─────────┘
│ │
│ │
└────────────────────────────────┤
│
┌────────────▼────────────┐
│ 查询阶段 │
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ 用户提问:"什么是 │
│ LangChain4j?" │
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ 查询嵌入 │
│ Embedding Query │
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ 向量相似度检索 │
│ FindRelevant() │
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ 检索结果:Top 5 │
│ 相关文档片段 │
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ 构建 Prompt: │
│ 上下文 + 问题 │
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ LLM 生成答案 │
│ Generate() │
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ 返回最终答案 │
│ "LangChain4j 是..." │
└─────────────────────────┘
5.2 完整代码示例
java
public class RAGPipeline {
private final EmbeddingModel embeddingModel;
private final EmbeddingStore<TextSegment> embeddingStore;
private final ChatLanguageModel chatModel;
public RAGPipeline() {
// 1. 初始化嵌入模型
this.embeddingModel = new BgeSmallEnV15QuantizedEmbeddingModel();
// 2. 初始化向量存储
this.embeddingStore = new InMemoryEmbeddingStore<>();
// 3. 初始化聊天模型
this.chatModel = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("llama3.1")
.build();
// 4. 加载文档
loadDocuments();
}
private void loadDocuments() {
// 加载文档
List<Document> documents = FileSystemDocumentLoader.loadDocuments(
"docs/", DocumentType.PDF, DocumentType.TXT
);
// 分割文档
DocumentSplitter splitter = DocumentByCharacterSplitter.builder()
.chunkSize(500)
.chunkOverlap(50)
.build();
// 嵌入并存储
for (Document document : documents) {
List<TextSegment> segments = splitter.split(document);
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
embeddingStore.addAll(embeddings, segments);
}
}
public String query(String question) {
// 1. 嵌入查询
Embedding queryEmbedding = embeddingModel.embed(question).content();
// 2. 检索相关文档
List<EmbeddingMatch<TextSegment>> matches = embeddingStore.findRelevant(
queryEmbedding, 5
);
// 3. 构建上下文
String context = matches.stream()
.map(EmbeddingMatch::embedded)
.map(TextSegment::text)
.collect(Collectors.joining("\n\n"));
// 4. 构建 Prompt
String prompt = String.format(
"基于以下上下文回答问题:\n\n%s\n\n问题:%s",
context, question
);
// 5. 生成答案
return chatModel.generate(prompt);
}
}
六、常见问题
Q1: 如何选择文档分割策略?
A: 根据文档类型选择:
| 文档类型 | 推荐策略 | Chunk Size | Overlap |
|---|---|---|---|
| 技术文档 | 按段落分割 | 1000 字符 | 100 字符 |
| 代码 | 按行分割 | 200 字符 | 20 字符 |
| 新闻 | 按字符分割 | 500 字符 | 50 字符 |
| 书籍 | 按章节分割 | 2000 字符 | 200 字符 |
Q2: 如何优化检索性能?
A: 使用以下策略:
java
// 1. 批量嵌入
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
// 2. 使用高性能向量数据库
// Redis、Milvus、Weaviate
// 3. 限制检索范围
Filter filter = Metadata.metadataKey("category").isEqualTo("AI");
// 4. 缓存热门查询
Cache<String, List<EmbeddingMatch<TextSegment>>> cache = ...;
Q3: 如何处理多语言文档?
A: 使用多语言嵌入模型:
java
EmbeddingModel multilingualModel = OpenAiEmbeddingModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("text-embedding-3-small") // 支持多语言
.build();
七、小结
本文深入剖析了 LangChain4j RAG 的核心抽象,包括:
- Document 模型:文档加载、分割策略、TextSegment
- Embedding 机制:嵌入模型、批量嵌入、双模态嵌入
- 向量存储抽象:统一接口、Redis/PgVector/Milvus 适配
- MetadataFilter:检索过滤、高级过滤
- 完整流程:从文档加载到答案生成的全链路
核心思想: 通过统一的抽象层,简化 RAG 系统的开发复杂度。
下一步学习:
- 文章 8:《LangChain4j 高级 RAG:查询压缩、重排序与多路召回》
参考文献:
- LangChain4j RAG 教程:https://docs.langchain4j.dev/tutorials/rag/
- BGE 嵌入模型:https://github.com/FlagOpen/FlagEmbedding
- PgVector 官方文档:https://github.com/pgvector/pgvector