SpringBoot+LangChain4j+Ollama+RAG(检索增强生成)实现私有文档向量化检索回答

场景

SpringBoot+LangChain4j+Ollama实现本地大模型语言LLM的搭建、集成和示例流程:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/160340066

在上面的基础上,学习RAG的使用。

一、为什么需要RAG?------ 大模型的局限性

大型语言模型(LLM)虽然强大,但存在两个核心痛点:

1、知识陈旧:

模型训练完成后知识就被"冻结",无法知道训练之后发生的新事件。

2、"幻觉"问题:

当模型不确定答案时,会"编造"看似合理但错误的信息,尤其在专业领域或企业私有数据上。

RAG(Retrieval-Augmented Generation,检索增强生成) 正是解决这些问题的核心技术。

它让模型在回答前,先从你的知识库(文档、数据库、网页)中检索相关信息,然后基于这些事实生成答案。

打个比方:

传统 LLM 像一个闭卷考试的考生,只能靠记忆;RAG 则给了他一套开卷的参考书,可以随时查阅。

二、RAG 核心流程(三步走)

RAG 分为两个阶段:

索引阶段(Indexing) 和 检索生成阶段(Retrieval & Generation)。

2.1 索引阶段(文档入库)

这一步将你的文档"加工"成可检索的格式,存入向量数据库。

原始文档 → 加载 → 解析 → 分块 → 向量化 → 存储

加载(Load):读取各种格式的文件(PDF、Word、TXT、网页等)。

解析(Parse):提取纯文本,去除格式、图片等无关信息。

分块(Split):将长文档切分成适合模型处理的短片段(chunk)。原因:模型上下文窗口有限,且细粒度检索更精准。

向量化(Embed):用嵌入模型(Embedding Model)将每个文本片段转换成高维向量(例如 768 维浮点数数组)。向量可以捕获语义信息。

存储(Store):将向量和原始文本存入向量数据库,便于后续相似度检索。

2.2 检索生成阶段(问答)

用户提问时,执行以下步骤:

用户问题 → 向量化 → 相似度检索 → 增强上下文 → 生成答案

查询向量化:用同一个嵌入模型将用户问题也转换成向量。

相似度检索:在向量数据库中寻找与问题向量最相似的 K 个文本片段(例如余弦相似度)。

增强上下文:将检索到的片段与原始问题组合成一个"增强提示"(Augmented Prompt)。

生成答案:把增强提示发给 LLM,让它基于提供的材料生成准确答案。

三、LangChain4j 中 RAG 的核心组件

Document 代表一个原始文档(包含文本、元数据)

DocumentParser 解析特定格式(PDF、DOCX等)

DocumentSplitter 将长文档切分成 TextSegment

TextSegment 一个文本片段(chunk)

EmbeddingModel 将文本转换为向量

EmbeddingStore 向量数据库的抽象接口

EmbeddingStoreIngestor 自动化管道:分块 → 向量化 → 入库

ContentRetriever 根据问题检索相关片段

RetrievalAugmentor 增强器,将检索结果注入到 AI 请求中

AiServices 构建 AI 服务,支持 RAG

注:

博客:
https://blog.csdn.net/badao_liumang_qizhi

实现

四、代码详解:从零搭建一个 RAG 问答系统

前置:

拉取嵌入模型(用于向量化):

复制代码
ollama pull nomic-embed-text

4.1 添加依赖(pom.xml 关键部分)

复制代码
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>spring-langchain4j-ollama-rag</artifactId>
    <version>1.0</version>

    <properties>
        <java.version>17</java.version>
        <langchain4j.version>1.0.0-beta4</langchain4j.version>
    </properties>

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

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 核心:用于启用 @AiService 声明式 AI 服务 -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-spring-boot-starter</artifactId>
        </dependency>
        <!-- 具体模型:用于集成本地 Ollama 服务 -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-ollama-spring-boot-starter</artifactId>
        </dependency>
        <!-- 文档加载:支持 PDF、TXT、DOCX 等 -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>

    </dependencies>

为什么不需要 langchain4j-embeddings-bge-small-en-v15?

我们改用 Ollama 的嵌入模型,避免了本地 DLL 加载问题,且与现有 Ollama 服务共享基础设施。

4.2 配置文件 application.yml

复制代码
​
langchain4j:
  ollama:
    chat-model:
      base-url: http://localhost:11434
      model-name: qwen2:7b
      temperature: 0.7
      timeout: PT600S   # 10 分钟
      connect-timeout: PT300S   # 5 分钟
      read-timeout: PT300S   # 5 分钟
      log-requests: true
      log-responses: true
    embedding-model:
      base-url: http://localhost:11434
      model-name: nomic-embed-text

​

chat-model:用于最终生成答案的 LLM。

embedding-model:用于将文档片段和用户问题转换为向量。两者可以不同,且通常嵌入模型更轻量

4.3 文档索引服务:将文档入库

复制代码
package com.badao.ai.service;


import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;

@Service
public class DocumentIndexingService {

    @Autowired
    private EmbeddingModel embeddingModel;

    @Autowired
    private EmbeddingStore<TextSegment> embeddingStore;  // 注入 Spring 管理的 Bean

    @PostConstruct
    public void init() throws IOException {
        ClassPathResource resource = new ClassPathResource("knowledge/knowledge.txt");
        Path docPath = resource.getFile().toPath();
        Document document = FileSystemDocumentLoader.loadDocument(docPath);
        List<Document> documents = List.of(document);

        EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
                .embeddingModel(embeddingModel)
                .embeddingStore(embeddingStore)   // 使用注入的存储
                .build();

        ingestor.ingest(documents);
        System.out.println("文档已成功索引,共处理了 " + documents.size() + " 个文档。");
    }
}

解释:

TextSegment.from(document, 500, 50):将文档按 500 个字符切块,相邻块重叠 50 个字符(避免丢失边界信息)。

EmbeddingStoreIngestor:封装了"分块 → 向量化 → 存储"的完整流程。你也可以自定义分块策略。

@PostConstruct:Spring 容器启动后自动执行索引,确保知识库就绪

4.4构建带 RAG 能力的 AI 服务

复制代码
package com.badao.ai.config;


import com.badao.ai.service.RAGAssistant;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.rag.DefaultRetrievalAugmentor;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.store.embedding.EmbeddingStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RagAiConfig {

    @Bean
    public RAGAssistant ragAssistant(ChatModel chatModel,
                                     EmbeddingStore<TextSegment> embeddingStore,
                                     EmbeddingModel embeddingModel) {
        // 1. 创建内容检索器:从向量存储中检索相关片段
        var contentRetriever = EmbeddingStoreContentRetriever.builder()
                .embeddingStore(embeddingStore)
                .embeddingModel(embeddingModel)
                .maxResults(3)   // 最多返回 3 个相关片段
                .minScore(0.7)   // 最低相似度分数
                .build();

        // 2. 创建检索增强器
        var retrievalAugmentor = DefaultRetrievalAugmentor.builder()
                .contentRetriever(contentRetriever)
                .build();

        // 3. 构建 AI 服务
        return AiServices.builder(RAGAssistant.class)
                .chatModel(chatModel)
                .retrievalAugmentor(retrievalAugmentor)
                .build();
    }
}

关键组件:

EmbeddingStoreContentRetriever:根据用户问题在向量库中检索相似片段。

DefaultRetrievalAugmentor:将检索到的内容自动插入到用户消息之前,构造增强提示。

AiServices.retrievalAugmentor():将增强器绑定到 AI 服务。

4.5 Assistant 接口定义

复制代码
package com.badao.ai.service;


import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;

public interface RAGAssistant {
    @SystemMessage("你是一个知识库助手,请基于提供的上下文信息回答问题。如果无法从上下文中找到答案,请如实告知。")
    String chat(@UserMessage String userMessage);
}

4.6 Controller 暴露接口

复制代码
package com.badao.ai.controller;

import com.badao.ai.service.RAGAssistant;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/rag")
public class RAGController {

    private final RAGAssistant ragAssistant;

    public RAGController(RAGAssistant ragAssistant) {
        this.ragAssistant = ragAssistant;
    }

    @GetMapping("/ask")
    public String ask(@RequestParam String question) {
        return ragAssistant.chat(question);
    }

}

4.7 测试验证

准备一个测试文档 knowledge.txt,放在src-resource下knowledge下,内容:

公司内部规定:员工每天工作时间为 9:00 - 18:00,午休一小时。

年假规则:入职满一年后享有 5 天年假,满三年后享有 10 天年假。

启动应用,访问测试接口

http://localhost:885/rag/ask?question= 员工几点下班?

常见问题与解决方案

1、本地嵌入模型加载失败(AccessDeniedException)

DJL 库在 Windows 上解压 DLL 失败 改用 Ollama 的嵌入模型(如 nomic-embed-text)

2、检索结果不相关 分块太大/太小,或嵌入模型不适合领域 调整分块大小;

尝试其他嵌入模型(bge-m3、all-MiniLM-L6-v2)

3、模型不遵循检索内容 系统提示词未强调"基于资料"

在 @SystemMessage 中加入明确指令

4、响应慢 向量检索或 LLM 推理慢

使用更快的嵌入模型(如 nomic-embed-text)、开启 GPU、缓存常见检索结果

5、重启后数据丢失

使用内存存储 改用持久化向量数据库(Redis、Chroma)

相关推荐
就叫飞六吧2 小时前
Docker Hub 上主流的nginx发行
java·nginx·docker
MiNG MENS2 小时前
基于SpringBoot和Leaflet的行政区划地图掩膜效果实战
java·spring boot·后端
IT_陈寒2 小时前
Vite静态资源加载把我坑惨了
前端·人工智能·后端
2601_949814692 小时前
Spring Boot中的404错误:原因、影响及处理策略
java·spring boot·后端
RDCJM2 小时前
Spring Boot spring.factories文件详细说明
spring boot·后端·spring
小码哥_常2 小时前
从ORDER BY RAND()踩坑,看透SQL性能优化
后端
remember_me2 小时前
LangGraph 使用指南
后端
ILYT NCTR2 小时前
搭建Golang gRPC环境:protoc、protoc-gen-go 和 protoc-gen-go-grpc 工具安装教程
开发语言·后端·golang
小雅痞3 小时前
[Java][Leetcode simple] 28. 找出字符串中第一个匹配项的下标
java·开发语言·leetcode