【Langchain4j-Java AI开发】07-RAG 检索增强生成

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();
    }
}

关键步骤详解

  1. 文档分割策略
java 复制代码
// 递归分割器(推荐)
DocumentSplitter splitter = DocumentSplitters.recursive(300, 0);

// 按段落分割
DocumentSplitter splitter = DocumentSplitters.recursive(500, 50, "\n\n");

// 按句子分割
DocumentSplitter splitter = DocumentSplitters.recursive(200, 20, "\\.\\s+");
  1. 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();
  1. 检索参数调优
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();

工作流程

  1. 初始检索:获取前 10 个候选片段
  2. 重排序:使用专门模型重新评分
  3. 筛选:只保留分数 > 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-smalltext-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();

下一步学习

参考资料

  • 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
相关推荐
360智汇云2 小时前
存储压缩:不是“挤水分”,而是让数据“轻装上阵
大数据·人工智能
JoannaJuanCV2 小时前
自动驾驶—CARLA仿真(30)交通管理器(Traffic Manager)
java·redis·自动驾驶
小熊熊知识库2 小时前
AI架构详解以及免费AI如何薅
人工智能·python·ai使用
hakesashou2 小时前
python变量如何加入到文件路径
python
咚咚王者3 小时前
人工智能之数学基础 信息论:第二章 核心度量
人工智能
梦弦183 小时前
Django:Python高效Web开发利器
python·django
Trent19853 小时前
影楼精修-眼镜祛反光算法详解
图像处理·人工智能·算法·计算机视觉·aigc
Knight_AL3 小时前
Spring AOP 中 JoinPoint 使用指南
java·python·spring
jmxwzy3 小时前
点赞系统问题
java·redis·tidb·pulsar