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相关依赖:

<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中配置:

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. 创建向量存储服务

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服务

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. 文档加载和向量化

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. 文档上传控制器

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. 使用示例

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:带重排序

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应用提供了强大的知识增强能力,使得模型能够基于实际文档生成准确、可靠的回答。

参考资料

Logo

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

更多推荐