基于 Spring AI 构建 RAG 知识库检索系统:设计与实现

基于 Spring AI 构建 RAG 知识库检索系统:设计与实现

一、引言

随着大语言模型的广泛应用,检索增强生成(Retrieval-Augmented Generation,RAG)已成为解决模型幻觉、提升回答准确性的核心方案。本文将基于 Spring AI 框架,详细讲解如何构建一套完整的 RAG 知识库检索系统,涵盖向量数据库配置、知识库管理、智能检索策略等核心模块的设计与实现。

二、技术栈与核心依赖

本系统基于 Spring Boot 构建,核心依赖包括:

  • Spring AI:提供向量嵌入、向量存储、Prompt 模板、文本拆分等核心能力
  • PgVector:PostgreSQL 的向量扩展,作为向量数据库存储知识库向量
  • JdbcTemplate:操作 PgVector 数据库
  • TokenTextSplitter:基于 Token 的文本拆分工具,解决大文本处理问题
  • ChatClient:Spring AI 的对话客户端,用于问题重写等 LLM 交互场景

三、核心模块设计与实现

3.1 向量数据库配置(PgVector)

向量数据库是 RAG 系统的核心存储层,负责存储知识库文本的向量表示,支撑相似性检索。本系统通过 Spring AI 的 PgVectorStore 实现向量存储的配置与初始化。

3.1.1 配置类实现
java 复制代码
package org.dromara.online.rag.module.vector;

import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.pgvector.PgVectorStore;
import org.springframework.ai.vectorstore.pgvector.autoconfigure.PgVectorStoreProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * 向量数据库配置
 *
 * @Author: 
 * @Date: 2026/06/05
 */
@Configuration
public class VectorConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.ai.vectorstore.pgvector")
    public PgVectorStoreProperties pgVectorStoreProperties() {
        return new PgVectorStoreProperties();
    }

    @Bean
    public VectorStore initVectorStore(JdbcTemplate jdbcTemplate, EmbeddingModel dashscopeEmbeddingModel,
                                       PgVectorStoreProperties pgVectorStoreProperties) {
        VectorStore vectorStore = PgVectorStore.builder(jdbcTemplate, dashscopeEmbeddingModel)
            .dimensions(pgVectorStoreProperties.getDimensions()) // 向量维度,与嵌入模型匹配
            .distanceType(pgVectorStoreProperties.getDistanceType()) // 距离计算方式(如余弦相似度)
            .indexType(pgVectorStoreProperties.getIndexType()) // 向量索引类型
            .initializeSchema(pgVectorStoreProperties.isInitializeSchema()) // 自动初始化数据库表结构
            .schemaName(pgVectorStoreProperties.getSchemaName()) // 数据库schema
            .vectorTableName(pgVectorStoreProperties.getTableName()) // 向量表名
            .maxDocumentBatchSize(pgVectorStoreProperties.getMaxDocumentBatchSize()) // 批量处理大小
            .removeExistingVectorStoreTable(pgVectorStoreProperties.isRemoveExistingVectorStoreTable()) // 是否删除现有表
            .build();
        return vectorStore;
    }
}
3.1.2 核心配置说明
  • 通过@ConfigurationProperties绑定spring.ai.vectorstore.pgvector前缀的配置,实现灵活的参数配置;
  • 基于PgVectorStore.builder构建向量存储实例,核心参数包括向量维度、距离计算类型、索引类型等,需与嵌入模型(EmbeddingModel)的输出维度匹配;
  • 依赖JdbcTemplate实现数据库交互,依赖EmbeddingModel实现文本到向量的转换。

3.2 RAG 核心配置属性

为了灵活控制 RAG 系统的行为,我们通过配置类绑定系统参数,支持问题重写开关、检索策略参数等动态调整。

3.2.1 配置属性类
java 复制代码
package org.dromara.online.rag.module.Resume.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * RAG系统配置属性
 *
 * @Author: 
 * @Date: 2026/06/16
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.ai.rag")
public class RagProperties {
    private Rewrite rewrite = new Rewrite(); // 问题重写配置
    private Search search = new Search();    // 检索策略配置

    @Data
    public static class Rewrite {
        private boolean enabled = true; // 是否开启问题重写
    }

    @Data
    public static class Search {
        private int shortQueryLength = 4; // 短问题长度阈值
        private int topkShort = 20;       // 短问题检索返回条数
        private int topkMedium = 12;      // 中等长度问题检索返回条数
        private int topkLong = 8;         // 长问题检索返回条数
        private double minScoreShort = 0.25; // 短问题最小相似度分数
        private double minScoreDefault = 0.28; // 默认最小相似度分数
    }
}
3.2.2 配置文件示例(application.yml)
yaml 复制代码
spring:
  ai:
    rag:
      rewrite:
        enabled: false # 关闭问题重写(可根据场景动态调整)
      search:
        short-query-length: 4
        topk-short: 20
        topk-medium: 12
        topk-long: 8
        min-score-short: 0.18 # 短问题相似度阈值调低,提升召回率
        min-score-default: 0.28

3.3 RAG 核心业务逻辑(RagService)

RagService 是系统的核心业务层,封装了知识库添加、智能检索、问题重写等核心能力。

3.3.1 核心依赖注入与初始化
java 复制代码
package org.dromara.online.rag.module.Resume.service;

import com.alibaba.nacos.common.utils.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.dromara.online.rag.module.Resume.domain.enums.KnowledgeStatus;
import org.dromara.online.rag.module.Resume.properties.RagProperties;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.document.Document;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.util.*;

/**
 * Rag相关操作
 *
 * @Author: 
 * @Date: 2026/06/15
 */
@Component
@Slf4j
public class RagService {
    private static final int MAX_BATCH_SIZE = 10; // 向量入库批量大小
    private static final String KNOWLEDGE_STATUS = "knowledge_status"; // 知识库状态元数据key
    private static final String KNOWLEDGE_ID = "knowledge_id"; // 知识库ID元数据key
    @jakarta.annotation.Resource
    VectorService vectorService; // 向量操作服务(封装VectorStore)
    RagProperties queryProperties; // RAG配置属性
    PromptTemplate rewriteTemplate; // 问题重写Prompt模板
    @jakarta.annotation.Resource
    ChatClient.Builder chatClient; // LLM对话客户端构建器
    TokenTextSplitter textSplitter; // 文本拆分工具
    // 检索策略参数
    private int shortQueryLength;
    private int topkShort;
    private double minScoreShort;
    private int topkMedium;
    private double minScoreDefault;
    private int topkLong;

    // 构造函数注入配置与Prompt模板
    public RagService(RagProperties queryProperties,
                      @Value("classpath:prompts/rag-rewrite.st") Resource resource) {
        this.queryProperties = queryProperties;
        // 初始化检索策略参数
        this.shortQueryLength = queryProperties.getSearch().getShortQueryLength();
        this.topkShort = queryProperties.getSearch().getTopkShort();
        this.topkMedium = queryProperties.getSearch().getTopkMedium();
        this.topkLong = queryProperties.getSearch().getTopkLong();
        this.minScoreShort = queryProperties.getSearch().getMinScoreShort();
        this.minScoreDefault = queryProperties.getSearch().getMinScoreDefault();
        // 加载问题重写Prompt模板
        this.rewriteTemplate = new PromptTemplate(resource);
        // 初始化文本拆分工具(基于Token)
        this.textSplitter = TokenTextSplitter.builder().build();
    }
}
3.3.2 知识库添加(文本拆分 + 批量向量化)

知识库文本通常较长,直接向量化会导致精度下降,因此需要先拆分再批量入库,同时通过元数据标记知识库状态和 ID。

java 复制代码
/**
 * 添加知识库
 *
 * @param knowledgeId 知识库唯一标识
 * @param content     知识库文本内容
 */
public void addDocument(String knowledgeId, String content) {
    // 参数校验
    Assert.hasLength(content.trim(), "content cannot be null");
    Assert.hasLength(knowledgeId.trim(), "knowledgeId cannot be null");
    try {
        // 1. 文本拆分:将长文本拆分为Token级别的小片段
        List<Document> chunks = textSplitter.split(Document.builder().text(content).build());
        log.info("知识库id = {},知识库拆分完成: {}", knowledgeId, chunks.size());
        
        // 2. 为每个文本片段添加元数据(状态、知识库ID)
        chunksAddTempMetadata(chunks, knowledgeId, KnowledgeStatus.PENDING);
        
        // 3. 批量向量化入库(避免单次入库过多导致性能问题)
        int totalChunks = chunks.size();
        int batchCount = (totalChunks + MAX_BATCH_SIZE - 1) / MAX_BATCH_SIZE; // 向上取整计算批次数
        log.info("开始分批向量化: 总共 {} 个chunks,分 {} 批处理,每批最多 {} 个",
            totalChunks, batchCount, MAX_BATCH_SIZE);
        for (int i = 0; i < batchCount; i++) {
            int start = i * MAX_BATCH_SIZE;
            int end = Math.min((i + 1) * MAX_BATCH_SIZE, totalChunks);
            vectorService.add(chunks.subList(start, end));
        }
        
        // 4. 更新知识库状态(标记为已完成)
        chunksUpdateTempMetadata(knowledgeId);
        log.info("向量化完成: 总共 {} 个chunks", totalChunks);
    } catch (Exception e) {
        log.error("知识库id = {} 添加失败", knowledgeId);
        // 异常回滚:删除已入库的该知识库片段
        chunksDeleteTempMetadata(knowledgeId);
        throw new RuntimeException(e);
    }
}

// 为文本片段添加元数据
private void chunksAddTempMetadata(List<Document> chunks, String knowledgeId, KnowledgeStatus knowledgeStatus) {
    chunks.stream()
        .forEach(chunk -> {
            chunk.getMetadata().put(KNOWLEDGE_STATUS, knowledgeStatus);
            chunk.getMetadata().put(KNOWLEDGE_ID, knowledgeId);
        });
}

// 删除指定知识库的所有文本片段
private void chunksDeleteTempMetadata(String knowledgeId) {
    vectorService.deleteByKnowledgeId(knowledgeId);
}

// 更新指定知识库的状态元数据
private void chunksUpdateTempMetadata(String knowledgeId) {
    vectorService.updateByKnowledgeId(knowledgeId);
}
3.3.3 智能向量检索(多候选查询 + 动态策略)

检索环节是 RAG 系统的核心,本系统实现了 "多候选查询 + 基于问题长度的动态检索策略",提升召回率和准确性。

3.3.3.1 检索上下文封装

通过 Record 封装检索上下文,包含原始问题、候选查询、检索参数等:

复制代码
// 检索上下文:封装检索所需的所有信息
public record QueryContext(String originalQuestion, List<String> candidateQueries, SearchParams searchParams) {}

// 检索参数:知识库ID、返回条数、最小相似度分数
public record SearchParams(List<String> knowledgeIds, int topK, double minScore) {}
3.3.3.2 核心检索逻辑
java 复制代码
/**
 * 向量检索:遍历候选查询,返回首个命中的相关文档
 *
 * @param queryContext 检索上下文
 * @return 相关文档列表
 */
public List<Document> retrieveRelevantDocs(QueryContext queryContext) {
    for (String candidateQuery : queryContext.candidateQueries()) {
        if (candidateQuery.isBlank()) {
            continue;
        }
        // 调用向量服务进行相似性检索
        List<Document> docs = vectorService.similaritySearch(
            candidateQuery, // 检索词(候选查询)
            queryContext.searchParams().knowledgeIds(), // 目标知识库ID列表
            queryContext.searchParams().topK(), // 返回条数
            queryContext.searchParams().minScore() // 最小相似度分数(过滤低相关度结果)
        );
        log.info("检索候选 query='{}',命中 {} 条", candidateQuery, docs.size());
        if (CollectionUtils.isNotEmpty(docs)) {
            return docs; // 只要有候选查询命中,立即返回结果
        }
    }
    return List.of(); // 无命中返回空列表
}
3.3.3.3 动态检索策略(基于问题长度)

在构建检索上下文时,根据问题长度动态调整检索参数(topK、minScore),平衡召回率和精准度:

java 复制代码
/**
 * 构建检索上下文:标准化问题、生成候选查询、动态配置检索参数
 *
 * @param originalQuestion 原始用户问题
 * @param historyQuestions 对话历史
 * @param knowledgeIds     目标知识库ID列表
 * @return 检索上下文
 */
public RagService.QueryContext buildQueryContext(String originalQuestion, List<Message> historyQuestions,
                                                 List<String> knowledgeIds) {
    // 1. 问题标准化:去除首尾空格
    String normalizeQuestion = normalizeQuestion(originalQuestion);
    // 2. 问题重写:基于LLM生成更优的检索词(可选,由配置开关控制)
    String rewrittenQuestion = rewriteQuestion(normalizeQuestion, historyQuestions);
    // 3. 构建候选查询列表(原始问题+重写问题)
    Set<String> candidates = new LinkedHashSet<>();
    candidates.add(normalizeQuestion);
    candidates.add(rewrittenQuestion);
    // 4. 动态解析检索参数(基于问题长度)
    SearchParams searchParams = resolveSearchParams(normalizeQuestion, knowledgeIds);
    // 5. 封装检索上下文
    return new QueryContext(normalizeQuestion, new ArrayList<>(candidates), searchParams);
}

/**
 * 解析检索参数:根据问题长度选择不同的topK和minScore
 *
 * @param question     标准化后的问题
 * @param knowledgeIds 目标知识库ID列表
 * @return 检索参数
 */
private SearchParams resolveSearchParams(String question, List<String> knowledgeIds) {
    // 计算问题紧凑长度(去除所有空格)
    int compactLength = question.replaceAll("\\s+", "").length();
    if (compactLength <= shortQueryLength) {
        // 短问题:增大topK,降低minScore,提升召回率
        return new SearchParams(knowledgeIds, topkShort, minScoreShort);
    }
    if (compactLength <= 12) {
        // 中等长度问题:平衡召回率和精准度
        return new SearchParams(knowledgeIds, topkMedium, minScoreDefault);
    }
    // 长问题:减小topK,保证精准度
    return new SearchParams(knowledgeIds, topkLong, minScoreDefault);
}

// 问题标准化
private String normalizeQuestion(String originalQuestion) {
    return originalQuestion == null ? "" : originalQuestion.trim();
}
3.3.4 问题重写(可选能力)

基于 LLM 将用户原始问题重写为更适合向量检索的形式,提升召回率(由配置开关控制是否开启):

java 复制代码
/**
 * 问题重写:基于Prompt模板调用LLM生成更优的检索词
 *
 * @param normalizeQuestion 标准化后的原始问题
 * @param historyQuestions  对话历史
 * @return 重写后的问题
 */
public String rewriteQuestion(String normalizeQuestion, List<Message> historyQuestions) {
    // 未开启重写或问题为空,直接返回原问题
    if (!queryProperties.getRewrite().isEnabled() || normalizeQuestion.isEmpty()) {
        return normalizeQuestion;
    }
    try {
        // 构建Prompt变量
        Map<String, Object> variables = new HashMap<>();
        variables.put("question", normalizeQuestion);
        variables.put("history", historyQuestions);
        // 渲染Prompt模板
        String render = rewriteTemplate.render(variables);
        // 调用LLM生成重写后的问题
        ChatResponse chatResponse = chatClient.build().prompt()
            .user(render)
            .call()
            .chatResponse();
        String rewritten = chatResponse.getResult().getOutput().getText();
        if (rewritten == null || rewritten.isBlank()) {
            return normalizeQuestion;
        }
        String normalized = rewritten.trim();
        log.info("Query rewrite: id = {} origin='{}', rewritten='{}', historySize={},totalToken = {}",
            chatResponse.getMetadata().getId(),
            normalizeQuestion,
            normalized,
            historyQuestions.size(),
            chatResponse.getMetadata().getUsage().getTotalTokens()
        );
        return normalized;
    } catch (Exception e) {
        log.error("Query rewrite 失败,使用原问题继续检索: {}", e.getMessage());
        return normalizeQuestion;
    }
}

四、系统设计亮点

4.1 灵活的配置体系

通过@ConfigurationProperties绑定配置,支持问题重写开关、检索参数(topK、minScore)等动态调整,无需修改代码即可适配不同场景。

4.2 高性能的知识库入库

  • 文本拆分:基于 Token 的拆分方式,适配 LLM 的上下文长度限制;
  • 批量入库:避免单次入库过多导致数据库压力过大;
  • 异常回滚:入库失败时自动删除已入库的片段,保证数据一致性。

4.3 智能的检索策略

  • 多候选查询:原始问题 + 重写问题,提升召回率;
  • 动态检索参数:根据问题长度调整 topK 和 minScore,平衡召回率与精准度;
  • 相似度过滤:通过 minScore 过滤低相关度结果,减少噪声。

4.4 可扩展的架构设计

  • 向量服务解耦:VectorService 封装 VectorStore 的底层操作,便于替换向量数据库(如切换为 Milvus、Chroma 等);
  • Prompt 模板化:问题重写的 Prompt 通过文件加载,便于迭代优化。

五、应用场景与扩展方向

5.1 典型应用场景

  • 智能问答:基于企业知识库的智能客服、内部助手;
  • 文档检索:结构化 / 非结构化文档的相似性检索;
  • 对话增强:结合对话历史的上下文感知检索。

5.2 扩展方向

  1. 多模态支持:扩展图片、PDF 等多模态文档的解析与向量化;
  2. 检索结果重排序:引入 Rerank 模型,对检索结果进一步排序,提升精准度;
  3. 缓存优化:对高频查询的检索结果进行缓存,提升响应速度;
  4. 知识库更新:支持知识库的增量更新、删除、版本管理;
  5. 监控与可观测性:增加检索命中率、响应时间等指标监控。

六、总结

本文基于 Spring AI 框架,详细讲解了 RAG 知识库检索系统的核心模块设计与实现,包括向量数据库配置、知识库管理、智能检索策略等。该系统具备配置灵活、性能高效、检索智能等特点,可快速适配企业级知识库检索场景。通过合理的文本拆分、批量处理、动态检索策略,平衡了系统的召回率与精准度,为构建高性能 RAG 系统提供了完整的解决方案。