)
十二.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向量数据库RedisStackDeepSeek来实现RAG功能)步骤:1.和以往一样还是建立Module和上一节向量一样加pomyml也类似不过改了模型变成了deepseek然后就是主启动原本的多模型modelclient和model的config类不变2.接下来就是需要重视的业务类的编写了:提供ops.txt脚本放到resources中让他存入向量数据库RedisStack,形成文档知识库版本1:1. 新建一个InitVectorDatabaseConfig类初始化向量数据库对应的数据。即将上述的ops.text中的数据以文本向量化的形式作为向量化数据存储入向量数据库中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.文件转换成向量开启分词 ListDocument list new TokenTextSplitter().transform(textReader.read()); // 3.写入向量数据库Redis,无法去重复版 vectorStore.add(list); }}2.此时建立一个controllerpackage 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?msg00000 * http://localhost:8012/rag4aiops?msgC2222 * param msg * return */ GetMapping(/rag4aiops) public FluxString 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.先加入RedisConfigpackage 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 RedisTemplateString, Object redisTemplate(RedisConnectionFactory redisConnectionFactor) { RedisTemplateString,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:Configuration public class InitVectorDatabaseConfig { Autowired private RedisTemplateString,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. 建子模块Module,改pom(dashscope就行)写yml,主启动和以往一样接下来是业务类2.新建Tool工具类类似于Utils工具类package ai.study.utils; import org.springframework.ai.tool.annotation.Tool; import java.time.LocalDateTime; public class DateTimeTools { /** * 1.定义 function calltool call * 2. returnDirect * true tool直接返回不走大模型直接给客户 * false 拿到tool返回的结果给大模型最后由大模型回复 */ //必须加描述descriptionLLM 靠这个判断调用哪个 Tool(description 获取当前时间, returnDirect false) public String getCurrentTime() { return LocalDateTime.now().toString(); } }描述Description就是“咒语” LLM 决定调不调用你的函数完全取决于你写的Description。如果描述写得模糊比如只写“获取信息”模型可能就不知道什么时候该用它。3.controller类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,然后就是改写pomdependencies !--注意事项重要 spring-ai-starter-mcp-server-webflux不能和artifactIdspring-boot-starter-web/artifactId依赖并存 否则会使用tomcat启动,而不是netty启动从而导致mcpserver启动失败但程序运行是正常的mcp客户端连接不上。 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter/artifactId /dependency !--mcp-server-webflux-- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webflux/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies写yml# spring.ai.dashscope.api-key${aliQwen-api} # mcp-server Config spring.ai.mcp.server.typeasync spring.ai.mcp.server.namecustomer-define-mcp-server spring.ai.mcp.server.version1.0.0然后就是主启动,接下来就是业务类的编写a. 天气预报WeatherService服务类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) { MapString, String map Map.of( 北京, 11111降雨频繁其中今天和后天雨势较强部分地区有暴雨并伴强对流天气需注意, 上海, 22222多云,15℃~27℃,南风3级当前温度27℃。, 深圳, 333333多云40天阴16天雨30天晴3天 ); return map.getOrDefault(city, 抱歉未查询到对应城市); } }b.ToolCallbackProvider接口配置类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,然后就是改pomdependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !--spring-ai-alibaba dashscope-- dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-dashscope/artifactId /dependency !-- 2.mcp-clent 依赖 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId /dependency然后就是写yml: 客户端需要知道 Server 在哪里# SpringAIAlibaba Config spring.ai.dashscope.api-key${aliQwen-api} # mcp-client Config spring.ai.mcp.client.typeasync spring.ai.mcp.client.request-timeout60s spring.ai.mcp.client.toolcallback.enabledtrue # 指向你的 MCP Server 地址 spring.ai.mcp.client.sse.connections.mcp-server1.urlhttp://localhost:8014mcp-server1是给这个连接起的别名。你可以配置多个 URL连接来自全世界的MCP Server主启动编写完就是业务类的编写:1. LLMConfig并添加tool调用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.controllerpackage 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 FluxString chat(RequestParam(name msg,defaultValue 北京) String msg) { System.out.println(使用了mcp); return chatClient.prompt(msg).stream().content(); } RequestMapping(/mcpclient/chat2) public FluxString 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-keyhttps://lbsyun.baidu.com/接下来我们加入编码开发:建完module之后修改pom!-- 1.大模型依赖 -- dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-dashscope/artifactId /dependency !-- 2.mcp-clent 依赖 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId /dependency写ymlserver.port8016 # 设置全局编码格式 server.servlet.encoding.enabledtrue server.servlet.encoding.forcetrue server.servlet.encoding.charsetUTF-8 spring.application.namespringAI-16chat-mcpclient-call-baidumcp # LLM Config spring.ai.dashscope.api-key${aliQwen-api} # mcp-client Config spring.ai.mcp.client.request-timeout20s spring.ai.mcp.client.toolcallback.enabledtrue # spring.ai.mcp.client.stdio.servers-configurationclasspath:/mcp-server.json5写mcp-server.json5,放在resources下:{ mcpServers: { baidu-map: { command: cmd, args: [/c, npx, -y, baidumap/mcp-server-baidu-map], env: {BAIDU_MAP_API_KEY: 你猜} } } }注意此时用json5而不是json的一大原因在于json不能写注释写主启动启动此时可以看到后台mcp配置然后写业务类业务类中SaaLLMConfigcontroller和上节一摸一样此处就不再演示此时查询:http://localhost:8016/mcpclient/chat?msg查询北京39.9042归到位置十五.SAA生态篇后续不再更新具体看上方资源