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[(🗄️ 向量数据库<br>支持按国家/语义检索)]:::db end subgraph DataLoader[数据加载与预处理模块] direction TB OriginalDocs[📚 原始文档<br>国家历史地理风俗] --> EntityExtract[🏷️ 大模型自动抽取<br>国家名称 & 描述信息] EntityExtract --> Splitter[✂️ 文档分割器] Splitter --> DocChunks[📄 文档分块<br>含元数据: 国家/地区] DocChunks --> CalcEmb[🧮 计算向量嵌入] CalcEmb -->|存入向量与元数据| VectorStore end subgraph RAGApp[智能问答应用] direction TB Question[❓ 用户问题<br>例: 日本有哪些传统节日?] --> App[④ 应用入口]:::app App -->|1. 问题分析与意图识别| IntentCheck{是否涉及<br>已知国家/地区?} IntentCheck -->|是| SearchByCountry[🔍 按国家元数据过滤] IntentCheck -->|否| SemanticSearch[🔍 纯语义相似搜索] SearchByCountry -->|组合查询| VectorStore SemanticSearch --> VectorStore VectorStore -->|返回相似文档块<br>+ 所属国家信息| App App --> Prompt[⑤ 组装增强提示词<br>包含: 检索到的风俗/地理描述<br>+ 原始问题] Prompt --> LLM[🤖 大语言模型]:::llm LLM --> Answer[✨ 增强答案<br>例: 依据日本文化资料,主要节日有...] 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();
    }
}
相关推荐
MicrosoftReactor2 小时前
技术速递|底层机制:GitHub Agentic Workflows 的安全架构
安全·ai·github·agent·安全架构
Java后端的Ai之路3 小时前
【AI应用开发】-怎么解决Lost in the Middle(中间迷失)现象?
人工智能·agent·rag·中间迷失·lost
HinsCoder3 小时前
【miclaw】——小米手机龙虾配置教程
人工智能·智能手机·llm·agent·openclaw·miclaw·手机龙虾
only-qi3 小时前
一篇文章讲明白:RAG + MCP + Skills + LangChain + LangGraph
ai·langchain·rag·langgraph·mcp·skills
深藏功yu名3 小时前
Day24(进阶篇):向量数据库 Chroma_FAISS 深度攻坚 —— 索引优化、性能调优与生产级落地
数据库·人工智能·python·ai·agent·faiss·chroma
Dearfrienda3 小时前
Claude国内使用(切换国内模型服务)
ai
lljss20203 小时前
MiMo V2 Pro 在opencode 和 在Claude Code 中的表现不同的根本原因
大模型
imbackneverdie4 小时前
如何从海量文献中跨界汲取创新灵感?
论文阅读·人工智能·ai·自然语言处理·aigc·ai写作·ai工具
wang_yb4 小时前
数据团队该醒醒了:AI智能体不是你的下一个仪表盘
ai·数据分析·databook