前言

随着大语言模型(LLM)的普及,越来越多的开发者希望将 AI 能力集成到自己的 Java 应用中。然而,LLM 存在一个天然的局限性——知识截止日期,它无法感知你的私有数据、最新文档或企业内部知识库。

RAG(Retrieval-Augmented Generation,检索增强生成) 正是解决这一问题的核心技术。而 Spring AI 作为 Spring 生态对 AI 能力的官方集成框架,提供了一套优雅、开箱即用的 RAG 工具链。

本文将带你从原理到实战,完整走通一个基于 Spring AI 的 RAG 应用。


一、什么是 RAG?

RAG 的核心思路很简单:

用户提问 → 从知识库检索相关文档 → 将文档 + 问题一起喂给 LLM → 得到有依据的回答

相比直接问 LLM,RAG 的优势在于:

对比项 纯 LLM RAG
私有数据 ❌ 不支持 ✅ 支持
知识时效性 ❌ 有截止日期 ✅ 实时更新
回答可溯源 ❌ 难以验证 ✅ 可追溯来源
幻觉风险 ⚠️ 较高 ✅ 显著降低

二、Spring AI RAG 核心组件

Spring AI 将 RAG 流程抽象为以下几个核心组件:

2.1 Document(文档)

Document doc = new Document("Spring AI 是 Spring 官方推出的 AI 集成框架...", 
    Map.of("source", "官方文档", "page", "1"));

Document 是知识库的基本单元,包含文本内容和元数据(metadata)。

2.2 DocumentReader(文档读取器)

Spring AI 内置多种读取器:

// 读取 PDF
DocumentReader pdfReader = new PagePdfDocumentReader("classpath:knowledge.pdf");

// 读取文本文件
DocumentReader txtReader = new TextReader("classpath:docs.txt");

// 读取 Tika 支持的格式(Word、Excel 等)
DocumentReader tikaReader = new TikaDocumentReader(resource);

List<Document> documents = pdfReader.get();

2.3 TextSplitter(文本分割器)

LLM 有 Token 限制,长文档需要切分:

TextSplitter splitter = new TokenTextSplitter(
    800,   // chunk size(每块 token 数)
    400,   // overlap(重叠 token 数,保证上下文连贯)
    5,     // min chunk size
    10000, // max chunk size
    true   // keep separator
);

List<Document> chunks = splitter.apply(documents);

2.4 EmbeddingModel(向量化模型)

将文本转换为向量,是语义检索的基础:

@Autowired
EmbeddingModel embeddingModel;

float[] vector = embeddingModel.embed("Spring AI 是什么?");

Spring AI 支持 OpenAI、Azure OpenAI、Ollama、DashScope 等多种 Embedding 模型。

2.5 VectorStore(向量数据库)

存储和检索向量化后的文档:

@Autowired
VectorStore vectorStore;

// 写入文档
vectorStore.add(chunks);

// 相似度检索
List<Document> results = vectorStore.similaritySearch(
    SearchRequest.query("如何配置 Spring AI?")
                 .withTopK(5)
                 .withSimilarityThreshold(0.7)
);

Spring AI 支持的向量数据库包括:

  • Chroma(本地开发首选)
  • Milvus
  • Pinecone
  • Redis
  • PgVector(PostgreSQL 扩展)
  • Weaviate
  • 等 20+ 种

三、完整实战:构建企业知识库问答系统

3.1 项目依赖

<dependencies>
    <!-- Spring AI OpenAI Starter -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
    </dependency>
    
    <!-- Chroma 向量数据库 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-chroma-store-spring-boot-starter</artifactId>
    </dependency>
    
    <!-- PDF 读取 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-pdf-document-reader</artifactId>
    </dependency>
</dependencies>

<!-- Spring AI BOM -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

3.2 配置文件

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4o
      embedding:
        options:
          model: text-embedding-3-small
    vectorstore:
      chroma:
        host: localhost
        port: 8000
        collection-name: knowledge-base

3.3 文档摄入服务(Ingestion)

@Service
public class DocumentIngestionService {

    @Autowired
    private VectorStore vectorStore;

    /**
     * 将 PDF 文档摄入向量数据库
     */
    public void ingestPdf(Resource pdfResource) {
        // 1. 读取 PDF
        DocumentReader reader = new PagePdfDocumentReader(pdfResource,
            PdfDocumentReaderConfig.builder()
                .withPageTopMargin(0)
                .withPageExtractedTextFormatter(
                    ExtractedTextFormatter.builder()
                        .withNumberOfTopTextLinesToDelete(0)
                        .build())
                .withPagesPerDocument(1)
                .build());

        List<Document> documents = reader.get();

        // 2. 切分文档
        TextSplitter splitter = new TokenTextSplitter();
        List<Document> chunks = splitter.apply(documents);

        // 3. 写入向量数据库(自动完成 Embedding)
        vectorStore.add(chunks);
        
        log.info("成功摄入 {} 个文档块", chunks.size());
    }
}

3.4 RAG 问答服务(核心)

@Service
public class RagChatService {

    @Autowired
    private ChatClient.Builder chatClientBuilder;

    @Autowired
    private VectorStore vectorStore;

    private static final String SYSTEM_PROMPT = """
        你是一个专业的企业知识库助手。
        请严格基于以下检索到的上下文内容回答用户问题。
        如果上下文中没有相关信息,请明确告知用户,不要编造答案。
        
        上下文:
        {context}
        """;

    public String chat(String userQuestion) {
        // 1. 从向量数据库检索相关文档
        List<Document> relevantDocs = vectorStore.similaritySearch(
            SearchRequest.query(userQuestion)
                         .withTopK(4)
                         .withSimilarityThreshold(0.65)
        );

        // 2. 拼接上下文
        String context = relevantDocs.stream()
            .map(Document::getContent)
            .collect(Collectors.joining("\n\n---\n\n"));

        // 3. 构建 Prompt 并调用 LLM
        ChatClient chatClient = chatClientBuilder.build();
        
        return chatClient.prompt()
            .system(s -> s.text(SYSTEM_PROMPT).param("context", context))
            .user(userQuestion)
            .call()
            .content();
    }
}

3.5 使用 Spring AI 内置 RAG 流水线(推荐)

Spring AI 1.0 提供了更优雅的 QuestionAnswerAdvisor,一行代码搞定 RAG:

@Service
public class RagChatServiceV2 {

    private final ChatClient chatClient;

    public RagChatServiceV2(ChatClient.Builder builder, VectorStore vectorStore) {
        this.chatClient = builder
            .defaultAdvisors(
                new QuestionAnswerAdvisor(
                    vectorStore,
                    SearchRequest.defaults()
                                 .withTopK(5)
                                 .withSimilarityThreshold(0.7)
                )
            )
            .build();
    }

    public String chat(String question) {
        return chatClient.prompt()
            .user(question)
            .call()
            .content();
    }
}

QuestionAnswerAdvisor 内部自动完成:检索 → 注入上下文 → 调用 LLM 的完整流程。

3.6 REST 接口

@RestController
@RequestMapping("/api/rag")
public class RagController {

    @Autowired
    private RagChatServiceV2 ragChatService;

    @Autowired
    private DocumentIngestionService ingestionService;

    @PostMapping("/chat")
    public ResponseEntity<String> chat(@RequestBody Map<String, String> body) {
        String answer = ragChatService.chat(body.get("question"));
        return ResponseEntity.ok(answer);
    }

    @PostMapping("/ingest")
    public ResponseEntity<String> ingest(@RequestParam("file") MultipartFile file) 
            throws IOException {
        ingestionService.ingestPdf(file.getResource());
        return ResponseEntity.ok("文档摄入成功");
    }
}

四、RAG 效果优化技巧

4.1 Chunk 策略优化

// 按句子分割,保留语义完整性
TokenTextSplitter splitter = new TokenTextSplitter(
    512,  // 适中的 chunk size
    128,  // 足够的 overlap
    ...
);

经验值:

  • Chunk Size:256~1024 tokens(根据文档类型调整)
  • Overlap:Chunk Size 的 10%~20%
  • 技术文档建议更小的 chunk,叙述性文档可以更大

4.2 混合检索(Hybrid Search)

纯向量检索有时会漏掉关键词精确匹配的结果,结合 BM25 关键词检索效果更好:

// PgVector 支持混合检索
SearchRequest request = SearchRequest.query(question)
    .withTopK(10)
    .withFilterExpression("metadata.category == 'technical'"); // 元数据过滤

4.3 重排序(Reranking)

检索到的文档按相关性重新排序,提升 Top-K 质量:

// 使用 Cohere Rerank 或本地 Cross-Encoder 模型
List<Document> reranked = rerankingService.rerank(question, relevantDocs);

4.4 查询改写(Query Rewriting)

用户的原始问题可能不够清晰,先让 LLM 改写再检索:

String rewrittenQuery = chatClient.prompt()
    .system("将用户问题改写为更适合语义检索的形式,保留核心意图")
    .user(originalQuestion)
    .call()
    .content();

List<Document> docs = vectorStore.similaritySearch(
    SearchRequest.query(rewrittenQuery).withTopK(5)
);

五、完整架构图

┌─────────────────────────────────────────────────────────┐
│                    RAG 应用架构                          │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  【摄入阶段 Ingestion】                                   │
│  原始文档 → DocumentReader → TextSplitter                │
│         → EmbeddingModel → VectorStore                  │
│                                                         │
│  【查询阶段 Query】                                       │
│  用户问题 → EmbeddingModel → VectorStore 相似检索          │
│         → 相关文档 + 原始问题 → LLM → 最终回答              │
│                                                         │
└─────────────────────────────────────────────────────────┘

六、总结

Spring AI 的 RAG 实现非常完整,核心优势在于:

  • 与 Spring 生态无缝集成,零学习成本
  • 抽象层设计优雅,切换 LLM / 向量库只需改配置
  • QuestionAnswerAdvisor 开箱即用,几行代码完成 RAG
  • 支持 20+ 向量数据库,生产环境选择灵活

RAG 是当前 AI 应用落地最成熟的方案之一,结合 Spring AI 的工程化能力,可以快速构建出企业级的知识库问答、智能客服、文档助手等应用。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐