系列篇章💥

No. 文章
1 LangChain4j Java AI 应用开发实战(一):LangChain4j 快速入门指南
2 LangChain4j Java AI 应用开发实战(二):大模型参数调优实战:Temperature、TopP、MaxTokens 深度解析
3 LangChain4j Java AI 应用开发实战(三):多模态 AI 开发 - 图片理解与图像生成实战
4 LangChain4j Java AI 应用开发实战(四):提示词工程进阶 - 模板化与结构化 Prompt 设计
5 LangChain4j Java AI 应用开发实战(五):流式响应与对话记忆 - 提升用户体验的关键技术
6 LangChain4j Java AI 应用开发实战(六):声明式 AI Service - LangChain4j 的核心编程模型
7 LangChain4j Java AI 应用开发实战(七):结构化输出实战 - 从非结构化文本提取 POJO 对象
8 LangChain4j Java AI 应用开发实战(八):用户隔离与持久化记忆 - 企业级对话系统设计
9 LangChain4j Java AI 应用开发实战(九):Few-Shot Learning - 少样本提示提升模型准确率
10 LangChain4j Java AI 应用开发实战(十):Embedding 模型与文本分类语义向量化
11 LangChain4j Java AI 应用开发实战(十一):Function Calling 工具调用 - 让 AI 执行真实操作
12 LangChain4j Java AI 应用开发实战(十二):向量数据Chroma/Qdrant/Milvus实践对比
13 LangChain4j Java AI 应用开发实战(十三):三行代码实现 RAG - Easy RAG 框架详解

目录


前言

在前一篇文章中,我们深入对比了四大向量数据库的选型策略。但当你真正开始构建 RAG(检索增强生成)应用时,可能会发现:配置向量数据库、设计文档分割策略、管理嵌入模型、编写检索逻辑……这些步骤太繁琐了! 有没有一种方式,能够像"Hello World"一样简单,3 行代码就搞定 RAG?

答案是肯定的!Easy RAG 框架正是 LangChain4j 为快速原型开发设计的开箱即用解决方案。它将文档解析、文本分割、向量化、存储、检索等复杂流程全部封装,让你只需关注业务逻辑,无需陷入底层细节。

本文将带你从零开始掌握 Easy RAG 框架,通过一个真实的汽车租赁客服机器人案例,演示如何:

  • 用 3 行代码完成 RAG 全流程搭建
  • 理解 Easy RAG 内部的"魔法":自动分割、向量化、存储
  • 掌握交互式对话的实现技巧
  • 识别 Easy RAG 的适用场景与局限性
  • 平滑过渡到 Naive RAG 和 Advanced RAG

准备好了吗?让我们开启 RAG 的极简之旅!


一、RAG 是什么?为什么需要它?

1.1 大模型的幻觉问题

在使用大语言模型(LLM)时,你是否遇到过这样的问题:

// 用户提问
String question = "我能取消预订吗?";

// 直接调用 LLM
String answer = chatModel.generate(question);

// 可能的回答(幻觉):
// "是的,您可以随时取消预订,没有任何限制。" ❌

// 但根据真实的服务条款:
// "预订可以在预订期开始前 7 天取消,如果预订期少于 3 天则不允许取消。" ✅

问题根源:LLM 的知识来自训练数据,无法知晓你的私有业务规则,只能"编造"答案。

1.2 RAG 的解决方案

**RAG(Retrieval-Augmented Generation,检索增强生成)**通过在生成答案前先从知识库中检索相关文档,将真实信息提供给 LLM,从而消除幻觉。

传统方式:
用户问题 → LLM → 可能幻觉 ❌

RAG 方式:
用户问题 → 检索相关文档 → 拼接 Prompt → LLM → 基于事实的回答 ✅

1.3 RAG 核心流程

一个完整的 RAG 系统包含 7 个步骤:

1. 文档加载(Document Loading)
   ↓ 从文件系统、数据库、API 加载原始文档
2. 文本分割(Text Splitting)
   ↓ 将长文档切分为适合向量化的片段
3. 向量化(Embedding)
   ↓ 将文本片段转换为高维向量
4. 向量存储(Vector Store)
   ↓ 将向量存入数据库
5. 检索(Retrieval)
   ↓ 将用户问题向量化,搜索相似片段
6. 提示词组装(Prompt Assembly)
   ↓ 将检索结果拼接到 System Message
7. 生成(Generation)
   ↓ LLM 基于上下文生成回答

传统实现:需要手动编写每一步的代码,至少 100+ 行
Easy RAG:框架自动完成所有步骤,只需 3 行核心代码!


二、Easy RAG 框架核心概念

2.1 什么是 Easy RAG?

Easy RAG 是 LangChain4j 提供的高级抽象层,通过 EmbeddingStoreIngestor 类实现一键文档摄入。它的核心理念是:

“约定优于配置”:提供合理的默认值,让开发者快速上手,同时保留自定义扩展的能力。

2.2 Easy RAG vs Naive RAG vs Advanced RAG

特性 Easy RAG Naive RAG Advanced RAG
代码量 3-10 行 50-100 行 100-300 行
学习曲线 平缓 中等 陡峭
灵活性
适用场景 原型验证 学习原理 生产环境
自定义程度 默认配置 完全可控 精细调优
性能优化 基础 手动优化 高级优化

建议

  • 初学者:从 Easy RAG 入手,快速看到效果
  • 学习者:通过 Naive RAG 理解每个环节
  • 生产者:使用 Advanced RAG 进行精细调优

2.3 Easy RAG 的技术架构

┌─────────────────────────────────────────────┐
│           AiServices (AI 助手)               │
│  - ChatModel (GPT-4o-mini)                  │
│  - ChatMemory (MessageWindowChatMemory)     │
│  - ContentRetriever (内容检索器)             │
└──────────────────┬──────────────────────────┘
                   │
                   ↓
┌─────────────────────────────────────────────┐
│    EmbeddingStoreContentRetriever            │
│  - 将查询向量化                              │
│  - 在向量存储中搜索 Top-K 片段               │
└──────────────────┬──────────────────────────┘
                   │
                   ↓
┌─────────────────────────────────────────────┐
│      InMemoryEmbeddingStore                  │
│  - 内存向量存储(HashMap)                   │
│  - 支持序列化持久化                          │
└──────────────────┬──────────────────────────┘
                   ↑
                   │
┌─────────────────────────────────────────────┐
│      EmbeddingStoreIngestor                  │
│  - 自动文档分割(300 tokens/段)             │
│  - 自动向量化(all-MiniLM-L6-v2)            │
│  - 自动存储(批量插入)                      │
└──────────────────┬──────────────────────────┘
                   ↑
                   │
┌─────────────────────────────────────────────┐
│         FileSystemDocumentLoader             │
│  - 加载 TXT/PDF/Word 等格式文档              │
└─────────────────────────────────────────────┘

三、实战案例:汽车租赁客服机器人

3.1 业务场景

假设你是"Miles of Smiles"汽车租赁公司的技术负责人,需要开发一个智能客服机器人,能够回答用户关于预订、取消政策、车辆使用规则等问题。

服务条款文档示例miles-of-smiles-terms-of-use.txt):

4. Cancellation Policy
4.1 Reservations can be cancelled up to 7 days prior to the start of the booking period.
4.2 If the booking period is less than 3 days, cancellations are not permitted.

5. Use of Vehicle
5.1 All cars rented from Miles of Smiles must not be used:
- for any illegal purpose or in connection with any criminal offense.
- for teaching someone to drive.
- in any race, rally or contest.
- while under the influence of alcohol or drugs.

用户需求

  • “我能取消预订吗?”
  • “我出了事故,需要额外付费吗?”
  • “我可以用租来的车参加拉力赛吗?”

3.2 完整代码实现

步骤 1:Maven 依赖配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.langchain4j</groupId>
    <artifactId>easy-rag-example</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>dev.langchain4j</groupId>
                <artifactId>langchain4j-bom</artifactId>
                <version>1.14.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- LangChain4j 核心库 -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j</artifactId>
        </dependency>

        <!-- Easy RAG 模块(关键依赖) -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-easy-rag</artifactId>
        </dependency>

        <!-- OpenAI 聊天模型 -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-open-ai</artifactId>
        </dependency>

        <!-- 日志框架 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.5.12</version>
        </dependency>
    </dependencies>
</project>

关键点langchain4j-easy-rag 是核心依赖,它会自动引入文档加载器、分割器、嵌入模型等传递依赖。

步骤 2:定义 AI 助手接口

package com.langchain4j.rag;

/**
 * AI 助手接口 - 声明式编程风格
 * 
 * LangChain4j 会在运行时动态生成实现类,
 * 你只需定义接口,框架自动处理底层复杂性。
 */
public interface Assistant {
    
    /**
     * 回答用户问题
     * 
     * @param query 用户的自然语言查询
     * @return AI 生成的回答
     */
    String answer(String query);
}

设计哲学:与 Spring Data JPA 类似,定义接口即可,无需编写实现类。

步骤 3:主程序实现

package com.langchain4j.rag;

import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatModel;
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.util.List;

import static dev.langchain4j.data.document.loader.FileSystemDocumentLoader.loadDocuments;

public class EasyRagExample {

    // 配置 OpenAI 聊天模型(使用演示端点)
    private static final ChatModel CHAT_MODEL = OpenAiChatModel.builder()
            .apiKey("demo")  // 生产环境请替换为真实 API Key
            .modelName("gpt-4o-mini")
            .baseUrl("http://langchain4j.dev/demo/openai/v1")
            .build();

    public static void main(String[] args) {

        // ==================== 第一步:加载文档 ====================
        
        /**
         * 从 classpath 的 documents/ 目录加载所有 .txt 文件
         * 
         * 支持的格式:TXT、PDF、Word、HTML 等(需添加对应依赖)
         * 返回 List<Document>,每个 Document 包含文本内容和元数据
         */
        List<Document> documents = loadDocuments(
            toPath("documents/"),  // 文档目录路径
            glob("*.txt")          // Glob 模式过滤
        );

        System.out.println("加载了 " + documents.size() + " 个文档");

        // ==================== 第二步:构建 AI 助手 ====================
        
        /**
         * 使用 AiServices 构建具备 RAG 能力的助手
         * 
         * 配置说明:
         * - chatModel: 底层大语言模型
         * - chatMemory: 对话记忆,保留最近 10 条消息
         * - contentRetriever: 内容检索器,从向量存储中查找相关片段
         */
        Assistant assistant = AiServices.builder(Assistant.class)
                .chatModel(CHAT_MODEL)
                .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
                .contentRetriever(createContentRetriever(documents))
                .build();

        // ==================== 第三步:启动对话 ====================
        
        System.out.println("\n=== Miles of Smiles 智能客服机器人 ===");
        System.out.println("请输入您的问题(输入 'exit' 退出):\n");
        
        startConversationWith(assistant);
    }

    // ==================== 创建内容检索器(核心封装)====================
    
    /**
     * 创建基于向量存储的内容检索器
     * 
     * 这是 Easy RAG 的"魔法"所在——看似只有 3 行代码,
     * 内部自动完成了:分割 → 向量化 → 存储 的全流程
     * 
     * @param documents 原始文档列表
     * @return 封装好的内容检索器
     */
    private static ContentRetriever createContentRetriever(List<Document> documents) {

        // 创建空的内存向量存储
        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();

        /**
         * 一键摄入文档(Easy RAG 核心 API)
         * 
         * 自动完成:
         * 1. 文档分割:按 300 tokens 左右分段,重叠 30 tokens
         * 2. 文本向量化:使用本地 all-MiniLM-L6-v2 模型(384 维)
         * 3. 向量存储:批量插入到 InMemoryEmbeddingStore
         */
        EmbeddingStoreIngestor.ingest(documents, embeddingStore);

        System.out.println("文档摄入完成,已索引 " + documents.size() + " 个文档");

        /**
         * 从向量存储创建内容检索器
         * 
         * 默认配置:
         * - 返回最相关的 3 个片段
         * - 使用余弦相似度排序
         * - 最小相似度阈值:0.0(不过滤)
         */
        return EmbeddingStoreContentRetriever.from(embeddingStore);
    }

    // ==================== 工具方法 ====================
    
    /**
     * 启动交互式对话
     */
    private static void startConversationWith(Assistant assistant) {
        java.util.Scanner scanner = new java.util.Scanner(System.in);
        
        while (true) {
            System.out.print("用户: ");
            String userQuery = scanner.nextLine();
            
            if ("exit".equalsIgnoreCase(userQuery)) {
                break;
            }
            
            String answer = assistant.answer(userQuery);
            System.out.println("助手: " + answer);
            System.out.println();
        }
        
        scanner.close();
    }

    /**
     * 将 classpath 路径转换为 Path 对象
     */
    private static java.nio.file.Path toPath(String relativePath) {
        try {
            java.net.URL fileUrl = EasyRagExample.class.getClassLoader().getResource(relativePath);
            return java.nio.file.Paths.get(fileUrl.toURI());
        } catch (java.net.URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 创建 Glob 模式匹配器
     */
    private static java.nio.file.PathMatcher glob(String glob) {
        return java.nio.file.FileSystems.getDefault().getPathMatcher("glob:" + glob);
    }
}

3.3 运行效果

# 编译运行
mvn clean compile exec:java -Dexec.mainClass="com.langchain4j.rag.EasyRagExample"

输出示例

加载了 2 个文档
文档摄入完成,已索引 2 个文档

=== Miles of Smiles 智能客服机器人 ===
请输入您的问题(输入 'exit' 退出):

用户: 我能取消预订吗?
助手: 根据服务条款,您可以在预订期开始前 7 天取消预订。但如果预订期少于 3 天,则不允许取消。

用户: 我出了事故,需要额外付费吗?
助手: 是的,根据服务条款第 6.1 条,用户需要对租赁期间发生的任何损坏、损失或盗窃承担责任。

用户: 我可以用租来的车参加拉力赛吗?
助手: 不可以。根据服务条款第 5.1 条,Miles of Smiles 的车辆不得用于任何比赛、拉力赛或竞赛活动。

用户: exit

神奇之处:整个 RAG 流程(文档加载→分割→向量化→存储→检索→生成)仅用了约 50 行代码,其中核心逻辑只有 3 行!


四、Easy RAG 内部揭秘

4.1 EmbeddingStoreIngestor:一键摄入的魔法

EmbeddingStoreIngestor.ingest() 是 Easy RAG 的核心 API,它在幕后完成了大量工作:

// 你看到的代码(极简)
EmbeddingStoreIngestor.ingest(documents, embeddingStore);

// 实际执行的流程(复杂)
for (Document document : documents) {
    // 1. 文档分割
    List<TextSegment> segments = DocumentSplitter.split(document);
    // 默认配置:
    // - 每段最大 300 tokens
    // - 重叠 30 tokens(保持上下文连贯)
    // - 按段落边界分割(避免语义断裂)
    
    // 2. 批量向量化
    List<Embedding> embeddings = EmbeddingModel.embedAll(segments);
    // 使用默认的 all-MiniLM-L6-v2 模型
    // 输出 384 维向量
    
    // 3. 批量存储
    embeddingStore.addAll(embeddings, segments);
    // 内存 HashMap 存储
    // 键:向量 ID,值:向量 + 文本片段
}

4.2 默认配置详解

Easy RAG 的默认配置经过精心调优,适合大多数场景:

配置项 默认值 说明
文档分割器 DocumentByParagraphSplitter 按段落分割
每段最大长度 300 tokens 平衡精度和性能
重叠长度 30 tokens 保持上下文连贯
嵌入模型 all-MiniLM-L6-v2 本地轻量级模型
向量维度 384 适合大部分场景
向量存储 InMemoryEmbeddingStore 内存存储,零配置
检索 Top-K 3 返回最相关的 3 个片段
相似度度量 Cosine Similarity 余弦相似度

4.3 ContentRetriever 工作原理

当用户提问时,ContentRetriever 执行以下流程:

// 用户问题
String query = "我能取消预订吗?";

// 1. 将问题向量化
Embedding queryVector = embeddingModel.embed(query).content();

// 2. 在向量存储中搜索
List<EmbeddingMatch<TextSegment>> matches = embeddingStore.search(
    EmbeddingSearchRequest.builder()
        .queryEmbedding(queryVector)
        .maxResults(3)  // 返回 Top-3
        .build()
).matches();

// 3. 提取文本片段
List<String> relevantSegments = matches.stream()
    .map(match -> match.embedded().text())
    .collect(Collectors.toList());

// 4. 拼接到 Prompt
String systemMessage = "你是一个客服助手。请基于以下信息回答问题:\n" + 
    String.join("\n", relevantSegments);

// 5. 发送给 LLM
String answer = chatModel.generate(systemMessage + "\n用户问题:" + query);

关键点:整个过程对开发者透明,你只需调用 assistant.answer(query)


五、Easy RAG 的优势与局限性

5.1 优势

✅ 1. 极速上手

// 传统 RAG:100+ 行代码
DocumentSplitter splitter = new DocumentByParagraphSplitter(300, 30);
List<TextSegment> segments = splitter.splitAll(documents);
EmbeddingModel model = new AllMiniLmL6V2EmbeddingModel();
List<Embedding> embeddings = model.embedAll(segments).content();
InMemoryEmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>();
store.addAll(embeddings, segments);
ContentRetriever retriever = EmbeddingStoreContentRetriever.from(store);

// Easy RAG:3 行代码
InMemoryEmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>();
EmbeddingStoreIngestor.ingest(documents, store);
ContentRetriever retriever = EmbeddingStoreContentRetriever.from(store);

代码量减少 95%!

✅ 2. 零配置启动

无需关心:

  • 选择哪种分割策略
  • 嵌入模型的维度
  • 向量数据库的配置
  • 检索参数的调优

开箱即用,3 分钟看到效果!

✅ 3. 适合原型验证

  • POC 演示:快速向客户展示 RAG 能力
  • 内部测试:验证业务可行性
  • 学习实验:理解 RAG 基本概念

5.2 局限性

❌ 1. 灵活性不足

问题:无法自定义分割策略、嵌入模型、向量数据库等。

场景

  • 需要按句子分割而非段落
  • 需要使用 OpenAI 的嵌入模型(更高精度)
  • 需要使用 Milvus 存储(大规模数据)

解决方案:过渡到 Naive RAG 或 Advanced RAG。

❌ 2. 性能瓶颈

问题

  • InMemoryEmbeddingStore 受 JVM 内存限制
  • 默认分割策略可能不适合所有文档类型
  • 检索参数无法调优

数据量限制

  • 推荐:< 10 万条文本片段
  • 极限:< 50 万条(需增加 JVM 堆内存)

❌ 3. 缺乏高级功能

缺失功能

  • ❌ 元数据过滤(如只搜索特定部门的文档)
  • ❌ 混合搜索(向量 + 关键词)
  • ❌ 重排序(Re-Ranking)
  • ❌ 查询压缩(多轮对话上下文)
  • ❌ 路由分发(按意图选择检索器)

解决方案:使用 Advanced RAG(后续文章讲解)。

5.3 适用场景总结

场景 推荐度 理由
学习 RAG 概念 ⭐⭐⭐⭐⭐ 最简单的方式理解 RAG
原型验证 ⭐⭐⭐⭐⭐ 快速搭建 Demo
小规模知识库 ⭐⭐⭐⭐ < 10 万文档,性能足够
个人项目 ⭐⭐⭐⭐ 零运维成本
企业生产环境 ⭐⭐ 需要更精细的控制
大规模数据 需要分布式向量数据库
高精度需求 ⭐⭐ 需要自定义嵌入模型和检索策略

六、常见问题与避坑指南

❌ 问题 1:文档未加载成功

现象documents.size() == 0

原因

  • 文件路径错误
  • Glob 模式不匹配
  • 资源未在 classpath 中

解决方案

// 1. 检查文件是否存在于 src/main/resources/documents/
// 2. 打印绝对路径调试
Path docPath = toPath("documents/");
System.out.println("文档目录:" + docPath.toAbsolutePath());

// 3. 确认 Maven 打包时包含资源文件
// pom.xml 中应有:
// <resources>
//     <resource>
//         <directory>src/main/resources</directory>
//     </resource>
// </resources>

❌ 问题 2:回答不准确

现象:AI 回答与文档内容不符

原因

  • 检索到的片段不相关
  • Top-K 设置过小
  • 文档分割不当

解决方案

// 1. 增加 Top-K(返回更多片段)
ContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
    .embeddingStore(embeddingStore)
    .maxResults(5)  // 从 3 增加到 5
    .minScore(0.6)  // 设置最小相似度阈值
    .build();

// 2. 检查检索到的片段
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
    .queryEmbedding(queryVector)
    .maxResults(5)
    .build();
List<EmbeddingMatch<TextSegment>> matches = embeddingStore.search(request).matches();
for (EmbeddingMatch<TextSegment> match : matches) {
    System.out.println("相似度:" + match.score());
    System.out.println("内容:" + match.embedded().text());
}

❌ 问题 3:内存溢出(OOM)

现象java.lang.OutOfMemoryError: Java heap space

原因:InMemoryEmbeddingStore 加载了大量向量

解决方案

# 1. 增加 JVM 堆内存
java -Xmx8g -Xms4g -jar your-app.jar

# 2. 切换到外部向量数据库(如 Chroma、Qdrant)
EmbeddingStore<TextSegment> store = ChromaEmbeddingStore.builder()
    .baseUrl("http://localhost:8000")
    .collectionName("my_collection")
    .build();

❌ 问题 4:中文支持不佳

现象:中文文档检索效果差

原因:默认的 all-MiniLM-L6-v2 主要针对英文训练

解决方案

// 使用多语言嵌入模型
import dev.langchain4j.model.embedding.onnx.bgesmallenv15q.BgeSmallEnV15QuantizedEmbeddingModel;

EmbeddingModel model = new BgeSmallEnV15QuantizedEmbeddingModel();

// 或者使用 HuggingFace 的多语言模型
import dev.langchain4j.model.huggingface.HuggingFaceEmbeddingModel;

EmbeddingModel model = HuggingFaceEmbeddingModel.builder()
    .modelId("intfloat/multilingual-e5-large")
    .accessToken(System.getenv("HF_API_KEY"))
    .build();

❌ 问题 5:多轮对话上下文丢失

现象:用户问"它能退款吗?",AI 不知道"它"指什么

原因:ChatMemory 未正确配置

解决方案

// 确保配置了 ChatMemory
Assistant assistant = AiServices.builder(Assistant.class)
    .chatModel(CHAT_MODEL)
    .chatMemory(MessageWindowChatMemory.withMaxMessages(10))  // 保留最近 10 条消息
    .contentRetriever(retriever)
    .build();

七、从 Easy RAG 到 Naive RAG 的平滑过渡

当你需要更多控制权时,可以逐步从 Easy RAG 迁移到 Naive RAG。

7.1 自定义文档分割

import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.splitter.DocumentBySentenceSplitter;

// 使用按句子分割(而非默认的按段落)
DocumentSplitter splitter = new DocumentBySentenceSplitter(
    200,   // 每段最大 200 tokens
    20     // 重叠 20 tokens
);

List<TextSegment> segments = splitter.splitAll(documents);

7.2 自定义嵌入模型

import dev.langchain4j.model.openai.OpenAiEmbeddingModel;

// 使用 OpenAI 的嵌入模型(更高精度)
EmbeddingModel model = OpenAiEmbeddingModel.builder()
    .apiKey(System.getenv("OPENAI_API_KEY"))
    .modelName("text-embedding-3-small")
    .build();

List<Embedding> embeddings = model.embedAll(segments).content();

7.3 自定义向量数据库

import dev.langchain4j.store.embedding.chroma.ChromaEmbeddingStore;

// 使用 Chroma 向量数据库(持久化)
EmbeddingStore<TextSegment> store = ChromaEmbeddingStore.builder()
    .baseUrl("http://localhost:8000")
    .collectionName("customer_service")
    .build();

store.addAll(embeddings, segments);

7.4 自定义检索参数

ContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
    .embeddingStore(store)
    .maxResults(5)           // 返回 Top-5
    .minScore(0.7)           // 最小相似度 0.7
    .build();

迁移策略

  1. 第一步:保持 Easy RAG 结构,只替换某个组件(如嵌入模型)
  2. 第二步:逐步暴露更多配置(如分割策略、检索参数)
  3. 第三步:完全切换到 Naive RAG,手动控制每个环节

八、Easy RAG 的典型应用场景

8.1 企业内部知识库问答

  • 数据量:< 10 万文档
  • 技术方案:Easy RAG + InMemoryEmbeddingStore
  • 特点:快速部署,零运维成本

8.2 产品文档助手

  • 数据量:< 1 万页文档
  • 技术方案:Easy RAG + PDF 文档加载器
  • 特点:支持 PDF 格式,自动提取文本

8.3 FAQ 智能客服

  • 数据量:< 5 千条问答对
  • 技术方案:Easy RAG + 结构化数据导入
  • 特点:高精度回答,引用来源标注

8.4 个人学习笔记助手

  • 数据量:< 1 千篇文章
  • 技术方案:Easy RAG + Markdown 加载器
  • 特点:支持 Markdown 格式,语义搜索

8.5 会议记录检索

  • 数据量:< 1 万条会议记录
  • 技术方案:Easy RAG + 元数据过滤
  • 特点:按日期、参会人过滤

九、性能优化技巧

虽然 Easy RAG 主打 simplicity,但你仍然可以进行一些优化:

9.1 批量加载优化

// ❌ 低效:逐个文档加载
for (File file : files) {
    Document doc = loadDocument(file);
    ingest(doc);
}

// ✅ 高效:批量加载
List<Document> documents = loadDocuments(directory, glob("*.txt"));
EmbeddingStoreIngestor.ingest(documents, embeddingStore);

9.2 持久化避免重复计算

// 首次运行:摄入文档并序列化
EmbeddingStoreIngestor.ingest(documents, embeddingStore);
embeddingStore.serializeToFile("/data/vectors.store");

// 后续运行:直接加载
InMemoryEmbeddingStore<TextSegment> store = 
    InMemoryEmbeddingStore.fromFile("/data/vectors.store");

性能提升:避免每次启动都重新向量化,节省 90% 时间。

9.3 调整 JVM 参数

# 针对大规模数据(> 10 万片段)
java -Xmx8g -Xms4g -XX:+UseG1GC -jar app.jar

9.4 异步加载文档

// 在后台线程加载文档,避免阻塞主线程
CompletableFuture.runAsync(() -> {
    List<Document> documents = loadDocuments(path, glob);
    EmbeddingStoreIngestor.ingest(documents, store);
    System.out.println("文档加载完成");
});

结语

现在我们已经掌握了 Easy RAG 框架的核心用法,理解了如何通过 3 行代码快速构建检索增强生成应用。通过汽车租赁客服机器人的实战案例,我们见证了 Easy RAG 的强大之处:将文档加载、分割、向量化、存储、检索等复杂流程全部封装,让开发者能够专注于业务逻辑。

同时我们也清醒地认识到 Easy RAG 的局限性:灵活性不足、性能瓶颈、缺乏高级功能。但这正是学习的意义所在——先通过 Easy RAG 快速入门,理解 RAG 的基本概念,再逐步过渡到 Naive RAG 和 Advanced RAG,掌握更精细的控制能力


在这里插入图片描述

🎯🔖更多专栏系列文章:AI大模型提示工程完全指南AI大模型探索之路(零基础入门)AI大模型预训练微调进阶AI大模型开源精选实践AI大模型Spring AI开发实战🔥🔥🔥 其他专栏可以查看博客主页

🔔 关于作者:资深程序老猿,10年+架构经验,现专注 AIGC 探索与实践。
👍 若文章对你有所触动,恳请点赞 ⭐ 关注 ⭐ 收藏!AI 浪潮已至,愿与你同行。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐