LangChain4j RAG 核心:Document、Embedding 与向量存储抽象

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 的核心抽象,包括:

  1. Document 模型:文档加载、分割策略、TextSegment
  2. Embedding 机制:嵌入模型、批量嵌入、双模态嵌入
  3. 向量存储抽象:统一接口、Redis/PgVector/Milvus 适配
  4. MetadataFilter:检索过滤、高级过滤
  5. 完整流程:从文档加载到答案生成的全链路

核心思想: 通过统一的抽象层,简化 RAG 系统的开发复杂度。

下一步学习:

  • 文章 8:《LangChain4j 高级 RAG:查询压缩、重排序与多路召回》

参考文献:

相关推荐
笨笨马甲2 小时前
Qt 音视频编解码
开发语言·qt
港股研究社2 小时前
腾讯音乐的多元增长新路径:音乐IP经济
大数据·人工智能·tcp/ip
快乐柠檬不快乐2 小时前
使用Python操作文件和目录(os, pathlib, shutil)
jvm·数据库·python
Halo_tjn2 小时前
Java 三个修饰符 相关知识点
java·开发语言
深圳季连AIgraphX2 小时前
UROVAs 端到端自动驾驶模型训练、开闭环测试与上车联调
人工智能·机器学习·自动驾驶
这张生成的图像能检测吗2 小时前
(论文速读)基于深度学习的电动汽车直流充电桩开路故障精确诊断多特征融合模型
人工智能·深度学习·计算机视觉·故障诊断
进击的小头2 小时前
第11篇:频率响应绘制方法——伯德图(Bode Plot)
python·算法
2401_883035462 小时前
C++20概念(Concepts)入门指南
开发语言·c++·算法
番茄去哪了2 小时前
Java基础面试题day01
java·开发语言·后端·javase·八股·面向对象编程