在这里插入图片描述

🍃 予枫个人主页

📚 个人专栏: 《Java 从入门到起飞》《读研码农的干货日常

💻 Debug 这个世界,Return 更好的自己!

引言

大模型幻觉是落地路上的“绊脚石”,而RAG(检索增强生成)正是破解这一痛点的核心方案。作为Java开发者,如何依托Spring AI、LangChain4j等生态,实现从文档解析、向量存储到检索生成的全链路落地?本文拆解RAG核心逻辑,手把手教你用Java搭建可直接复用的RAG系统,覆盖多格式文档处理、主流向量库整合与全流程优化,干货拉满,建议收藏!

一、RAG核心认知:为什么它能解决大模型幻觉?

1.1 RAG是什么?核心逻辑拆解 🧩

RAG(Retrieval-Augmented Generation,检索增强生成),简单来说就是“先检索、再生成”——在大模型生成回答前,先从私有文档/知识库中检索相关信息,将其作为上下文喂给大模型,让生成的内容更精准、更贴合实际需求,从根源上减少幻觉。

核心优势对比传统大模型生成:

  • 传统大模型:依赖训练数据,对私有数据、实时数据一无所知,易“一本正经地胡说八道”;
  • RAG:结合检索能力,可接入私有文档,无需重新训练大模型,低成本实现精准回答,且支持数据实时更新。

划重点:RAG不是替换大模型,而是给大模型“装一个外挂知识库”,让它的回答有迹可循、有据可依。

1.2 RAG适用场景与全链路流程

1.2.1 核心适用场景

  • 企业知识库问答(如产品手册、内部文档查询);
  • 客服机器人(精准回复用户问题,减少人工干预);
  • 专业领域问答(医疗、法律、技术文档检索与生成);
  • 私有数据生成(如公司财报、内部资料总结)。

1.2.2 RAG全链路核心流程(Java视角)

  1. 文档处理:加载PDF/Word/Markdown等多格式文档,进行解析与文本分块;
  2. 向量转换:通过Embedding模型将文本块转换为向量,存入向量数据库;
  3. 检索环节:接收用户查询,转换为向量后在向量库中检索相关文本块,优化检索结果;
  4. 生成环节:将检索到的相关文本作为上下文,调用大模型生成精准回答。

✅ 温馨提示:觉得这部分内容有帮助的话,麻烦点赞收藏,后续实操环节需要反复对照哦~

二、文档处理环节:Java实现多格式文档解析与分块

文档处理是RAG的“基础工程”,核心目标是将非结构化文档(PDF/Word等)转换为可处理的文本块,既要保证信息不丢失,又要兼顾后续检索的精准度。这里我们采用Spring AI与LangChain4j结合的方式,实现多格式文档的高效解析。

2.1 依赖引入(Maven)

<!-- Spring AI 核心依赖 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-core</artifactId>
    <version>1.0.0-M1</version>
</dependency>
<!-- 文档加载器(支持PDF/Word/Markdown) -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-document-reader</artifactId>
    <version>1.0.0-M1</version>
</dependency>
<!-- LangChain4j 依赖(增强文档处理能力) -->
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-core</artifactId>
    <version>0.24.0</version>
</dependency>
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-document-parser</artifactId>
    <version>0.24.0</version>
</dependency>

2.2 多格式文档加载与解析(Java代码实现)

支持PDF、Word(.docx)、Markdown三种主流格式,核心是通过Spring AI的DocumentReader和LangChain4j的Parser实现解析:

import org.springframework.ai.document.Document;
import org.springframework.ai.document.reader.pdf.PdfDocumentReader;
import org.springframework.ai.document.reader.word.DocxDocumentReader;
import org.springframework.ai.document.reader.markdown.MarkdownDocumentReader;
import java.io.File;
import java.util.List;

public class DocumentLoaderUtil {

    // 加载PDF文档
    public static List<Document> loadPdf(String filePath) {
        File file = new File(filePath);
        PdfDocumentReader reader = new PdfDocumentReader(file);
        return reader.read();
    }

    // 加载Word文档
    public static List<Document> loadDocx(String filePath) {
        File file = new File(filePath);
        DocxDocumentReader reader = new DocxDocumentReader(file);
        return reader.read();
    }

    // 加载Markdown文档
    public static List<Document> loadMarkdown(String filePath) {
        File file = new File(filePath);
        MarkdownDocumentReader reader = new MarkdownDocumentReader(file);
        return reader.read();
    }

    public static void main(String[] args) {
        // 测试加载PDF文档
        List<Document> pdfDocs = loadPdf("D:/test/rag_demo.pdf");
        System.out.println("PDF文档解析完成,共" + pdfDocs.size() + "个文档块");
    }
}

2.3 文本分块策略:平衡检索精度与效率

文本分块是关键一步——分块太大,检索时精准度低;分块太小,会丢失上下文关联。推荐两种主流分块策略,可根据文档类型灵活选择:

策略1:固定长度分块(基础款)

适合结构化文档(如技术手册),设置固定块大小(如500字符),重叠长度(如50字符),避免上下文断裂:

import dev.langchain4j.data.document.splitter.DocumentSplitter;
import dev.langchain4j.data.document.splitter.TokenSplitter;
import dev.langchain4j.data.document.Document;
import java.util.List;

public class TextSplitterUtil {

    // 固定长度分块(按字符数)
    public static List<Document> splitByChar(List<Document> documents) {
        // 块大小:500字符,重叠:50字符
        DocumentSplitter splitter = new TokenSplitter(500, 50);
        return splitter.split(documents);
    }
}

策略2:语义分块(进阶款)

适合非结构化文档(如报告、论文),基于语义相似度分块,保留完整语义单元,需结合Embedding模型:

// 语义分块(依赖Embedding模型,后续章节会讲解)
public static List<Document> splitBySemantic(List<Document> documents, EmbeddingModel embeddingModel) {
    // 基于语义相似度分块,最小块大小100字符,最大500字符
    SemanticDocumentSplitter splitter = new SemanticDocumentSplitter(embeddingModel, 100, 500, 0.3);
    return splitter.split(documents);
}

小技巧:对于技术文档,优先使用固定长度分块,效率更高;对于散文、报告类文档,用语义分块更能保证上下文完整性。

三、向量环节:Embedding模型接入与向量数据库整合

向量环节是RAG的“核心引擎”——将文本块转换为向量(Embedding),存入向量数据库,才能实现高效的相似性检索。本节讲解Java生态下Embedding模型接入,以及Milvus、Redis Stack两种主流向量数据库的整合。

3.1 Embedding模型接入(Java实现)

Embedding模型的核心作用是将文本转换为高维向量,这里推荐使用开源的BGE模型,或接入第三方API(如百度文心一言、阿里通义千问),以下是开源BGE模型的接入示例:

import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.embedding.bge.BigGeometryEmbeddingModel;
import dev.langchain4j.data.embedding.Embedding;
import java.util.List;

public class EmbeddingUtil {

    // 初始化BGE开源Embedding模型(本地部署,无需APIKey)
    public static EmbeddingModel initBgeEmbedding() {
        // 模型路径:本地部署的BGE模型路径
        return new BigGeometryEmbeddingModel("D:/models/bge-large-zh-v1.5");
    }

    // 将文本块转换为向量
    public static List<Embedding> embedDocuments(List<Document> documents, EmbeddingModel embeddingModel) {
        return embeddingModel.embedDocuments(documents);
    }

    // 将用户查询转换为向量
    public static Embedding embedQuery(String query, EmbeddingModel embeddingModel) {
        return embeddingModel.embedQuery(query);
    }
}

补充:如果不想本地部署模型,也可以使用Spring AI接入第三方Embedding API,只需配置APIKey即可,代码更简洁,适合快速落地。

3.2 主流向量数据库整合(Java代码)

3.2.1 Redis Stack整合(轻量首选)

Redis Stack支持向量存储与相似性检索,适合中小规模RAG系统,整合步骤如下:

  1. 引入依赖:
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-redis</artifactId>
    <version>1.0.0-M1</version>
</dependency>
  1. 配置Redis连接与向量存储:
import org.springframework.ai.redis.RedisVectorStore;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

public class RedisVectorStoreUtil {

    // 初始化Redis向量存储
    public static RedisVectorStore initRedisVectorStore(RedisConnectionFactory connectionFactory, EmbeddingModel embeddingModel) {
        // 向量维度:BGE模型输出维度为1024
        return new RedisVectorStore(connectionFactory, "rag_vector_store", 1024, embeddingModel);
    }

    // 存储向量(文本块+向量)
    public static void storeVectors(RedisVectorStore vectorStore, List<Document> documents) {
        vectorStore.add(documents);
    }

    // 相似性检索
    public static List<Document> searchVectors(RedisVectorStore vectorStore, String query, int topK) {
        return vectorStore.similaritySearch(query, topK);
    }
}

3.2.2 Milvus整合(大规模首选)

Milvus是开源向量数据库,适合大规模向量存储(百万级以上),支持更复杂的检索策略,整合示例:

  1. 引入依赖:
<dependency>
    <groupId>io.milvus</groupId>
    <artifactId>milvus-sdk-java</artifactId>
    <version>2.4.4</version>
</dependency>
  1. Milvus向量存储与检索实现:
import io.milvus.client.MilvusClient;
import io.milvus.client.MilvusClientBuilder;
import io.milvus.param.ConnectParam;
import io.milvus.param.collection.CreateCollectionParam;
import io.milvus.param.collection.FieldType;
import java.util.List;

public class MilvusVectorStoreUtil {

    // 初始化Milvus客户端
    public static MilvusClient initMilvusClient() {
        ConnectParam connectParam = ConnectParam.newBuilder()
                .withHost("localhost")
                .withPort(19530)
                .build();
        return new MilvusClientBuilder().withConnectParam(connectParam).build();
    }

    // 创建向量集合(表)
    public static void createCollection(MilvusClient client, String collectionName) {
        FieldType idField = FieldType.newBuilder()
                .withName("id")
                .withDataType(FieldType.DataType.Int64)
                .withPrimaryKey(true)
                .withAutoID(true)
                .build();

        FieldType vectorField = FieldType.newBuilder()
                .withName("vector")
                .withDataType(FieldType.DataType.FloatVector)
                .withDimension(1024) // 与Embedding模型维度一致
                .build();

        CreateCollectionParam createParam = CreateCollectionParam.newBuilder()
                .withCollectionName(collectionName)
                .addFieldType(idField)
                .addFieldType(vectorField)
                .build();

        client.createCollection(createParam);
    }

    // 存储向量与检索逻辑(省略,核心是将Embedding向量存入Milvus,调用相似性检索接口)
}

✅ 互动提问:你平时做RAG开发,更倾向于用Redis还是Milvus?评论区留言交流~

四、检索与生成环节:混合检索优化与大模型整合

检索与生成是RAG的“最终输出环节”——通过优化检索策略提升结果精准度,再将检索到的上下文与大模型结合,生成符合需求的回答。本节讲解混合检索、重排序优化,以及Java与大模型的无缝整合。

4.1 混合检索:提升检索精准度的关键

单一的向量检索可能存在“语义偏差”,混合检索(向量检索+关键词检索)能兼顾语义相似度与关键词匹配,适合技术文档等场景:

import org.springframework.ai.vectorstore.VectorStore;
import java.util.List;
import java.util.stream.Collectors;

public class HybridSearchUtil {

    // 混合检索(向量检索+关键词检索)
    public static List<Document> hybridSearch(VectorStore vectorStore, String query, int topK) {
        // 1. 向量检索(获取语义相似的文本块)
        List<Document> vectorDocs = vectorStore.similaritySearch(query, topK * 2);
        // 2. 关键词检索(筛选包含查询关键词的文本块)
        List<Document> keywordDocs = vectorDocs.stream()
                .filter(doc -> doc.getContent().contains(query))
                .collect(Collectors.toList());
        // 3. 合并去重,取前topK个结果
        return keywordDocs.stream()
                .distinct()
                .limit(topK)
                .collect(Collectors.toList());
    }
}

4.2 检索结果重排序:优化上下文质量

检索到的文本块可能存在相关性高低不一的情况,通过重排序(基于文本相似度得分),将最相关的文本块放在前面,提升大模型生成质量:

import dev.langchain4j.data.document.Document;
import java.util.Comparator;
import java.util.List;

public class ReRankUtil {

    // 基于相似度得分重排序(降序,得分越高越相关)
    public static List<Document> reRankDocuments(List<Document> documents) {
        return documents.stream()
                .sorted(Comparator.comparingDouble(Document::getScore).reversed())
                .collect(Collectors.toList());
    }
}

4.3 与大模型整合:生成精准回答(Java实现)

这里以接入阿里通义千问大模型为例,结合检索到的上下文,生成回答:

  1. 引入依赖:
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-tongyi</artifactId>
    <version>1.0.0-M1</version>
</dependency>
  1. 大模型调用与回答生成:
import org.springframework.ai.tongyi.TongYiChatClient;
import org.springframework.ai.tongyi.TongYiChatOptions;
import java.util.List;
import java.util.stream.Collectors;

public class LLMGenerateUtil {

    // 初始化通义千问客户端
    public static TongYiChatClient initTongYiClient(String apiKey) {
        TongYiChatOptions options = TongYiChatOptions.builder()
                .apiKey(apiKey)
                .model("qwen-plus") // 选择合适的模型
                .temperature(0.7) // 生成多样性,0-1之间
                .build();
        return new TongYiChatClient(options);
    }

    // 结合检索上下文生成回答
    public static String generateAnswer(TongYiChatClient llmClient, String query, List<Document> retrievedDocs) {
        // 构建上下文(将检索到的文本块拼接)
        String context = retrievedDocs.stream()
                .map(Document::getContent)
                .collect(Collectors.joining("\n\n"));

        // 构建提示词(Prompt)
        String prompt = String.format("请根据以下上下文,回答用户的问题:\n上下文:%s\n用户问题:%s\n要求:回答准确,基于上下文,不要编造信息。", context, query);

        // 调用大模型生成回答
        return llmClient.call(prompt);
    }
}

五、RAG全链路代码整合与常见问题优化

5.1 全链路代码整合(完整示例)

将前面的文档处理、向量存储、检索生成环节整合,形成完整的Java版RAG系统:

public class RAGFullLinkDemo {

    public static void main(String[] args) {
        // 1. 初始化核心组件
        EmbeddingModel embeddingModel = EmbeddingUtil.initBgeEmbedding();
        RedisConnectionFactory redisConnectionFactory = getRedisConnectionFactory(); // 自定义Redis连接配置
        RedisVectorStore vectorStore = RedisVectorStoreUtil.initRedisVectorStore(redisConnectionFactory, embeddingModel);
        TongYiChatClient llmClient = LLMGenerateUtil.initTongYiClient("你的通义千问APIKey");

        // 2. 文档处理(加载PDF文档,分块)
        List<Document> rawDocs = DocumentLoaderUtil.loadPdf("D:/test/rag_demo.pdf");
        List<Document> splitDocs = TextSplitterUtil.splitByChar(rawDocs);

        // 3. 向量存储(将分块后的文本转换为向量,存入Redis)
        RedisVectorStoreUtil.storeVectors(vectorStore, splitDocs);

        // 4. 检索环节(混合检索+重排序)
        String userQuery = "RAG的核心流程是什么?";
        List<Document> retrievedDocs = HybridSearchUtil.hybridSearch(vectorStore, userQuery, 5);
        List<Document> rankedDocs = ReRankUtil.reRankDocuments(retrievedDocs);

        // 5. 生成回答
        String answer = LLMGenerateUtil.generateAnswer(llmClient, userQuery, rankedDocs);
        System.out.println("RAG生成回答:\n" + answer);
    }

    // 自定义Redis连接配置
    private static RedisConnectionFactory getRedisConnectionFactory() {
        // 此处省略Redis连接配置,根据实际环境调整
        return null;
    }
}

5.2 常见问题与优化方案

问题1:检索结果不精准,大模型仍有幻觉

  • 优化方案:
    1. 调整分块策略,缩小块大小,增加重叠长度;
    2. 采用混合检索(向量+关键词),提升匹配度;
    3. 更换更精准的Embedding模型(如BGE-large、通义千问Embedding)。

问题2:向量存储速度慢,检索效率低

  • 优化方案:
    1. 中小规模数据用Redis Stack,大规模数据用Milvus;
    2. 对文本块进行去重、过滤(过滤无效内容,如空白、重复文本);
    3. 降低Embedding向量维度(如从1024维降至512维,牺牲少量精度换取效率)。

问题3:大模型生成回答冗长,偏离主题

  • 优化方案:
    1. 优化Prompt,明确要求“简洁、精准,基于上下文”;
    2. 限制检索结果的数量(如top3-top5),避免上下文过多;
    3. 调整大模型的temperature参数(降低至0.5以下),减少生成多样性。

六、结尾总结

本文从RAG核心原理出发,拆解了检索增强生成的全链路流程,重点讲解了Java生态下的落地实现——包括Spring AI/LangChain4j的文档处理、Embedding模型接入、主流向量数据库(Redis/Milvus)整合,以及混合检索、重排序与大模型的无缝衔接,最后提供了全链路代码示例与常见问题优化方案。

RAG是解决大模型幻觉的关键技术,而Java作为企业级开发的主流语言,在RAG落地中具有天然的生态优势。掌握本文的内容,你可以快速搭建属于自己的Java版RAG系统,适配企业知识库、客服机器人等多种场景。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐