Spring with AI (5): 搜索扩展——向量数据库与RAG(下)

本文代码:
https://github.com/JunTeamCom/ai-demo/tree/release-5.0
https://github.com/JunTeamCom/ai-demo-tools/tree/release-5.0/data-loader

本章讲解查询向量数据库、RAG应用。

Spring with AI系列,只关注上层AI的应用程序(基于JAVA搭建),不关注底层的LLM原理、搭建等技术。

RAG能通过实时搜索数据库的方式,扩展已经训练固化的大模型的知识能力。

RAG(R etrieval-A ugmented G eneration)检索增强生成

retrieval /rɪˈtriːvl/ n.检索

augmented /ɔ:g'mentɪd/ adj.增强的

generation /ˌdʒenəˈreɪʃn/ n.生成

通过检索、增强大模型生成的内容。

在前文中,我们通过文本模板,补充了"规则",进而增强了大模型生成的内容。

然而这只是简单的"文本匹配+配置文件"的方式,面对复杂问题和庞大的知识库,完全不具备现实意义。

RAG整体的流程和组成部分如下:
flowchart LR subgraph VectorDB向量数据库模块 VectorStore(向量数据库):::db end subgraph DocumentLoader文档加载模块 OriginalDocs① 原始文档 --> Splitter文档分割器:::process Splitter --> DocChunks② 文档分块 DocChunks --> CalcEmb③ 计算向量嵌入:::process CalcEmb -->|存入| VectorStore end subgraph RAGApp支持RAG的应用 Question问题 --> App④ 应用:::app App -->|搜索相似文档| VectorStore VectorStore -->|返回相似文档| App App --> Prompt⑤ 组装提示词 Prompt --> LLM大语言模型 LLM --> Answer答案 Answer --> App App --> Output输出答案 end classDef process fill:#a5d6a7,stroke:#388e3c,stroke-width:2px classDef app fill:#ffab91,stroke:#e64a19,stroke-width:2px classDef db fill:#bbdefb,stroke:#1976d2,stroke-width:2px classDef llm fill:#ffcc80,stroke:#f57c00,stroke-width:2px

本Demo的具体场景化,上图变为:

具体场景的流程如下:
flowchart LR subgraph VectorDB向量数据库模块 VectorStore(🗄️ 向量数据库\支持按国家/语义检索):::db end subgraph DataLoader数据加载与预处理模块 direction TB OriginalDocs📚 原始文档\国家历史地理风俗 --> EntityExtract🏷️ 大模型自动抽取\国家名称 \& 描述信息 EntityExtract --> Splitter✂️ 文档分割器 Splitter --> DocChunks📄 文档分块\含元数据: 国家/地区 DocChunks --> CalcEmb🧮 计算向量嵌入 CalcEmb -->|存入向量与元数据| VectorStore end subgraph RAGApp智能问答应用 direction TB Question❓ 用户问题\例: 日本有哪些传统节日? --> App④ 应用入口:::app App -->|1. 问题分析与意图识别| IntentCheck{是否涉及<br>已知国家/地区?} IntentCheck -->|是| SearchByCountry🔍 按国家元数据过滤 IntentCheck -->|否| SemanticSearch🔍 纯语义相似搜索 SearchByCountry -->|组合查询| VectorStore SemanticSearch --> VectorStore VectorStore -->|返回相似文档块<br>+ 所属国家信息| App App --> Prompt⑤ 组装增强提示词\包含: 检索到的风俗/地理描述\+ 原始问题 Prompt --> LLM🤖 大语言模型:::llm LLM --> Answer✨ 增强答案\例: 依据日本文化资料,主要节日有... Answer --> App App --> Output📢 输出答案 end classDef process fill:#a5d6a7,stroke:#388e3c,stroke-width:2px classDef app fill:#ffab91,stroke:#e64a19,stroke-width:2px classDef db fill:#bbdefb,stroke:#1976d2,stroke-width:2px classDef llm fill:#ffcc80,stroke:#f57c00,stroke-width:2px

可以看到:

RAG,通过搜索能力、增强大模型生成内容的质量。

核心的一个误解,就是搜索能力不是在大模型生成完之后补充结果、也不是把搜索内容导入到大模型内部。

而是组装到发给大模型的提示词里

1 依赖与配置

1.1 Qdrant配置

回到AI-Demo项目,添加向量数据库和RAG依赖:

spring-ai-starter-vector-store-qdrant(通过Qdrant搜索Starter)

spring-ai-advisors-vector-store(通过Advisors Vector搜索Dependency)

spring-ai-rag(通过Spring RAG搜索Dependency)

配置数据源(注意在Qdrant中配置好API Key、并配置到环境变量):

yaml 复制代码
spring:
  application:
    name: ai-demo
  ai:
    openai:
      base-url: https://dashscope.aliyuncs.com/compatible-mode  # Qwen的OpenAI式Endpoint
      api-key: ${DASHSCOPE_API_KEY}
      chat:
        options:
            model: qwen3.5-plus
      embedding:  # 新增嵌入模型配置
        options:
          model: text-embedding-v2  # 阿里云支持的嵌入模型
    vectorstore:
      qdrant:
        host: 127.0.0.1
        port: 6334
        api-key: ${QDRANT_API_KEY}
        initialize-schema: true
        collection-name: ai_demo

然后启动Qdrant服务(参考上一节)。

1.2 Lombok配置

为了方便打日志、构建实体等,添加Lombok依赖与插件(初始化创建项目时,可以直接添加Starter实现):

xml 复制代码
<!-- pom.xml配置-->

<!-- 1. 依赖配置 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
<!-- 2. 插件配置 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>

1.3 Prompt模板配置

修改Prompt模板,完全基于搜索内容进行推理:

plaintext 复制代码
你是一个有用的助手,负责回答有关{countryTitle}的历史地理风俗问题。

根据"文档"部分中的上下文且无先验知识,回答用户关于历史地理风俗的问题。
如果答案不在"文档"部分,请回复"我不知道"。

文档:
----------
{rules}
----------

而填入的{rules},就是上一节导入向量数据库的内容。

2 代码修改

2.1 规则定义

搜索向量数据库,根据"问题+国家"搜索信息。

java 复制代码
package com.junteam.ai.demo.service;

import java.util.stream.Collectors;

import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
import org.springframework.stereotype.Service;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class ChatRulesService {
    private VectorStore vectorStore;

    public ChatRulesService(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    public String getRulesFor(String countryTitle, String question) {
        var searchRequest = SearchRequest
                .builder()
                .query(question)
                .filterExpression(new FilterExpressionBuilder()
                        .eq("countryTitle", countryTitle)
                        .build())
                .build();
        log.info("Search Request:" + searchRequest);

        var similarDocuments = vectorStore.similaritySearch(searchRequest);
        if (similarDocuments.isEmpty()) {
            return countryTitle + "的信息不可用。";
        }
        return similarDocuments.stream()
                .map(Document::getText)
                .collect(Collectors.joining(System.lineSeparator()));
    }
}

2.2 规则使用

将上一步从向量数据库搜索到的信息(system方法调用、文本嵌入)、以及问题(user方法调用、聊天的问题),拼装到大模型。

java 复制代码
package com.junteam.ai.demo.service.impl;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

import com.junteam.ai.demo.model.ChatAnswer;
import com.junteam.ai.demo.model.ChatQuestion;
import com.junteam.ai.demo.service.ChatRulesService;
import com.junteam.ai.demo.service.ChatService;

@Service
public class OpenAIChatServiceImpl implements ChatService {

    private final ChatClient chatClient;
    private final ChatRulesService chatRulesService;

    public OpenAIChatServiceImpl(ChatClient.Builder chatClientBuilder, ChatRulesService chatRulesService) {
        this.chatClient = chatClientBuilder.build();
        this.chatRulesService = chatRulesService;
    }

    @Value("classpath:/promptTemplates/questionPromptTemplate.st")
    Resource questionPromptTemplate;

    @Override
    public ChatAnswer ask(ChatQuestion chatQuestion) {
        var chatRules = chatRulesService.getRulesFor(chatQuestion.title(), chatQuestion.question());
        var answer = chatClient
                .prompt()
                .system(systemSpec -> systemSpec
                        .text(questionPromptTemplate)
                        .param("countryTitle", chatQuestion.title())
                        .param("rules", chatRules))
                .user(chatQuestion.question())
                .call();
        var answerText = answer.content();
        return new ChatAnswer(chatQuestion.title(), answerText);
    }
}

3 测试验证

bash 复制代码
curl http://localhost:8080/web/ask \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"title": "德国", "question": "购物一般有哪些渠道?"}' 

返回结果形如:

json 复制代码
{
  "title":"德国",
  "answer":"根据文档,德国购物一般有以下渠道:\n\n1.  **网购**:一般使用易贝、亚马逊平台。\n2.  **超市**:买东西一般去超市。\n3.  **生活用品店**:购买常常去 IDEL。\n4.  **化妆品护肤品店**:一般去 ROSSMAN、DM 购买。"
}

整理出内容如下:

根据文档,德国购物一般有以下渠道:

  1. 网购:一般使用易贝、亚马逊平台。
  2. 超市:买东西一般去超市。
  3. 生活用品店:购买常常去 IDEL。
  4. 化妆品护肤品店:一般去 ROSSMAN、DM 购买。

可见是"向量数据库的知识"+"大模型的推理能力",组合实现了答案输出。

RAG的大部分工作都在实际提示词发送给大模型之前完成。

4 顾问选项

RAG在Spring AI中的这种工作模式,称为"顾问"(Advisor)。

4.1 问答顾问

默认的顾问选项,称为"问答顾问"(QuestionAnswerAdvisor);

可以通过.advisors()方法修改控制:

java 复制代码
var answer = chatClient
        .prompt()
        .system(systemSpec -> systemSpec
                .text(questionPromptTemplate)
                .param("countryTitle", chatQuestion.title())
                .param("rules", chatRules))
        .user(chatQuestion.question())
        .advisors(
            QuestionAnswerAdvisor.builder(vectorStore).build())
        .call();
var answerText = answer.content();

或者可以定义Config类,在ChatClient的构建过程中定义:

java 复制代码
@Configuration
public class AiConfig {
    @Bean
    ChatClient chatClient(
            ChatClient.Builder chatClientBuilder, VectorStore vectorStore) {
        return chatClientBuilder
                .defaultAdvisors(
                        QuestionAnswerAdvisor.builder(vectorStore).build())
                .build();
    }
}

4.2 增强搜索顾问

增强搜索顾问(RetrievalAugmentationAdvisor)提供更加灵活强大的顾问选项:

java 复制代码
package com.junteam.ai.demo.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AiConfig {
    @Bean
    ChatClient chatClient(ChatClient.Builder chatClientBuilder, VectorStore vectorStore) {
        // 构建RAG顾问
        var advisor = RetrievalAugmentationAdvisor.builder()
                .documentRetriever(VectorStoreDocumentRetriever.builder()
                        .vectorStore(vectorStore)
                        .build())
                .build();
        // 设置顾问配置项
        return chatClientBuilder
                .defaultAdvisors(advisor)
                .build();
    }
}

可以修改.documentRetriever()方法的构造入参,实现多种顾问方式。

5 补充优化

基于RetrievalAugmentationAdvisor,一些对聊天问题的重要扩充项:

翻译、重写(简化)、扩展相关话题

java 复制代码
package com.junteam.ai.demo.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.preretrieval.query.expansion.MultiQueryExpander;
import org.springframework.ai.rag.preretrieval.query.transformation.RewriteQueryTransformer;
import org.springframework.ai.rag.preretrieval.query.transformation.TranslationQueryTransformer;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AiConfig {
    @Bean
    ChatClient chatClient(ChatClient.Builder chatClientBuilder, VectorStore vectorStore) {
        // 构建RAG顾问
        var advisor = RetrievalAugmentationAdvisor.builder()
                .documentRetriever(VectorStoreDocumentRetriever.builder()
                        .vectorStore(vectorStore)
                        .build())
                // 对聊天的问题进行加工处理
                .queryTransformers(
                        // 翻译问题
                        TranslationQueryTransformer.builder()
                                .chatClientBuilder(chatClientBuilder)
                                .targetLanguage("Chinese")
                                .build(),
                        // 重写(简化)问题
                        RewriteQueryTransformer.builder()
                                .chatClientBuilder(chatClientBuilder)
                                .build())
                // 扩展相关话题(扩展5个)
                .queryExpander(MultiQueryExpander.builder()
                        .chatClientBuilder(chatClientBuilder)
                        .numberOfQueries(5)
                        .includeOriginal(false)
                        .build())
                .build();
        // 设置顾问配置项
        return chatClientBuilder
                .defaultAdvisors(advisor)
                .build();
    }
}
相关推荐
feiwuw19 小时前
fastgpt介绍和初步使用
ai·fastgpt
beyond阿亮19 小时前
PicoClaw皮皮虾: 端侧设备能跑AI智能体 超轻量AI智能体 极低成本硬件跑AI Agent,内存小于10MB
人工智能·ai·openclaw·picoclaw
星辰AI20 小时前
数据增强方法:提升模型泛化能力的利器
人工智能·ai·语言模型
小羔羊的官方学习账号20 小时前
Claude Code学习笔记2 - Claude.md 文件和使用命令
笔记·ai·claude code
SLD_Allen20 小时前
RAG三大主流架构:Classic RAG、Graph RAG、Agentic RAG的区别
架构·rag·agentic rag·classic rag·graph rag
踏着七彩祥云的小丑20 小时前
AI学习——搜索工具集成
人工智能·ai
Thomas_Sir20 小时前
第14课:OpenClaw|定时任务与Cron【让OpenClaw“无人值守”】
人工智能·openai
武子康20 小时前
Ollama 2026最新实践:从本地大模型到本地+云端+Agent工具链
人工智能·ai·chatgpt·ollama·deepseek
JaydenAI20 小时前
[MAF预定义ChatClient中间件-05]动态修改对话配置的两种解决方案
ai·c#·agent·maf·chatclient管道
汤姆yu20 小时前
微软MAI-Image-2.5模型深度解析
人工智能·大模型