找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1898

积分

0

好友

257

主题
发表于 前天 22:30 | 查看: 2| 回复: 0

在 AI 应用开发中,大模型常常面临“知识过期”和“不懂私有数据”的痛点,而检索增强生成(RAG)技术正是解决这一问题的核心方案。RAG 能够将大模型与私有知识库结合,让生成的回答更准确、更具针对性。本文将深入拆解 RAG 的技术原理与核心流程,重点讲解其与 Embedding 技术的协同逻辑,并结合智普 AI 实现本地知识库检索的完整实操,帮助大家掌握在 Spring AI 框架中开发 RAG 模块的精髓。

一、什么是 RAG?核心原理与核心价值

(一)RAG 定义

Retrieval-Augmented Generation(RAG)即检索增强生成,是一种结合“检索”与“生成”的 AI 技术。它通过在大模型生成回答前,从外部知识库中检索与用户问题相关的信息,并将这些信息作为上下文传递给大模型,从而让大模型基于外部知识生成更准确、更可靠的回答。

(二)RAG 的核心价值

RAG 的核心价值是为大模型“外接动态知识库”,在不重新训练或微调模型的前提下,低成本解决知识过期、私有数据不可用、模型幻觉与高昂成本四大核心痛点,让生成的回答更准、更新、更合规、也更易于维护。

  • 解决知识局限:无需重新训练大模型,即可让其掌握私有数据(如企业文档、专业知识库)或最新信息,有效避免“知识过期”问题。
  • 提升回答准确性:基于具体的外部知识生成回答,显著减少大模型“一本正经地胡说八道”(幻觉)的现象。
  • 降低开发成本:无需投入海量算力进行模型微调,通过简单的知识库配置即可快速扩展大模型能力。
  • 支持灵活更新:外部知识库可独立进行更新,无需改动大模型或应用代码,能够快速适配变化的业务需求。

(三)RAG 与 Embedding 的协同逻辑:语义检索的核心支撑

RAG 技术的核心在于“精准检索”,而 Embedding 技术是实现这一目标的关键前提,二者形成了“基础支撑”与“上层应用”的紧密协同关系。

简单来说,Embedding 是 RAG 技术的“语义翻译官”。它将非结构化的文本(知识库片段、用户问题)翻译成机器可理解、可计算的向量语言。如果没有 Embedding 提供的语义量化能力,RAG 就无法实现高效的“检索增强”,只能依赖大模型有限的原生知识库。

二、RAG 的核心流程

RAG 技术的完整流程可分为三个核心阶段,每个阶段都与 Embedding 技术深度协同:

(一)索引阶段:知识库向量化存储

  1. 将本地知识库(文档、数据库等)解析为文本片段(Chunks),再通过 Embedding 模型将这些片段转换为语义向量,并保存到向量数据库中。

(二)检索阶段:相似文本精准匹配

  1. 将用户提出的问题通过相同的 Embedding 模型转换为语义向量。然后,在向量数据库中,通过相似度算法(如余弦相似度)计算问题向量与所有文本片段向量的相似度,筛选出 TopK(如 Top2、Top3)个最相似的文本片段。

(三)生成阶段:结合上下文生成回答

将检索到的相关文本片段与用户问题一起作为上下文传递给大模型,辅助其生成准确回答。而这些片段之所以能精准匹配用户问题,其本质是 Embedding 技术保证了“问题语义”与“片段语义”的一致性映射。

下图展示了 AI 系统基于 RAG 处理文档并回答用户问题的完整流程图:

RAG检索增强生成完整流程图

三、实操准备:环境配置(基于 Spring AI + 智普 AI)

(一)准备工作

需要提前完成智普 AI 账号注册与 API Key 的获取,并创建一个 Spring Boot 项目。

1. 创建 Spring Boot 项目

  • 项目名称:Weiz-SpringAI-RAG
  • 核心配置:JDK 17、Spring Boot 3.5.0、Maven
  • 依赖选择:Spring Web(后续通过 pom.xml 补充 Embedding 相关依赖)

项目结构如下:

Weiz-SpringAI-RAG/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/
│   │   │       └── example/
│   │   │           └── weizspringai/
│   │   │               ├── WeizSpringAiRAGApplication.java
│   │   │               ├── controller/
│   │   │               └── service/
│   │   └── resources/
│   └── test/
└── pom.xml

2. 配置 pom.xml 依赖

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>Weiz-SpringAI</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>Weiz-SpringAI-RAG</artifactId>
    <name>Weiz-SpringAI-RAG</name>
    <description>Weiz-SpringAI-RAG</description>

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

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-zhipuai</artifactId>
        </dependency>

        <!-- spring-ai-client-chat 中包括 TokenTextSplitter、TextReader、Document 等工具 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-client-chat</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>
</project>

3. application.properties 配置

# 应用名称
spring.application.name=Weiz-SpringAI-RAG
# 服务端口
server.port=8080
# 智普 AI 基础配置
spring.ai.zhipuai.api-key=你的智普 AI API Key
spring.ai.zhipuai.base-url=https://open.bigmodel.cn/api/paas
# Embedding 模型配置(用于文本向量化)
spring.ai.zhipuai.embedding.options.model=embedding-2
# Chat 模型配置(用于生成回答)
spring.ai.zhipuai.chat.options.model=GLM-4-Flash

注意,需将“你的智普 AI API Key”替换为实际获取的密钥。

(二)准备本地知识库文件

src/main/resources 目录下创建 户外旅行安全指南.txt ,作为本地知识库,内容如下:

户外旅行安全指南
----
1.露营安全
- 选址:避开低洼积水区、陡坡和可能落石的区域,优先选择平坦硬质地面。
- 防火:远离干草、枯枝等易燃物,使用炉具时保持通风,睡前彻底熄灭明火。
- 防虫:携带驱蚊液、防虫网,避免在草丛密集处搭建帐篷,睡前检查帐篷内是否有虫类。
- 应急:随身携带手电筒、急救包和足够的饮用水,提前下载离线地图。
----
2.徒步安全
- 路线规划:提前查询路线难度、天气情况,选择与自身体能匹配的路线,告知亲友行程。
- 装备要求:必须穿防滑登山鞋、戴防晒帽,携带充足食物和水,配备登山杖和护膝。
- 环境应对:遇到暴雨立即寻找高地躲避,避免涉险过河;遭遇野生动物保持冷静,切勿投喂或驱赶。
- 体能管理:保持匀速前进,每小时休息 10-15 分钟,避免过度疲劳。
----
3.城市漫游安全
- 交通:骑行遵守交通规则,不逆行、不闯红灯,佩戴安全头盔;乘坐公共交通保管好个人财物。
- 选址:避开偏僻小巷和治安较差区域,优先选择人流密集的商业街区和景点。
- 应急:保存当地派出所、医院的联系方式,携带少量现金备用,手机保持电量充足。

四、实操案例:RAG 本地知识库检索完整实现

本案例将实现 RAG 的全流程:加载本地知识库并向量化、接收用户问题并检索相似文本、结合上下文生成回答。

1. 核心工具类:复用相似度计算逻辑

可以复用上一篇文章中的 SimilarityCalculator.java 类,该类封装了余弦相似度、欧氏距离等多种主流算法,支持 RAG 检索阶段的相似度计算。

2. 编写 RagService

创建 com.example.weizspringai.service.RagService 类,整合索引、检索、生成三个RAG 核心逻辑:

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

@Service
public class RagService {
    // Embedding 模型(文本向量化)
    private final EmbeddingModel embeddingModel;
    // Chat 模型(生成回答)
    private final ChatClient chatClient;
    // 本地知识库文本片段
    private final List<String> docChunks = new ArrayList<>();
    // 文本片段对应的向量
    private final List<float[]> docVectors = new ArrayList<>();
    // 相似度算法(默认余弦相似度,可根据需求修改)
    private final EmbeddingService.SimilarityAlgorithm similarityAlgorithm = EmbeddingService.SimilarityAlgorithm.COSINE;

    // 构造方法注入依赖,初始化知识库(索引阶段)
    public RagService(EmbeddingModel embeddingModel, ChatClient.Builder chatClientBuilder) throws IOException {
        this.embeddingModel = embeddingModel;
        this.chatClient = chatClientBuilder.build();
        // 加载本地知识库文件并切分片段
        loadAndSplitDocument();
    }

    /**
     * 加载本地知识库文件,切分为文本片段(索引阶段第一步)
     */
    private void loadAndSplitDocument() throws IOException {
        // 读取 resources 目录下的知识库文件
        Resource resource = new ClassPathResource("户外旅行安全指南.txt");
        String content = new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
        // 按 "----" 切分文本(根据文件格式自定义切分规则)
        String[] chunks = content.split("----");
        for (String chunk : chunks) {
            String cleanChunk = chunk.strip();
            if (!cleanChunk.isBlank()) {
                docChunks.add(cleanChunk);
                // 文本片段向量化并缓存(索引阶段第二步、第三步)
                docVectors.add(embeddingModel.embed(cleanChunk));
            }
        }
    }

    /**
     * 处理用户提问,返回 RAG 增强后的回答(检索+生成阶段)
     * @param question 用户问题
     * @return 大模型生成的回答
     */
    public String answer(String question) {
        // 1. 用户问题向量化(检索阶段第一步)
        float[] questionVector = embeddingModel.embed(question);
        // 2. 检索 Top2 最相似的文本片段(检索阶段第二步、第三步)
        List<String> topRelevantChunks = retrieveTopRelevantChunks(questionVector, 2);
        // 3. 构建上下文(生成阶段第一步)
        String context = String.join("\n---\n", topRelevantChunks);
        // 4. 构建提示词(生成阶段第二步)
        String prompt = String.format(
                "以下是户外旅行安全指南的知识:\n%s\n请基于上述知识,简洁明了地回答问题:%s",
                context, question
        );
        // 5. 调用 Chat 模型生成回答(生成阶段第三步)
        return chatClient.prompt()
                .system("你是户外旅行安全助手,仅基于提供的上下文回答问题,不添加额外信息。")
                .user(prompt)
                .call()
                .content();
    }

    /**
     * 检索 TopK 最相似的文本片段(检索阶段核心逻辑)
     * @param questionVector 用户问题向量
     * @param topK 返回前 K 个相似片段
     * @return TopK 相似文本片段
     */
    private List<String> retrieveTopRelevantChunks(float[] questionVector, int topK) {
        List<ChunkSimilarity> similarityList = new ArrayList<>();
        // 计算问题向量与所有文本片段向量的相似度(使用指定算法)
        for (int i = 0; i < docVectors.size(); i++) {
            double sim = calculateSimilarity(questionVector, docVectors.get(i));
            similarityList.add(new ChunkSimilarity(i, sim));
        }
        // 按相似度降序排序,取前 topK 个
        similarityList.sort((a, b) -> Double.compare(b.similarity, a.similarity));
        return similarityList.stream()
                .limit(topK)
                .map(item -> docChunks.get(item.index))
                .toList();
    }

    /**
     * 相似度计算(调用工具类,支持算法切换)
     */
    private double calculateSimilarity(float[] a, float[] b) {
        return switch (similarityAlgorithm) {
            case COSINE -> SimilarityCalculator.cosineSimilarity(a, b);
            case EUCLIDEAN -> SimilarityCalculator.euclideanSimilarity(a, b);
            case PEARSON -> SimilarityCalculator.pearsonCorrelation(a, b);
            case MANHATTAN -> SimilarityCalculator.manhattanSimilarity(a, b);
            default -> SimilarityCalculator.cosineSimilarity(a, b); // 默认 fallback 到余弦相似度
        };
    }

    /**
     * 辅助类:存储文本片段索引与相似度
     */
    private static class ChunkSimilarity {
        int index;
        double similarity;
        ChunkSimilarity(int index, double similarity) {
            this.index = index;
            this.similarity = similarity;
        }
    }
}

3. 编写 RagController

创建 com.example.weizspringai.controller.RagController 类,提供 RAG 检索接口:

import com.example.springaiembedding.service.RagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;

@RestController
@RequestMapping("/rag")
public class RagController {
    @Autowired
    private RagService ragService;

    /**
     * RAG 本地知识库检索接口
     * @param question 用户问题
     * @return 包含问题与回答的响应
     */
    @GetMapping("/ask")
    public Map<String, String> ask(@RequestParam("question") String question) {
        String answer = ragService.answer(question);
        return Map.of(
                "question", question,
                "answer", answer
        );
    }
}

4. 测试 RAG 本地知识库检索

  1. 启动 Weiz-SpringAI-Rag 项目,浏览器访问: http://localhost:8080/rag/ask?question=露营选址有什么安全要求?
  2. 响应结果如下:
    {
      "question": "露营选址有什么安全要求?",
      "answer": "露营选址需避开低洼积水区、陡坡和可能落石的区域,优先选择平坦硬质地面,同时要远离干草、枯枝等易燃物,避免在草丛密集处搭建帐篷。"
    }

    再测试其他问题,如 http://localhost:8080/rag/ask?question=徒步时遇到暴雨该怎么办? ,响应:

    {
    "question": "徒步时遇到暴雨该怎么办?",
    "answer": "徒步遇到暴雨时,应立即寻找高地躲避,避免涉险过河,同时注意远离陡坡和可能落石的区域,确保自身安全。"
    }

    测试结果表明,大模型能够基于本地知识库文件的内容生成准确回答,验证了 RAG 技术的核心价值。

五、RAG 优化思路(为后续进阶铺垫)

当前 RAG 实现是一个基础版本,存在一些可优化点,为后续生产级应用铺垫思路:

  1. 文本切分策略优化
    当前按固定符号切分,可优化为按 Token 数量切分(如每段 400-800 字符),或使用语义分割模型,避免语义断裂,保证块内信息的完整性。

  2. 向量数据库集成
    当前使用内存缓存向量,仅适用于小型知识库。生产环境需替换为 Milvus、Pinecone、Redis 或 PostgreSQL(pgvector)等专业向量数据库,以支持海量数据的高效存储与检索。

  3. 相似度阈值过滤
    在检索阶段,可以设置一个相似度阈值(如 0.7)。仅保留相似度高于此阈值的文本片段作为上下文,避免引入低相关性的噪声信息,影响回答质量。

  4. 上下文扩展
    检索到最相关的核心片段后,可以同时获取其前后相邻的文本片段,以提供更完整的背景信息,提升生成答案的语义连贯性和丰富度。

  5. 引入 Spring AI 开箱即用 RAG 组件
    Spring AI 框架提供了如 VectorStoreRetrievalAugmentor 等更高级的抽象和开箱即用的 RAG 组件。这些组件可以大幅简化开发流程,内置了自动化的文档加载、索引、检索优化和提示词工程等功能,更能适应生产级复杂场景的需求。

  6. 算法动态选择
    根据知识库的具体类型(如短文本问答、长篇专业文档、稀疏特征数据),可以设计策略自动匹配最优的相似度算法。例如,处理专业文档时优先使用皮尔逊相关系数,而处理稀疏向量时则考虑杰卡德相似度。

总结

本文深入解析了 RAG 技术的核心原理、工作流程,重点厘清了其与 Embedding 技术的协同逻辑,并通过 Spring AI 结合智普 AI 实现了 RAG 本地知识库检索的完整案例。RAG 技术通过“检索外部知识 + 增强生成能力”的模式,有效解决了大模型的知识局限问题。而 Embedding 技术提供的语义向量化能力,无疑是 RAG 实现精准检索的核心基石。

核心结论如下:

  1. RAG 的核心价值在于“无需微调,快速扩展大模型能力”,非常适配私有数据、最新知识等动态场景。
  2. Embedding 是 RAG 的“语义基础”,其生成的向量质量直接决定了检索阶段的精准度,进而对最终回答的准确性产生决定性影响。
  3. 基础版 RAG 实现相对简单,但若要应用于生产环境,必须关注文本切分、向量数据库集成、检索算法优化等关键点,以全面提升系统的稳定性、效率与准确性。

后续我们将针对这些优化点展开更详细的讲解,帮助大家构建健壮、高效的生产级 RAG 应用。如果你想深入探讨更多 AI 与开发技术,欢迎来 云栈社区 交流分享。




上一篇:UI设计师如何提升配图审美?从线上真实APP案例中找灵感
下一篇:企业级AI Agent落地难?从自动化到决策的六个成熟度层级解读
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-12 01:10 , Processed in 0.202334 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表