Spring AI Alibaba 个人学习笔记

一、Spring Al Alibaba之理论概述

https://java2ai.com/docs/1.0.0.2/overview/?spm=5176.29160081.0.0.62682498rm0RnD

1.SAA为什么会出现之AB法则(Before|After)

  • 随着人工智能(AI)技术的迅猛发展,越来越多的开发者开始将目光投向AI应用的开发。

  • 然而,目前市场上大多数AI框架和工具如LangChain、PyTorch等主要支持Python,而Java开发者常常面临工具缺乏和学习门槛较高的问题,

  • 但是不用担心,谁让Java/Spring群体强大那?,O(n_n)O

  • 任何一个框架/XXX云服务器,想要大面积推广,应该不会忘记庞大的Spring社区和Java程序员

(1)Before

(2)After

2. 是什么?

2.1 什么是Spring AI Alibaba?

​ SpringAIAlbaba项目的产生背景是生成式AI与大模型在过去一年的快速发展,大家应该有直观的感受,周边所

有人都在聊模型服务,但是训练大模型本身是少部分企业和算法工程师的职责,我们作为使用方、开发者,更关

注的应该是如何为我们的应用接入生成式AI能力。

​ 对应用来说,最直观的AI模型接入方式就是使用Open API,包括阿里云通义系列模型、OpenAl等都有提供

Open API访问方式,这种方式最灵活、但可想而知对于开发者成本会非常高,我们要理解API规范,还要学习与

AI模型交互的各种模式。如果我们是使用 Spring开发的AI应用,那么我们可以使用RestTemplate这样的工具,

它可以减少我们调用API的成本 ,但对于一些通用的AI应用开发范式,RestTemplate并不能给我们带来什么帮

助。因此,对于Java开发者来说,我们需要一款A应用开发框架简化AI应用开发

​ 在这样的背景下,Spring官方开源了SpringAI框架,用来简化Spring开发者开发智能体应用的过程。随后阿里

巴巴开源了 Spring Al Alibaba,它基于 Spring Al ,同时与阿里云百炼大模型服务通义系列大模型 做了深度集成
与最佳实践 。基于 Spring Al libaba,Java开发者可以非常方便的开发Al 智能体应用

​ 阿里巴巴和Spring官方一直保持着非常成功的合作,在微服务时代共同合作打造了SpringCloudAlibaba微服务框架与整体解决方案,该框架已经是国内使用最广泛的开源微服务框架之一,整体生态star 数超过10w。

2.2 SAA公式化一句话表达

​ SpringAIAlibaba开源项目基于SpringAI构建 ,是阿里云通义系列模型及服务在JavaAI应用开发领域的最佳实践 ,提供高层次的AI API抽象云原生基础设施集成方案企业级AI应用生态集成

2.3 官网知识出处

2.3.1 Spring Ai 官网

https://spring.io/projects/spring-ai#learn

2.3.2 Spring Al Alibaba 1.0 GA 正式发布

https://java2ai.com/

https://java2ai.com/blog/spring-ai-alibaba-10-ga-release/?spm=5176.29160081.0.0.2856aa5cww2t9D

2.3.3阿里云百炼平台

https://bailian.console.aliyun.com/console?tab=model#/model-market ![image-20251111222337300](https://i-blog.csdnimg.cn/img_convert/521af73920480217307d26e011e0099a.png) #### 2.3.4 能干嘛 ![image-20251111222450961](https://i-blog.csdnimg.cn/img_convert/8e95f7ee7aa2b1c70bbaac15077de5e4.png) ​ Spring Al Alibaba**基于Spring Al** 构建,因此SAA**继承了SpringAl 的所有原子能力抽象** 并在此基础上扩充丰富了**模型、向量存储、记忆、RAG**等核心组件适配,让其能够接入阿里云的AI生态。 #### 2.3.5 去哪下? * SpringAI官网:https://spring.io/projects/spring-ai#overview * SpringAI Alibaba官网:https://java2ai.com * SpringAI Alibaba仓库:https://github.com/alibaba/spring-ai-alibaba * SpringAI Alibaba官方示例仓库:https://github.com/springaialibaba/spring-ai-alibaba-examples * Spring AI 1.0 GA 文章:https://java2ai.com/blog/spring-ai-100-ga-released * Spring AI仓库:https://github.com/spring-projects/spring-ai #### 2.3.6怎么玩? [Spring AI Alibaba 可用组件列表与使用指南-阿里云Spring AI Alibaba官网官网](https://java2ai.com/docs/1.0.0.2/tutorials/starters-and-quick-guide/?spm=5176.29160081.0.0.2856aa5coaEidK) ![image-20251111223555456](https://i-blog.csdnimg.cn/img_convert/82bae6ae99d1d3678255cea9885d9005.png) #### 2.3.7 主流Java AI框架选型对比(SpringAI、SpringAI Alibaba、LangChain4J) https://java2ai.com/docs/1.0.0.2/faq/?spm=4347728f.6d9f13c1.0.0.1f317187SMnP6V | 对比维度 | Spring AI Alibaba | Spring AI | LangChain4J | |----------------------|-----------------------------|----------------------|-------------| | **Spring Boot 集成** | 原生支持 | 原生支持 | 社区适配 | | **文本模型** | 主流模型,可扩展 | 主流模型,可扩展 | 主流模型,可扩展 | | **音视频、多模态、向量模型** | 支持 | 支持 | 支持 | | **RAG检索增强生成** | 模块化 RAG | 模块化 RAG | 模块化 RAG | | **向量数据库** | 主流向量数据库 阿里云ADB、OpenSearch等 | 主流向量数据库 | 主流向量数据库 | | **MCP 支持** | 支持 Nacos MCP Registry 支持 | 支持 | 支持 | | **函数调用** | 支持(20+官方工具集成) | 支持 | 支持 | | **提示词模版** | 硬编码,无声明式注解 | 硬编码,无声明式注解 | 声明式注解 | | **提示词管理** | Nacos 配置中心 | 无 | 无 | | **Chat Memory** | 优化版JDBC、Redis、ElasticSearch | JDBC、Neo4j、Cassandra | 多种实现适配 | | **可观测性** | 支持,可接入阿里云ARMS | 支持 | 部分支持 | | **工作流 Workflow** | 支持,兼容 Dify、百炼 DSL | 无 | 无 | | **多智能体 Multi-agent** | 支持,官方通用智能体实现 | 无 | 无 | | **模型评测** | 支持 | 支持 | 支持 | | **社区活跃度与文档健全性** | 官方社区,活跃度高 | 官方社区,活跃度高 | 个人发起社区 | | **开发提效组件** | 丰富,包括调试、代码生成工具等 | 无 | 无 | | **Example 仓库** | 丰富,活跃度高 | 较少 | 丰富,活跃度高 | * **Spring AI Alibaba**:在与 Spring Boot 的集成、向量数据库支持、MCP 支持、函数调用、提示词管理、Chat Memory、可观测性、工作流 Workflow、多智能体 Multi-agent、开发提效组件和 Example 仓库等方面具有明显优势,特别适合使用阿里云服务的项目。 * **Spring AI**:在文本模型、音视频多模态向量模型、RAG、函数调用、模型评测等方面表现良好,但在提示词管理、Chat Memory、可观测性、工作流 Workflow、多智能体 Multi-agent、开发提效组件和 Example 仓库等方面相对较少。 * **LangChain4J**:在文本模型、音视频多模态向量模型、RAG、函数调用、模型评测、Example 仓库等方面表现良好,但在 Spring Boot 集成、提示词管理、Chat Memory、可观测性、工作流 Workflow、多智能体 Multi-agent、开发提效组件等方面相对较少。 ## 二、永远的HelloWorld ### 1.前置约定 #### 1.1 动手前模型约定 | 模型供应商 | 主要特点 | 优势 | 备注 | |----------|------------------------------|-------------------------------|---------------------------------------------------------------------------------------------| | OpenAI | GPT系列(如GPT-4),具备强大的文本生成与理解能力 | 灵活性高,适用于多种应用场景。YYDS 大模型界的事实标准 | 暂停对国服的API服务,需要通过 Azure 接入 | | 阿里百炼 | 提供多种大模型服务(如通义千问系列) | 性能接近GPT-4,API价格较低。支持企业迁移解决方案 | 主要面向企业用户 所有新用户可获得超过5000万Tokens的免费额度及4500张图片生成额度,以鼓励更多企业使用。 | | DeepSeek | 开源大模型,支持多语言 | 推理与编码任务表现优异,社区活跃,支持多样化应用 | 性价比高,输入价格(缓存未命中):1元/百万Tokens 敏感词封号严重 | | 智谱清言 | 基于GLM架构,支持多轮对话与复杂指令处理 | 指令理解能力强,支持多场景下的定制化解决方案 | 模型全面;在国庆月特别活动中,智谱清言宣布用户可以以最低1折调用所有模型,并每位用户将获赠1亿Tokens的额度 | | 硅基流动 | 专注于AI基础设施,提供SiliconCloud平台 | 高效推理,多模态支持,降低使用门槛,提升开发效率 | 主要面向技术开发者。提供了一系列开源大模型的API服务,其中多个开源大模型如Qwen2、GLM4和Yi1.5均为永久免费,这使得开发者可以自由使用这些模型进行应用开发,而无需承担费用 | | Ollama | 支持本地部署,集成多种开源模型,隐私保护优先 | 强调用户隐私和自主性 | 需要较高的硬件配置以支持本地部署 | #### 1.2 SpringAI Alibaba 与 SpringAISpringBoot版本依赖关系 [常见问题解答-阿里云Spring AI Alibaba官网官网](https://java2ai.com/docs/1.0.0.2/faq/?spm=4347728f.6d9f13c1.0.0.17177187POpLHJ#%E6%80%8E%E4%B9%88%E7%A1%AE%E5%AE%9A-spring-ai-alibaba-%E4%B8%8E-spring-aispring-boot-%E7%89%88%E6%9C%AC%E7%9A%84%E5%85%BC%E5%AE%B9%E5%85%B3%E7%B3%BB) ![image-20251111230014462](https://i-blog.csdnimg.cn/img_convert/73156c756cd9452ea996533061f8ed43.png) #### 1.3 配置门道和关键点 ​ 通过后续讲解配置规则,所有调用均基于OpenAl协议标准或者SpringAIAalibaba官方推荐模型服务灵积(DashScope)整合规则,实现一致的接口设计与规范,确保多模型切换的便利性,提供高度可扩展的开发支持 ### 2.阿里云百炼平台入口官网 #### 2.1 接入阿里百炼平台的通义模型 [大模型服务平台百炼控制台](https://bailian.console.aliyun.com/#/home) ![image-20251111230321854](https://i-blog.csdnimg.cn/img_convert/6686557a22c40dcc60365e81f4a729a6.png) #### 2.2 大模型调用三件套 ##### 2.2.1 获得API-key ![image-20251111230543045](https://i-blog.csdnimg.cn/img_convert/aab59beac42f2a78d260959165735a39.png) ##### 2.2.2获得模型名 选择一个模型,点击它 ![image-20251111230732188](https://i-blog.csdnimg.cn/img_convert/371ded30ccb4261b4b8589da670b93d8.png) 模型编号/模型名称 ![image-20251111231004318](https://i-blog.csdnimg.cn/img_convert/0aec40889bfa936c2cb2132fa70830eb.png) 查看API参考 ![image-20251111231228428](https://i-blog.csdnimg.cn/img_convert/79c6037c37ea1d70b981d51cb5ee916d.png) ##### 2.2.3 拿到base_url开发地址 ![image-20251111231410597](https://i-blog.csdnimg.cn/img_convert/8564674483ca01371f4f640e1e84babf.png) #### 2.3小总结 ##### 2.3.1 API key ![image-20251111231918439](https://i-blog.csdnimg.cn/img_convert/d4b1f5e1f9d9768dc4a27930752590d1.png) ##### 2.3.2 模型名 ![image-20251111232018632](https://i-blog.csdnimg.cn/img_convert/b684e23b5f485e3600b053eeb5875e61.png) ##### 2.3.2 调用地址 使用SDK调用时需配置的base_url:`https://dashscope.aliyuncs.com/compatible-mode/v1` 使用HTTP方式调用时需配置的endpoint:`POST https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions` ![image-20251111232134667](https://i-blog.csdnimg.cn/img_convert/d606f5dd78265f7945d0893aa8a476b2.png) ### 3.IDEA工具中建project父工程 #### 3.1 创建SpringAIAlibaba-zsh_test1 ![image-20251112225949418](https://i-blog.csdnimg.cn/img_convert/f19da860982c0b6a53d73be64283bc2a.png) #### 3.2 使用bom管理依赖版本 https://java2ai.com/docs/1.0.0.2/tutorials/starters-and-quick-guide/?spm=5176.29160081.0.0.2856aa5c0l3sEA#%E4%BD%BF%E7%9bom-%E7%AE%A1%E7%90%86%E4%BE%9D%E8%B5%96%E7%89%88%E6%9C%AC%E3%80%81 ![image-20251112230444319](https://i-blog.csdnimg.cn/img_convert/213b31e29694ff3b79dd71cc53285343.png) ![image-20251112230744343](https://i-blog.csdnimg.cn/img_convert/f72ebbed89beeb3a8ffd7488fec23d2c.png) ```xml org.springframework.boot spring-boot-starter-parent 3.5.7 4.0.0 com.zsh.test SpringAIAlibaba-zsh_test1 0.0.1-SNAPSHOT pom SpringAIAlibaba-Maven 父工程 POM 配置 UTF-8 UTF-8 17 17 17 3.5.7 1.0.0 1.0.0.2 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-dependencies ${spring-boot.version} pom import com.alibaba.cloud.ai spring-ai-alibaba-bom ${SpringAIAlibaba.version} pom import org.springframework.ai spring-ai-bom ${spring-ai.version} pom import org.springframework.boot spring-boot-maven-plugin spring-milestones Spring Milestones https://repo.spring.io/milestone false ``` #### 3.3 开发5步骤 ##### 3.3.1 建Module ![image-20251113221822374](https://i-blog.csdnimg.cn/img_convert/4bb154a76d99ab8fd7cf6b630949c58e.png) ![image-20251113222129872](https://i-blog.csdnimg.cn/img_convert/63d7b886eed7f62f926ffb530913162a.png) ##### 3.3.2 改POM ```xml 4.0.0 com.zsh.test SpringAIAlibaba-zsh_test1 0.0.1-SNAPSHOT com.zsh.test SAA-01HelloWorld 0.0.1-SNAPSHOT SAA-01HelloWorld SAA-01HelloWorld org.springframework.boot spring-boot-starter-web com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope ${SpringAIAlibaba.version} org.projectlombok lombok true cn.hutool hutool-all 5.8.22 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin 3.11.0 -parameters 17 17 spring-milestones Spring Milestones https://repo.spring.io/milestone false ``` ###### (1)模型服务灵积(DashScope)/阿里云百炼大模型服务平台 模型服务灵积(DashScope):https://dashscope.aliyun.com/ ![image-20251113224631062](https://i-blog.csdnimg.cn/img_convert/150374e5eb1b25209a233b188b1b47e6.png) 升级后叫做:阿里云百炼大模型服务平台:[阿里云百炼大模型服务平台 - 1T参数量Qwen3-Max-Preview免费体验](https://www.aliyun.com/product/bailian?spm=5176.28630291.0.0.24bf7eb5XZJsAk) ![image-20251113224756139](https://i-blog.csdnimg.cn/img_convert/47a3b1b4f1aac8f5e2be3a5db155ff78.png) ###### (2)核心组件-知识出处 [Spring AI Alibaba 可用组件列表与使用指南-阿里云Spring AI Alibaba官网官网](https://java2ai.com/docs/1.0.0.2/tutorials/starters-and-quick-guide/?spm=5176.29160081.0.0.2856aa5c0l3sEA#%E4%BD%BF%E7%9bom-%E7%AE%A1%E7%90%86%E4%BE%9D%E8%B5%96%E7%89%88%E6%9C%AC%E3%80%81) ![image-20251113225009073](https://i-blog.csdnimg.cn/img_convert/24ae3449d8982cf9e192027f2610511f.png) ##### 3.3.3 写yml 俩种写法: ```properties server.port=8001 #大模型对话中文乱码UTF8编码处理 server.servlet.encoding.enabled=true server.servlet.encoding.force=true server.servlet.encoding.charset=UTF-8 spring.application.name=SAA-01HelloWorld # ====SpringAIAlibaba Config============= spring.ai.dashscope.api-key=${aliQwen-api} spring.ai.dashscope.base-url=https://dashscope.aliyuncs.com/compatible-mode/v1 spring.ai.dashscope.chat.options.model=qwen-plus ``` ```yml server: port: 8001 spring: application: name: SAA-01HelloWorld # 大模型对话中文乱码UTF8编码处理 servlet: encoding: enabled: true force: true charset: UTF-8 # ====SpringAIAlibaba Config============= ai: dashscope: api-key: ${aliQwen-api} base-url: https://dashscope.aliyuncs.com/compatible-mode/v1 chat: options: model: qwen-plus ``` ##### 3.3.4 主启动 ```java package com.zsh.test.saa01helloworld; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Saa01HelloWorldApplication { public static void main(String[] args) { SpringApplication.run(Saa01HelloWorldApplication.class, args); } } ``` ##### 3.3.5 业务类 ###### (1)ApiKey不可以明文-需配置进环境变量 修改环境变量,K-V键值对设置,重启IDEA ![image-20251113230623158](https://i-blog.csdnimg.cn/img_convert/b0b444e7335cedc6957039234621e531.png) ![image-20251113230551331](https://i-blog.csdnimg.cn/img_convert/cf7e02893c93c546bb124de4ca2f3821.png) ###### (2)配置类SaaLLMConfig ```java package com.zsh.test.config;/** * @author ZhaoShuhao * @data 2025/11/13 23:12 */ import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @description: * @author: zhaoshuhao * @date: 2025/11/13 */ @Configuration public class SaaLLMConfig { /*方式1 1.1 yml文件配置:spring.ai.dashscope.api-key=${aliQwen-api} 1.2 @Value("${spring.ai.dashscope.api-key}") private String apiKey;、 1.3 @Bean public DashScopeApi dashScopeApi() { return DashScopeApi.builder().apiKey(apiKey).build(); } */ /** * 方式2 * yml文件配置:spring.ai.dashscope.api-key=${aliQwen-api} * @return */ @Bean public DashScopeApi dashScopeApi() { return DashScopeApi.builder() .apiKey(System.getenv("aliQwen-api")) .build(); } } ``` 方式一: ```java package com.zsh.test.config;/** * @author ZhaoShuhao * @data 2025/11/13 23:12 */ import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @description: * @author: zhaoshuhao * @date: 2025/11/13 */ @Configuration public class SaaLLMConfig { /** * 方式1:${} * 持有yml文件配置:spring.ai.dashscope.api-key=${aliQwen-api} */ @Value("${spring.ai.dashscope.api-key}") private String apiKey; @Bean public DashScopeApi dashScopeApi() { return DashScopeApi.builder().apiKey(apiKey).build(); } } ``` 方式二: ```java package com.zsh.test.config;/** * @author ZhaoShuhao * @data 2025/11/13 23:12 */ import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @description: * @author: zhaoshuhao * @date: 2025/11/13 */ @Configuration public class SaaLLMConfig { /** * 方式2:System.getenv("环境变量") * 持有yml文件配置:spring.ai.dashscope.api-key=${aliQwen-api} * @return */ @Bean public DashScopeApi dashScopeApi() { return DashScopeApi.builder() .apiKey(System.getenv("aliQwen-api")) .build(); } } ``` ###### (3)对话模型(Chat Model) * ChatModel,文本聊天交互模型 * 知识出处:[对话模型(Chat Model)-阿里云Spring AI Alibaba官网官网](https://java2ai.com/docs/1.0.0.2/tutorials/basics/chat-model/?spm=5176.29160081.0.0.2856aa5ctpxysy) ![image-20251113232955560](https://i-blog.csdnimg.cn/img_convert/f5f36441fd76c006e195f7cb805529f9.png) * Controller ```java package com.zsh.test.Controller;/** * @author ZhaoShuhao * @data 2025/11/13 23:25 */ import jakarta.annotation.Resource; import org.springframework.ai.chat.model.ChatModel; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; /** * @description: * @author: zhaoshuhao * @date: 2025/11/13 */ @RestController public class ChatHelloController { @Resource //阿里云百炼 private ChatModel dashScopeChatModel; /** * http://localhost:8001/hello/dochat * @param msg * @return */ @GetMapping("/hello/dochat") public String doChat(@RequestParam(name = "msg",defaultValue = "你是谁") String msg) { String result = dashScopeChatModel.call(msg); System.out.println("响应:" + result); return result; } /** * http://localhost:8001/hello/streamchat * @param msg * @return */ @GetMapping(value = "/hello/streamchat", produces = "text/plain;charset=UTF-8") public Flux streamChat(@RequestParam(name = "msg", defaultValue = "你是谁") String msg) { return dashScopeChatModel.stream(msg); } } ``` ###### (4)测试 * 普通对话:http://localhost:8001/hello/dochat ![image-20251113233657119](https://i-blog.csdnimg.cn/img_convert/5db0289501a9aaec761923b117e7c78f.png) ![image-20251113233518050](https://i-blog.csdnimg.cn/img_convert/103bee99dab7eb834f45b31b3f47f449.png) * 响应式FluxAPI流式对话:http://localhost:8001/hello/streamchat ![image-20251113233714556](https://i-blog.csdnimg.cn/img_convert/d25df858433dbd146bd7cd332b79f69b.png) ![image-20251113233535564](https://i-blog.csdnimg.cn/img_convert/417182b71c83042b23328c6b281cc9a7.png) ##### 3.3.6 问题思考 ###### (1)切换其他大模型 [大模型服务平台百炼控制台](https://bailian.console.aliyun.com/console?tab=model#/model-market/all) ![image-20251113235525599](https://i-blog.csdnimg.cn/img_convert/74227fb25a670d6f9e15be5c0f149224.png) ```yml server: port: 8001 spring: application: name: SAA-01HelloWorld # 大模型对话中文乱码UTF8编码处理 servlet: encoding: enabled: true force: true charset: UTF-8 # ====SpringAIAlibaba Config============= ai: dashscope: api-key: ${aliQwen-api} base-url: https://dashscope.aliyuncs.com/compatible-mode/v1 chat: options: # model: qwen-plus model: deepseek-v3 ``` ![image-20251114000229766](https://i-blog.csdnimg.cn/img_convert/a024020d84855e4a9f429ac8a39f0fe3.png) ###### (2)SAA和OpenAI协议对比 ![image-20251114000021675](https://i-blog.csdnimg.cn/img_convert/5ab4b30bcff9517d9d5867855d108108.png) ## 三、Ollama私有化部署和对接本地大模型 ### 3.1 Ollama本地大模型部署 #### 3.1.1 LLM大模型工具OIlama ##### (1)是什么? * Docker Hub玩镜像 * OllamaHub玩模型 官网:https://ollama.com/ ![image-20251114193011876](https://i-blog.csdnimg.cn/img_convert/38915514a1f0e83ab615fa8720b524af.png) ​ Ollama是一个功能强大的开源框架,旨在简化在Docker容器中部署和管理大型语言模型(LLM)的过程。它帮助用户快速在本地运行大模型,通过简单的安装指令,用户可以执行一条命令就在本地运行开源大型语言模型,如Llama 2。 ​ Ollama极大地简化了在Docker容器内部署和管理LLM的过程,它优化了设置和配置细节,包括GPU使用情况,并 将模型权重、配置和数据捆绑到一个包中,定义成Modelfile。此外,Ollama还提供了多种大型语言模型的开源仓 库,用户可以通过简单的命令行操作来下载和运行这些模型。 ​ 总的来说,Ollama是一个用于在本地高效运行大型语言模型的工具,为开发者和研究人员提供了极大的便利。 ##### (2)能干嘛? 产品定位: ![image-20251114193226829](https://i-blog.csdnimg.cn/img_convert/9f342b94db4e24c6f79e121d86b73d4f.png) ##### (3)去哪下? https://ollama.com/download ![image-20251114193554676](https://i-blog.csdnimg.cn/img_convert/fb977788a89356b076366d3be1d37dfb.png) ##### (4)怎么玩? ![image-20251114193703197](https://i-blog.csdnimg.cn/img_convert/38f830b9a2d1bcfca0d2cf39dc7b9958.png) #### 3.1.2安装Ollama * 自定义Ollama安装路径:OllamaSetup.exe /DIR=D:\\SoftWareApp\\Ollama * 然后Ollama就会进入安装,点击Install后,可以看到Ollama的安装路径就变成了我们指定的目录 了,这样大模型数据包也会默认下载在指定目录中。 ![image-20251117231713751](https://i-blog.csdnimg.cn/img_convert/4455436e25363a4b0352ef483545ce4b.png) ![image-20251117231953874](https://i-blog.csdnimg.cn/img_convert/a76abde737137380ffe59f8143385e2a.png) * 手动创建大模型存储目录 * 新建环境变量 ![image-20251117232623307](https://i-blog.csdnimg.cn/img_convert/a68bfd381304d7ebe0553e7a6b834038.png) * 复制转移大模型存储目录 ![image-20251117233834100](https://i-blog.csdnimg.cn/img_convert/d74b1f4618638ae35a9675511df5c9e3.png) #### 3.1.3安装通义千问大模型 * 验证是否安装成功: * ```shell netstat -ano |findstr 11434 ``` * ```shell ollama --version ``` ![image-20251118222338685](https://i-blog.csdnimg.cn/img_convert/591797ed476c984cd7b82be0b4f0db53.png) * 千问模型为例:ollama run qwen3:4b > 说明:Ollama的运行会受到所使用模型大小的影响; > > 1、例如,运行一个7B(70亿参数)的模型至少需要8GB的可用内存(RAM而运行一个13B(130亿参数》的模型需要16GB的内存,33B(330亿参数》的模型需要32GB的内存; > > 2、需要考虑有足够的磁盘空间,大模型的文件大小可能比较大,建议至少为Ollama和其模型预留50GB的磁盘空间: > > 3、性能较高的CPU可以提供更好的运算速度和效率,多核处理器能够更好地处理并行任务,选择具有足够核心数的CPU; > > 4、显卡 (GPU):Ollama支持纯CPU运行,但如果电脑配备了NVIDIA GPU,可以利用GPU进行加速,提高模型的运行速度和性能; ![image-20251118222846390](https://i-blog.csdnimg.cn/img_convert/96d3ce18e09fd8fba325f674eb14656e.png) * 查看模型列表:ollama list * ![image-20251118222644580](https://i-blog.csdnimg.cn/img_convert/605444eeb54e795f6a8aa2fddb2d88cf.png) * deepseek:ollama run deepseek-r1:8b * ![image-20251118223116942](https://i-blog.csdnimg.cn/img_convert/54d3b275a916c6be18ffbc6b9f7d19bc.png) * 结束对话:Use **Ctrl + d** or **/bye** to exit. ### 3.2 微服务对接本地大模型 #### 3.2.1 建module(SAA-02-Ollama) ![image-20251118224008394](https://i-blog.csdnimg.cn/img_convert/0307a9b1050e8c652c93851dbc7c1a1b.png) ##### (1)改pom ```xml 4.0.0 com.zsh.test SpringAIAlibaba-zsh_test1 0.0.1-SNAPSHOT com.zsh.test SAA-02-Ollama 0.0.1-SNAPSHOT SAA-02-Ollama SAA-02-Ollama org.springframework.boot spring-boot-starter-web com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope ${SpringAIAlibaba.version} org.springframework.ai spring-ai-starter-model-ollama 1.0.0 org.projectlombok lombok true cn.hutool hutool-all 5.8.22 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin 3.11.0 -parameters 17 17 spring-milestones Spring Milestones https://repo.spring.io/milestone false ``` ##### (2)写yml application.properties ```properties spring.application.name=SAA-02-Ollama server.port=8002 server.servlet.encoding.enabled=true server.servlet.encoding.force=true server.servlet.encoding.charset=UTF-8 # ====ollama Config============= spring.ai.dashscope.api-key=${aliQwen-api} spring.ai.ollama.base-url=http://localhost:11434 spring.ai.ollama.chat.model=qwen3:4b ``` application.yml ```yml server: port: 8002 spring: application: name: SAA-02-Ollama servlet: encoding: enabled: true force: true charset: UTF-8 # ====ollama Config============= ai: dashscope: api-key: ${aliQwen-api} ollama: base-url: http://localhost:11434 chat: model: qwen3:4b ``` 选一个即可 ##### (3)主启动 ```java package com.zsh.test.saa02ollama; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Saa02OllamaApplication { public static void main(String[] args) { SpringApplication.run(Saa02OllamaApplication.class, args); } } ``` #### 3.2.2 业务类 ##### (1)controller ```java package com.zsh.test.saa02ollama.controller;/** * @author ZhaoShuhao * @data 2025/11/18 23:16 */ import jakarta.annotation.Resource; import org.springframework.ai.chat.model.ChatModel; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; /** * @description: * @author: zhaoshuhao * @date: 2025/11/18 */ @RestController public class OllamaController { /*@Resource(name = "ollamaChatModel") private ChatModel chatModel;*/ //方式2 @Resource @Qualifier("ollamaChatModel") private ChatModel chatModel; /** * http://localhost:8002/ollama/chat?msg=你是谁 * @param msg * @return */ @GetMapping("/ollama/chat") public String chat(@RequestParam(name = "msg") String msg) { String result = chatModel.call(msg); System.out.println("---结果:" + result); return result; } @GetMapping(value ="/ollama/streamchat", produces = "text/plain;charset=UTF-8") public Flux streamchat(@RequestParam(name = "msg",defaultValue = "你是谁") String msg) { return chatModel.stream(msg); } } ``` ##### (2)结果展示 ![image-20251118233759226](https://i-blog.csdnimg.cn/img_convert/dc5bfcb1c07889644bf9a721474b75ab.png) ![image-20251118234701081](https://i-blog.csdnimg.cn/img_convert/30c89af13b770496b7f59c115ae04b14.png) ## 四、ChatClient VS ChatModel 想象一下你要点一杯咖啡: * **ChatModel** = 咖啡机本身(你直接操作机器) * **ChatClient** = 咖啡店点单系统(你告诉店员"我要一杯拿铁",店员帮你搞定所有细节) | 特性 | ChatModel | ChatClient | |----------|-------------|---------------| | **层级** | 底层接口 | 高层API、服务层 | | **复杂度** | 低(直接操作) | 高(封装了复杂逻辑) | | **代码量** | 多(需要自己处理细节) | 少(Fluent API) | | **高级功能** | 不支持 | 支持(RAG、聊天记忆等) | | **适用场景** | 简单交互 | 复杂AI应用 | | **学习曲线** | 简单 | 稍高(但值得) | ### 4.1 问题回顾 #### 4.1.1 之前的调用都是使用ChatModel进行 ```java public interface ChatModel extends Model,StreamingChatModel ``` #### 4.1.2 认识一个新的接口ChatClient ![image-20251123230718196](https://i-blog.csdnimg.cn/img_convert/d1652bc8f6ac53474d7daf397e0d8d28.png) * Chatclert提供了与AI模型通信的FluentAPI,它支持同步和反应式(Reactive》编程模型。与ChatModel、Message、ChatMemory等原子API相比,使用ChatCliert可以将与LLM及其他组件交互的复杂性隐藏在背后,因为基于LLM的应用程序通常要多个组件协同工作(例如,提示词模板、聊天记忆、LLMModel、输出解析器、RAG组件:嵌入模型和存储),并且通常涉及多个交互,因此协调它们会让编码变得繁琐。当然使用ChatMode等原子API可以为应用程序带来更多的灵活性,成本就是您需要编写大量样板代码。 * ChatClient类似于应用程序开发中的服务层,它为应用程序直接提供AI服务,开发者可以使用ChatClientFluent API快速完成一整套AI交互流程的组装。 * 包括一些基础功能,如: * 定制和组装模型的输入(Prompt) * 格式化解析模型的输出(Structured Output) * 调整模型交互参数(ChatOptions) * 还支持更多高级功能: * 聊天记忆(Chat Memory) * 工具/函数调用(Function Calling) * RAG ### 4.2 ChatModel(对话模型) #### 4.2.1 官网 https://java2ai.com/docs/1.0.0.2/tutorials/basics/chat-model/?spm=4347728f.13601988.0.0.654f6001MkbUyJ https://java2ai.com/ecosystem/spring-ai/reference/chat-model * 对话模型(Chat Model)接收一系列消息(Message)作为输入,与模型LLM服务进行交互,并接收返回的聊天消息(ChatMessage)作为输出。相比于普通的程序输入,模型的输入与输出消息(Message)不止支持纯字符文本,还支持包括语音、图片、视频等作为输入输出。同时,在SpringAlAlibaba中,消息中还支持包含不同的角色,帮助底层模型区分来自模型、用户和系统指令等的不同消息。 * SpringAlAlibaba复用了SpringAl抽象的Model APl,并与通义系列大模型服务进行适配(如通义千问、通义万相等),目前支持纯文本聊天、文生图、文生语音、语音转文本等。以下是框架定义的几个 * 核心API: * ChatModel,文本聊天交互模型,支持纯文本格式作为输入,并将模型的输出以格式化文本形式返回。 * mageModel,接收用户文本输入,并将模型生成的图片作为输出返回。 * AudioModel,接收用户文本输入,并将模型合成的语音作为输出返回。 * Spring Al Alibaba支持以上Model抽象与通义系列模型的适配,并通过`spring-ai-alibaba-starter` AutoConfiguration自动初始化了默认实例,因此我们可以在应用程序中直接注入ChatModel、ImageModel等bean,当然在需要的时候也可以自定义Model实例。 #### 4.2.2 说明 * 对话模型(ChatModel)是底层接口,直接与具体大语言模型交互, * 提供call()和stream()方法,适合简单大模型交互场景 ### 4.3 ChatClient() #### 4.3.1 官网 https://java2ai.com/docs/1.0.0.2/tutorials/basics/chat-client/?spm=4347728f.13601988.0.0.654f6001MkbUyJ https://java2ai.com/ecosystem/spring-ai/reference/chat-client * ChatClient提供了与AI模型通信的Fluent API,它支持同步和反应式(Reactive)编程模型。与ChatModel、Message、ChatMemory等原子API相比,使用ChatClient可以将与 LLM及其他组件交互的复杂性隐藏在背后,因为基于LLM的应用程序通常要多个组件协同工作(例如,提示词模板、聊天记忆、LLM Model、输出解析器、RAG组件:嵌入模型和存储),并且通常涉及多个交互,因此协调它们会让编码变得繁琐。当然使用ChatModel等原子API可以为应用程序带来更多的灵活性,成本就是您需要编写大量样板代码。 * ChatClient类似于应用程序开发中的服务层,它为应用程序直接提供AI服务,开发者可以使用ChatClient Fluent API快速完成一整套Al交互流程的组装。 * 包括一些基础功能,如: * 定制和组装模型的输入(Prompt) * 格式化解析模型的输出(Structured Output) * 调整模型交互参数(ChatOptions) * 还支持更多高级功能: * 聊天记忆(Chat Memory) * 工具/函数调用(Function Calling) * RAG #### 4.3.2 样板代码?ChatClient对ChatModel吐槽 ![image-20251123232847784](https://i-blog.csdnimg.cn/img_convert/dc4ad3b4ba5ef5ebae48e0cd00538a1b.png) #### 4.3.3 说明 * ChatClient是高级封装 * 基于ChatModel构建 * 勾建标准化复杂AI服务 * 支持同步和流式交互,集成多种高级功能。 ### 4.4 编码案例 #### 4.4.1 建立module ##### (1)创建Module:SAA-03-ChatModel-ChatClient ![image-20251123233331321](https://i-blog.csdnimg.cn/img_convert/423513d59715f9aac959efd76c40c69b.png) ##### (2)改POM ```xml 4.0.0 com.zsh.test SpringAIAlibaba-zsh_test1 0.0.1-SNAPSHOT com.zsh.test SAA-03-ChatModel-ChatClient 0.0.1-SNAPSHOT SAA-03-ChatModel-ChatClient SAA-03-ChatModel-ChatClient org.springframework.boot spring-boot-starter-web com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope ${SpringAIAlibaba.version} org.projectlombok lombok true cn.hutool hutool-all 5.8.22 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin 3.11.0 -parameters 17 17 spring-milestones Spring Milestones https://repo.spring.io/milestone false ``` ##### (3)写yml ```yaml server: port: 8003 servlet: encoding: enabled: true force: true charset: UTF-8 spring: application: name: SAA-03ChatModelChatClient ai: dashscope: api-key: ${aliQwen-api} ``` ##### (4)主启动 ```java package com.zsh.test.saa03chatmodelchatclient; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Saa03ChatModelChatClientApplication { public static void main(String[] args) { SpringApplication.run(Saa03ChatModelChatClientApplication.class, args); } } ``` #### 4.4.2 业务类(一版:只有ChatModel) ##### (1)新建配置类SaaLLMConfig ```java package com.zsh.test.saa03chatmodelchatclient.config; import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** /** * @description: * @author: zhaoshuhao * @date: 2025/11/24 */ @Configuration public class SaaLLMConfig { @Bean public DashScopeApi dashScopeApi() { return DashScopeApi.builder() .apiKey(System.getenv("aliQwen-api")) .build(); } } ``` ##### (2)controller ```java package com.zsh.test.saa03chatmodelchatclient.controller; import jakarta.annotation.Resource; import org.springframework.ai.chat.model.ChatModel; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * @author ZhaoShuhao * @data 2025/11/24 21:53 */ @RestController public class ChatModelController { @Resource //阿里云百炼 private ChatModel dashScopeChatModel; @GetMapping("/chatmodel/dochat") public String doChat(@RequestParam(name = "msg",defaultValue = "你是谁") String msg) { String result = dashScopeChatModel.call(msg); System.out.println("响应:" + result); return result; } } ``` ##### (3)新增ChatClient进行注入(不支持自动装配) ```java package com.zsh.test.saa03chatmodelchatclient.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.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * @author ZhaoShuhao * @data 2025/11/24 21:53 */ @RestController public class ChatModelController { @Resource //阿里云百炼 private ChatModel dashScopeChatModel; @Resource private ChatClient charClient; @GetMapping("/chatmodel/dochat") public String doChat(@RequestParam(name = "msg",defaultValue = "你是谁") String msg) { String result = dashScopeChatModel.call(msg); System.out.println("响应:" + result); return result; } } ``` 但是出现异常: ![image-20251124215738920](https://i-blog.csdnimg.cn/img_convert/9b1eea8b722e8e64dfbc1f162cf6e463.png) **结论:ChatClient不支持自动注入,只能手动注入,《(T。T)/\~\~** #### 4.4.3 业务类(二版:只有ChatClient) ##### (1)知识出处 chat源码:https://java2ai.com/docs/1.0.0.2/spring-ai-sourcecode-explained/chapter-1-chat-first-experience/ ![image-20251124221440965](https://i-blog.csdnimg.cn/img_convert/88ffb9523bde5fd109a6dcc6226d7eef.png) ChatClient使用:https://java2ai.com/docs/1.0.0.2/tutorials/basics/chat-client/ ![image-20251124221531159](https://i-blog.csdnimg.cn/img_convert/c61146f9d6fc9f66d9c182a9ea0c8057.png) ##### (2)新建ChatClientController ChatClient也是基于ChatModel的 ```java package com.zsh.test.saa03chatmodelchatclient.controller; 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.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * @author ZhaoShuhao * @data 2025/11/24 22:16 */ @RestController public class ChatClientController { private final ChatClient dashScopechatClient; /** * 使用自动配置的 ChatClient.Builder * @param dashscopeChatModel */ public ChatClientController(ChatModel dashscopeChatModel) { this.dashScopechatClient = ChatClient.builder(dashscopeChatModel).build(); } /** * http://localhost:8003/chatclient/dochat * @param msg * @return */ @GetMapping("/chatclient/dochat") public String doChat(@RequestParam(name = "msg",defaultValue = "2加4等于几") String msg) { String result = dashScopechatClient.prompt().user(msg).call().content(); System.out.println("响应:" + result); return result; } } ``` #### 4.4.3 业务类(三版:ChatModel+ChatClient) ##### (1)修改配置类SaaLLMConfig ```java package com.zsh.test.saa03chatmodelchatclient.config; import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.context.annotation.Bean; /** /** * @description: * @author: zhaoshuhao * @date: 2025/11/24 */ @Configuration public class SaaLLMConfig { @Bean public DashScopeApi dashScopeApi() { return DashScopeApi.builder() .apiKey(System.getenv("aliQwen-api")) .build(); } /** * 知识出处: * https://java2ai.com/docs/1.0.0.2/tutorials/basics/chat-client/?spm=5176.29160081.0.0.2856aa5cmUTyXC#%E5%88%9B%E5%BB%BA-chatclient * @param dashscopeChatModel * @return */ @Bean public ChatClient chatClient(ChatModel dashscopeChatModel) { return ChatClient.builder(dashscopeChatModel).build(); } } ``` ##### (2)新建chatClientControllerV2 ```java package com.zsh.test.saa03chatmodelchatclient.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.RequestParam; /** * @author ZhaoShuhao * @data 2025/11/24 22:24 */ public class ChatClientControllerV2 { /** * chatModel + ChatClient 混合使用 */ @Resource private ChatModel chatModel; @Resource private ChatClient dashScopechatClientv2; /** * http://localhost:8003/chatclientv2/dochat * @param msg * @return */ @GetMapping("/chatclientv2/dochat") public String doChat(@RequestParam(name = "msg",defaultValue = "你是谁") String msg) { String result = dashScopechatClientv2.prompt().user(msg).call().content(); System.out.println("ChatClient响应:" + result); return result; } /** * http://localhost:8003/chatmodelv2/dochat * @param msg * @return */ @GetMapping("/chatmodelv2/dochat") public String doChat2(@RequestParam(name = "msg",defaultValue = "你是谁") String msg) { String result = chatModel.call(msg); System.out.println("ChatModel响应:" + result); return result; } } ``` #### 4.4.4 小总结 ##### (1)生产推荐 * 混合使用 * 两者不是非此即彼,可以同时出现交替使用 ##### (2)对比 | 对比项 | ChatModel | ChatClient | |-------|--------------------------|-----------------------------------------------------------| | 注入形式 | 自动注入 | 手动注入且依赖ChatModel,使用自动配置的 ChatClient.Builder | | 调用方式 | 直接调用call() 或者 stream()方法 | 链式调用,支持同步和反应式(Reactive)编程模型, 自动封装提示词和解析响应 | | 结构化输出 | 需手动解析响应文本 | 支持自动映射为Java对象 | | 适用场景 | 实现简单功能和场景 | 快速开发复杂功能的场景,如企业级智能XXX问答系统 | | 功能扩展 | 偏弱 | 强,支持聊天记忆(Chat Memory)/工具Tool/函数调用(Function Calling)/RAG等等 | ### 4.5 问题思考 要求同时存在多种大模型产品在系统里共存使用,该怎么操作? ## 五、Server-SentEvents(SSE)实现Stream流式输出及多模型共存 ### 5.1 ResponseStreaming流式输出 #### 5.1.1 是什么 * 流式输出(StreamingOutput) * 是一种逐步返回大模型生成结果的技术,生成一点返回一点,允许服务器将响应内容 * 分批次实时传输给客户端,而不是等待全部内容生成完毕后再一次性返回。 * 这种机制能显著提升用户体验,尤其适用于大模型响应较慢的场景(如生成长文本或复杂推理结果)。 #### 5.1.2 SpringAlAlibaba流式输出有两种 * 通过ChatModel实现stream实现流式输出 * 通过ChatClient实现stream实现流式输出 #### 5.1.3 前置知识点说明---Springboot3响应式编程 B站教学视频:https://www.bilibili.com/video/BV1Es4y1q7Bf?spm_id_from=333.788.videopod.episodes\&vd_source=f3f60f7acbef49d38b97c4d660d439fc\&p=110 ![image-20251126174613223](https://i-blog.csdnimg.cn/img_convert/e942ceec3d5742b45cebf76304cc31e0.png) 响应式编程官网介绍:https://projectreactor.io/docs/core/release/reference/#intro-reactive ![image-20251126174627904](https://i-blog.csdnimg.cn/img_convert/a87cfcb69821b9bfd7e6e0bdd7deeb03.png) ### 5.2 SSE(Server-SentEvents)服务器发送事件 * Server-Sent:由服务器发送。 * Events:事件,指服务器主动推送给客户端的数据或消息 * Server-SentEvents(SSE)服务器发送事件,实现流式输出 * 总结:Server-SentEvents(SSE),是一种让服务器能够主动、持续地向客户端(比如你的网页浏览器)推送数据的技术 > * Server-Sent Events (SSE) 是一种允许服务端可以持续推送数据片段(如逐词或逐句)到前端的 Web 技术。通过单向的HTTP长连接,使用一个长期存在的连接,让服务器可以主动将数据"推"给客户端,SSE是轻量级的单向通信协议,适合AI对话这类服务端主导的场景 > > * 核心概念:""SE 的核心思想是:客户端发起一个请求,服务器保持这个连接打开并在有新数据时,通过这个连接将数据发送给客户端。这与传统的请求-响应模式(客户端请求一次,服务器响应一次,连接关闭)有本质区别。SSE下一代(Stream able Http) | 特性 | Server-Sent Events (SSE) | WebSocket | |-------|--------------------------|---------------------------| | 通信方向 | 单向(服务器 -\> 客户端) | 双向 | | 协议 | 基于 HTTP | 独立的 `ws://` 或 `wss://` 协议 | | 数据类型 | 文本 | 文本和二进制 | | 复杂性 | 简单 | 相对复杂 | | 连接开销 | 较低 | 较高 | | 自动重连 | 是 | 需要手动实现 | | 浏览器支持 | 广泛支持(除 IE) | 广泛支持 | ### 5.3 开发步骤 要求同时存在多种大模型产品在系统里共存使用 #### 5.3.1 新建子模块Module ##### (1)SAA-04StreamingOutput ![image-20251126225921877](https://i-blog.csdnimg.cn/img_convert/a734a6ec49fa9f5eb2270848d72902fa.png) ##### (2)改POM ```xml 4.0.0 com.zsh.test SpringAIAlibaba-zsh_test1 0.0.1-SNAPSHOT com.zsh.test SAA-04StreamingOutput 0.0.1-SNAPSHOT SAA-04StreamingOutput SAA-04StreamingOutput org.springframework.boot spring-boot-starter-web com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope ${SpringAIAlibaba.version} org.projectlombok lombok 1.18.38 cn.hutool hutool-all 5.8.22 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin 3.11.0 -parameters 17 17 spring-milestones Spring Milestones https://repo.spring.io/milestone false ``` ##### (3)改YML ```java server: port: 8004 servlet: encoding: enabled: true force: true charset: UTF-8 spring: application: name: SAA-04StreamingOutput ai: dashscope: api-key: ${aliQwen-api} ``` ##### (4)主启动 ```java package com.zsh.test.saa04; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Saa04StreamingOutputApplication { public static void main(String[] args) { SpringApplication.run(Saa04StreamingOutputApplication.class, args); } } ``` #### 5.3.2 业务类 ##### (1)通过ChatModel实现stream实现流式输出 * 配置类LLMConfig ```java package com.zsh.test.saa04.config; import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; import org.springframework.ai.chat.model.ChatModel; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author ZhaoShuhao * @data 2025/11/26 23:05 */ @Configuration public class SaaLLMConfig { // 模型名称常量定义 private final String DEEPSEEK_MODEL = "deepseek-v3"; private final String QWEN_MODEL = "qwen-plus"; @Bean(name = "deepseek") public ChatModel deepSeek() { return DashScopeChatModel.builder() .dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv("aliQwen-api")) .build()) .defaultOptions( DashScopeChatOptions.builder().withModel(DEEPSEEK_MODEL).build() ) .build(); } @Bean(name = "qwen") public ChatModel qwen() { return DashScopeChatModel.builder().dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv("aliQwen-api")) .build()) .defaultOptions( DashScopeChatOptions.builder() .withModel(QWEN_MODEL) .build() ) .build(); } } ``` * controller第1版 ```java package com.zsh.test.saa04.controller; import jakarta.annotation.Resource; import org.springframework.ai.chat.model.ChatModel; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; /** * @author ZhaoShuhao * @data 2025/11/26 23:06 */ @RestController public class StreamOutputController { //V1 通过ChatModel实现stream实现流式输出 @Resource(name = "deepseek") private ChatModel deepseekChatModel; @Resource(name = "qwen") private ChatModel qwenChatModel; @GetMapping(value = "/stream/chatflux1") public Flux chatflux(@RequestParam(name = "question",defaultValue = "你是谁") String question) { return deepseekChatModel.stream(question); } @GetMapping(value = "/stream/chatflux2") public Flux chatflux2(@RequestParam(name = "question",defaultValue = "你是谁") String question) { return qwenChatModel.stream(question); } } ``` ##### (2)通过ChatClient实现stream实现流式输出 * 配置类LLMConfig ```java package com.zsh.test.saa04.config; import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author ZhaoShuhao * @data 2025/11/26 23:05 */ @Configuration public class SaaLLMConfig { // 模型名称常量定义 private final String DEEPSEEK_MODEL = "deepseek-v3"; private final String QWEN_MODEL = "qwen-plus"; @Bean(name = "deepseek") public ChatModel deepSeek() { return DashScopeChatModel.builder() .dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv("aliQwen-api")) .build()) .defaultOptions( DashScopeChatOptions.builder().withModel(DEEPSEEK_MODEL).build() ) .build(); } @Bean(name = "qwen") public ChatModel qwen() { return DashScopeChatModel.builder().dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv("aliQwen-api")) .build()) .defaultOptions( DashScopeChatOptions.builder() .withModel(QWEN_MODEL) .build() ) .build(); } @Bean(name = "deepseekChatClient") public ChatClient deepseekChatClient(@Qualifier("deepseek") ChatModel deepSeek) { return ChatClient.builder(deepSeek) .defaultOptions(ChatOptions.builder() .model(DEEPSEEK_MODEL) .build()) .build(); } @Bean(name = "qwenChatClient") public ChatClient qwenChatClient(@Qualifier("qwen") ChatModel qwen) { return ChatClient.builder(qwen) .defaultOptions(ChatOptions.builder() .model(QWEN_MODEL) .build()) .build(); } } ``` * controller第2版 ```java package com.zsh.test.saa04.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.RequestParam; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; /** * @author ZhaoShuhao * @data 2025/11/26 23:06 */ @RestController public class StreamOutputController { //V1 通过ChatModel实现stream实现流式输出 @Resource(name = "deepseek") private ChatModel deepseekChatModel; @Resource(name = "qwen") private ChatModel qwenChatModel; //V2 通过ChatClient实现stream实现流式输出 @Resource(name = "deepseekChatClient") private ChatClient deepseekChatClient; @Resource(name = "qwenChatClient") private ChatClient qwenChatClient; @GetMapping(value = "/stream/chatflux1") public Flux chatflux(@RequestParam(name = "question",defaultValue = "你是谁") String question) { return deepseekChatModel.stream(question); } @GetMapping(value = "/stream/chatflux2") public Flux chatflux2(@RequestParam(name = "question",defaultValue = "你是谁") String question) { return qwenChatModel.stream(question); } @GetMapping(value = "/stream/chatflux3") public Flux chatflux3(@RequestParam(name = "question",defaultValue = "你是谁") String question) { return deepseekChatClient.prompt(question).stream().content(); } @GetMapping(value = "/stream/chatflux4") public Flux chatflux4(@RequestParam(name = "question",defaultValue = "你是谁") String question) { return qwenChatClient.prompt(question).stream().content(); } } ``` ### 5.4 新增前端代码trytry #### 5.4.1前端效果,Flux\< T \>本质提一嘴 ![image-20251127170711155](https://i-blog.csdnimg.cn/img_convert/d2fd24e0d6782238b2dc034d5b413120.png) ![image-20251127172727422](https://i-blog.csdnimg.cn/img_convert/425a87101b93e791ca624b5ca2d098fd.png) #### 5.4.2 SSE ##### (1)index.html ```html SSE流式chat


``` ##### (2)存放位置 ![image-20251127171743938](https://i-blog.csdnimg.cn/img_convert/b57a9a94f40e7bbd29d895548e0beab8.png) #### 5.4.3测试 http://localhost:8004/index.html ![image-20251127172740866](https://i-blog.csdnimg.cn/img_convert/2c2aa22df74d9f0257de43cb8aa1f995.png) ## 六、提示词Prompt ### 6.1 DeepSeek提示词样例 https://api-docs.deepseek.com/zh-cn/prompt-library/ ![image-20251127180754375](https://i-blog.csdnimg.cn/img_convert/9388e274fe35fca4c898774232246384.png) ### 6.2 是什么 #### 6.2.1 官网 https://java2ai.com/docs/1.0.0.2/tutorials/basics/prompt/?spm=5176.29160081.0.0.2856aa5cdeol7a * Prompt 是引导 AI 模型生成特定输出的输入格式,Prompt 的设计和措辞会显著影响模型的响应。 * Prompt 最开始只是简单的字符串,随着时间的推移,prompt 逐渐开始包含特定的占位符,例如 AI 模型可以识别的 "USER:"、"SYSTEM:" 等。阿里云通义模型可通过将多个消息字符串分类为不同的角色,然后再由 AI 模型处理,为 prompt 引入了更多结构。每条消息都分配有特定的角色,这些角色对消息进行分类,明确 AI 模型提示的每个部分的上下文和目的。这种结构化方法增强了与 AI 沟通的细微差别和有效性,因为 prompt 的每个部分在交互中都扮演着独特且明确的角色。 * Prompt 中的主要角色(Role)包括: * 系统角色(System Role):指导 AI 的行为和响应方式,设置 AI 如何解释和回复输入的参数或规则。这类似于在发起对话之前向 AI 提供说明。 * 用户角色(User Role):代表用户的输入 - 他们向 AI 提出的问题、命令或陈述。这个角色至关重要,因为它构成了 AI 响应的基础。 * 助手角色(Assistant Role):AI 对用户输入的响应。这不仅仅是一个答案或反应,它对于保持对话的流畅性至关重要。通过跟踪 AI 之前的响应(其"助手角色"消息),系统可确保连贯且上下文相关的交互。助手消息也可能包含功能工具调用请求信息。它就像 AI 中的一个特殊功能,在需要执行特定功能(例如计算、获取数据或不仅仅是说话)时使用。 * 工具/功能角色(Tool/Function Role):工具/功能角色专注于响应工具调用助手消息返回附加信息。 #### 6.2.2先从最简单的API调用说起 ![image-20251127181325901](https://i-blog.csdnimg.cn/img_convert/d7420ce976b1daa72ba60dcf1f586b08.png) ![image-20251127181339103](https://i-blog.csdnimg.cn/img_convert/7e75d15d2280e46c580104660a57cea2.png) #### 6.2.3再从源码Prompt说起 ##### (1)String 最初的Prompt只是简单的文本字符串提问 ##### (2)Message ![image-20251127215310292](https://i-blog.csdnimg.cn/img_convert/e1344ccd922d6365bba99f878602a8a4.png) enum MessageType:Prompt 中的四大角色(Role) ![image-20251127215442216](https://i-blog.csdnimg.cn/img_convert/55191e1fa06b20e533a49c8db94b24f1.png) ##### (3)Prompt ![image-20251127215656562](https://i-blog.csdnimg.cn/img_convert/ede7adf2054ebdaf0c9bd4d429c4bed4.png) ### 6.3 Prompt中的四大角色(Role) * Prompt 中的主要角色(Role》包括: * 系统角色(SystemRole):指导Al的行为和响应方式,设置AI如何解释和回复输入的参数或规则。这类似于在发起对话之前向AI提供说明。 * 用户角色(User Role):代表用户的输入-他们向AI提出的问题、命令或陈述。这个角色至关重要,因为它构成了AI响应的基础。 * 助手角色(Assistant Role):AI对用户输入的响应。这不仅仅是一个答案或反应,它对于保持对话的流畅性至关重要。通过跟踪A之前的响应(其"助手角色"消息),系统可确保连贯且上下文相关的交互。助手消息也可能包含功能工具调用请求信息。它就像AI中的一个特殊功能,在需要执行特定功能(例如计算、获取数据或不仅仅是说话)时使用。 * 工具/功能角色(Tool/FunctionRole》:工具/功能角色专注于响应工具调用助手消息返回附加信息。 #### (1)SYSTEM(value:"system") 设定AI行为边界/角色/定位。指导AI的行为和响应方式,设置AI如何解释和回复输入的 #### (2)USER(value:"user") 用户原始提问输入。代表用户的输入他们向AI提出的问题、命令或陈述。 #### (3)ASSISTANT(value:"assistant") * AI返回的响应信息,定义为"助手角色"消息。用它可以确保上下文能够连贯的交互。 * 记忆对话,积累回答 * ![image-20251127220121831](https://i-blog.csdnimg.cn/img_convert/95ce861f9652494f5e1e517311a48856.png) #### (4)TOOL(value:"tool") 桥接外部服务,可以进行函数调用如,支付/数据查询等操作,类似调用第3方util工具类 ### 6.4 开发步骤 #### 6.4.1 新建子模块Module(springAI-05chat-Prompt) springAI-05chat-Prompt ![image-20251127220635975](https://i-blog.csdnimg.cn/img_convert/9d81a18683c231b2d26c77507cd16e0f.png) #### 6.4.2 改POM ```xml 4.0.0 com.zsh.test SpringAIAlibaba-zsh_test1 0.0.1-SNAPSHOT com.zsh.test SAA-05chat-Prompt 0.0.1-SNAPSHOT springAI-05chat-Prompt springAI-05chat-Prompt org.springframework.boot spring-boot-starter-web cn.hutool hutool-all 5.8.22 org.projectlombok lombok 1.18.34 org.springframework.boot spring-boot-starter-test test com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope ${SpringAIAlibaba.version} org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin 3.11.0 -parameters 17 17 ``` #### 6.4.3 写yml ```yaml server: port: 8005 servlet: encoding: enabled: true force: true charset: UTF-8 spring: application: name: SAA-05Prompt ai: dashscope: api-key: ${aliQwen-api} ``` #### 6.4.3 主启动 ```java package com.zsh.test.springai05; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringAi05chatPromptApplication { public static void main(String[] args) { SpringApplication.run(SpringAi05chatPromptApplication.class, args); } } ``` #### 6.4.4 业务类 ##### (1)配置类LLMConfig ```java package com.zsh.test.springai05.config; import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author ZhaoShuhao * @data 2025/11/27 22:22 */ @Configuration public class SaaLLMConfig { // 模型名称常量定义 private final String DEEPSEEK_MODEL = "deepseek-v3"; private final String QWEN_MODEL = "qwen-plus"; @Bean(name = "deepseek") public DashScopeChatModel deepSeek() { return DashScopeChatModel.builder() .dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv("aliQwen-api")) .build()) .defaultOptions( DashScopeChatOptions.builder().withModel(DEEPSEEK_MODEL).build() ) .build(); } @Bean(name = "qwen") public DashScopeChatModel qwen() { return DashScopeChatModel.builder().dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv("aliQwen-api")) .build()) .defaultOptions( DashScopeChatOptions.builder() .withModel(QWEN_MODEL) .build() ) .build(); } @Bean(name = "deepseekChatClient") public ChatClient deepseekChatClient(@Qualifier("deepseek") ChatModel deepSeek) { return ChatClient.builder(deepSeek) .defaultOptions(ChatOptions.builder() .model(DEEPSEEK_MODEL) .build()) .build(); } @Bean(name = "qwenChatClient") public ChatClient qwenChatClient(@Qualifier("qwen") ChatModel qwen) { return ChatClient.builder(qwen) .defaultOptions(ChatOptions.builder() .model(QWEN_MODEL) .build()) .build(); } } ``` ##### (2)Controller(ChatClient) * 通过ChatClient实现 * SYSTEM(value: "system") * USER(value:"user") ```java package com.zsh.test.springai05.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.RestController; import reactor.core.publisher.Flux; /** * @author ZhaoShuhao * @data 2025/11/27 23:05 */ @RestController public class PromptController { @Resource(name = "deepseek") private ChatModel deepseekChatModel; @Resource(name = "qwen") private ChatModel qwenChatModel; @Resource(name = "deepseekChatClient") private ChatClient deepseekChatClient; @Resource(name = "qwenChatClient") private ChatClient qwenChatClient; // http://localhost:8005/prompt/chat?question=火锅介绍下 @GetMapping("/prompt/chat") public Flux chat(String question) { return deepseekChatClient.prompt() // AI 能力边界 .system("你是一个法律助手,只回答法律问题,其它问题回复,我只能回答法律相关问题,其它无可奉告") .user(question) .stream() .content(); } } ``` * 测试 ![image-20251127230928254](https://i-blog.csdnimg.cn/img_convert/bc753de1987919143295a42baef795b7.png) ##### (3)Controller(ChatModel) **通过ChatModel实现**:当然使用ChatModel等原子API可以为应用程序带来更多的灵活性,成本就是您需要编写大量样板代码。 ```java @GetMapping("/prompt/chat2") public Flux chat2(String question) { // 用户消息 UserMessage userMessage = new UserMessage(question); // 系统消息 SystemMessage systemMessage = new SystemMessage("你是一个讲故事的助手,每个故事控制在300字以内"); Prompt prompt = new Prompt(userMessage, systemMessage); return deepseekChatModel.stream(prompt); } @GetMapping("/prompt/chat3") public Flux chat3(String question) { // 用户消息 UserMessage userMessage = new UserMessage(question); // 系统消息 SystemMessage systemMessage = new SystemMessage("你是一个讲故事的助手,每个故事控制在300字以内"); Prompt prompt = new Prompt(userMessage, systemMessage); return deepseekChatModel.stream(prompt) .map(response -> response.getResults().get(0).getOutput().getText()); } ``` ![image-20251127231604386](https://i-blog.csdnimg.cn/img_convert/e8d5c67094e2f3227536cde65cdcd6cd.png) ##### (4)Controller(ASSISTANT) * ASSISTANT(value:"assistant") * Al返回的响应信息,定义为"助手角色"消息。用它可以确保上下文能够连贯的交互。 * 记忆对话,积累回答 * ![image-20251127231949119](https://i-blog.csdnimg.cn/img_convert/be9d79c8d5b7dfc3e5f600e5e8d2ce9c.png) ```java @GetMapping("/prompt/chat4") public String chat4(String question) { AssistantMessage assistantMessage = deepseekChatClient.prompt() .user(question) .call() .chatResponse() .getResult() .getOutput(); return assistantMessage.getText(); } ``` ##### (5)Controller(TOOL) * TOOL(value:"tool") * 桥接外部服务,可以进行函数调用如,支付/数据查询等操作,类似调用第3方util工具类 * 测试效果 ![image-20251127232310555](https://i-blog.csdnimg.cn/img_convert/ba17f4082c920a67c6c79980a26a75fa.png) ```java /** * http://localhost:8005/prompt/chat5?city=北京 * 近似理解Tool后面章节讲解...... * @param city * @return */ @GetMapping("/prompt/chat5") public String chat5(String city) { String answer = deepseekChatClient.prompt() .user(city + "未来3天天气情况如何?") .call() .chatResponse() .getResult() .getOutput() .getText(); ToolResponseMessage toolResponseMessage = new ToolResponseMessage( List.of(new ToolResponseMessage.ToolResponse("1","获得天气",city) ) ); String toolResponse = toolResponseMessage.getText(); String result = answer + toolResponse; return result; } ``` #### 6.4.5小总结 ![image-20251127232650939](https://i-blog.csdnimg.cn/img_convert/5a1ae316ced3eef982661565c4d9a3c2.png) ## 七、提示词Prompt Template ### 7.1 Prompt演化历程 #### 7.1.1 简单纯字符串提问问题 最初的Prompt只是简单的文本字符串。 #### 7.1.2 多角色信息 * (1)将消息分为不同角色(如用户、助手、系统等),设置功能边界,增强交互的复杂性和上下文感知能力 * (2)springai vs langchain4j vs spring ai alibaba * langchain4j * ![image-20251128172623654](https://i-blog.csdnimg.cn/img_convert/0b90f738e7c455e428ec0bb73f652f2d.png) * springai * ![image-20251128172650717](https://i-blog.csdnimg.cn/img_convert/59e221a66762730612b5d4e23152a537.png) * spring ai alibaba * ![image-20251128172716009](https://i-blog.csdnimg.cn/img_convert/f896627dcbe62f8eeacb917a11c86e34.png) #### 7.1.3 占位符(Prompt Template) 引入占位符(如 **{占位符变量名}** ) 以动态插入内容。 ### 7.2 提示词模板是什么 #### 7.2.1知识出处 https://java2ai.com/docs/1.0.0.2/tutorials/basics/prompt/?spm=4347728f.4dc6f515.0.0.538b4305NobuzA#prompt-template ![image-20251128173956005](https://i-blog.csdnimg.cn/img_convert/b7576a2f1b17a3023a5f1334a47a6731.png) #### 7.2.2 模板 (1)入职邀请函模板 ![image-20251128174217878](https://i-blog.csdnimg.cn/img_convert/f59c00fe977865d48d35926938c934ff.png) (2)短信模板 (3)邮件模板 ### 7.3 开发步骤 #### 7.3.1新建子模块Module(**SAA-06PromptTemplate**) **SAA-06PromptTemplate** #### 7.3.2 改POM ```xml 4.0.0 com.zsh.test SpringAIAlibaba-zsh_test1 0.0.1-SNAPSHOT com.zsh.test SAA-06PromptTemplate 0.0.1-SNAPSHOT SAA-06PromptTemplate SAA-06PromptTemplate org.springframework.boot spring-boot-starter-web com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope ${SpringAIAlibaba.version} org.projectlombok lombok 1.18.38 cn.hutool hutool-all 5.8.22 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin 3.11.0 -parameters 21 21 spring-milestones Spring Milestones https://repo.spring.io/milestone false ``` #### 7.3.3 写YML ```yaml server: port: 8006 servlet: encoding: enabled: true force: true charset: UTF-8 spring: application: name: SAA-06PromptTemplate ai: dashscope: api-key: ${aliQwen-api} ``` #### 7.3.4 主启动 ```java package com.zsh.test.saa06; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Saa06PromptTemplateApplication { public static void main(String[] args) { SpringApplication.run(Saa06PromptTemplateApplication.class, args); } } ``` #### 7.3.5 业务类 ##### (1)配置类 ```java package com.zsh.test.saa06.config; import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author ZhaoShuhao * @data 2025/11/30 16:35 * ChatModel+ChatClient+多模型共存 */ @Configuration public class SaaLLMConfig { // 模型名称常量定义 private final String DEEPSEEK_MODEL = "deepseek-v3"; private final String QWEN_MODEL = "qwen-plus"; @Bean(name = "deepseek") public ChatModel deepSeek() { return DashScopeChatModel.builder() .dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv("aliQwen-api")) .build()) .defaultOptions( DashScopeChatOptions.builder().withModel(DEEPSEEK_MODEL).build() ) .build(); } @Bean(name = "qwen") public ChatModel qwen() { return DashScopeChatModel.builder().dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv("aliQwen-api")) .build()) .defaultOptions( DashScopeChatOptions.builder() .withModel(QWEN_MODEL) .build() ) .build(); } @Bean(name = "deepseekChatClient") public ChatClient deepseekChatClient(@Qualifier("deepseek") ChatModel deepSeek) { return ChatClient.builder(deepSeek) .defaultOptions(ChatOptions.builder() .model(DEEPSEEK_MODEL) .build()) .build(); } @Bean(name = "qwenChatClient") public ChatClient qwenChatClient(@Qualifier("qwen") ChatModel qwen) { return ChatClient.builder(qwen) .defaultOptions(ChatOptions.builder() .model(QWEN_MODEL) .build()) .build(); } } ``` ##### (2)PromptTemplate基本使用 ```java package com.zsh.test.saa06.controller; import jakarta.annotation.Resource; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; import java.util.Map; /** * @author ZhaoShuhao * @data 2025/11/30 16:40 */ @RestController public class PromptTemplateController { @Resource(name = "deepseek") private ChatModel deepseekChatModel; @Resource(name = "qwen") private ChatModel qwenChatModel; @Resource(name = "deepseekChatClient") private ChatClient deepseekChatClient; @Resource(name = "qwenChatClient") private ChatClient qwenChatClient; /** * @Description: PromptTemplate基本使用,使用占位符设置模版 PromptTemplate * @Auther: zzyybs@126.com * 测试地址 * http://localhost:8006/prompttemplate/chat?topic=java&output_format=html&wordCount=200 */ @GetMapping("/prompttemplate/chat") public Flux chat(String topic, String output_format, String wordCount) { PromptTemplate promptTemplate = new PromptTemplate("" + "讲一个关于{topic}的故事" + "并以{output_format}格式输出," + "字数在{wordCount}左右"); // PromptTempate -> Prompt Prompt prompt = promptTemplate.create(Map.of( "topic", topic, "output_format",output_format, "wordCount",wordCount)); return deepseekChatClient.prompt(prompt).stream().content(); } } ``` ![image-20251130164517022](https://i-blog.csdnimg.cn/img_convert/79328b471312d4e31cbaaf5b412c00f6.png) ##### (3)PromptTemplate读取模版文件实现模版功能 新建txt文件,作为模版文件 ```tex 讲一个关于{topic}的故事,并以{output_format}格式输出。 ``` ![image-20251130165300707](https://i-blog.csdnimg.cn/img_convert/c7e7c6400084fa29d9a23c38265be9cc.png) 添加代码,读取模板配置: ```java package com.zsh.test.saa06.controller; import jakarta.annotation.Resource; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; import java.util.Map; /** * @author ZhaoShuhao * @data 2025/11/30 16:40 */ @RestController public class PromptTemplateController { @Resource(name = "deepseek") private ChatModel deepseekChatModel; @Resource(name = "qwen") private ChatModel qwenChatModel; @Resource(name = "deepseekChatClient") private ChatClient deepseekChatClient; @Resource(name = "qwenChatClient") private ChatClient qwenChatClient; @Value("classpath:/PromptTemplate/test.txt") private org.springframework.core.io.Resource userTemplate; /** * @Description: PromptTemplate基本使用,使用占位符设置模版 PromptTemplate * 测试地址 * http://localhost:8006/prompttemplate/chat?topic=java&output_format=html&wordCount=200 */ @GetMapping("/prompttemplate/chat") public Flux chat(String topic, String output_format, String wordCount) { PromptTemplate promptTemplate = new PromptTemplate("" + "讲一个关于{topic}的故事" + "并以{output_format}格式输出," + "字数在{wordCount}左右"); // PromptTempate -> Prompt Prompt prompt = promptTemplate.create(Map.of( "topic", topic, "output_format",output_format, "wordCount",wordCount)); return deepseekChatClient.prompt(prompt).stream().content(); } /** * @Description: PromptTemplate读取模版文件实现模版功能 * 测试地址 * http://localhost:8006/prompttemplate/chat2?topic=java&output_format=html */ @GetMapping("/prompttemplate/chat2") public String chat2(String topic,String output_format) { PromptTemplate promptTemplate = new PromptTemplate(userTemplate); Prompt prompt = promptTemplate.create(Map.of("topic", topic, "output_format", output_format)); return deepseekChatClient.prompt(prompt).call().content(); } } ``` ![image-20251130165429945](https://i-blog.csdnimg.cn/img_convert/5413912e494b8955fe64807e5af08b8c.png) ##### (4)PromptTemplate多角色设定 ```java /** * @Description: * 系统消息(SystemMessage):设定AI的行为规则和功能边界(xxx助手/什么格式返回/字数控制多少)。 * 用户消息(UserMessage):用户的提问/主题 * http://localhost:8006/prompttemplate/chat3?sysTopic=法律&userTopic=知识产权法 * * http://localhost:8006/prompttemplate/chat3?sysTopic=法律&userTopic=圣诞节 */ @GetMapping("/prompttemplate/chat3") public String chat3(String sysTopic, String userTopic) { // 1.SystemPromptTemplate SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("你是{systemTopic}助手,只回答{systemTopic}其它无可奉告,以HTML格式的结果。"); Message sysMessage = systemPromptTemplate.createMessage(Map.of("systemTopic", sysTopic)); // 2.PromptTemplate PromptTemplate userPromptTemplate = new PromptTemplate("解释一下{userTopic}"); Message userMessage = userPromptTemplate.createMessage(Map.of("userTopic", userTopic)); // 3.组合【关键】 多个 Message -> Prompt Prompt prompt = new Prompt(List.of(sysMessage, userMessage)); // 4.调用 LLM return deepseekChatClient.prompt(prompt).call().content(); } ``` ![image-20251130165805930](https://i-blog.csdnimg.cn/img_convert/72c67803600efaf28cba489c8cd00ef3.png) ![image-20251130165845930](https://i-blog.csdnimg.cn/img_convert/14c5219bd66d362957151ab445f89ba0.png) ##### (5)ChatModel实现 * 通过PromptTemplate人物设定 ```java /** * @Description: 人物角色设定,通过SystemMessage来实现人物设定,本案例用ChatModel实现 * 设定AI为"医疗专家"时,仅回答医学相关问题 * 设定AI为编程助手"时,专注于技术问题解答 * http://localhost:8006/prompttemplate/chat4?question=牡丹花 */ @GetMapping("/prompttemplate/chat4") public String chat4(String question) { //1 系统消息 SystemMessage systemMessage = new SystemMessage("你是一个Java编程助手,拒绝回答非技术问题。"); //2 用户消息 UserMessage userMessage = new UserMessage(question); //3 系统消息+用户消息=完整提示词 //Prompt prompt = new Prompt(systemMessage, userMessage); Prompt prompt = new Prompt(List.of(systemMessage, userMessage)); //4 调用LLM String result = deepseekChatModel.call(prompt).getResult().getOutput().getText(); System.out.println(result); return result; } ``` ![image-20251130170639568](https://i-blog.csdnimg.cn/img_convert/80eddebe1a4b93a167b5b670e82e995d.png) * 通过ChatClient实现 ```java /** * @Description: 人物角色设定,通过SystemMessage来实现人物设定,本案例用ChatClient实现 * 设定AI为"医疗专家"时,仅回答医学相关问题 * 设定AI为编程助手"时,专注于技术问题解答 * http://localhost:8006/prompttemplate/chat5?question=火锅 */ @GetMapping("/prompttemplate/chat5") public Flux chat5(String question) { return deepseekChatClient.prompt() .system("你是一个Java编程助手,拒绝回答非技术问题。") .user(question) .stream() .content(); } ``` ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png) ## 八、格式化输出(Structured Output) ### 8.1 是什么? https://java2ai.com/docs/1.0.0.2/tutorials/basics/structured-output/ ![image-20251201231551827](https://i-blog.csdnimg.cn/img_convert/cb7b7af4dbc58b3d2dbb6c9377b799cd.png) ### 8.2 开发步骤 假设我们期望将模型输出转换为Record记录类结构体,不再是传统的String #### 8.2.1新建子模块Module(SAA-07StructuredOutput) #### 8.2.2 改POM ```xml 4.0.0 com.zsh.test SpringAIAlibaba-zsh_test1 0.0.1-SNAPSHOT com.zsh.test SAA-07StructuredOutput 0.0.1-SNAPSHOT SAA-07StructuredOutput SAA-07StructuredOutput org.springframework.boot spring-boot-starter-web com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope ${SpringAIAlibaba.version} cn.hutool hutool-all 5.8.22 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin 3.11.0 -parameters 17 17 spring-milestones Spring Milestones https://repo.spring.io/milestone false ``` #### 8.2.3 写yml ```yaml server: port: 8007 servlet: encoding: enabled: true force: true charset: UTF-8 spring: application: name: SAA-07StructuredOutput ai: dashscope: api-key: ${aliQwen-api} ``` #### 8.2.4 主启动 ```java package com.zsh.test.saa07; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Saa07StructuredOutputApplication { public static void main(String[] args) { SpringApplication.run(Saa07StructuredOutputApplication.class, args); } } ``` #### 8.2.5 业务类 ##### (1)配置类 ```java package com.zsh.test.saa07.config; import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author ZhaoShuhao * @data 2025/12/1 23:31 * ChatModel+ChatClient+多模型共存 */ @Configuration public class SaaLLMConfig { // 模型名称常量定义 private final String DEEPSEEK_MODEL = "deepseek-v3"; private final String QWEN_MODEL = "qwen-plus"; @Bean(name = "deepseek") public ChatModel deepSeek() { return DashScopeChatModel.builder() .dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv("aliQwen-api")) .build()) .defaultOptions( DashScopeChatOptions.builder().withModel(DEEPSEEK_MODEL).build() ) .build(); } @Bean(name = "qwen") public ChatModel qwen() { return DashScopeChatModel.builder().dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv("aliQwen-api")) .build()) .defaultOptions( DashScopeChatOptions.builder() .withModel(QWEN_MODEL) .build() ) .build(); } @Bean(name = "deepseekChatClient") public ChatClient deepseekChatClient(@Qualifier("deepseek") ChatModel deepSeek) { return ChatClient.builder(deepSeek) .defaultOptions(ChatOptions.builder() .model(DEEPSEEK_MODEL) .build()) .build(); } @Bean(name = "qwenChatClient") public ChatClient qwenChatClient(@Qualifier("qwen") ChatModel qwen) { return ChatClient.builder(qwen) .defaultOptions(ChatOptions.builder() .model(QWEN_MODEL) .build()) .build(); } } ``` ##### (2)新建记录类StudentRecord ```java package com.zsh.test.saa07.records; /** * @author ZhaoShuhao * @data 2025/12/1 23:32 * jdk14后的新特性,记录类替代lombok */ public record StudentRecord(String id,String sname,String major,String email) { } ``` 学习资料:https://www.bilibili.com/video/BV1PY411e7J6?spm_id_from=333.788.videopod.episodes\&vd_source=f3f60f7acbef49d38b97c4d660d439fc\&p=199 ![image-20251201233510987](https://i-blog.csdnimg.cn/img_convert/2124060940383f73bf69c4e12880a0f7.png) ##### (3)StructuredOutputController(第一版) ```java package com.zsh.test.saa07.controller; import com.zsh.test.saa07.records.StudentRecord; import jakarta.annotation.Resource; import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.function.Consumer; /** * @author ZhaoShuhao * @data 2025/12/1 23:35 */ @RestController public class StructuredOutputController { @Resource(name = "qwenChatClient") private ChatClient qwenChatClient; /** * http://localhost:8007/structuredoutput/chat?sname=李四&email=zzyybs@126.com * @param sname * @return */ @GetMapping("/structuredoutput/chat") public StudentRecord chat(String sname,String email) { return qwenChatClient.prompt() .user(new Consumer() { @Override public void accept(ChatClient.PromptUserSpec promptUserSpec) { promptUserSpec.text("学号1001,我叫{sname},大学专业是计算机科学与技术,邮箱{email}") .param("sname",sname) .param("email",email); } }).call().entity(StudentRecord.class); } } ``` ![image-20251201233903486](https://i-blog.csdnimg.cn/img_convert/61aa3a810975193959d9d6f1df1abb5b.png) ##### (4)StructuredOutputController(第二版) ```java /** * http://localhost:8007/structuredoutput/chat2?sname=孙伟&email=zzyybs@126.com * @param sname * @return */ @GetMapping("/structuredoutput/chat2") public StudentRecord chat2(@RequestParam(name = "sname") String sname, @RequestParam(name = "email") String email) { String stringTemplate = """ 学号1002,我叫{sname},大学专业是软件工程,邮箱{email} """; return qwenChatClient.prompt() .user(promptUserSpec -> promptUserSpec.text(stringTemplate) .param("sname",sname) .param("email",email)) .call() .entity(StudentRecord.class); } ``` ## 九、ChatMemory连续对话保存和持久化 ### 9.1 是什么? #### 9.1.1 官网介绍 对话记忆介绍:https://java2ai.com/docs/1.0.0.2/tutorials/basics/memory/ "大模型的对话记忆"这一概念,根植于人工智能与自然语言处理领域,特别是针对具有深度学习能力的 大型语言模型而言,它指的是模型在与用户进行交互式对话过程中,能够追踪、理解并利用先前对话上 下文的能力。此机制使得大模型不仅能够响应即时的输入请求,还能基于之前的交流内容能够在对话 中记住先前的对话内容,并根据这些信息进行后续的响应。这种记忆机制使得模型能够在对话中持续跟 踪和理解用户的意图和上下文,从而实现更自然和连贯的对话。 #### 9.1.2 记忆对话,积累回答 一句话:SpringAlAlibaba中的聊天记忆提供了维护Al聊天应用程序的对话上下文和历史的机制。 ![image-20251203232456029](https://i-blog.csdnimg.cn/img_convert/fad9c0b30302c43d6ac0f6ce39d403ff.png) #### 9.1.3 记忆类型 ![image-20251203232723749](https://i-blog.csdnimg.cn/img_convert/cae6559629bd661fb5653b631ecc825d.png) * 因大模型本身不存储数据,需将历史对话信息一次性提供给它以实现连续对话,不然服务一重启就什么都没了...所以,必须持久化 * 痛点2个 * 持久化媒介 * 消息对话窗口,聊天记录上限 ### 9.2 持久化开发步骤 #### 9.2.1 需求说明 * 将客户和大模型的对话问答保存进Redis进行持久化记忆留存 * 官网:https://java2ai.com/docs/1.0.0.2/tutorials/basics/memory/?spm=4347728f.4dc6f515.0.0.538b4305NobuzA * ![image-20251203233033948](https://i-blog.csdnimg.cn/img_convert/c94131e1fef5bc31e3e1daae1fdb6144.png) * 当然,开发者也可以自行实现ChatMemory基于类似于文件、Redis等方式进行上下文内容的存储和记录。 #### 9.2.2 新建子模块(SAA-08Persistent) ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png) #### 9.2.3 改Pom ```XML 4.0.0 com.zsh.test SpringAIAlibaba-zsh_test1 0.0.1-SNAPSHOT com.zsh.test SAA-08Persistent 0.0.1-SNAPSHOT SAA-08Persistent SAA-08Persistent org.springframework.boot spring-boot-starter-web com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope ${SpringAIAlibaba.version} com.alibaba.cloud.ai spring-ai-alibaba-starter-memory-redis ${SpringAIAlibaba.version} redis.clients jedis org.projectlombok lombok 1.18.38 cn.hutool hutool-all 5.8.22 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin 3.11.0 -parameters 17 17 spring-milestones Spring Milestones https://repo.spring.io/milestone false ``` #### 9.2.4 写yml ```yaml server: port: 8008 servlet: encoding: charset: utf-8 enabled: true force: true spring: application: name: SAA-08Persistent ai: dashscope: api-key: ${aliQwen-api} data: redis: host: localhost port: 6379 database: 0 connect-timeout: 3 timeout: 2 ``` #### 9.2.5 主启动 ```java package com.zsh.test.saa08; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author ZhaoShuhao * @data 2025/12/3 23:33 * 将客户和大模型的对话问答保存进Redis进行持久化记忆留存 */ @SpringBootApplication public class Saa08PersistentApplication { public static void main(String[] args) { SpringApplication.run(Saa08PersistentApplication.class, args); } } ``` #### 9.2.6 业务类 ##### (1)ChatMemoryRepository接口 * 实现SpringAI框架规定的chatMemoryRRepository接口 官网:https://docs.spring.io/spring-ai/reference/index.html ![image-20251207174737132](https://i-blog.csdnimg.cn/img_convert/d4888d40321e326e44f0c5a423dbd0b9.png) * 接口ChatMemoryRepository ![image-20251207174838125](https://i-blog.csdnimg.cn/img_convert/549e25529f3cf956490a17e6f46b2abe.png) * RedisChatMemoryRepository源码 ![image-20251207174928576](https://i-blog.csdnimg.cn/img_convert/c9a3bf4095bfe1b038213441fdfcb593.png) * 编码新建RedisMemoryConfig配置类 ```java package com.zsh.test.saa08.config; import com.alibaba.cloud.ai.memory.redis.RedisChatMemoryRepository; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author ZhaoShuhao * @data 2025/12/7 17:50 * redis聊天记忆配置类 */ @Configuration public class RedisMemoryConfig { @Value("${spring.data.redis.host}") private String host; @Value("${spring.data.redis.port}") private int port; @Bean public RedisChatMemoryRepository redisChatMemoryRepository() { return RedisChatMemoryRepository.builder() .host(host) .port(port) .build(); } } ``` ##### (2)MessageWindowChatMemory消息窗口聊天记忆 官网:https://docs.spring.io/spring-ai/reference/api/chat-memory.html#_message_window_chat_memory ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png) ##### (3)顾问(Advisors)MessageChatMemoryAdvisor 官网:https://java2ai.com/docs/1.0.0.2/overview/?spm=4347728f.4dc6f515.0.0.273f43051xpgRX ![image-20251207180019583](https://i-blog.csdnimg.cn/img_convert/e8df9883a5304255bac267c029d9b1da.png) 官网:https://java2ai.com/blog/spring-ai-100-ga-released/?spm=4347728f.45ed3faa.0.0.451949faPdHUBx#%E9%A1%BE%E9%97%AEadvisors ![image-20251207180149590](https://i-blog.csdnimg.cn/img_convert/7ed4a3f3073adc63a5a71226236c6381.png) 官网:https://java2ai.com/ecosystem/spring-ai/reference/memory ![image-20251207181342924](https://i-blog.csdnimg.cn/img_convert/d3a862e73ba038136bdd97da773ae98b.png) ##### (4)配置类SaaLLMConfig ![image-20251207181922575](https://i-blog.csdnimg.cn/img_convert/d20dc364031c40b82b4c2cef6f31db69.png) ```java package com.zsh.test.saa08.config; import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; import com.alibaba.cloud.ai.memory.redis.RedisChatMemoryRepository; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author ZhaoShuhao * @data 2025/12/7 18:15 * ChatModel+ChatClient+多模型共存 */ @Configuration public class SaaLLMConfig { // 模型名称常量定义 private final String DEEPSEEK_MODEL = "deepseek-v3"; private final String QWEN_MODEL = "qwen-plus"; @Bean(name = "deepseek") public ChatModel deepSeek() { return DashScopeChatModel.builder() .dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv("aliQwen-api")) .build()) .defaultOptions( DashScopeChatOptions.builder().withModel(DEEPSEEK_MODEL).build() ) .build(); } @Bean(name = "qwen") public ChatModel qwen() { return DashScopeChatModel.builder().dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv("aliQwen-api")) .build()) .defaultOptions( DashScopeChatOptions.builder() .withModel(QWEN_MODEL) .build() ) .build(); } @Bean(name = "qwenChatClient") public ChatClient qwenChatClient(@Qualifier("qwen") ChatModel qwen, RedisChatMemoryRepository redisChatMemoryRepository) { MessageWindowChatMemory windowChatMemory = MessageWindowChatMemory.builder() .chatMemoryRepository(redisChatMemoryRepository) .maxMessages(10) .build(); return ChatClient.builder(qwen) .defaultOptions(ChatOptions.builder().model(QWEN_MODEL).build()) .defaultAdvisors(MessageChatMemoryAdvisor.builder(windowChatMemory).build()) .build(); } /** * @param deepSeek * @return */ @Bean(name = "deepseekChatClient") public ChatClient deepseekChatClient(@Qualifier("deepseek") ChatModel deepSeek, RedisChatMemoryRepository redisChatMemoryRepository) { MessageWindowChatMemory windowChatMemory = MessageWindowChatMemory.builder() .chatMemoryRepository(redisChatMemoryRepository) .maxMessages(10) .build(); return ChatClient.builder(deepSeek) .defaultOptions(ChatOptions.builder().model(DEEPSEEK_MODEL).build()) .defaultAdvisors(MessageChatMemoryAdvisor.builder(windowChatMemory).build()) .build(); } } ``` ##### (5)ChatMemory4RedisController ```java package com.zsh.test.saa08.controller; import jakarta.annotation.Resource; import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID; /** * @author ZhaoShuhao * @data 2025/12/7 18:20 */ @RestController public class ChatMemory4RedisController { @Resource(name = "qwenChatClient") private ChatClient qwenChatClient; @GetMapping("/chatmemory/chat") public String chat(String msg, String userId) { /*return qwenChatClient.prompt(msg).advisors(new Consumer() { @Override public void accept(ChatClient.AdvisorSpec advisorSpec) { advisorSpec.param(CONVERSATION_ID, cid); } }).call().content();*/ return qwenChatClient.prompt(msg) .advisors(advisorSpec -> advisorSpec.param(CONVERSATION_ID, userId)) .call() .content(); } } ``` #### 9.2.7 测试 ```js http://localhost:8008/chatmemory/chat?msg=2加5等于多少&userId=7 //可以看出,是根据userId来进行区分,隔离不同用户之间的消息 ``` ![image-20251207183447163](https://i-blog.csdnimg.cn/img_convert/387db774d9007ab0e6b7de7b645682b6.png) ![image-20251207183419583](https://i-blog.csdnimg.cn/img_convert/0a5c86c87f92f34f7849840d27f7e5d5.png) ## 十、文生图 ### 10.1 阿里百炼文生图 官网:https://help.aliyun.com/zh/model-studio/text-to-image?s ![image-20251209221038313](https://i-blog.csdnimg.cn/img_convert/2b365650abdaa67af5a5beb5f07ee55b.png) ### 10.2 通义万象-文生图V2版本API参考 官网:https://help.aliyun.com/zh/model-studio/text-to-image-v2-api-reference?spm=a2c4g.11186623.help-menu-2400256.d_2_2_3.25127c3b02eD93\&scm=20140722.H_2862677._.OR_help-T_cn\~zh-V_1 ![image-20251209221213929](https://i-blog.csdnimg.cn/img_convert/aa9392ff7fd4784d196c3fbddeb007d6.png) ### 10.3 新建子模块(SAA-09Text2image) ![image-20251209221408935](https://i-blog.csdnimg.cn/img_convert/844508a72cac6a4b29967622ecaa4e1d.png) ### 10.4 改POM ```xml 4.0.0 com.zsh.test SpringAIAlibaba-zsh_test1 0.0.1-SNAPSHOT com.zsh.test SAA-09Text2image 0.0.1-SNAPSHOT SAA-09Text2image SAA-09Text2image org.springframework.boot spring-boot-starter-web com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope ${SpringAIAlibaba.version} org.projectlombok lombok 1.18.38 cn.hutool hutool-all 5.8.22 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin 3.11.0 -parameters 17 17 spring-milestones Spring Milestones https://repo.spring.io/milestone false ``` ### 10.5 写yml ```yaml server: port: 8009 servlet: encoding: charset: utf-8 enabled: true force: true spring: application: name: SAA-09Text2image ai: dashscope: api-key: ${aliQwen-api} image: options: model: wanx2.1-t2i-turbo # 增加重试配置 retry: max-attempts: 10 initial-interval: 2000 multiplier: 2.0 max-interval: 60000 ``` ### 10.6 主启动 ```java package com.zsh.test.saa09; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Saa09Text2imageApplication { public static void main(String[] args) { SpringApplication.run(Saa09Text2imageApplication.class, args); } } ``` ### 10.7 业务类 ```java package com.zsh.test.saa09.controller; import com.alibaba.cloud.ai.dashscope.audio.DashScopeSpeechSynthesisModel; import com.alibaba.cloud.ai.dashscope.audio.DashScopeSpeechSynthesisOptions; import com.alibaba.cloud.ai.dashscope.audio.synthesis.SpeechSynthesisModel; import com.alibaba.cloud.ai.dashscope.audio.synthesis.SpeechSynthesisOptions; import com.alibaba.cloud.ai.dashscope.audio.synthesis.SpeechSynthesisPrompt; import com.alibaba.cloud.ai.dashscope.audio.synthesis.SpeechSynthesisResponse; import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions; import jakarta.annotation.Resource; import org.springframework.ai.image.ImageModel; import org.springframework.ai.image.ImagePrompt; import org.springframework.ai.image.ImageResponse; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.io.File; import java.io.FileOutputStream; import java.nio.ByteBuffer; import java.util.UUID; /** * @author ZhaoShuhao * @data 2025/12/9 22:21 */ @RestController public class Text2ImageController { // img model public static final String IMAGE_MODEL = "wan2.2-t2i-flash"; @Resource private ImageModel imageModel; @GetMapping(value = "/t2i/image") public String image(@RequestParam(name = "prompt",defaultValue = "迪迦奥特曼") String prompt) { try { // 使用配置中的重试机制,增加n和size参数 ImageResponse response = imageModel.call( new ImagePrompt(prompt, DashScopeImageOptions.builder() .withModel(IMAGE_MODEL) .withN(1) // 生成图片数量 .withWidth(1024) // 图片宽度 .withHeight(1024) // 图片高度 .build()) ); if (response != null && response.getResult() != null) { return response.getResult().getOutput().getUrl(); } else { return "Image generation failed or still pending."; } } catch (RuntimeException e) { // 更详细的错误处理 if (e.getMessage() != null && e.getMessage().contains("Image generation still pending")) { return "图片生成仍在进行中,请稍后重试。建议等待30-60秒后再次请求。"; } // 记录异常并返回友好提示 e.printStackTrace(); return "图片生成失败: " + e.getMessage(); } } } ``` ![image-20251209223912064](https://i-blog.csdnimg.cn/img_convert/a29e784b6ef8beb78d3090ea72b5117d.png) ## 十一、文生音 ### 11.1 阿里百炼文生音 #### 11.1.1 语音合成-CosyVoice 官网:https://help.aliyun.com/zh/model-studio/cosyvoice-large-model-for-speech-synthesis/?spm=a2c4g.11186623.help-menu-2400256.d_2_6_0.2a7474473XyDNE\&scm=20140722.H_2817551...OR_help-T_cn\~zh-V_1 ![image-20251211220435874](https://i-blog.csdnimg.cn/img_convert/d30d913f803ae496d31340fc0fa30acb.png) #### 11.1.2 语音合成-CosyVoice Java SDK ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png) #### 11.1.3SpeechSynthesizer类提供了语音合成的关键接口 ​ 提交文本后,服务端立即处理并返回完整的语音合成结果,整个过程是阻塞式的,客户端需要等待服务端完成处理后才能继续下一步操作,适合短文本语音合成场景 ![image-20251211220911912](https://i-blog.csdnimg.cn/img_convert/6c1e42aa675439a9ecfca104efac5acf.png) #### 11.1.4 阿里内置接口一览 ![image-20251211221601602](https://i-blog.csdnimg.cn/img_convert/b469854055ed5728e5a871764cd3c160.png) #### 11.1.5 DashScopeSpeechSynthesisOptions (1)SpeechSynthesisParam的链式方法配置模型、音色等参数 官网:https://help.aliyun.com/zh/model-studio/cosyvoice-java-sdk?spm=a2c4g.11186623.help-menu-2400256.d_2_6_0_0.1aa4383a1fw0oG#adcb5e9bddbyq ![image-20251211222047870](https://i-blog.csdnimg.cn/img_convert/34941749ea3051fa6f889f4c114d3889.png) ![image-20251211222135162](https://i-blog.csdnimg.cn/img_convert/a1f9428656c6a55b74e5675c4055323e.png) ### 11.2 开发步骤 #### 11.2.1 新建子模块(SAA-10Text2voice) ![image-20251211222417785](https://i-blog.csdnimg.cn/img_convert/1ce81848b003331955012a863a3e5bac.png) #### 11.2.2 改POM ```xml 4.0.0 com.zsh.test SpringAIAlibaba-zsh_test1 0.0.1-SNAPSHOT com.zsh.test SAA-10Text2voice 0.0.1-SNAPSHOT SAA-10Text2voice SAA-10Text2voice org.springframework.boot spring-boot-starter-web com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope ${SpringAIAlibaba.version} org.projectlombok lombok 1.18.38 cn.hutool hutool-all 5.8.22 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin 3.11.0 -parameters 17 17 spring-milestones Spring Milestones https://repo.spring.io/milestone false ``` #### 11.2.3 写yml ```yaml server: port: 8010 servlet: encoding: charset: utf-8 enabled: true force: true spring: application: name: SAA-10Text2voice ai: dashscope: api-key: ${aliQwen-api} ``` #### 11.2.4 主启动 ```java package com.zsh.test.saa10; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Saa10Text2voiceApplication { public static void main(String[] args) { SpringApplication.run(Saa10Text2voiceApplication.class, args); } } ``` #### 11.2.5 音色列表配置 https://help.aliyun.com/zh/model-studio/cosyvoice-voice-list?spm=a2c4g.11186623.0.0.854b383aUON8WF ![image-20251211223229777](https://i-blog.csdnimg.cn/img_convert/65221eca97bbbd4e07675f428db5750b.png) ![image-20251211223439577](https://i-blog.csdnimg.cn/img_convert/ee900e82881bf8fd33c661f069a4852a.png) #### 11.2.6 controller ```java package com.zsh.test.saa10.controller; import com.alibaba.cloud.ai.dashscope.audio.DashScopeSpeechSynthesisOptions; import com.alibaba.cloud.ai.dashscope.audio.synthesis.SpeechSynthesisModel; import com.alibaba.cloud.ai.dashscope.audio.synthesis.SpeechSynthesisPrompt; import com.alibaba.cloud.ai.dashscope.audio.synthesis.SpeechSynthesisResponse; import jakarta.annotation.Resource; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.io.FileOutputStream; import java.nio.ByteBuffer; import java.util.UUID; /** * @author ZhaoShuhao * @data 2025/12/11 22:32 */ @RestController public class Text2VoiceController { @Resource private SpeechSynthesisModel speechSynthesisModel; // voice model public static final String BAILIAN_VOICE_MODEL = "cosyvoice-v2"; // voice timber 音色列表:https://help.aliyun.com/zh/model-studio/cosyvoice-java-sdk#722dd7ca66a6x public static final String BAILIAN_VOICE_TIMBER = "longyingcui";//龙应催 /** * http://localhost:8010/t2v/voice * @param msg * @return */ @GetMapping("/t2v/voice") public String voice(@RequestParam(name = "msg",defaultValue = "温馨提醒,支付宝到账100元请注意查收") String msg) { String filePath = "D:\\JavaProjectCode\\JavaTest\\SpringAIAlibaba-zsh_test1\\SAA-10Text2voice\\src\\main\\resources\\voice\\" + UUID.randomUUID() + ".mp3"; //1 语音参数设置 DashScopeSpeechSynthesisOptions options = DashScopeSpeechSynthesisOptions.builder() .model(BAILIAN_VOICE_MODEL) .voice(BAILIAN_VOICE_TIMBER) .build(); //2 调用大模型语音生成对象 SpeechSynthesisResponse response = speechSynthesisModel.call(new SpeechSynthesisPrompt(msg, options)); //3 字节流语音转换 ByteBuffer byteBuffer = response.getResult().getOutput().getAudio(); //4 文件生成 try (FileOutputStream fileOutputStream = new FileOutputStream(filePath)) { fileOutputStream.write(byteBuffer.array()); } catch (Exception e) { System.out.println(e.getMessage()); } //5 生成路径OK return filePath; } } ``` ![image-20251211224646249](https://i-blog.csdnimg.cn/img_convert/e548ce025558fead6be03e951ae9f41f.png) #### 11.2.7 FlowableTestVoiceController(使用官网SDK测试) ![image-20251211231851008](https://i-blog.csdnimg.cn/img_convert/af1b89cf507f2cd1e42fdacb789d97e4.png) ```xml com.alibaba dashscope-sdk-java 2.21.8 ``` ```java package com.zsh.test.saa10.controller; import com.alibaba.dashscope.audio.tts.SpeechSynthesisResult; import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisAudioFormat; import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam; import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer; import com.alibaba.dashscope.common.ResultCallback; import com.zsh.test.saa10.util.TimeUtils; /** * @author ZhaoShuhao * @data 2025/12/11 22:50 * 流式调用测试 */ public class FlowableTestVoiceController { private static String[] textArray = {"流式文本语音合成SDK,", "可以将输入的文本", "合成为语音二进制数据,", "相比于非流式语音合成,", "流式合成的优势在于实时性", "更强。用户在输入文本的同时", "可以听到接近同步的语音输出,", "极大地提升了交互体验,", "减少了用户等待时间。", "适用于调用大规模", "语言模型(LLM),以", "流式输入文本的方式", "进行语音合成的场景。"}; private static String model = "cosyvoice-v2"; // 模型 private static String voice = "longxiaochun_v2"; // 音色 public static void streamAudioDataToSpeaker() { // 配置回调函数 ResultCallback callback = new ResultCallback() { @Override public void onEvent(SpeechSynthesisResult result) { // System.out.println("收到消息: " + result); if (result.getOutput() != null) { // 此处实现处理音频数据的逻辑 System.out.println(TimeUtils.getTimestamp() + " 收到音频"); } } @Override public void onComplete() { System.out.println(TimeUtils.getTimestamp() + " 收到Complete,语音合成结束"); } @Override public void onError(Exception e) { System.out.println("出现异常:" + e.toString()); } }; // 请求参数 SpeechSynthesisParam param = SpeechSynthesisParam.builder() // 若没有将API Key配置到环境变量中,需将下面这行代码注释放开,并将your-api-key替换为自己的API Key //.apiKey("your-api-key") .apiKey(System.getenv("aliQwen-api")) // 如果环境变量中有配置 .model(model) .voice(voice) .format(SpeechSynthesisAudioFormat .PCM_22050HZ_MONO_16BIT) // 流式合成使用PCM或者MP3 .build(); SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, callback); // 带Callback的call方法将不会阻塞当前线程 try { for (String text : textArray) { // 发送文本片段,在回调接口的onEvent方法中实时获取二进制音频 synthesizer.streamingCall(text); } // 等待结束流式语音合成 synthesizer.streamingComplete(); } catch (Exception e) { throw new RuntimeException(e); } finally { // 任务结束关闭websocket连接 synthesizer.getDuplexApi().close(1000, "bye"); } // 首次发送文本时需建立 WebSocket 连接,因此首包延迟会包含连接建立的耗时 System.out.println( "[Metric] requestId为:" + synthesizer.getLastRequestId() + ",首包延迟(毫秒)为:" + synthesizer.getFirstPackageDelay()); } public static void main(String[] args) { streamAudioDataToSpeaker(); System.exit(0); } } ``` ![image-20251211231808400](https://i-blog.csdnimg.cn/img_convert/9dd0fa091b71e035d0421a673d265595.png) ## 十二、向量化和向量数据库 ### 12.1 向量 * Vector是向量或矢量的意思,向量是数学里的概念,而矢量是物理里的概念,但二者描述的是同一件事。 * 定义:**向量是用于表示具有大小和方向的量。** * 向量可以在不同的维度空间中定义,最常见的是二维和三维空间中的向量,但理论上也可以有更高维的向量。 * 例如,在二维平面上的一个向量可以写作(xy),这里x和y分别表示该向量沿两个坐标轴方向上的分量; * 而在三维空间里,则会有一个额外的z坐标,即(x,Y,z) ### 12.2 文本向量化 #### 12.2.1 是什么 ![image-20251222232509707](https://i-blog.csdnimg.cn/img_convert/aef8c81bdfe5359ff07db5f1733cfa52.png) #### 12.2.2 官网-嵌入模型 (Embedding Model) https://java2ai.com/docs/1.0.0.2/tutorials/basics/embedding/?spm=5176.29160081.0.0.2856aa5cXggpMJ ![image-20251222234911693](https://i-blog.csdnimg.cn/img_convert/4f26bfdc4ca070b7771c1fd6ce4dae6f.png) #### 12.2.3 案列 ![image-20251223223707878](https://i-blog.csdnimg.cn/img_convert/d226eb4db57e2330425d2db9e27549c5.png) ![image-20251223223740993](https://i-blog.csdnimg.cn/img_convert/3b477552b399f5e0934c9e5ccc587cb5.png) ![image-20251223223830749](https://i-blog.csdnimg.cn/img_convert/d4897439930f9dd9a96f46047aa84ebe.png) #### 12.2.4 小总结 ![image-20251223223913918](https://i-blog.csdnimg.cn/img_convert/47a2f2e4f4abdecca04343f8e7543b77.png) ### 12.3 向量数据库 #### 12.3.1 是什么 > * 一种专门用于存储、管理和检索向量数据(即高维数值数组)的数据库系统。 > * 其核心功能是通过高效的索引结构和相似性计算算法,支持大规模向量数据的快速查询与分析,向量数据库维度越高,查询精准度也越高,查询效果也越好。 官网:https://java2ai.com/docs/1.0.0.2/tutorials/basics/vectorstore/ ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png) #### 12.3.2 下方是LangChain4J支持的向量数据库List清单 https://docs.langchain4j.dev/integrations/embedding-stores/ | Embedding Store:嵌入存储 | Storing Metadata:存储元数据 | Filtering by Metadata:按元数据过滤 | Removing Embeddings:移除嵌入 | |-------------------------------------------------------------------------------------------------------------------|------------------------|------------------------------|--------------------------| | [In-memory](https://docs.langchain4j.dev/integrations/embedding-stores/in-memory) | ✅ | ✅ | ✅ | | [AlloyDB for Postgres](https://docs.langchain4j.dev/integrations/embedding-stores/alloydb) | ✅ | ✅ | ✅ | | [Astra DB](https://docs.langchain4j.dev/integrations/embedding-stores/astra-db) | ✅ | | | | [Azure AI Search](https://docs.langchain4j.dev/integrations/embedding-stores/azure-ai-search) | ✅ | ✅ | ✅ | | [Azure CosmosDB Mongo vCore](https://docs.langchain4j.dev/integrations/embedding-stores/azure-cosmos-mongo-vcore) | ✅ | | | | [Azure CosmosDB NoSQL](https://docs.langchain4j.dev/integrations/embedding-stores/azure-cosmos-nosql) | ✅ | | | | [Cassandra](https://docs.langchain4j.dev/integrations/embedding-stores/cassandra) | ✅ | | | | [Chroma](https://docs.langchain4j.dev/integrations/embedding-stores/chroma) | ✅ | ✅ | ✅ | | [ClickHouse](https://docs.langchain4j.dev/integrations/embedding-stores/clickhouse) | ✅ | ✅ | ✅ | | [Cloud SQL for Postgres](https://docs.langchain4j.dev/integrations/embedding-stores/cloud-sql) | ✅ | ✅ | ✅ | | [Coherence](https://docs.langchain4j.dev/integrations/embedding-stores/coherence) | ✅ | ✅ | ✅ | | [Couchbase](https://docs.langchain4j.dev/integrations/embedding-stores/couchbase) | ✅ | | ✅ | | [DuckDB](https://docs.langchain4j.dev/integrations/embedding-stores/duckdb) | ✅ | ✅ | ✅ | | [Elasticsearch](https://docs.langchain4j.dev/integrations/embedding-stores/elasticsearch) | ✅ | ✅ | ✅ | | [Infinispan](https://docs.langchain4j.dev/integrations/embedding-stores/infinispan) | ✅ | ✅ | ✅ | | [JVector](https://docs.langchain4j.dev/integrations/embedding-stores/jvector) | | | ✅ | | [Mariadb](https://docs.langchain4j.dev/integrations/embedding-stores/mariadb) | ✅ | ✅ | ✅ | | [Milvus](https://docs.langchain4j.dev/integrations/embedding-stores/milvus) | ✅ | ✅ | ✅ | | [MongoDB Atlas](https://docs.langchain4j.dev/integrations/embedding-stores/mongodb-atlas) | ✅ | ✅ | ✅ | | [Neo4j](https://docs.langchain4j.dev/integrations/embedding-stores/neo4j) | ✅ | | | | [OpenSearch](https://docs.langchain4j.dev/integrations/embedding-stores/opensearch) | ✅ | | | | [Oracle](https://docs.langchain4j.dev/integrations/embedding-stores/oracle) | ✅ | ✅ | ✅ | | [PGVector](https://docs.langchain4j.dev/integrations/embedding-stores/pgvector) | ✅ | ✅ | ✅ | | [Pinecone](https://docs.langchain4j.dev/integrations/embedding-stores/pinecone) | ✅ | ✅ | ✅ | | [Qdrant](https://docs.langchain4j.dev/integrations/embedding-stores/qdrant) | ✅ | ✅ | ✅ | | [Redis](https://docs.langchain4j.dev/integrations/embedding-stores/redis) | ✅ | | ✅ | | [SQL Server](https://docs.langchain4j.dev/integrations/embedding-stores/sqlserver) | ✅ | ✅ | ✅ | | [Tablestore](https://docs.langchain4j.dev/integrations/embedding-stores/tablestore) | ✅ | ✅ | ✅ | | [Vearch](https://docs.langchain4j.dev/integrations/embedding-stores/vearch) | ✅ | | | | [Vespa](https://docs.langchain4j.dev/integrations/embedding-stores/vespa) | | | | | [Weaviate](https://docs.langchain4j.dev/integrations/embedding-stores/weaviate) | ✅ | | ✅ | | [YugabyteDB](https://docs.langchain4j.dev/integrations/embedding-stores/yugabytedb) | ✅ | ✅ | ✅ | #### 12.3.3 下方是SpringAI支持的向量数据库List清单 https://docs.spring.io/spring-ai/reference/api/vectordbs.html These are the available implementations of the `VectorStore` interface: 这些是 `VectorStore` 接口的可用实现: * [Azure Vector Search](https://docs.spring.io/spring-ai/reference/api/vectordbs/azure.html) - The [Azure](https://learn.microsoft.com/en-us/azure/search/vector-search-overview) vector store. Azure Vector Search - Azure 向量存储。 * [Apache Cassandra](https://docs.spring.io/spring-ai/reference/api/vectordbs/apache-cassandra.html) - The [Apache Cassandra](https://cassandra.apache.org/doc/latest/cassandra/vector-search/overview.html) vector store. Apache Cassandra - Apache Cassandra 向量存储。 * [Chroma Vector Store](https://docs.spring.io/spring-ai/reference/api/vectordbs/chroma.html) - The [Chroma](https://www.trychroma.com/) vector store. Chroma 向量存储 - Chroma 向量存储。 * [Elasticsearch Vector Store](https://docs.spring.io/spring-ai/reference/api/vectordbs/elasticsearch.html) - The [Elasticsearch](https://www.elastic.co/) vector store. Elasticsearch 向量存储 - Elasticsearch 向量存储。 * [GemFire Vector Store](https://docs.spring.io/spring-ai/reference/api/vectordbs/gemfire.html) - The [GemFire](https://tanzu.vmware.com/content/blog/vmware-gemfire-vector-database-extension) vector store. GemFire 向量存储 - GemFire 向量存储。 * [MariaDB Vector Store](https://docs.spring.io/spring-ai/reference/api/vectordbs/mariadb.html) - The [MariaDB](https://mariadb.com/) vector store. MariaDB 向量存储 - MariaDB 向量存储。 * [Milvus Vector Store](https://docs.spring.io/spring-ai/reference/api/vectordbs/milvus.html) - The [Milvus](https://milvus.io/) vector store. Milvus 向量存储 - Milvus 向量存储。 * [MongoDB Atlas Vector Store](https://docs.spring.io/spring-ai/reference/api/vectordbs/mongodb.html) - The [MongoDB Atlas](https://www.mongodb.com/atlas/database) vector store. MongoDB Atlas 向量存储 - MongoDB Atlas 向量存储。 * [Neo4j Vector Store](https://docs.spring.io/spring-ai/reference/api/vectordbs/neo4j.html) - The [Neo4j](https://neo4j.com/) vector store. Neo4j 向量存储 - Neo4j 向量存储。 * [OpenSearch Vector Store](https://docs.spring.io/spring-ai/reference/api/vectordbs/opensearch.html) - The [OpenSearch](https://opensearch.org/platform/search/vector-database.html) vector store. OpenSearch 向量存储 - OpenSearch 向量存储。 * [Oracle Vector Store](https://docs.spring.io/spring-ai/reference/api/vectordbs/oracle.html) - The [Oracle Database](https://docs.oracle.com/en/database/oracle/oracle-database/23/vecse/overview-ai-vector-search.html) vector store. Oracle 向量存储 - Oracle 数据库向量存储。 * [PgVector Store](https://docs.spring.io/spring-ai/reference/api/vectordbs/pgvector.html) - The [PostgreSQL/PGVector](https://github.com/pgvector/pgvector) vector store. PgVector 存储 - PostgreSQL/PGVector 向量存储。 * [Pinecone Vector Store](https://docs.spring.io/spring-ai/reference/api/vectordbs/pinecone.html) - [Pinecone](https://www.pinecone.io/) vector store. Pinecone 向量存储 - Pinecone 向量存储。 * [Qdrant Vector Store](https://docs.spring.io/spring-ai/reference/api/vectordbs/qdrant.html) - [Qdrant](https://www.qdrant.tech/) vector store. Qdrant 向量存储 - Qdrant 向量存储。 * [Redis Vector Store](https://docs.spring.io/spring-ai/reference/api/vectordbs/redis.html) - The [Redis](https://redis.io/) vector store. Redis 向量存储 - Redis 向量存储。 * [SAP Hana Vector Store](https://docs.spring.io/spring-ai/reference/api/vectordbs/hana.html) - The [SAP HANA](https://news.sap.com/2024/04/sap-hana-cloud-vector-engine-ai-with-business-context/) vector store. SAP Hana 向量存储 - SAP HANA 向量存储。 * [Typesense Vector Store](https://docs.spring.io/spring-ai/reference/api/vectordbs/typesense.html) - The [Typesense](https://typesense.org/docs/0.24.0/api/vector-search.html) vector store. Typesense 向量存储 - Typesense 向量存储。 * [Weaviate Vector Store](https://docs.spring.io/spring-ai/reference/api/vectordbs/weaviate.html) - The [Weaviate](https://weaviate.io/) vector store. Weaviate Vector Store - Weaviate 向量存储。 * [SimpleVectorStore](https://github.com/spring-projects/spring-ai/blob/main/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/SimpleVectorStore.java) - A simple implementation of persistent vector storage, good for educational purposes. SimpleVectorStore - 一种简单的持久化向量存储实现,适合教育用途。 ### 12.4能干嘛 > * 捕捉复杂的词汇关系(如语义相似性、同义词、多义词) > * 向量嵌入为检索增强生成(RAG)应用程序提供支持 > * 将文本映射到高维空间中的点,使语义相似的文本在这个空间中距离较近。 > * 例如,"肯德基"和"麦当劳"的向量可能会比"肯德基"和"新疆大盘鸡"的向量更接近 ![image-20251223225228972](https://i-blog.csdnimg.cn/img_convert/f7b9739dd129aff0f4f6e7fee28a99e2.png) ### 12.5 开发步骤 #### 12.5.1 建module(SAA-11Embed2vector) #### 12.5.2 改pom ```xml 4.0.0 com.zsh.test SpringAIAlibaba-zsh_test1 0.0.1-SNAPSHOT com.zsh.test SAA-11Embed2vector 0.0.1-SNAPSHOT SAA-11Embed2vector SAA-11Embed2vector 17 org.springframework.boot spring-boot-starter-web com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope ${SpringAIAlibaba.version} org.springframework.ai spring-ai-starter-vector-store-redis ${spring-ai.version} org.projectlombok lombok 1.18.38 cn.hutool hutool-all 5.8.22 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin 3.11.0 -parameters 21 21 spring-milestones Spring Milestones https://repo.spring.io/milestone false ``` #### 12.5.3 写yml ```yaml server: port: 8011 servlet: encoding: charset: utf-8 enabled: true force: true spring: application: name: SAA-11Embed2vector # ====SpringAIAlibaba Config============= ai: dashscope: api-key: ${aliQwen-api} chat: options: model: qwen-plus embedding: options: model: text-embedding-v3 # =======Redis Stack========== vectorstore: redis: initialize-schema: true index-name: custom-index prefix: custom-prefix data: redis: host: localhost port: 6379 username: default password: "" ``` (1)阿里云百炼平台向量大模型 [大模型服务平台百炼控制台](https://bailian.console.aliyun.com/?tabapi=&tab=model#/model-market/all?capabilities=TR) ![image-20251229215921034](https://i-blog.csdnimg.cn/img_convert/b839db97c6aef5dea829fa21acff9701.png) (2)配置参考信息来源和知识出处 https://docs.spring.io/spring-ai/reference/api/vectordbs/redis.html ![image-20251229215725369](https://i-blog.csdnimg.cn/img_convert/551faa4a9d868dd3a52ff462b48f8e0b.png) #### 12.5.4 主启动 ```java package com.atguigu.study; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Saa11Embed2vectorApplication { public static void main(String[] args) { SpringApplication.run(Saa11Embed2vectorApplication.class, args); } } ``` #### 12.5.5 用redisStack作为向量存储 RedisStack=**原生Redis +搜索 +图 +时间序列+JSON +概率结构 +可视化工具+开发框架支持** 官网:https://docs.spring.io/spring-ai/reference/api/vectordbs/redis.html ![image-20251229220933022](https://i-blog.csdnimg.cn/img_convert/b1194f4428b61e7e8884d21bb86ea3bd.png) ##### (1)RedisStack是什么 ![image-20251229221033635](https://i-blog.csdnimg.cn/img_convert/ad72976eb6de19d9d0bfa30e5423584f.png) Redis Stack 是 Redis Labs 推出的一个\*\*"增强版 Redis",不是 Redis 的替代品,而是在原生 Redis 基 础上的功能扩展包\*\*,专为构建现代实时应用而设计。 ##### (2)RedisStack相比原生Redis 的优势 ![image-20251229221212619](https://i-blog.csdnimg.cn/img_convert/0c716fac7c9f4bfe36c009ffbbfe1fdd.png) ##### (3)RedisStack核心组件 * RediSearch::提供全文搜索能力,支持复杂的文本搜索、聚合和过滤,以及向量数据的存储和检索 * RedisJSON:原生支持JSON数据的存储、索引I和查询,可高效存储和操作嵌套的JSON文档。 * RedisGraph:支持图数据模型,使用Cypher查询语言进行图遍历查询。 * RedisBloom:支持 Bloom、Cuckoo、Count-Min Sketch等概率数据结构. ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png) ##### (4)RedisStack安装 linux下,命令: ```bash docker run -d --name redis-stack-server -p 6379:6379 redis/redis-stack-server ``` windows下安装步骤: 下载地址:[Docker: Accelerated Container Application Development](https://www.docker.com/) 安装步骤:[Windows安装Docker(Docker Desktop)_docker-desktop-CSDN博客](https://blog.csdn.net/sgx1825192/article/details/146965328) ![image-20251229223145055](https://i-blog.csdnimg.cn/img_convert/a52de3ce1acfa9873848b9a9078705ed.png) ![image-20260103161028773](https://i-blog.csdnimg.cn/img_convert/6337d044b71178bb04dcfac1d3a7b9f7.png) ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png) ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png) ```json { "builder": { "gc": { "defaultKeepStorage": "20GB", "enabled": true } }, "experimental": false, "registry-mirrors": [ "https://docker.xuanyuan.me", "https://docker.1ms.run" ] } ``` ![image-20260103224251696](https://i-blog.csdnimg.cn/img_convert/bfe99637d49958f33b2924f3484e7140.png) ![image-20260104215039233](https://i-blog.csdnimg.cn/img_convert/98c3ba33b7d83c2b3187323179a4a98c.png) #### 12.5.6 业务类 ##### (1)知识出处 官网:https://docs.spring.io/spring-ai/reference/api/vectordbs.html ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png) ##### (2)controller * 文本向量化 * 向量化存储 * 向量化查询 ```java package com.zsh.test.saa11.controller; import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.embedding.EmbeddingRequest; import org.springframework.ai.embedding.EmbeddingResponse; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.Arrays; import java.util.List; /** * @author ZhaoShuhao * @data 2026/1/4 21:57 */ @RestController @Slf4j public class Embed2VectorController { @Resource private EmbeddingModel embeddingModel; @Resource private VectorStore vectorStore; /** * 文本向量化 * http://localhost:8011/text2embed?msg=射雕英雄传 * * @param msg * @return */ @GetMapping("/text2embed") public EmbeddingResponse text2Embed(String msg) { //EmbeddingResponse embeddingResponse = embeddingModel.call(new EmbeddingRequest(List.of(msg), null)); EmbeddingResponse embeddingResponse = embeddingModel.call(new EmbeddingRequest(List.of(msg), DashScopeEmbeddingOptions.builder().withModel("text-embedding-v3").build())); System.out.println(Arrays.toString(embeddingResponse.getResult().getOutput())); return embeddingResponse; } /** * 添加向量 * http://localhost:8011/embed2vector/add * * @return */ @GetMapping("/embed2vector/add") public void add() { List documents = List.of( new Document("i study LLM"), new Document("i love java"), new Document("文本向量化测试") ); vectorStore.add(documents); } /** * 获取向量 * http://localhost:8011/embed2vector/get?msg=LLM * * @param msg * @return */ @GetMapping("/embed2vector/get") public List getAll(@RequestParam(name = "msg") String msg) { SearchRequest searchRequest = SearchRequest.builder() .query(msg) .topK(2) .build(); List list = vectorStore.similaritySearch(searchRequest); System.out.println(list); return list; } } ``` ##### (3)测试 文本向量化展示: ```http http://localhost:8011/text2embed?msg=射雕英雄传 ``` ![image-20260104221148537](https://i-blog.csdnimg.cn/img_convert/5b29a881519691c2d03f2acfd22cab18.png) 文本向量化添加: http://localhost:8011/embed2vector/add ```bash # redis-cli -p 6379 --raw 127.0.0.1:6379> keys * sample_bicycle:1005 sample_session:678901234 sample_bicycle:1096 custom-prefix26cba1d6-da9f-4934-93a2-1d4203fcf61a custom-prefix68dc3792-eb9d-42d3-9bb6-902dba9ff3c3 custom-prefix14189354-8cbc-4c56-944f-fa089a751133 custom-prefix31ea7e68-8c53-4814-9115-61908bfc8f3d ``` ![image-20260104222148506](https://i-blog.csdnimg.cn/img_convert/0fee8345e4967918d2e031c1114130db.png) ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png) 文本向量化查询:越接近哪个,就会查出哪个 ```http http://localhost:8011/embed2vector/get?msg=文本 ``` ![image-20260104221812454](https://i-blog.csdnimg.cn/img_convert/c5a36ae2b14092aeb1ccc272832614ac.png) ### 12.6 知识图谱 ![image-20251223225800723](https://i-blog.csdnimg.cn/img_convert/630ede012f0fa1a5ed5d9db82dda9c7b.png) ## 十三、RAG (Retrieval Augmented Generation) * 解释:**检索增强生成** * 作用:AI智能运维助手,通过提供的错误编码,给出异常解释,辅助运维人员更好的定位问题和维护系统 * 怎么实现:**SpringAl+阿里百炼嵌入模型text-embedding-v3+向量数据库Redisstack+DeepSeek来实现RAG功能。** ### 13.1 LLM的缺陷 * LLM的知识不是实时的,不具备知识更新. * LLM可能不知道你私有的领域/业务知识 * LLM有时会在回答中生成看似合理但实际上是错误的信息 ### 13.2 RAG简介 #### 13.2.1 官网 ##### (1)RAG ![image-20260104231716850](https://i-blog.csdnimg.cn/img_convert/c8f6e864dcf97d3bcf9645e3bd2597f4.png) ![image-20260104231752941](https://i-blog.csdnimg.cn/img_convert/c620331e3d54e97ec3f6d1e2b4ffcaf5.png) 什么是幻觉?已读乱回、已读不回、似是而非 ##### (2)springai https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html ![image-20260104232025461](https://i-blog.csdnimg.cn/img_convert/2876093e5c7eb5b4a0f519a441dec468.png) ##### (3)springai alibaba 文档检索 (Document Retriever) https://java2ai.com/docs/1.0.0.2/tutorials/basics/retriever/?spm=5176.29160081.0.0.2856aa5cXggpMl ![image-20260104232202150](https://i-blog.csdnimg.cn/img_convert/edc2e0df4e7fd4a51ba7c12634f279b9.png) ![image-20260104232235291](https://i-blog.csdnimg.cn/img_convert/0a09195342e7857e79b9a5e11812ab9b.png) #### 13.2.2 核心设计概念 * RAG技术就像给AI大模型装上了「实时百科大脑」,为了让大模型获取足够的上下文,以便获得更加广泛的信息源,通过先查资料后回答的机制,让AI摆脱传统模型的"知识遗忘和幻觉回复"困境 * 类似考试时有不懂的,给你准备了小抄,对大模型知识盲区的一种补充 ### 13.3 能干嘛 ​ 通过引入外部知识源来增强LLM的输出能力,传统的LLM通常基于其训练数据生成响应,但这些数据可能**过时或不够全面** 。**RAG允许型在生成答案之前,从特定的知识库中检索相关信息,从而提供更准确和上下文相关的回答** ### 13.4 怎么玩 RAG流程分为两个不同的阶段:索引和检索 ![image-20260104232637584](https://i-blog.csdnimg.cn/img_convert/4f99e4727e474b2578746452005402c6.png) ![image-20260104232756794](https://i-blog.csdnimg.cn/img_convert/232252210479030a238521ac76218de3.png) ![image-20260104232822415](https://i-blog.csdnimg.cn/img_convert/3228c8fc7162c944bf84452a45547362.png) ### 13.5 开发步骤 #### 13.5.1 建module(SAA-12RAG4AiOps),改pom ```xml 4.0.0 com.zsh.test SpringAIAlibaba-zsh_test1 0.0.1-SNAPSHOT com.zsh.test SAA-12RAG4AiOps 0.0.1-SNAPSHOT SAA-12RAG4AiOps SAA-12RAG4AiOps 17 org.springframework.boot spring-boot-starter-web com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope ${SpringAIAlibaba.version} org.springframework.ai spring-ai-starter-vector-store-redis ${spring-ai.version} org.projectlombok lombok 1.18.38 cn.hutool hutool-all 5.8.22 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin 3.11.0 -parameters 17 17 spring-milestones Spring Milestones https://repo.spring.io/milestone false ``` #### 13.5.2 写yml ![image-20260105224557331](https://i-blog.csdnimg.cn/img_convert/a8fe2cb39e9f7f5bfbabf823732aa925.png) ![image-20260105224637739](https://i-blog.csdnimg.cn/img_convert/c934d9c974133e644d2460012ca0174d.png) 官网:https://docs.spring.io/spring-ai/reference/api/vectordbs/redis.html ![image-20260105224908034](https://i-blog.csdnimg.cn/img_convert/d53035f4d79652e8737fb2afd07bd6f1.png) ![image-20260105225019917](https://i-blog.csdnimg.cn/img_convert/0bf30192b3bdc92eb42d388f7ebc628d.png) ```yaml server: port: 8012 servlet: encoding: enabled: true force: true charset: UTF-8 spring: application: name: SAA-12RAG4AiDatabase ai: dashscope: api-key: ${aliQwen-api} chat: options: model: deepseek-r1 embedding: options: model: text-embedding-v3 vectorstore: redis: initialize-schema: true index-name: atguigu-index prefix: atguigu-prefix data: redis: host: localhost port: 6379 username: default password: ``` #### 13.5.3 主启动 ```java package com.zsh.test.saa12; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Saa12Rag4AiOpsApplication { public static void main(String[] args) { SpringApplication.run(Saa12Rag4AiOpsApplication.class, args); } } ``` ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png) #### 13.5.4 业务类 ##### (1)形成文档知识库 提供ErrorCode脚本让他存入向量数据库RedisStack,形成文档知识库 ```txt 00000 系统OK正确执行后的返回 A0001 用户端错误一级宏观错误码 A0100 用户注册错误二级宏观错误码 B1111 支付接口超时 C2222 Kafka消息解压严重 ``` ![image-20260105230436013](https://i-blog.csdnimg.cn/img_convert/672a42d1487ffd5fdfc526dddab96f6a.png) ##### (2)SpringAI源代码接口 ![image-20260105230610661](https://i-blog.csdnimg.cn/img_convert/2888b7cfbc5125761a1d1e151a4bde8e.png) ##### (3)用redis作为向量存储 ```bash docker run -d -name redis-stack-server -p 6379:6379 redis/redis-stack-server ``` ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png) ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png) ##### (4)配置类(第一版) ```java package com.zsh.test.saa12.config; import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author ZhaoShuhao * @data 2026/1/5 23:09 */ @Configuration public class SaaLLMConfig { // 模型名称常量定义 private final String DEEPSEEK_MODEL = "deepseek-v3"; private final String QWEN_MODEL = "qwen-plus"; @Bean(name = "deepseek") public ChatModel deepSeek() { return DashScopeChatModel.builder() .dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv("aliQwen-api")) .build()) .defaultOptions( DashScopeChatOptions.builder().withModel(DEEPSEEK_MODEL).build() ) .build(); } @Bean(name = "qwen") public ChatModel qwen() { return DashScopeChatModel.builder().dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv("aliQwen-api")) .build()) .defaultOptions( DashScopeChatOptions.builder() .withModel(QWEN_MODEL) .build() ) .build(); } @Bean(name = "deepseekChatClient") public ChatClient deepseekChatClient(@Qualifier("deepseek") ChatModel deepSeek) { return ChatClient.builder(deepSeek) .defaultOptions(ChatOptions.builder() .model(DEEPSEEK_MODEL) .build()) .build(); } @Bean(name = "qwenChatClient") public ChatClient qwenChatClient(@Qualifier("qwen") ChatModel qwen) { return ChatClient.builder(qwen) .defaultOptions(ChatOptions.builder() .model(QWEN_MODEL) .build()) .build(); } } ``` ```java package com.zsh.test.saa12.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; /**InitVectorDatabaseConfig第一版 * @author ZhaoShuhao * @data 2026/1/5 23:10 */ @Configuration public class InitVectorDatabaseConfig { @Autowired private VectorStore vectorStore; @Value("classpath:study/ops.txt") private Resource sqlFile; @PostConstruct public void init() { // 1.读取文件 TextReader textReader = new TextReader(sqlFile); textReader.setCharset(Charset.defaultCharset()); // 2.文件转换成向量(分词) List list = new TokenTextSplitter().transform(textReader.read()); // 3.写入向量数据库(Redis),无法去重复版 vectorStore.add(list); } } ``` ##### (5)controller(第一版) ```java package com.zsh.test.saa12.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; /** * @author ZhaoShuhao * @data 2026/1/5 23:14 */ @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 rag(String msg) { String systemInfo = """ 你是一个运维工程师,按照给出的编码给出对应故障解释,否则回复找不到信息。 """; RetrievalAugmentationAdvisor advisor = RetrievalAugmentationAdvisor.builder() .documentRetriever( VectorStoreDocumentRetriever.builder() .vectorStore(vectorStore) .build() ) .build(); return chatClient.prompt() .system(systemInfo) .user(msg) .advisors(advisor) // RAG功能,向量数据库查询 .stream() .content(); } } ``` ##### (6)测试(第一版) http://localhost:8012/rag4aiops?msg=A0001 http://localhost:8012/rag4aiops?msg=00000 ![image-20260105231852962](https://i-blog.csdnimg.cn/img_convert/0820abb86cc2f47a5d899f725d5e29be.png) ##### (7)第一版问题 重启服务后,发现重复数据写入问题需考虑,不然每次重启都要新增 ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png) ##### (8)向量数据库去重问题解决(第二版) **使用RedisSetNx去重** **新增RedisConfig** ```java package com.zsh.test.saa12.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; /** * @author ZhaoShuhao * @data 2026/1/5 23:22 */ @Configuration @Slf4j public class RedisConfig { /** * RedisTemplate配置 * redis序列化的工具配置类,下面这个请一定开启配置 * 127.0.0.1:6379> keys * * 1) "ord:102" 序列化过 * 2) "\xac\xed\x00\x05t\x00\aord:102" 野生,没有序列化过 * this.redisTemplate.opsForValue(); //提供了操作string类型的所有方法 * this.redisTemplate.opsForList(); // 提供了操作list类型的所有方法 * this.redisTemplate.opsForSet(); //提供了操作set的所有方法 * this.redisTemplate.opsForHash(); //提供了操作hash表的所有方法 * this.redisTemplate.opsForZSet(); //提供了操作zset的所有方法 * @param redisConnectionFactor * @return */ @Bean public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactor) { RedisTemplate 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; } } ``` **InitVectorDatabaseConfig数据去重处理(第二版)**:性能高+线程安全问题OK ```java package com.zsh.test.saa12.config; import cn.hutool.crypto.SecureUtil; 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 org.springframework.data.redis.core.RedisTemplate; import java.nio.charset.Charset; import java.util.List; /** * @author ZhaoShuhao * @data 2026/1/5 23:10 */ @Configuration public class InitVectorDatabaseConfig { @Autowired private VectorStore vectorStore; @Value("classpath:study/ops.txt") private Resource sqlFile; //使用redish 作为向量数据库,并且新增加重复数据处理 @Autowired private RedisTemplate redisTemplate; @PostConstruct public void init() { // 1.读取文件 TextReader textReader = new TextReader(sqlFile); textReader.setCharset(Charset.defaultCharset()); // 2.文件转换成向量(分词) List list = new TokenTextSplitter().transform(textReader.read()); // 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("------向量初始化数据已经加载过,请不要重复操作"); } } } ``` ![image-20260105232952467](https://i-blog.csdnimg.cn/img_convert/dc847999492e282e5ef51d728a6fec59.png) ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png) ## 十四、Tool Calling工具调用 ### 14.1 是什么? * ToolCalling(也称为FunctionCalling)它允许大模型与一组API或工具进行交互,将LLM的智能与外部工具或API无缝连接,从而增强大模型其功能。 * LLM本身并不执行函数,它只是指示应该调用哪个函数以及如何调用 * 一句话表示:**LLM的外部utils工具类** #### 14.1.1 SpringAI https://docs.spring.io/spring-ai/reference/api/tools.html ![image-20260113223647211](https://i-blog.csdnimg.cn/img_convert/c99af1ae176ee3eece6cbfc025d3e40d.png) #### 14.1.2 SpringAI Alibaba https://java2ai.com/docs/1.0.0.2/tutorials/basics/tool-calling/?spm=5176.29160081.0.0.2856aa5cgvn0gm ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png) https://java2ai.com/docs/frameworks/agent-framework/advanced/agent-tool#tool-calling ![image-20260113223942997](https://i-blog.csdnimg.cn/img_convert/5c7cc61b99a5ff67e2c901f81afdceee.png) ### 14.2 能干嘛? #### 14.2.1 访问实时数据 ![image-20260113224143850](https://i-blog.csdnimg.cn/img_convert/10723c4a022b9b6e93d32962e99ff45e.png) #### 14.2.2 执行某种工具类/辅助类操作 * 大语言模型(LLMs)不仅仅是文本生成的能手 * 它们还能触发并调用第3方函数 * 比如:发邮件/查询微信/调用支付宝/查看顺丰快递单据号等等. ### 14.3 怎么玩? 工作流程如下: ![image-20260113224504611](https://i-blog.csdnimg.cn/img_convert/b58e902135977afd346b1a49a4f8faa4.png) ### 14.4 开发步骤 #### 14.4.1 新建子模块(SAA-13ToolCalling) ![image-20260113224927760](https://i-blog.csdnimg.cn/img_convert/35ac7a5eee466182f555c7402bf2634f.png) #### 14.4.2 改pom ```xml 4.0.0 com.zsh.test SpringAIAlibaba-zsh_test1 0.0.1-SNAPSHOT com.zsh.test SAA-13ToolCalling 0.0.1-SNAPSHOT SAA-13ToolCalling SAA-13ToolCalling 17 org.springframework.boot spring-boot-starter-web com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope ${SpringAIAlibaba.version} org.projectlombok lombok 1.18.38 cn.hutool hutool-all 5.8.22 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin 3.11.0 -parameters 17 17 spring-milestones Spring Milestones https://repo.spring.io/milestone false ``` #### 14.4.3 写yml ```yaml server: port: 8013 servlet: encoding: enabled: true force: true charset: UTF-8 spring: application: name: SAA-13ToolCalling ai: dashscope: api-key: ${aliQwen-api} ``` #### 14.4.4 主启动 ```java package com.zsh.test.saa13tool; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Saa13ToolCallingApplication { public static void main(String[] args) { SpringApplication.run(Saa13ToolCallingApplication.class, args); } } ``` ![image-20260113225357619](https://i-blog.csdnimg.cn/img_convert/f4e137d0e8d2d7bdf0f7a26b248b49ce.png) #### 14.4.5 业务类 ##### 14.4.5.1 先不使用ToolCalling ###### (1)没有配置LLMConfig ###### (2)controller ```java package com.zsh.test.saa13tool.controller; import jakarta.annotation.Resource; import org.springframework.ai.chat.model.ChatModel; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; /** * @author ZhaoShuhao * @data 2026/1/13 22:55 */ @RestController public class NoToolCallingController { @Resource private ChatModel chatModel; @GetMapping("/notoolcall/chat") public Flux chat(@RequestParam(name = "msg",defaultValue = "你是谁现在几点") String msg) { return chatModel.stream(msg); } } ``` ###### (3)测试 http://localhost:8013/notoolcall/chat ![image-20260113225752064](https://i-blog.csdnimg.cn/img_convert/36f6c8a8567333b3e4174365c2b0940c.png) ##### 14.4.5.2 使用ToolCalling ###### 14.4.5.2.1 通过ChatModel实现 ###### (1)没有配置LLMConfig ###### (2)新建Tool工具类,类似utils工具类 ```java package com.zsh.test.saa13tool.util; import org.springframework.ai.tool.annotation.Tool; import java.time.LocalDateTime; /** * @author ZhaoShuhao * @data 2026/1/13 23:01 */ public class DateTimeTools { /** * 1.定义 function call(tool call) * 2. returnDirect * true = tool直接返回不走大模型,直接给客户 * false = 拿到tool返回的结果,给大模型,最后由大模型回复 */ @Tool(description = "获取当前时间", returnDirect = false) public String getCurrentTime() { return LocalDateTime.now().toString(); } } ``` ![image-20260113230611841](https://i-blog.csdnimg.cn/img_convert/666a6617f67426210cf796962a12f095.png) ###### (3)controller ```java package com.zsh.test.saa13tool.controller; import com.zsh.test.saa13tool.util.DateTimeTools; import jakarta.annotation.Resource; 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; /** * @author ZhaoShuhao * @data 2026/1/13 23:06 */ @RestController public class ToolCallingController { @Resource private ChatModel chatModel; @GetMapping("/toolcall/chat") public String chat(@RequestParam(name = "msg",defaultValue = "你是谁,现在几点了") String msg) { // 1.工具注册到工具集合里 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(); } } ``` ###### (4)测试 http://localhost:8013/toolcall/chat ![image-20260113230854925](https://i-blog.csdnimg.cn/img_convert/9f6662c16b77618fd2d7f1a577740429.png) ###### 14.4.5.2.2 通过ChatClient实现 ###### (1)ChatClient本身不会主动装配,直接定义无法使用 ![image-20260113231002666](https://i-blog.csdnimg.cn/img_convert/0e39e9be41a72a47192b35f8753d8d53.png) ###### (2)配置类LLMConfig ```java package com.zsh.test.saa13tool.config; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author ZhaoShuhao * @data 2026/1/13 23:10 */ @Configuration public class SaaLLMConfig { @Bean public ChatClient chatClient(ChatModel chatModel) { return ChatClient.builder(chatModel).build(); } } ``` ###### (3)controller ```java @RestController public class ToolCallingController { @Resource private ChatModel chatModel; @Resource private ChatClient chatClient; @GetMapping("/toolcall/chat") public String chat(@RequestParam(name = "msg",defaultValue = "你是谁现在几点") String msg) { // 1.工具注册到工具集合里 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(); } @GetMapping("/toolcall/chat2") public Flux chat2(@RequestParam(name = "msg",defaultValue = "你是谁现在几点") String msg) { return chatClient.prompt(msg) .tools(new DateTimeTools()) .stream() .content(); } } ``` ###### (4)测试 http://localhost:8013/toolcall/chat2 ![image-20260113231501504](https://i-blog.csdnimg.cn/img_convert/cdf9a3ab238834a322a7bbb50427a558.png) ![image-20260113231624360](https://i-blog.csdnimg.cn/img_convert/8084a8c33f5123c7dee3833594833179.png) ![image-20260113231906560](https://i-blog.csdnimg.cn/img_convert/130da3033f2cf181888f9132ba639f54.png) ![image-20260113231731863](https://i-blog.csdnimg.cn/img_convert/18ab0f4488c78babc3846af1f3aae2ae.png) ![image-20260113231826935](https://i-blog.csdnimg.cn/img_convert/05f13906fec3faa284a4ee345addd4c5.png) #### 14.4.6 小总结 * 新建定义一个Too红具类 * ChatModel/ChatClient使用 * Tool Calling使用注意事项:ToolCalling使用的前提是大模型支持functioncall才能正常调用。 ## 十五、MCP模型上下文协议(Model Context Protocol) ### 15.1 为什么会有MCP出现,之前痛点是什么? (1)之前每个大模型(如DeepSeek、ChatGPT)需要为每个工具单独开发接口((FunctionCalling),导致重复劳动 (2)痛点:共用+数量 ![image-20260120223954824](https://i-blog.csdnimg.cn/img_convert/ab1a0a75f5fc7f9efb01aa0e215ff533.png) ### 15.2 MCP入门概念 #### 15.2.1 官网 (1)MCP自身协议官网:https://modelcontextprotocol.io/introduction ![image-20260120224355056](https://i-blog.csdnimg.cn/img_convert/14c58466e5bc464898cd1f56f9fe0255.png) (2)SpringAI官网支持MCP:https://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png) (3)SpringAI Aibaba官网支持MCP:https://java2ai.com/integration/mcps/mcp-overview ![image-20260120225339385](https://i-blog.csdnimg.cn/img_convert/675aab29a46e0000f34c72a1955787a5.png) #### 15.2.2 是什么?能干嘛?怎么玩? **(1)是什么?** ​ Java界的SpringCloud Openfeign,只不过Openfeign是用于微服务通讯的,而MCP用于**大模型通讯** 的,但它们都是**为了通讯获取某项数据**的一种机制 **(2)能干嘛?** ​ 提供了一种**标准化** 的方式来连接LLMs需要的上下文,MCP就类似于一个Agent时代的Type-C协议,希望能**将不同来源的数据、工具、服务统一起来供大模型调用** ![image-20260120225918425](https://i-blog.csdnimg.cn/img_convert/eecabb47b976566cfb95ade65899b4ec.png) ![image-20260120225953852](https://i-blog.csdnimg.cn/img_convert/ce8babdddfbdfcc3a599c0f550305cc9.png) **(3)怎么玩?** 调用上万个通用的MCP:https://mcp.so/zh ![image-20260120230241569](https://i-blog.csdnimg.cn/img_convert/595d2693de9736151b27675458744335.png) ### 15.3 MCP架构知识 #### 15.3.1 MCP架构 ![image-20260120230524830](https://i-blog.csdnimg.cn/img_convert/0855f9886ae85446c6437ffed09f4201.png) ![image-20260120230821385](https://i-blog.csdnimg.cn/img_convert/130883cb15a57cb16da04f04025b5b23.png) #### 15.3.2 通信协议-俩种模式 ![image-20260120231048018](https://i-blog.csdnimg.cn/img_convert/3f5203f1d33c949504cf3a64d99c49be.png) ![image-20260120231222609](https://i-blog.csdnimg.cn/img_convert/0f58100807bd74a186e85dbe40c87503.png) ### 15.4 小总结 * TooICalling:工具类,为了让大模型使用Util工具 * RAG:知识库,为了让大模型获取足够的上下文 * MCP协议,为了让大模型之间的相互调用 ![image-20260120231841679](https://i-blog.csdnimg.cn/img_convert/e704d1407ddb8c1355b900e20944dc7b.png) ![image-20260120231706696](https://i-blog.csdnimg.cn/img_convert/3b90e0e185b0401ad8d7fa846ac4ad98.png) ![image-20260120231736061](https://i-blog.csdnimg.cn/img_convert/9e6cb0e76baea4b21f44e20341a8669a.png) ![image-20260120231546220](https://i-blog.csdnimg.cn/img_convert/b273d9e41922e5b807091d4579ffb28b.png) ### 15.5 本地MCP-开发步骤 #### 15.5.1 MCP-Server服务端实现 ##### (1)新建子模块SAA-14LocalMcpServer ![image-20260122214323399](https://i-blog.csdnimg.cn/img_convert/356b37767d3805519be90e0bd7304d51.png) ##### (2)改pom ```xml 4.0.0 com.zsh.test SpringAIAlibaba-zsh_test1 0.0.1-SNAPSHOT SAA-14LocalMcpServer 0.0.1-SNAPSHOT SAA-14LocalMcpServer SAA-14LocalMcpServer 17 org.springframework.boot spring-boot-starter org.springframework.ai spring-ai-starter-mcp-server-webflux 1.0.1 compile org.projectlombok lombok 1.18.38 cn.hutool hutool-all 5.8.22 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin 3.11.0 -parameters 17 17 spring-snapshots Spring Snapshots https://repo.spring.io/snapshot true spring-milestones Spring Milestones https://repo.spring.io/milestone false ``` ##### (3)写yml ```yaml # 服务器配置 server: port: 8014 # 应用程序运行端口 servlet: encoding: # 配置字符编码 enabled: true # 启用字符编码设置 force: true # 强制使用指定字符编码 charset: UTF-8 # 字符编码格式 # Spring应用配置 spring: application: name: SAA-14LocalMcpServer # 应用名称 # Spring AI MCP服务器配置 ai: mcp: server: type: async # 服务器类型 - 异步 name: customer-define-mcp-server # 服务器名称 version: 1.0.0 # 服务器版本号 ``` ##### (4)主启动 ```java package com.zsh.test.saa14; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Saa14LocalMcpServerApplication { public static void main(String[] args) { SpringApplication.run(Saa14LocalMcpServerApplication.class, args); } } ``` ##### (5)业务类 * 天气预报WeatherService服务类 ```java package com.zsh.test.saa14.service; import org.springframework.ai.tool.annotation.Tool; import org.springframework.stereotype.Service; import java.util.Map; /** * @author ZhaoShuhao * @data 2026/1/22 22:03 */ @Service public class WeatherService { @Tool(description = "根据城市名称获取天气预报") public String getWeatherByCity(String city) { Map map = Map.of( "北京", "11111降雨频繁,其中今天和后天雨势较强,部分地区有暴雨并伴强对流天气,需注意", "上海", "22222多云,15℃~27℃,南风3级,当前温度27℃。", "深圳", "333333多云40天,阴16天,雨30天,晴3天" ); return map.getOrDefault(city, "抱歉:未查询到对应城市!"); } } ``` * ToolCal1backProvider接口配置类 ```java package com.zsh.test.saa14.config; import com.zsh.test.saa14.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; /** * @author ZhaoShuhao * @data 2026/1/22 22:05 */ @Configuration public class McpServerConfig { /** * 将工具方法暴露给外部 mcp client 调用 * @param weatherService * @return */ @Bean public ToolCallbackProvider weatherTools(WeatherService weatherService) { return MethodToolCallbackProvider.builder() .toolObjects(weatherService) .build(); } } ``` ##### (6)自启动作为服务器端等待客户端调用即可 ![image-20260122220804323](https://i-blog.csdnimg.cn/img_convert/e4dea8efd79c94425672c2255b8c1201.png) #### 15.5.2 MCP-Client客户端实现 ##### (1)新建子模块SAA-15LocalMcpClient ![image-20260122221205280](https://i-blog.csdnimg.cn/img_convert/44c0a27ee6a6080fc5b8ad0d994c47a2.png) ##### (2)改pom ```java 4.0.0 com.zsh.test SpringAIAlibaba-zsh_test1 0.0.1-SNAPSHOT SAA-15LocalMcpClient 0.0.1-SNAPSHOT SAA-15LocalMcpClient SAA-15LocalMcpClient 17 org.springframework.boot spring-boot-starter-web com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope ${SpringAIAlibaba.version} org.springframework.ai spring-ai-starter-mcp-client 1.0.1 compile org.projectlombok lombok 1.18.38 cn.hutool hutool-all 5.8.22 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin 3.11.0 -parameters 17 17 spring-milestones Spring Milestones https://repo.spring.io/milestone false ``` ##### (3)写yml ```yaml server.port=8015 # 设置全局编码格式 server.servlet.encoding.enabled=true server.servlet.encoding.force=true server.servlet.encoding.charset=UTF-8 spring.application.name=SAA-15LocalMcpClient # ====SpringAIAlibaba Config============= spring.ai.dashscope.api-key=${aliQwen-api} # ====mcp-client Config 连接MCP server服务============= spring.ai.mcp.client.type=async spring.ai.mcp.client.request-timeout=60s spring.ai.mcp.client.toolcallback.enabled=true spring.ai.mcp.client.sse.connections.mcp-server1.url=http://localhost:8014 ``` ##### (4)主启动 ```java package com.zsh.test.saa15; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Saa15LocalMcpClientApplication { public static void main(String[] args) { SpringApplication.run(Saa15LocalMcpClientApplication.class, args); } } ``` ##### (5)业务类 * LLMConfig并添加tool调用 ```java package com.zsh.test.saa15.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; /** * @author ZhaoShuhao * @data 2026/1/22 22:23 */ @Configuration public class SaaLLMConfig { @Bean public ChatClient chatClient(ChatModel chatModel, ToolCallbackProvider tools) { return ChatClient.builder(chatModel) .defaultToolCallbacks(tools.getToolCallbacks()) //mcp协议,配置见yml文件 .build(); } } ``` * controller ```java package com.zsh.test.saa15.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; /** * @author ZhaoShuhao * @data 2026/1/22 22:25 */ @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 chat(@RequestParam(name = "msg",defaultValue = "北京") String msg) { System.out.println("使用了mcp"); return chatClient.prompt(msg).stream().content(); } @RequestMapping("/mcpclient/chat2") public Flux chat2(@RequestParam(name = "msg",defaultValue = "北京") String msg) { System.out.println("未使用mcp"); return chatModel.stream(msg); } } ``` ##### (6)MCP-Client invoke MCP-Server测试 * 使用mcp ![image-20260122224104543](https://i-blog.csdnimg.cn/img_convert/2abe3527fac5c00610918bc4917a9ed6.png) * 没有mcp支持,已读乱回 ![image-20260122224159852](https://i-blog.csdnimg.cn/img_convert/7d20f15eff6c7a3e2bf0829fc35763da.png) ### 15.6 远程MCP增强案例-对接互联网通用MCP服务(百度地图) #### 15.6.1 调用上万个通用的MCP 官网:https://mcp.so/zh ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png) #### 15.6.2 对接互联网通用MCP服务(百度地图) 官网:https://mcp.so/zh/server/baidu-map/baidu-maps ![image-20260122224637320](https://i-blog.csdnimg.cn/img_convert/0390778c07d194bddb0f130503dae9e9.png) #### 15.6.3 环境配置 ##### 15.6.3.1 原理说明 ![image-20260122224822118](https://i-blog.csdnimg.cn/img_convert/9dda5370a9fa6406ae3b1ca57f79f71d.png) ##### 15.6.3.2 下载最新版的NodeJS 官网: https://nodejs.org/zh-cn ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png) ![image-20260122225405683](https://i-blog.csdnimg.cn/img_convert/95ea0be36ed5d98dd1de634f07df7b4e.png) ##### 15.6.3.3 注册百度地图账号+申请API+Key (1)官网:https://lbsyun.baidu.com/ ![image-20260126214011981](https://i-blog.csdnimg.cn/img_convert/1fa2941642e16cf5c6e944c2cc34413d.png) (2)创建应用-申请api+key ![image-20260126214610486](https://i-blog.csdnimg.cn/img_convert/595e5988f272f1f1a2f111b514470900.png) ![image-20260126214721029](https://i-blog.csdnimg.cn/img_convert/4e76597f8e1b27893c7fb8b6440d4af4.png) ![image-20260126214804642](https://i-blog.csdnimg.cn/img_convert/57a0cdec406235ee70e22dab0903a460.png) (3)测试连接:[MCP Server 调试](https://mcp.so/zh/playground?server_uuid=74fb88aa-5cc6-46d6-a22e-d15493365891&server=baidu-map&tab=config) ![image-20260126215918097](https://i-blog.csdnimg.cn/img_convert/98bcb2e34c290c9f2161127ec24280e6.png) ##### 15.6.3.4 nodejs配置编码-Typescript接入(Claude for Desktop) ![image-20260126225611645](https://i-blog.csdnimg.cn/img_convert/b3c31271e654c93e60e76d606fd41abe.png) 参考文档:https://github.com/DonaldTrump-coder/Claude-for-Desktop-MCP?tab=readme-ov-file https://zhuanlan.zhihu.com/p/29820895586 (1)Claude for Desktop官网:https://claude.com/download ![image-20260126225517976](https://i-blog.csdnimg.cn/img_convert/8963519b76d0633de01d2c5625abc2ff.png) (2)下载Claude for Desktop ![image-20260128224750363](https://i-blog.csdnimg.cn/img_convert/062559c90772d925a6c074ac100723dd.png) (3)配置apikey ![image-20260128225322072](https://i-blog.csdnimg.cn/img_convert/48944835d11383c64445da045d495ff1.png) ![image-20260128225607392](https://i-blog.csdnimg.cn/img_convert/f0ed28b6360c7fee38f2606acae57188.png) (4)重启claude ![image-20260128232523882](https://i-blog.csdnimg.cn/img_convert/2310994167837e5f72d245082d832026.png) #### 15.6.4 新建子模块:SAA-16ChatMcpClientBaiduMcp ![image-20260126223224547](https://i-blog.csdnimg.cn/img_convert/54504166b9a110fa4bb68cf669cd4591.png) #### 15.6.5 改pom ```xml 4.0.0 com.zsh.test SpringAIAlibaba-zsh_test1 0.0.1-SNAPSHOT com.zsh.test SAA-16ChatMcpClientBaiduMcp 0.0.1-SNAPSHOT SAA-16ChatMcpClientBaiduMcp SAA-16ChatMcpClientBaiduMcp 17 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-web org.springframework.ai spring-ai-starter-model-openai 1.0.1 compile org.springframework.ai spring-ai-starter-mcp-client 1.0.1 compile cn.hutool hutool-all 5.8.22 org.projectlombok lombok 1.18.38 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin ``` #### 15.6.6 写yml ```yaml server.port=6016 spring.application.name=SAA-16ChatMcpClientBaiduMcp # 设置全局编码格式 server.servlet.encoding.enabled=true server.servlet.encoding.force=true server.servlet.encoding.charset=UTF-8 # ====LLM Config============= spring.ai.openai.api-key=${aliQwen-api} spring.ai.openai.base-url=https://dashscope.aliyuncs.com/compatible-mode spring.ai.openai.chat.options.model=qwen-plus # ====mcp-client Config============= spring.ai.mcp.client.toolcallback.enabled=true spring.ai.mcp.client.stdio.servers-configuration=classpath:/mcp-server.json ``` #### 15.6.7 mcp-server.json ```json { "mcpServers": { "baidu-map": { "command": "cmd", "args": ["/c", "npx", "-y", "@baidumap/mcp-server-baidu-map"], "env": {"BAIDU_MAP_API_KEY": "o5tPobdDbObuud2AvIiXHdeEJDFbTx2K"} } } } ``` ![image-20260128230431577](https://i-blog.csdnimg.cn/img_convert/d4818a243db7a349991e87e67ee895cb.png) #### 15.6.8 主启动 ```java package com.zsh.test.saa16; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Saa16ChatMcpClientBaiduMcpApplication { public static void main(String[] args) { SpringApplication.run(Saa16ChatMcpClientBaiduMcpApplication.class, args); } } ``` ![image-20260128230700885](https://i-blog.csdnimg.cn/img_convert/7f8dd50ddc02722aa30e550c977c7bf8.png) #### 15.6.9 业务类 ##### (1)LLMConfig ```java package com.zsh.test.saa16.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; /** * @author ZhaoShuhao * @data 2026/1/28 23:07 */ @Configuration public class SaaLLMConfig { @Bean public ChatClient chatClient(ChatModel chatModel, ToolCallbackProvider tools) { return ChatClient.builder(chatModel) //mcp协议,配置见yml文件,此处只赋能给ChatClient对象 .defaultToolCallbacks(tools.getToolCallbacks()) .build(); } ``` ##### (2)controller ```java package com.zsh.test.saa16.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.RestController; import reactor.core.publisher.Flux; /** * @author ZhaoShuhao * @data 2026/1/28 23:09 */ @RestController public class McpClientCallBaiDuMcpController { @Resource private ChatClient chatClient; //添加了MCP调用能力 @Resource private ChatModel chatModel; //没有添加MCP调用能力 /** * 添加了MCP调用能力 * http://localhost:6016/mcp/chat?msg=查询北京天气 * http://localhost:6016/mcp/chat?msg=查询61.149.121.66归属地 * http://localhost:6016/mcp/chat?msg=查询昌平到天安门路线规划 * * * @param msg * @return */ @GetMapping("/mcp/chat") public Flux chat(String msg) { return chatClient.prompt(msg).stream().content(); } /** * 没有添加MCP调用能力 *http://localhost:6016/mcp/chat2?msg=查询北京天气 * @param msg * @return */ @RequestMapping("/mcp/chat2") public Flux chat2(String msg) { return chatModel.stream(msg); } } ``` #### 15.6.10 测试 ##### (1)具备mcp能力 ![image-20260128232541208](https://i-blog.csdnimg.cn/img_convert/10442f2e5a5be281f69f599bb3cf0c19.png) ##### (2)不具备mcp能力 ![image-20260128232620360](https://i-blog.csdnimg.cn/img_convert/1942db92bfce63f017f261392855a98e.png) ### 15.7 MCP原理+源码分析 (1)原理 ![image-20260128233403712](https://i-blog.csdnimg.cn/img_convert/8ac5351f4c359e847d48a8d717dbc571.png) (2)下载到本地 ```bash npm i -g @baidumap/mcp-server-baidu-map npm config get prefix ``` ![image-20260128233622907](https://i-blog.csdnimg.cn/img_convert/a0d6ff0acc45f816b28493f00e2fd372.png) ![image-20260128233645003](https://i-blog.csdnimg.cn/img_convert/7cda22ce481ff4ce8d5341a8f163c46b.png) sU4-1770124013756)

15.6.5 改pom

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.zsh.test</groupId>
        <artifactId>SpringAIAlibaba-zsh_test1</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <groupId>com.zsh.test</groupId>
    <artifactId>SAA-16ChatMcpClientBaiduMcp</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>SAA-16ChatMcpClientBaiduMcp</name>
    <description>SAA-16ChatMcpClientBaiduMcp</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 1.大模型依赖 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-openai</artifactId>
            <version>1.0.1</version>
            <scope>compile</scope>
        </dependency>
        <!-- 2.mcp-clent 依赖 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-client</artifactId>
            <version>1.0.1</version>
            <scope>compile</scope>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.22</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.38</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

15.6.6 写yml

yaml 复制代码
server.port=6016

spring.application.name=SAA-16ChatMcpClientBaiduMcp
# 设置全局编码格式
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
server.servlet.encoding.charset=UTF-8


# ====LLM Config=============
spring.ai.openai.api-key=${aliQwen-api}
spring.ai.openai.base-url=https://dashscope.aliyuncs.com/compatible-mode
spring.ai.openai.chat.options.model=qwen-plus

# ====mcp-client Config=============
spring.ai.mcp.client.toolcallback.enabled=true
spring.ai.mcp.client.stdio.servers-configuration=classpath:/mcp-server.json

15.6.7 mcp-server.json

json 复制代码
{
  "mcpServers":
  {
    "baidu-map":
    {
      "command": "cmd",
      "args": ["/c", "npx", "-y", "@baidumap/mcp-server-baidu-map"],
      "env":  {"BAIDU_MAP_API_KEY": "o5tPobdDbObuud2AvIiXHdeEJDFbTx2K"}
    }
  }
}

外链图片转存中...(img-gQgGsqrW-1770124013757)

15.6.8 主启动

java 复制代码
package com.zsh.test.saa16;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Saa16ChatMcpClientBaiduMcpApplication {

    public static void main(String[] args) {
        SpringApplication.run(Saa16ChatMcpClientBaiduMcpApplication.class, args);
    }
}

外链图片转存中...(img-WFlR6w34-1770124013757)

15.6.9 业务类

(1)LLMConfig
java 复制代码
package com.zsh.test.saa16.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;

/**
 * @author ZhaoShuhao
 * @data 2026/1/28 23:07
 */
@Configuration
public class SaaLLMConfig
{
    @Bean
    public ChatClient chatClient(ChatModel chatModel, ToolCallbackProvider tools)
    {
        return ChatClient.builder(chatModel)
                //mcp协议,配置见yml文件,此处只赋能给ChatClient对象
                .defaultToolCallbacks(tools.getToolCallbacks())
                .build();
    }
(2)controller
java 复制代码
package com.zsh.test.saa16.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.RestController;
import reactor.core.publisher.Flux;

/**
 * @author ZhaoShuhao
 * @data 2026/1/28 23:09
 */
@RestController
public class McpClientCallBaiDuMcpController
{
    @Resource
    private ChatClient chatClient; //添加了MCP调用能力

    @Resource
    private ChatModel chatModel; //没有添加MCP调用能力

    /**
     * 添加了MCP调用能力
     * http://localhost:6016/mcp/chat?msg=查询北京天气
     * http://localhost:6016/mcp/chat?msg=查询61.149.121.66归属地
     * http://localhost:6016/mcp/chat?msg=查询昌平到天安门路线规划
     *
     *
     * @param msg
     * @return
     */
    @GetMapping("/mcp/chat")
    public Flux<String> chat(String msg)
    {
        return chatClient.prompt(msg).stream().content();
    }

    /**
     * 没有添加MCP调用能力
     *http://localhost:6016/mcp/chat2?msg=查询北京天气
     * @param msg
     * @return
     */
    @RequestMapping("/mcp/chat2")
    public Flux<String> chat2(String msg)
    {
        return chatModel.stream(msg);
    }
}

15.6.10 测试

(1)具备mcp能力

外链图片转存中...(img-vLjKQyy6-1770124013757)

(2)不具备mcp能力

外链图片转存中...(img-DpA3teiD-1770124013757)

15.7 MCP原理+源码分析

(1)原理

外链图片转存中...(img-VNbNTp8T-1770124013757)

(2)下载到本地

bash 复制代码
npm i -g @baidumap/mcp-server-baidu-map

npm config get prefix

外链图片转存中...(img-QiufP6JB-1770124013757)

外链图片转存中...(img-pkkmZR6I-1770124013758)

注:本文是自己整理的个人学习笔记,教学资源来自b站尚硅谷spring ai alibaba教程

相关推荐
IT·小灰灰2 小时前
基于DMXAPI与GLM-4.7-Flash构建零成本AI编程工作站:从API选型到流式生成实战
人工智能·aigc·ai编程
●VON2 小时前
React Native for OpenHarmony:深入剖析 Switch 组件的状态绑定、无障碍与样式定制
javascript·学习·react native·react.js·von
EnglishJun2 小时前
数据结构的学习(三)---双向链表与循环链表
数据结构·学习·链表
简佐义的博客2 小时前
跟着Nature学习如何联合多组学snRNA-seq + snATAC-seq + WGS+空间转录组分析重构肿瘤亚克隆演化树
学习·重构
weisian1512 小时前
进阶篇-11-数学篇-10--梯度在神经网络中的实际应用:从“猜答案”到“学会思考”的旅程
人工智能·深度学习·神经网络·梯度下降·反向传播·学习率·正向传播
im_AMBER2 小时前
Leetcode 112 两数相加 II
笔记·学习·算法·leetcode
jackywine62 小时前
从提示词工程Prompt Engineering 到 上下文工程 Context Engineering:和 AI 打交道的学问
人工智能
狮子座明仔2 小时前
AgentScope 深度解读:多智能体开发框架的工程化实践
人工智能·深度学习·语言模型·自然语言处理
卡兰芙的微笑2 小时前
编译鸿蒙6.0release版本出错
学习