Spring AI实现RAG(检索增强生成)详解与实践

Spring AI实现RAG(检索增强生成)详解与实践

一、什么是RAG?

RAG(Retrieval-Augmented Generation,检索增强生成)是一种结合信息检索和文本生成的技术。它通过从外部知识库中检索相关信息,然后将这些信息作为上下文提供给生成模型,从而生成更准确、更相关的回答。

RAG的核心原理

  1. 检索(Retrieval):从知识库中检索与查询相关的文档片段
  2. 增强(Augmentation):将检索到的信息作为上下文增强提示
  3. 生成(Generation):基于增强后的提示生成最终回答

RAG的优势

  • 知识更新:无需重新训练模型即可更新知识
  • 准确性:基于实际文档生成,减少幻觉问题
  • 可追溯性:可以追溯到信息来源
  • 成本效益:比微调模型更经济

二、Spring AI中的RAG支持

Spring AI提供了完整的RAG实现框架,包括:

  • 向量存储:支持多种向量数据库
  • 文档加载:支持多种文件格式
  • 文本分割:智能文档分块
  • 向量化:文本向量嵌入
  • 检索:相似度搜索

三、Spring AI实现RAG的步骤

1. 添加依赖

pom.xml中添加Spring AI RAG相关依赖:

xml 复制代码
<dependencies>
    <!-- Spring AI Core -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-core</artifactId>
        <version>1.0.0</version>
    </dependency>
    
    <!-- Spring AI OpenAI -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        <version>1.0.0</version>
    </dependency>
    
    <!-- Spring AI Vector Store (支持多种向量数据库) -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-pgvector-store</artifactId>
        <version>1.0.0</version>
    </dependency>
    
    <!-- 或者使用其他向量数据库 -->
    <!-- <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-chroma-store</artifactId>
        <version>1.0.0</version>
    </dependency> -->
    
    <!-- 文档处理 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-pdf-document-reader</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>

2. 配置向量存储和模型

application.yml中配置:

yaml 复制代码
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4
          temperature: 0.7
    
    vectorstore:
      pgvector:
        index-type: HNSW
        distance-type: COSINE_DISTANCE
        dimensions: 1536

3. 创建向量存储服务

java 复制代码
package com.example.rag.service;

import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class VectorStoreService {
    
    @Autowired
    private VectorStore vectorStore;
    
    /**
     * 添加文档到向量存储
     */
    public void addDocuments(List<Document> documents) {
        vectorStore.add(documents);
    }
    
    /**
     * 根据查询检索相似文档
     */
    public List<Document> search(String query, int topK) {
        return vectorStore.similaritySearch(query, topK);
    }
}

4. 实现RAG服务

java 复制代码
package com.example.rag.service;

import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.document.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
public class RAGService {
    
    @Autowired
    private ChatClient chatClient;
    
    @Autowired
    private VectorStoreService vectorStoreService;
    
    /**
     * RAG查询:检索 + 生成
     */
    public String query(String question) {
        // 1. 检索相关文档
        List<Document> relevantDocs = vectorStoreService.search(question, 5);
        
        // 2. 构建增强提示
        String context = relevantDocs.stream()
            .map(Document::getContent)
            .collect(Collectors.joining("\n\n"));
        
        String enhancedPrompt = String.format(
            "基于以下上下文信息回答问题。如果上下文中没有相关信息,请说明。\n\n" +
            "上下文:\n%s\n\n" +
            "问题:%s\n\n" +
            "回答:",
            context,
            question
        );
        
        // 3. 生成回答
        Prompt prompt = new Prompt(new UserMessage(enhancedPrompt));
        return chatClient.call(prompt).getResult().getOutput().getContent();
    }
}

5. 文档加载和向量化

java 复制代码
package com.example.rag.service;

import org.springframework.ai.document.Document;
import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.ai.transformer.splitter.TextSplitter;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.List;

@Service
public class DocumentService {
    
    @Autowired
    private VectorStoreService vectorStoreService;
    
    /**
     * 加载文档并存储到向量数据库
     */
    public void loadDocument(MultipartFile file) throws IOException {
        // 1. 读取文档
        TikaDocumentReader reader = new TikaDocumentReader(file.getInputStream());
        List<Document> documents = reader.get();
        
        // 2. 文档分割
        TextSplitter textSplitter = new TokenTextSplitter();
        List<Document> splitDocuments = textSplitter.apply(documents);
        
        // 3. 添加元数据
        for (Document doc : splitDocuments) {
            doc.getMetadata().put("source", file.getOriginalFilename());
            doc.getMetadata().put("type", getFileType(file.getOriginalFilename()));
        }
        
        // 4. 存储到向量数据库
        vectorStoreService.addDocuments(splitDocuments);
    }
    
    private String getFileType(String filename) {
        if (filename.endsWith(".pdf")) return "PDF";
        if (filename.endsWith(".docx")) return "Word";
        if (filename.endsWith(".txt")) return "Text";
        if (filename.endsWith(".md")) return "Markdown";
        return "Unknown";
    }
}

四、Spring AI RAG支持的文件类型

Spring AI通过Apache Tika支持多种文件格式:

1. 文档格式

  • PDF (.pdf):PDF文档
  • Word (.doc, .docx):Microsoft Word文档
  • Excel (.xls, .xlsx):Microsoft Excel表格
  • PowerPoint (.ppt, .pptx):Microsoft PowerPoint演示文稿
  • RTF (.rtf):富文本格式

2. 文本格式

  • 纯文本 (.txt):纯文本文件
  • Markdown (.md, .markdown):Markdown文档
  • HTML (.html, .htm):HTML网页
  • XML (.xml):XML文档
  • JSON (.json):JSON数据文件

3. 代码文件

  • Java (.java)
  • Python (.py)
  • JavaScript (.js)
  • TypeScript (.ts)
  • C/C++ (.c, .cpp, .h)
  • 其他编程语言

4. 配置文件

  • YAML (.yaml, .yml)
  • Properties (.properties)
  • INI (.ini)
  • CSV (.csv)

5. 其他格式

  • 图像 (.jpg, .png, .gif):通过OCR提取文本
  • 音频 (.mp3, .wav):通过语音识别提取文本
  • 视频 (.mp4, .avi):提取字幕和元数据

五、完整RAG实现示例

1. 文档上传控制器

java 复制代码
package com.example.rag.controller;

import com.example.rag.service.DocumentService;
import com.example.rag.service.RAGService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequestMapping("/api/rag")
public class RAGController {
    
    @Autowired
    private DocumentService documentService;
    
    @Autowired
    private RAGService ragService;
    
    /**
     * 上传文档
     */
    @PostMapping("/upload")
    public ResponseEntity<String> uploadDocument(@RequestParam("file") MultipartFile file) {
        try {
            documentService.loadDocument(file);
            return ResponseEntity.ok("文档上传成功");
        } catch (Exception e) {
            return ResponseEntity.badRequest().body("上传失败: " + e.getMessage());
        }
    }
    
    /**
     * RAG查询
     */
    @PostMapping("/query")
    public ResponseEntity<String> query(@RequestBody QueryRequest request) {
        String answer = ragService.query(request.getQuestion());
        return ResponseEntity.ok(answer);
    }
}

2. 使用示例

java 复制代码
package com.example.rag.example;

import com.example.rag.service.DocumentService;
import com.example.rag.service.RAGService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileInputStream;
import java.nio.file.Files;

@Component
public class RAGExample implements CommandLineRunner {
    
    @Autowired
    private DocumentService documentService;
    
    @Autowired
    private RAGService ragService;
    
    @Autowired
    private ResourceLoader resourceLoader;
    
    @Override
    public void run(String... args) throws Exception {
        // 1. 加载文档
        File pdfFile = resourceLoader.getResource("classpath:sample.pdf").getFile();
        documentService.loadDocument(
            new MockMultipartFile("sample.pdf", new FileInputStream(pdfFile))
        );
        
        // 2. 进行RAG查询
        String question = "文档中提到了哪些关键技术?";
        String answer = ragService.query(question);
        System.out.println("问题:" + question);
        System.out.println("回答:" + answer);
    }
}

3. 高级RAG:带重排序

java 复制代码
package com.example.rag.service;

import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.document.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class AdvancedRAGService {
    
    @Autowired
    private ChatClient chatClient;
    
    @Autowired
    private VectorStoreService vectorStoreService;
    
    /**
     * 带重排序的RAG查询
     */
    public String queryWithReranking(String question, int topK, int rerankTopK) {
        // 1. 初始检索(获取更多候选)
        List<Document> candidates = vectorStoreService.search(question, topK);
        
        // 2. 重排序(使用交叉编码器或LLM)
        List<Document> reranked = rerankDocuments(question, candidates, rerankTopK);
        
        // 3. 构建增强提示
        String context = reranked.stream()
            .map(Document::getContent)
            .collect(Collectors.joining("\n\n"));
        
        String enhancedPrompt = buildPrompt(context, question);
        
        // 4. 生成回答
        return chatClient.call(new Prompt(new UserMessage(enhancedPrompt)))
            .getResult().getOutput().getContent();
    }
    
    /**
     * 文档重排序
     */
    private List<Document> rerankDocuments(String question, 
                                          List<Document> candidates, 
                                          int topK) {
        // 使用LLM对文档进行相关性评分
        return candidates.stream()
            .map(doc -> {
                double score = scoreRelevance(question, doc.getContent());
                doc.getMetadata().put("rerank_score", score);
                return doc;
            })
            .sorted(Comparator.comparing(doc -> 
                (Double) doc.getMetadata().get("rerank_score")).reversed())
            .limit(topK)
            .collect(Collectors.toList());
    }
    
    private double scoreRelevance(String question, String content) {
        // 简化的相关性评分(实际可以使用交叉编码器)
        String prompt = String.format(
            "评估以下内容与问题的相关性(0-1分):\n" +
            "问题:%s\n" +
            "内容:%s\n" +
            "只返回分数:",
            question, content.substring(0, Math.min(500, content.length()))
        );
        
        String response = chatClient.call(new Prompt(new UserMessage(prompt)))
            .getResult().getOutput().getContent();
        
        try {
            return Double.parseDouble(response.trim());
        } catch (Exception e) {
            return 0.5;
        }
    }
    
    private String buildPrompt(String context, String question) {
        return String.format(
            "基于以下上下文信息回答问题。如果上下文中没有相关信息,请说明。\n\n" +
            "上下文:\n%s\n\n" +
            "问题:%s\n\n" +
            "回答:",
            context, question
        );
    }
}

六、RAG的最佳实践

1. 文档预处理

  • 清理:移除无关内容、格式化文本
  • 分块:合理设置分块大小(通常200-500 tokens)
  • 重叠:分块之间保留一定重叠(10-20%)

2. 检索优化

  • 混合检索:结合关键词检索和向量检索
  • 重排序:使用交叉编码器提高精度
  • 过滤:根据元数据过滤文档

3. 提示工程

  • 明确指令:清晰说明如何使用上下文
  • 格式要求:指定回答格式
  • 引用来源:要求模型引用信息来源

七、总结

通过Spring AI实现RAG,我们可以:

  • 支持多种文件格式的文档加载
  • 实现高效的向量存储和检索
  • 构建智能的问答系统
  • 提供可追溯的知识服务

RAG技术为AI应用提供了强大的知识增强能力,使得模型能够基于实际文档生成准确、可靠的回答。

参考资料

相关推荐
NAGNIP10 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab12 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab12 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP15 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年15 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼16 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS16 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区17 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈17 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang18 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx