springai Alibaba(下)

十二.RAG检索增强生成

12.1 基础概念

那么我们可以通过一个案例来告诉大家什么是RAG。假设我们现在有一个需求,就是AI智能运维助手,通过提供的错误编码,给出异常解释来辅助运维人员更好的定位问题和维护系统。

比如我们现在提供的错误代码中,00000代表success,A0001代表网络故障,A0002代表系统限流。但是传统的LLM是有缺陷的,比如LLM的知识不是实时的,不具备知识更新;LLM可能不知道你私有的领域/业务知识;LLM有时会在回答中生成看似合理但实际上是错误的信息。

LLM的知识仅限于它所接受的训练数据。如果你想让一个LLM了解特定领域的知识或专有数据,降低大模型出现幻觉的概率,你可以使用RAG;

简单来说,RAG(检索增强生成)是一种从你的数据中查找相关信息,并在将提示发送给LLM之前将其注入到提示中的方法。这样一来,LLM就能获得(希望是)相关的信息,并基于这些信息进行回答,从而降低产生幻觉的概率。

RAG技术就像给AI大模型装上了「实时百科大脑」,为了让大模型获取足够的上下文,以便获得更加广泛的信息源,通过先查资料后回答的机制,让AI摆脱传统模型的"知识遗忘和幻觉回复"困境。

类似考试时有不懂的,给你准备了小抄,对大模型知识盲区的一种补充

12.2 流程

RAG流程分为两个不同的阶段:索引(Index)检索(Retrieval)

索引(Index)

索引首先清理和提取各种格式的原始数据,如PDF,HTML等,然后将其转换为统一的纯文本格式。为了适应语言模型的上下文限制,文本被分割为更小的,可消化的块(chunk)。然后使用嵌入模型将块编码成向量表示,并存储在向量数据库中。这一步对于在随后的检索阶段实现高效的相似性搜索至关重要。知识库被分割成chunks,并将chunks向量化至向量库中。

检索(Retrieval)

检索阶段通常在线进行,此时用户提交的问题应使用检索文档来回答

在收到用户查询之后,RAG系统采用与索引阶段相同的编码模型将查询转化为向量表示,然后计算索引语料库中查询向量和块向量的相似性得分。该系统优先级和检索最高k(Top-K)块,显示最大的相似性查询。

12.3 实战案例

需求 : AI智能运维助手,通过提供的错误代码,给出异常解释辅助运维人员更好的定位问题和维护系统**(SpringAl+阿里百炼嵌入模型text-embedding-v3+向量数据库RedisStack+DeepSeek来实现RAG功能)**

步骤**:**

1.和以往一样,还是建立Module;和上一节向量一样加pom;yml也类似,不过改了模型变成了deepseek;然后就是主启动;原本的多模型modelclient和model的config类不变

2.接下来就是需要重视的业务类的编写了:

  • 提供ops.txt脚本放到resources中,让他存入向量数据库RedisStack,形成文档知识库

版本1:

  1. 新建一个InitVectorDatabaseConfig类

初始化向量数据库对应的数据。即将上述的ops.text中的数据以文本向量化的形式作为向量化数据存储入向量数据库中

java 复制代码
package ai.study.config;


import jakarta.annotation.PostConstruct;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;

import java.nio.charset.Charset;
import java.util.List;


@Configuration
public class InitVectorDatabaseConfig
{
    @Autowired
    private VectorStore vectorStore;

    @Value("classpath:ops.txt")
    private Resource sqlFile;// 读取的文件

    @PostConstruct
    public void init()
    {
        // 1.读取文件
        TextReader textReader = new TextReader(sqlFile);
        textReader.setCharset(Charset.defaultCharset());//文档最好要设置一个编码,默认是utf-8,防止乱码
        // 2.文件转换成向量(开启分词)
        List<Document> list = new TokenTextSplitter().transform(textReader.read());

        // 3.写入向量数据库(Redis),无法去重复版
        vectorStore.add(list);
    }}

2.此时建立一个controller

java 复制代码
package ai.study.controller;

import jakarta.annotation.Resource;
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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;


@RestController
public class RagController
{
    @Resource(name = "qwenChatClient")
    private ChatClient chatClient;
    @Resource
    private VectorStore vectorStore;

    /**
     * http://localhost:8012/rag4aiops?msg=00000
     * http://localhost:8012/rag4aiops?msg=C2222
     * @param msg
     * @return
     */
    @GetMapping("/rag4aiops")
    public Flux<String> rag(String msg)
    {
        String systemInfo = """
                你是一个运维工程师,按照给出的编码给出对应故障解释,否则回复找不到信息。
                """;

        //顾问增强器(属于chatmemory中的内容)
        RetrievalAugmentationAdvisor advisor = RetrievalAugmentationAdvisor.builder()
                .documentRetriever(//检索
                        VectorStoreDocumentRetriever.builder()
                                .vectorStore(vectorStore)
                                .build()
                )
                .build();

        return chatClient.prompt()
                .system(systemInfo)//ai的身份
                .user(msg)//用户输入
                .advisors(advisor) // RAG功能,向量数据库查询
                .stream()
                .content();
    }
}

但是上述版本会出现一个问题,就是每次我重启一次微服务,就会重新加载一次,然后redisstack中就会新增一次数据;因此需要重复数据过滤清洗的操作

版本二:---->向量数据库去重问题解决

使用redis setnx去重

1.先加入RedisConfig

java 复制代码
package ai.study.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@Slf4j
public class RedisConfig
{

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactor)
    {
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();

        redisTemplate.setConnectionFactory(redisConnectionFactor);
        //设置key序列化方式string
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置value的序列化方式json,使用GenericJackson2JsonRedisSerializer替换默认序列化
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }
}

2. 修改InitVectorDatabaseConfig:

java 复制代码
@Configuration
public class InitVectorDatabaseConfig
{


    @Autowired
    private RedisTemplate<String,String> redisTemplate;

     ......

        // 3.写入向量数据库(Redis),无法去重复版
        //vectorStore.add(list);

        //4 去重复版本

        String sourceMetadata = (String)textReader.getCustomMetadata().get("source");

        String textHash = SecureUtil.md5(sourceMetadata);
        String redisKey = "vector-xxx:" + textHash;

        // 判断是否存入过,redisKey如果可以成功插入表示以前没有过,可以假如向量数据
        Boolean retFlag = redisTemplate.opsForValue().setIfAbsent(redisKey, "1");

        System.out.println("****retFlag : "+retFlag);

        if(Boolean.TRUE.equals(retFlag))
        {
            //键不存在,首次插入,可以保存进向量数据库
            vectorStore.add(list);
        }else {
            //键已存在,跳过或者报错
            //throw new RuntimeException("---重复操作");
            System.out.println("------向量初始化数据已经加载过,请不要重复操作");
        
    }}

十三.ToolCalling

一句话概率,toolcalling就是LLM的外部utils工具类。ToolCalling(也称为FunctionCalling)它允许大模型与一组API或工具进行交互,将LLM的智能与外部工具或API无缝连接,从而增强大模型其功能。

但是注意,LLM本身并不执行函数,它只是指示应该调用哪个函数以及如何调用。如果不使用toolCalling,则会出现:

所以我们知道tool calling可以实现:

  1. 访问实时数据
  2. 执行某种工具类/辅助类操作

工作流程可以大致参考下述:

接下来就是开发步骤:

  1. 建子模块Module,改pom(dashscope就行),写yml,主启动和以往一样

接下来是业务类

  1. 新建Tool工具类,类似于Utils工具类
java 复制代码
package ai.study.utils;
import org.springframework.ai.tool.annotation.Tool;

import java.time.LocalDateTime;


public class DateTimeTools
{
    /**
     * 1.定义 function call(tool call)
     * 2. returnDirect
     *    true = tool直接返回不走大模型,直接给客户
     *    false = 拿到tool返回的结果,给大模型,最后由大模型回复
     */
    //必须加描述description,LLM 靠这个判断调用哪个
    @Tool(description = "获取当前时间", returnDirect = false)
    public String getCurrentTime()
    {
        return LocalDateTime.now().toString();
    }
}
  • 描述(Description)就是"咒语" : LLM 决定调不调用你的函数,完全取决于你写的 @Description。如果描述写得模糊(比如只写"获取信息"),模型可能就不知道什么时候该用它。

3.controller类

java 复制代码
import ai.study.utils.DateTimeTools;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;


@RestController
public class ToolCallingController
{
    @Resource
    private ChatModel chatModel;

    @GetMapping("/toolcall/chat")
    public String chat(@RequestParam(name = "msg",defaultValue = "你是谁,现在几点了") String msg)
    {
        // 1.工具注册到工具集合里
        //这是把你的 Java 对象"翻译"成大模型能理解的 JSON Schema 格式
        ToolCallback[] tools = ToolCallbacks.from(new DateTimeTools());
        // 2.将工具集配置进ChatOptions对象
        ChatOptions options = ToolCallingChatOptions.builder().toolCallbacks(tools).build();
        // 3.构建提示词
        Prompt prompt = new Prompt(msg, options);
        // 4.调用大模型
        return chatModel.call(prompt).getResult().getOutput().getText();
    }
}

结果如下:

十四.MCP

之前的ToolCalling中每个大模型(如DeepSeek、ChatGPT)需要为每个工具单独开发接口(FunctionCalling),导致重复劳动。

而MCP就可以解决上述问题,它提供了一种标准化的方式来连接LLMs需要的上下文,MCP就类似于一个Agent时代的Type-C协议,希望能将不同来源的数据、工具、服务统一起来供大模型调用。更多概念详情,可查看:MCP简单介绍

  • Tool Calling :是 "技能点"。适合简单的、项目内部的、特定的功能扩展。

  • MCP :是 "生态圈"。它试图统一所有 AI 工具的接入标准。

14.1 本地MCP开发步骤

1. MCP-Server服务端的实现

仍然是建Module,然后就是改写pom

XML 复制代码
    <dependencies>
        <!--注意事项(重要)
    spring-ai-starter-mcp-server-webflux不能和<artifactId>spring-boot-starter-web</artifactId>依赖并存,
    否则会使用tomcat启动,而不是netty启动,从而导致mcpserver启动失败,但程序运行是正常的,mcp客户端连接不上。
-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--mcp-server-webflux-->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

写yml

bash 复制代码
# spring.ai.dashscope.api-key=${aliQwen-api}

# ====mcp-server Config=============
spring.ai.mcp.server.type=async
spring.ai.mcp.server.name=customer-define-mcp-server
spring.ai.mcp.server.version=1.0.0

然后就是主启动,接下来就是业务类的编写

a. 天气预报WeatherService服务类

java 复制代码
package ai.study.service;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;

import java.util.Map;

@Service
public class WeatherService
{
    @Tool(description = "根据城市名称获取天气预报")
    public String getWeatherByCity(String city)
    {
        Map<String, String> map = Map.of(
                "北京", "11111降雨频繁,其中今天和后天雨势较强,部分地区有暴雨并伴强对流天气,需注意",
                "上海", "22222多云,15℃~27℃,南风3级,当前温度27℃。",
                "深圳", "333333多云40天,阴16天,雨30天,晴3天"
        );
        return map.getOrDefault(city, "抱歉:未查询到对应城市!");
    }
}

b.ToolCallbackProvider接口配置类

java 复制代码
package ai.study.config;

import ai.study.service.WeatherService;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;



@Configuration
public class McpServerConfig
{
    /**
     * 将工具方法暴露给外部 mcp client 调用
     * @param weatherService
     * @return
     */
    @Bean
    public ToolCallbackProvider weatherTools(WeatherService weatherService)
    {
        return MethodToolCallbackProvider.builder()
                .toolObjects(weatherService)
                .build();
    }
}

在之前的 Tool Calling 里,我们要手动注册工具;而这里的**MethodToolCallbackProvider** 像是一个"雷达探测器",它会自动扫描 WeatherService 中所有带 @Tool 注解的方法,并打包发布。外部 Client 只要一连接,就能立刻看到这些"技能清单"。

2. MCP-Client客户端的实现

客户端的职责是:发现服务端 --------握手连接--------托管给 LLM。

接下来还是新建子模块Module,然后就是改pom

XML 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--spring-ai-alibaba dashscope-->
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
        </dependency>
        <!-- 2.mcp-clent 依赖 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-client</artifactId>
        </dependency>

然后就是写yml: 客户端需要知道 Server 在哪里

bash 复制代码
# ====SpringAIAlibaba Config=============
spring.ai.dashscope.api-key=${aliQwen-api}

# ====mcp-client Config=============
spring.ai.mcp.client.type=async
spring.ai.mcp.client.request-timeout=60s
spring.ai.mcp.client.toolcallback.enabled=true
# 指向你的 MCP Server 地址
spring.ai.mcp.client.sse.connections.mcp-server1.url=http://localhost:8014

mcp-server1 是给这个连接起的别名。你可以配置多个 URL,连接来自全世界的MCP Server

主启动编写完,就是业务类的编写:

  1. LLMConfig并添加tool调用
java 复制代码
package ai.study.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SaaLLMConfig
{
    @Bean
    public ChatClient chatClient(ChatModel chatModel, ToolCallbackProvider tools)
    {
        return ChatClient.builder(chatModel)
                .defaultToolCallbacks(tools.getToolCallbacks())  //mcp协议,配置见yml文件
                .build();
    }
}
  • ToolCallbackProvider tools 是从哪来的? 这就是 MCP Starter 的"魔法"。当你开启了 MCP 客户端,Spring 会自动创建一个名为 tools 的 Bean,里面包含了从远程 8014 服务器抓取到的所有工具。

  • defaultToolCallbacks : 你把这些抓到的工具直接塞进了 ChatClient 的默认配置里。这意味着,只要你用这个 chatClient 说话,它就天生自带了远程服务器上的所有技能。

2.controller

java 复制代码
package ai.study.controller;

import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;



@RestController
public class McpClientController
{
    @Resource
    private ChatClient chatClient;//使用mcp支持

    @Resource
    private ChatModel chatModel;//没有纳入tool支持,普通调用

    // http://localhost:8015/mcpclient/chat?msg=上海
    @GetMapping("/mcpclient/chat")
    public Flux<String> chat(@RequestParam(name = "msg",defaultValue = "北京") String msg)
    {
        System.out.println("使用了mcp");
        return chatClient.prompt(msg).stream().content();
    }

    @RequestMapping("/mcpclient/chat2")
    public Flux<String> chat2(@RequestParam(name = "msg",defaultValue = "北京") String msg)
    {
        System.out.println("未使用mcp");
        return chatModel.stream(msg);
    }
}

此时访问即为:

14.2 远程MCP增强案例-对接互联网通用MCP服务(百度地图)

如果我们想要本地当MCP Client,然后远程的百度地图来MCP Server,那么此时我们先要能到可以调用上万个通用MCP的地方:https://mcp.so/zh

原理说明:

那么在此,我们环境的配置选择的是NodeJS,接着我们应该注册百度地图账号+申请API-key

https://lbsyun.baidu.com/

接下来,我们加入编码开发:

  • 建完module之后,修改pom
XML 复制代码
        <!-- 1.大模型依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
        </dependency>
        <!-- 2.mcp-clent 依赖 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-client</artifactId>
        </dependency>
  • 写yml
bash 复制代码
server.port=8016

# 设置全局编码格式
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
server.servlet.encoding.charset=UTF-8

spring.application.name=springAI-16chat-mcpclient-call-baidumcp

# ====LLM Config=============
spring.ai.dashscope.api-key=${aliQwen-api}


# ====mcp-client Config=============
spring.ai.mcp.client.request-timeout=20s
spring.ai.mcp.client.toolcallback.enabled=true
#
spring.ai.mcp.client.stdio.servers-configuration=classpath:/mcp-server.json5
  • 写mcp-server.json5,放在resources下:
bash 复制代码
{
  "mcpServers":
  {
    "baidu-map":
    {
      "command": "cmd",
      "args": ["/c", "npx", "-y", "@baidumap/mcp-server-baidu-map"],
      "env":  {"BAIDU_MAP_API_KEY": "你猜"}
    }
  }
}

注意此时用json5而不是json的一大原因在于json不能写注释

  • 写主启动启动,此时可以看到后台mcp配置,然后写业务类
  • 业务类中SaaLLMConfig,controller和上节一摸一样,此处就不再演示

此时查询:http://localhost:8016/mcpclient/chat?msg=查询北京39.9042归到位置

十五.SAA生态篇,后续不再更新

具体看上方资源

相关推荐
幸福的猪在江湖3 小时前
🤖 Claude Code 高级完全指南(七):Sub-Agents 与团队协作
aigc·ai编程
tinygone4 小时前
OpenClaw通过ACPX调用Claude Code实现飞书操作CC
人工智能·飞书·ai编程
Hooray4 小时前
AI 时代的管理后台框架,应该是什么样子?
前端·vue.js·ai编程
JavaGuide4 小时前
万字详解 RAG 基础概念:什么是 RAG? 为什么需要?工作原理是?
后端·ai编程
财经资讯数据_灵砚智能5 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(日间)2026年4月6日
大数据·人工智能·python·信息可视化·语言模型·自然语言处理·ai编程
花千树-0105 小时前
Java AI + TTS:让大模型开口说话
java·人工智能·ai·chatgpt·langchain·aigc·ai编程
神三元6 小时前
大模型工具调用输出的 JSON,凭什么能保证不出错?
前端·ai编程
智算菩萨7 小时前
PyCharm版本发展史:从诞生到AI时代的Python IDE演进历程
ide·人工智能·python·pycharm·ai编程
onlooker66667 小时前
Claude code 源码学习
学习·ai编程·claude code