使用Java实现RAG(检索增强生成)的完整指南

 更新时间:2026年04月26日 09:34:15   作者:花千树_010  
RAG(Retrieval-Augmented Generation,检索增强生成) 是目前最主流的企业 AI 应用模式,这篇文章主要为大家详细介绍了使用Java实现RAG的相关方法,文中的示例代码讲解详细,有需要的小伙伴可以了解下

适合人群:需要构建知识库问答系统的 Java 开发者

核心技术:RAG、向量数据库 Milvus、文本 Embedding

什么是 RAG?

RAG(Retrieval-Augmented Generation,检索增强生成) 是目前最主流的企业 AI 应用模式。

解决的问题:大模型的训练数据有截止日期,且不包含你的私有数据(内部文档、产品手册、规章制度等)。RAG 的思路是:

用户提问 → 在私有知识库中检索相关内容 → 将检索结果 + 问题一起发给 LLM → 得到基于私有知识的回答

优势

  • 无需微调模型,成本极低
  • 知识库可随时更新,实时生效
  • 可溯源,能告诉用户答案来自哪个文档

完整 RAG 流程

PDF/Word文档
     ↓
[文档加载器] PdfboxLoader / ApachePoiDocxLoader
     ↓
[文本切分器] StanfordNLPTextSplitter
     ↓
[Embedding 模型] OllamaEmbeddings
     ↓
[向量数据库] Milvus(存储)
     ↓ (查询时)
用户问题 → [向量检索] → 相关文档块 → [LLM] → 最终回答

前置配置

RAG Pipeline 依赖 Milvus 向量数据库和 Tesseract OCR,需要在 application.yml 中显式开启:

rag:
  ocr:
    tesseract:
      use: true   # 启用 Tesseract OCR(PDF 图片文字识别)
  vector:
    milvus:
      use: true   # 启用 Milvus 向量数据库

说明:j-langchain 通过 @ConditionalOnProperty 控制这两个组件的初始化,默认不加载。只有配置 use: true 后,TesseractActuatorMilvusContainer Bean 才会被注册到 Spring 容器中。未开启时运行 RAG 相关代码会因 Bean 缺失而报错。

Step 1:加载文档

j-langchain 内置多种文档加载器:

加载 PDF

@Test
public void loadPdfDocuments() {
    PdfboxLoader loader = PdfboxLoader.builder()
        .filePath("./files/pdf/en/Transformer.pdf")
        .build();
    loader.setExtractImages(false);  // 不提取图片,只提取文本

    List<Document> documents = loader.load();

    System.out.println("总页数:" + documents.size());
    // 每个 Document 对应 PDF 的一页
}

加载 Word 文档

ApachePoiDocxLoader loader = ApachePoiDocxLoader.builder()
    .filePath("./files/docx/en/Transformer.docx")
    .build();

List<Document> documents = loader.load();

每个 Document 对象包含:

  • pageContent:页面文本内容
  • metadata:来源、页码等元数据

Step 2:文本切分

PDF 文档一页可能有几千字,直接 Embedding 效果差,而且超出 LLM 的 context window。需要将长文档切分为小块:

@Test
public void splitDocuments() {
    List<Document> documents = loader.load();
    System.out.println("切分前:" + documents.size() + " 页");

    StanfordNLPTextSplitter splitter = StanfordNLPTextSplitter.builder()
        .chunkSize(1000)    // 每块最多 1000 字符
        .chunkOverlap(100)  // 相邻块重叠 100 字符,保证上下文连贯
        .build();

    List<Document> splits = splitter.splitDocument(documents);
    System.out.println("切分后:" + splits.size() + " 块");
}

为什么需要 chunkOverlap?

如果一句话跨两个块,重叠部分确保这句话完整出现在至少一个块中,避免语义截断。

Step 3:向量化并存入 Milvus

将每个文本块转换为向量(浮点数数组),存入向量数据库:

@Test
public void embedAndStore() {
    // ... 加载、切分文档 ...

    VectorStore vectorStore = Milvus.fromDocuments(
        splits,
        OllamaEmbeddings.builder()
            .model("nomic-embed-text")  // 本地 Embedding 模型,免费
            .vectorSize(768)            // 向量维度
            .build(),
        "MyKnowledgeBase"               // Milvus collection 名称
    );

    System.out.println("向量化完成!");
}

为什么用本地 Embedding?

nomic-embed-text 是一个高质量的开源 Embedding 模型,通过 Ollama 本地运行:

  • 零成本:不需要调 OpenAI API
  • 隐私安全:数据不出本地
  • 效果好:在中英文 Embedding 上表现优秀

启动 Milvus(Docker 一键启动):

docker run -d --name milvus \
  -p 19530:19530 \
  milvusdb/milvus:latest standalone

Step 4:完整 RAG 问答链

这是核心步骤:用用户的问题检索相关文档块,拼接上下文,让 LLM 基于上下文回答:

@Test
public void retrieveAndAsk() {
    // 假设文档已经存入 Milvus...
    BaseRetriever retriever = vectorStore.asRetriever();

    BaseRunnable<StringPromptValue, ?> prompt = PromptTemplate.fromTemplate(
        """
        请根据以下文档内容回答问题。如果文档中没有相关信息,请说"文档中未找到相关信息"。
        
        文档内容:
        ${context}
        
        问题:${question}
        
        回答:
        """
    );

    Function<Object, String> formatDocs = input -> {
        List<Document> docs = (List<Document>) input;
        StringBuilder sb = new StringBuilder();
        for (Document doc : docs) {
            sb.append(doc.getPageContent()).append("\n\n");
        }
        return sb.toString();
    };

    FlowInstance ragChain = chainActor.builder()
        .next(retriever)   // 向量检索:输入问题,返回相关文档列表
        .next(formatDocs)  // 将文档列表拼接为字符串
        .next(input -> Map.of(
            "context",  input,
            "question", ContextBus.get().getFlowParam()  // 获取原始问题
        ))
        .next(prompt)
        .next(llm)
        .next(new StrOutputParser())
        .build();

    ChatGeneration result = chainActor.invoke(
        ragChain,
        "Transformer 模型中的注意力机制是如何工作的?"
    );

    System.out.println(result.getText());
}

链路图解

"Transformer注意力机制..." 
    → retriever(相似度检索,返回最相关的5个文档块)
    → formatDocs(拼接文档块为字符串)
    → prompt(组装 Prompt:上下文 + 问题)
    → LLM(基于上下文生成回答)
    → StrOutputParser(提取文本)
    → "注意力机制通过计算 Query、Key、Value..."

Step 5:文档摘要(轻量版)

不需要向量库的简单场景——直接让 LLM 读文档摘要:

@Test
public void documentSummary() {
    // 加载 PDF
    List<Document> documents = loader.load();
    String content = documents.stream()
        .map(Document::getPageContent)
        .collect(Collectors.joining("\n"));

    // 长文档截取首尾
    String textToSummarize = content.length() < 2000 ? content
        : content.substring(0, 1000) + "\n...\n" + content.substring(content.length() - 1000);

    FlowInstance chain = chainActor.builder()
        .next(PromptTemplate.fromTemplate("请对以下内容摘要(100字以内):\n\n${text}"))
        .next(ChatOllama.builder().model("qwen2.5:0.5b").build())
        .next(new StrOutputParser())
        .build();

    ChatGeneration result = chainActor.invoke(chain, Map.of("text", textToSummarize));
    System.out.println(result.getText());
}

RAG vs 直接问 LLM

对比项直接问 LLMRAG
私有知识不知道知道(来自你的文档)
知识时效性训练截止日期实时更新
回答可溯源不行可以(返回来源文档)
成本稍高(Embedding + 向量库)
幻觉风险低(基于真实文档)

完整架构

离线阶段(建库):
文档 → 加载 → 切分 → Embedding → Milvus

在线阶段(问答):
问题 → Embedding → Milvus检索 → 拼接上下文 → LLM → 回答

到此这篇关于使用Java实现RAG(检索增强生成)的完整指南的文章就介绍到这了,更多相关Java实现RAG内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 解决spring-cloud-config 多服务共享公共配置的问题

    解决spring-cloud-config 多服务共享公共配置的问题

    这篇文章主要介绍了解决spring-cloud-config 多服务共享公共配置的问题,本文通过多种方法给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • SpringBoot中的@CrossOrigin注解详解

    SpringBoot中的@CrossOrigin注解详解

    这篇文章主要介绍了SpringBoot中的@CrossOrigin注解详解,跨源资源共享(CORS)是由大多数浏览器实现的W3C规范,允许您灵活地指定什么样的跨域请求被授权,而不是使用一些不太安全和不太强大的策略,需要的朋友可以参考下
    2023-11-11
  • Java基础详解之集合框架工具Collections

    Java基础详解之集合框架工具Collections

    这篇文章主要介绍了Java基础详解之集合框架工具Collections,文中有非常详细的代码示例,对正在学习java的小伙伴们有很好地帮助,需要的朋友可以参考下
    2021-04-04
  • Java获取字符串编码格式实现思路

    Java获取字符串编码格式实现思路

    文件编码的格式决定了文件可存储的字符类型,所以得到文件的类型至关重要,下文笔者讲述获取一个文本文件的格式信息的方法分享及java字符串编码格式实现,感兴趣的朋友一起看看吧
    2022-09-09
  • Mybatis resultType返回结果为null的问题排查方式

    Mybatis resultType返回结果为null的问题排查方式

    这篇文章主要介绍了Mybatis resultType返回结果为null的问题排查方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • 对数据进行分页显示到table中的实现方法

    对数据进行分页显示到table中的实现方法

    这篇文章主要介绍了对数据进行分页显示到table中的实现方法的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-05-05
  • RabbitMQ修改默认密码的操作步骤

    RabbitMQ修改默认密码的操作步骤

    这篇文章主要给大家介绍了关于RabbitMQ修改默认密码的操作步骤,在RabbitMQ中默认用户guest的密码是guest,出于安全考虑,最好不要在生产环境中使用默认用户和密码,需要的朋友可以参考下
    2024-11-11
  • java调用Oracle存储过程的方法实例

    java调用Oracle存储过程的方法实例

    这篇文章介绍了java调用Oracle存储过程的方法实例,有需要的朋友可以参考一下
    2013-09-09
  • Java之String类型的编码方式转换

    Java之String类型的编码方式转换

    这篇文章主要介绍了Java之String类型的编码方式转换,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • Spring InitializingBean的使用方式

    Spring InitializingBean的使用方式

    InitializingBean是Spring框架中的一个生命周期接口,用于在Bean的属性设置完成后执行自定义的初始化逻辑,本文给大家介绍spring InitializingBean的使用方式,感兴趣的朋友小编一起看看吧
    2026-01-01

最新评论