【LangChain4j系列05】RAG 检索增强生成完整指南

RAG(Retrieval-Augmented Generation)是 LLM 应用最核心的模式之一。本章从原理到实践,覆盖 Easy/Naive/Advanced RAG 三种方案,深入解析索引管线、检索策略、重排序等核心机制。


5.1 为什么需要 RAG?

LLM 的两个根本局限:

  1. 知识截止日期:训练数据有截止日期,不知道训练后发生的事情
  2. 私有知识盲区:不知道企业内部文档、产品手册、私有代码库

RAG 的解决思路:在提问之前,先找到相关文档片段,连同问题一起发给 LLM

swift 复制代码
传统 LLM 调用:
  User:"公司年假政策是什么?" → LLM → 编造/不知道

RAG 调用:
  User:"公司年假政策是什么?"
  → 检索: 从企业文档库找到 "员工手册第3章:年假政策..."
  → Prompt: "根据以下信息回答:\n[员工手册第3章:年假政策...]\n\n用户问题:公司年假政策是什么?"
  → LLM → 准确回答

5.2 三种 RAG 方案对比

方案 适合阶段 配置复杂度 质量
Easy RAG 原型验证 极低(一行代码) 一般
Naive RAG 入门 中等
Advanced RAG 生产环境 中-高 最优

5.3 Easy RAG:开箱即用

xml 复制代码
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-easy-rag</artifactId>
    <version>1.18.1-beta28</version>
</dependency>
java 复制代码
// 1. 加载文档(支持递归扫描)
List<Document> documents = FileSystemDocumentLoader.loadDocuments("/data/docs");

// 2. 创建内存向量库并一键摄入
InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
EmbeddingStoreIngestor.ingest(documents, embeddingStore);

// 3. 创建 AI Service
Assistant assistant = AiServices.builder(Assistant.class)
    .chatModel(model)
    .contentRetriever(EmbeddingStoreContentRetriever.from(embeddingStore))
    .build();

// 4. 直接使用
String answer = assistant.chat("怎么配置 Easy RAG?");

Easy RAG 幕后是什么?

  • 使用 Apache Tika 自动解析文档格式
  • 使用 bge-small-en-v1.5(24MB,完全离线)作为嵌入模型
  • 使用 300 token/30 token 重叠的递归分割器

⚠️ Easy RAG 适合原型和学习,生产环境建议升级到 Advanced RAG。


5.4 RAG 核心数据模型

Document → TextSegment → Embedding

scss 复制代码
Document (整个文档)
  │  .text()          --- 文本内容
  │  .metadata()      --- 元数据(文件名、来源、日期等)
  │  .toTextSegment() --- 转为 TextSegment
  │
  └── 分割 (DocumentSplitter)
        │
        ▼
      TextSegment (文档片段)
        │  .text()      --- 片段文本
        │  .metadata()  --- 继承自 Document + "index": "0", "1", ...
        │
        └── 嵌入 (EmbeddingModel)
              │
              ▼
            Embedding (向量)
              .vector()     --- float[]
              .dimension()  --- 向量维度

Metadata:Key-Value 元数据

java 复制代码
Metadata meta = new Metadata()
    .put("source", "employee_handbook_v2.pdf")
    .put("chapter", "3")
    .put("lastUpdated", "2025-03-15")
    .put("author", "HR Department");

// 支持类型:String, Integer, Long, Float, Double, UUID
// 不支持:Date(用 String 代替)、Boolean

Embedding 的数学操作

java 复制代码
Embedding queryEmbedding = embeddingModel.embed("What is RAG?").content();
Embedding docEmbedding = embeddingModel.embed("RAG stands for...").content();

// 余弦相似度
double similarity = CosineSimilarity.between(queryEmbedding, docEmbedding);

// 归一化(原地修改) ~将向量的长度(模长 / $L_2$ 范数)缩放到 1,同时保持向量的方向(语义信息)完全不变。归一化最核心的目的只有一个:大幅提升语义相似度计算的速度。
queryEmbedding.normalize();

5.5 索引管线(Ingestion Pipeline)

EmbeddingStoreIngestor 是文档到向量库的核心编排器:

java 复制代码
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
    // === 阶段1: 文档转换(清洗/增强) ===
    .documentTransformer(document -> {
        document.metadata().put("ingestedAt", Instant.now().toString());
        document.metadata().put("tenant", "acme-corp");
        return document;
    })

    // === 阶段2: 文档分割 ===
    .documentSplitter(DocumentSplitters.recursive(
        1000,                                    // 最大 chunk 大小(字符)
        200,                                     // 重叠量
        new OpenAiTokenCountEstimator("gpt-4o-mini") // Token 估算器
    ))

    // === 阶段3: TextSegment 转换 ===
    .textSegmentTransformer(textSegment -> {
        // 在片段前加上文件名,帮助 LLM 理解上下文
        String fileName = textSegment.metadata().getString("file_name");
        return TextSegment.from(
            "Source: " + fileName + "\n" + textSegment.text(),
            textSegment.metadata()
        );
    })

    // === 阶段4: 嵌入模型 ===
    .embeddingModel(embeddingModel)

    // === 阶段5: 向量存储 ===
    .embeddingStore(embeddingStore)

    .build();

// 执行摄入
ingestor.ingest(document1);
ingestor.ingest(document2, document3);
IngestionResult result = ingestor.ingest(List.of(doc4, doc5));
// result.tokenUsage() 返回本次摄入的 Token 消耗

5.6 文档加载与解析

10+ 文档加载器

java 复制代码
// 本地文件系统(支持 PathMatcher 过滤)
List<Document> docs = FileSystemDocumentLoader.loadDocuments("/data", new ApachePdfBoxDocumentParser());
List<Document> docs = FileSystemDocumentLoader.loadDocumentsRecursively("/data");

// ClassPath
List<Document> docs = ClassPathDocumentLoader.loadDocuments("manuals/");

// HTTP URL
Document doc = UrlDocumentLoader.load("https://example.com/page");

// 云存储
// AmazonS3DocumentLoader, AzureBlobStorageDocumentLoader,
// GoogleCloudStorageDocumentLoader, TencentCosDocumentLoader

// Web 抓取
// GitHubDocumentLoader, SeleniumDocumentLoader, PlaywrightDocumentLoader

7+ 文档解析器

解析器 适用格式
TextDocumentParser TXT, HTML, MD
ApachePdfBoxDocumentParser PDF
ApachePoiDocumentParser DOC, DOCX, PPT, XLS
ApacheTikaDocumentParser 几乎所有格式(自动检测)
DoclingDocumentParser 现代文档解析器
MarkdownDocumentParser Markdown
YamlDocumentParser YAML

文档分割策略

java 复制代码
// 按段落
DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(500, 50);

// 按句子(需要 OpenNLP)
DocumentBySentenceSplitter splitter = new DocumentBySentenceSplitter(300, 30);

// 递归分割(推荐:依次尝试 \n\n → \n → 空格 → 字符)
DocumentSplitters.recursive(1000, 200, tokenCountEstimator);

// 按行 / 按单词 / 按字符 / 按正则
new DocumentByLineSplitter(1000, 200);
new DocumentByWordSplitter(500, 50);
new DocumentByCharacterSplitter(1000, 200);
new DocumentByRegexSplitter("(?<=\\.)\\s+", 500, 50);

5.7 向量存储(Embedding Store)

核心接口

java 复制代码
public interface EmbeddingStore<Embedded> {
    String add(Embedding embedding);
    String add(Embedding embedding, Embedded embedded);  // Embedded 通常是 TextSegment
    void addAll(List<String> ids, List<Embedding> embeddings, List<Embedded> embedded);
    List<EmbeddingMatch<Embedded>> search(EmbeddingSearchRequest request);
    void remove(String id);
    void removeAll(Filter filter);
    void removeAll();
}

搜索请求

java 复制代码
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
    .queryEmbedding(queryEmbedding)         // 查询的向量
    .maxResults(5)                          // 返回最多 5 条
    .minScore(0.75)                         // 最低相似度阈值
    .filter(metadataKey("tenant")           // 元数据过滤
        .isEqualTo("acme-corp"))
    .build();

EmbeddingSearchResult<TextSegment> result = embeddingStore.search(request);
List<EmbeddingMatch<TextSegment>> matches = result.matches();

for (EmbeddingMatch<TextSegment> match : matches) {
    double score = match.score();               // 0.0 ~ 1.0
    String id = match.embeddingId();
    TextSegment segment = match.embedded();     // 原始文本片段
}

元数据过滤(Filter DSL)

java 复制代码
// 等值过滤
Filter filter = metadataKey("status").isEqualTo("published");

// 组合过滤
Filter filter = Filter.and(
    metadataKey("tenant").isEqualTo("acme"),
    Filter.or(
        metadataKey("type").isEqualTo("manual"),
        metadataKey("type").isEqualTo("policy")
    )
);

// 范围过滤
Filter filter = metadataKey("year").isGreaterThanOrEqualTo(2023);

// 集合过滤
Filter filter = metadataKey("category").isIn("java", "kotlin");

// 子字符串过滤(仅 Milvus、PgVector、Qdrant 支持)
Filter filter = metadataKey("title").containsString("RAG");

// 不支持的 EmbeddingStore 会抛出 UnsupportedFeatureException

5.8 Naive RAG

java 复制代码
ContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
    .embeddingStore(embeddingStore)
    .embeddingModel(embeddingModel)
    .maxResults(5)
    .minScore(0.75)
    .filter(metadataKey("tenant").isEqualTo("acme-corp"))
    // 动态过滤 ~调用时动态上下文信息,可以是租户信息、用户信息等
    .dynamicFilter(query -> {
        String userId = query.metadata().invocationParameters().get("userId");
        return metadataKey("userId").isEqualTo(userId);
    })
    .build();

Assistant assistant = AiServices.builder(Assistant.class)
    .chatModel(model)
    .contentRetriever(retriever)
    .build();

5.9 Advanced RAG:模块化管线

DefaultRetrievalAugmentor

java 复制代码
RetrievalAugmentor augmentor = DefaultRetrievalAugmentor.builder()
    // === 查询转换 ===
    .queryTransformer(new CompressingQueryTransformer(model))
    // CompressingQueryTransformer: 将 "他住在哪?" + 历史 → "张三住在哪?"

    // === 检索器(多个并行检索) ===
    .contentRetriever(embeddingRetriever)      // 向量检索
    .contentRetriever(webSearchRetriever)       // Web 搜索

    // === 聚合器 ===
    .contentAggregator(new ReRankingContentAggregator(scoringModel))
    // 使用 Cohere 等 ScoringModel 对结果重排序

    // === 注入器 ===
    .contentInjector(DefaultContentInjector.builder()
        .promptTemplate(PromptTemplate.from("用户问题:{{userMessage}}\n\n参考资料:\n{{contents}}"))
        .metadataKeysToInclude(List.of("source", "chapter"))  // 附带元数据
        .build())

    // === 并发执行器 ===
    .executor(Executors.newFixedThreadPool(4))

    .build();

Assistant assistant = AiServices.builder(Assistant.class)
    .chatModel(model)
    .retrievalAugmentor(augmentor)  // 使用 Advanced RAG
    .build();

查询转换器选择

转换器 功能 适用场景
DefaultQueryTransformer 原样传递 不需要改写
CompressingQueryTransformer LLM 压缩多轮对话 "他在哪?" → "张三住在哪?"
ExpandingQueryTransformer LLM 扩展为多个变体 提高检索召回率
RepeatingQueryTransformer 重复检索请求 社区模块

各种 ContentRetriever

java 复制代码
// 向量检索
EmbeddingStoreContentRetriever.builder()
    .embeddingStore(store).embeddingModel(model)
    .maxResults(5).minScore(0.75).build();

// Web 搜索
WebSearchContentRetriever.builder()
    .webSearchEngine(googleEngine)
    .maxResults(3).build();

// SQL 数据库(实验性)
SqlDatabaseContentRetriever.builder()
    .dataSource(dataSource).chatModel(model).build();

// Azure AI Search(全文+向量+混合)
AzureAiSearchContentRetriever.builder()...

// Neo4j 知识图谱 → Cypher 查询
Neo4jContentRetriever.builder()...

// Elasticsearch(全文+向量+混合)
ElasticsearchContentRetriever.builder()...

WebSearchContentRetriever 示例

java 复制代码
WebSearchEngine google = GoogleCustomWebSearchEngine.builder()
    .apiKey(System.getenv("GOOGLE_API_KEY"))
    .csi(System.getenv("GOOGLE_SEARCH_ENGINE_ID"))
    .build();

ContentRetriever webRetriever = WebSearchContentRetriever.builder()
    .webSearchEngine(google)
    .maxResults(3)
    .build();

5.10 RAG 作为 Tool(按需检索)

不是每次对话都需要 RAG。将检索包装为工具让 LLM 自己决定何时检索:

java 复制代码
class SearchTool {
    private final ContentRetriever retriever;

    @Tool("Search for technical information about LangChain4j")
    public String search(String query) {
        return retriever.retrieve(new Query(query)).stream()
            .map(content -> content.textSegment().text())
            .collect(Collectors.joining("\n\n"));
    }
}

Assistant assistant = AiServices.builder(Assistant.class)
    .chatModel(model)
    .tools(new SearchTool(retriever))
    .build();

// "你好" → LLM 不调用 search(节省 Token)
// "LangChain4j 怎么配置 RAG?" → LLM 调用 search

5.11 获取检索来源

使用 Result 获取

java 复制代码
interface Assistant {
    Result<String> chat(String userMessage);
}

Result<String> result = assistant.chat("如何配置 RAG?");
String answer = result.content();
List<Content> sources = result.sources();

for (Content source : sources) {
    String text = source.textSegment().text();
    String fileName = source.textSegment().metadata().getString("file_name");
    System.out.printf("[%s] %s%n", fileName, text);
}

流式获取

java 复制代码
assistant.chat("How to do RAG with LangChain4j?")
    .onRetrieved(sources -> {
        System.out.println("检索到 " + sources.size() + " 个来源");
    })
    .onPartialResponse(System.out::print)
    .onCompleteResponse(System.out::println)
    .onError(Throwable::printStackTrace)
    .start();

5.12 图文档转换(Graph Transformer)

将非结构化文档转换为知识图谱:

java 复制代码
GraphTransformer transformer = new LLMGraphTransformer(
    OpenAiChatModel.builder()
        .apiKey(System.getenv("OPENAI_API_KEY"))
        .build()
);

Document doc = Document.from(
    "Barack Obama was born in Hawaii and served as 44th President."
);
GraphDocument graph = transformer.transform(doc);

// 提取的节点和关系:
// GraphNode(name=Barack Obama, type=Person)
// GraphNode(name=Hawaii, type=Location)
// GraphEdge(from=Barack Obama, predicate=was born in, to=Hawaii)
// GraphEdge(from=Barack Obama, predicate=served as, to=President of the United States)

5.13 最佳实践速查

环节 建议
分割大小 300-1000 token,根据模型 context window 和内容密度调整
重叠 10-20% chunk 大小,保证跨块连续性
嵌入模型选择 中文特化模型(如 bge-large-zh)优于通用多语言模型
Query Embedding 与 Document Embedding 使用不同输入类型(Cohere/Voyage/Google 支持)
结果数 3-5 条通常够用,过多会分散 LLM 注意力
minScore 0.7-0.8 过滤噪音,但也可能漏掉边缘相关信息
元数据注入 将文件名/章节/日期注入 prompt,帮助 LLM 判断信息可信度
重排序 Cohere Rerank 等模型能显著提升结果排序质量

附录:

LangChain4j 中 RAG 相关类

相关推荐
xushichang123_1 小时前
如何观看 2026 亚马逊云科技中国峰会全场主题演讲与技术分论坛内容?
大数据·人工智能
科技新资讯1 小时前
视频出海精细化升级:帧级对口型技术与商用工具落地能力分析
人工智能·音视频
技术长镜头1 小时前
别再只说“走 B+Tree”:一条 SQL 在 InnoDB 中的完整寻址过程
后端·mysql
SomeB1oody1 小时前
【RustyML入门】3.8. 正则化与归一化层
开发语言·后端·机器学习·rust·教程
微硬创新1 小时前
大点数模拟量采集落地:耐达讯自动化32路0-20mA适配PROFINET总线实践解析
运维·人工智能·网络协议·自动化·信息与通信
zhiSiBuYu05171 小时前
Flask Session 与 Cookie 新手实战指南
后端·python·flask
招财小梗1 小时前
AI矩阵获客,品牌连锁落地有啥方案?
大数据·人工智能·矩阵
jianwuhuang821 小时前
告别手拷数据:怎么把平板电脑上Perplexity的表格导出来?详解“AI导出鸭”平板版底层逻辑
人工智能·电脑·ai导出鸭
Wang's Blog1 小时前
AI Agent白手起家57: 上下文记忆系统——基于 Redis 的长短期记忆实现
人工智能