一、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/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

2.3.4 能干嘛

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官网官网

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官网官网

1.3 配置门道和关键点
通过后续讲解配置规则,所有调用均基于OpenAl协议标准或者SpringAIAalibaba官方推荐模型服务灵积(DashScope)整合规则,实现一致的接口设计与规范,确保多模型切换的便利性,提供高度可扩展的开发支持
2.阿里云百炼平台入口官网
2.1 接入阿里百炼平台的通义模型

2.2 大模型调用三件套
2.2.1 获得API-key

2.2.2获得模型名
选择一个模型,点击它

模型编号/模型名称

查看API参考

2.2.3 拿到base_url开发地址

2.3小总结
2.3.1 API key

2.3.2 模型名

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

3.IDEA工具中建project父工程
3.1 创建SpringAIAlibaba-zsh_test1

3.2 使用bom管理依赖版本


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">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.zsh.test</groupId>
<artifactId>SpringAIAlibaba-zsh_test1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>SpringAIAlibaba-Maven 父工程 POM 配置</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<java.version>17</java.version>
<!-- Spring Boot 新建 2025.9-->
<spring-boot.version>3.5.7</spring-boot.version>
<!-- Spring AI 新建 2025.9-->
<spring-ai.version>1.0.0</spring-ai.version>
<!-- Spring AI Alibaba 新建 2025.9-->
<SpringAIAlibaba.version>1.0.0.2</SpringAIAlibaba.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>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring AI Alibaba -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-bom</artifactId>
<version>${SpringAIAlibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring AI -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
3.3 开发5步骤
3.3.1 建Module


3.3.2 改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-01HelloWorld</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SAA-01HelloWorld</name>
<description>SAA-01HelloWorld</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入 springai alibaba DashScope 模型适配的Starter -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${SpringAIAlibaba.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
(1)模型服务灵积(DashScope)/阿里云百炼大模型服务平台
模型服务灵积(DashScope):https://dashscope.aliyun.com/

升级后叫做:阿里云百炼大模型服务平台:阿里云百炼大模型服务平台 - 1T参数量Qwen3-Max-Preview免费体验

(2)核心组件-知识出处
Spring AI Alibaba 可用组件列表与使用指南-阿里云Spring AI Alibaba官网官网

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


(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官网官网

- 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<String> streamChat(@RequestParam(name = "msg", defaultValue = "你是谁") String msg) {
return dashScopeChatModel.stream(msg);
}
}
(4)测试


- 响应式FluxAPI流式对话:http://localhost:8001/hello/streamchat


3.3.6 问题思考
(1)切换其他大模型

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

(2)SAA和OpenAI协议对比

三、Ollama私有化部署和对接本地大模型
3.1 Ollama本地大模型部署
3.1.1 LLM大模型工具OIlama
(1)是什么?
- Docker Hub玩镜像
- OllamaHub玩模型

Ollama是一个功能强大的开源框架,旨在简化在Docker容器中部署和管理大型语言模型(LLM)的过程。它帮助用户快速在本地运行大模型,通过简单的安装指令,用户可以执行一条命令就在本地运行开源大型语言模型,如Llama 2。
Ollama极大地简化了在Docker容器内部署和管理LLM的过程,它优化了设置和配置细节,包括GPU使用情况,并
将模型权重、配置和数据捆绑到一个包中,定义成Modelfile。此外,Ollama还提供了多种大型语言模型的开源仓
库,用户可以通过简单的命令行操作来下载和运行这些模型。
总的来说,Ollama是一个用于在本地高效运行大型语言模型的工具,为开发者和研究人员提供了极大的便利。
(2)能干嘛?
产品定位:

(3)去哪下?

(4)怎么玩?

3.1.2安装Ollama
- 自定义Ollama安装路径:OllamaSetup.exe /DIR=D:\SoftWareApp\Ollama
- 然后Ollama就会进入安装,点击Install后,可以看到Ollama的安装路径就变成了我们指定的目录
了,这样大模型数据包也会默认下载在指定目录中。
- 然后Ollama就会进入安装,点击Install后,可以看到Ollama的安装路径就变成了我们指定的目录


-
手动创建大模型存储目录
- 新建环境变量

-
复制转移大模型存储目录

3.1.3安装通义千问大模型
-
验证是否安装成功:
shellnetstat -ano |findstr 11434shellollama --version
-
千问模型为例: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进行加速,提高模型的运行速度和性能;

-
查看模型列表:ollama list
-
deepseek:ollama run deepseek-r1:8b
-
结束对话:Use Ctrl + d or /bye to exit.
3.2 微服务对接本地大模型
3.2.1 建module(SAA-02-Ollama)

(1)改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-02-Ollama</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SAA-02-Ollama</name>
<description>SAA-02-Ollama</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-ai-alibaba dashscope-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${SpringAIAlibaba.version}</version>
</dependency>
<!--ollama-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
<version>1.0.0</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
<!--在这个Spring AI Alibaba项目中,添加Spring Milestones仓库是必要的,因为:
Spring AI可能是Spring生态系统中的较新组件
某些Spring组件的里程碑版本通常先发布到专门的milestone仓库
这样可以让开发者提前访问和测试新版本的功能
同时避免影响稳定性,因为这里明确禁用了快照版本
当Maven构建项目时,它会先在本地仓库查找依赖,如果找不到,就会按照配置的远程仓库顺序去搜索和下载所需的依赖包。-->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
(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<String> streamchat(@RequestParam(name = "msg",defaultValue = "你是谁") String msg)
{
return chatModel.stream(msg);
}
}
(2)结果展示


四、ChatClient VS ChatModel
想象一下你要点一杯咖啡:
- ChatModel = 咖啡机本身(你直接操作机器)
- ChatClient = 咖啡店点单系统(你告诉店员"我要一杯拿铁",店员帮你搞定所有细节)
| 特性 | ChatModel | ChatClient |
|---|---|---|
| 层级 | 底层接口 | 高层API、服务层 |
| 复杂度 | 低(直接操作) | 高(封装了复杂逻辑) |
| 代码量 | 多(需要自己处理细节) | 少(Fluent API) |
| 高级功能 | 不支持 | 支持(RAG、聊天记忆等) |
| 适用场景 | 简单交互 | 复杂AI应用 |
| 学习曲线 | 简单 | 稍高(但值得) |
4.1 问题回顾
4.1.1 之前的调用都是使用ChatModel进行
java
public interface ChatModel extends Model<Prompt,ChatResponse>,StreamingChatModel
4.1.2 认识一个新的接口ChatClient

- 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/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-starterAutoConfiguration自动初始化了默认实例,因此我们可以在应用程序中直接注入ChatModel、ImageModel等bean,当然在需要的时候也可以自定义Model实例。
4.2.2 说明
- 对话模型(ChatModel)是底层接口,直接与具体大语言模型交互,
- 提供call()和stream()方法,适合简单大模型交互场景
4.3 ChatClient()
4.3.1 官网
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吐槽

4.3.3 说明
- ChatClient是高级封装
- 基于ChatModel构建
- 勾建标准化复杂AI服务
- 支持同步和流式交互,集成多种高级功能。
4.4 编码案例
4.4.1 建立module
(1)创建Module:SAA-03-ChatModel-ChatClient

(2)改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-03-ChatModel-ChatClient</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SAA-03-ChatModel-ChatClient</name>
<description>SAA-03-ChatModel-ChatClient</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-ai-alibaba dashscope-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${SpringAIAlibaba.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
(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;
}
}
但是出现异常:

结论: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/

ChatClient使用:https://java2ai.com/docs/1.0.0.2/tutorials/basics/chat-client/

(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响应式编程

响应式编程官网介绍:https://projectreactor.io/docs/core/release/reference/#intro-reactive

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

(2)改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-04StreamingOutput</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SAA-04StreamingOutput</name>
<description>SAA-04StreamingOutput</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-ai-alibaba dashscope-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${SpringAIAlibaba.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
(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
javapackage 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<String> chatflux(@RequestParam(name = "question",defaultValue = "你是谁") String question)
{
return deepseekChatModel.stream(question);
}
@GetMapping(value = "/stream/chatflux2")
public Flux<String> 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<String> chatflux(@RequestParam(name = "question",defaultValue = "你是谁") String question)
{
return deepseekChatModel.stream(question);
}
@GetMapping(value = "/stream/chatflux2")
public Flux<String> chatflux2(@RequestParam(name = "question",defaultValue = "你是谁") String question)
{
return qwenChatModel.stream(question);
}
@GetMapping(value = "/stream/chatflux3")
public Flux<String> chatflux3(@RequestParam(name = "question",defaultValue = "你是谁") String question)
{
return deepseekChatClient.prompt(question).stream().content();
}
@GetMapping(value = "/stream/chatflux4")
public Flux<String> chatflux4(@RequestParam(name = "question",defaultValue = "你是谁") String question)
{
return qwenChatClient.prompt(question).stream().content();
}
}
5.4 新增前端代码trytry
5.4.1前端效果,Flux< T >本质提一嘴


5.4.2 SSE
(1)index.html
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>SSE流式chat</title>
<style> body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 20px;
}
#messageInput {
width: 90%;
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 10px;
}
button {
padding: 10px 20px;
font-size: 16px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
#messages {
margin-top: 20px;
padding: 15px;
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 8px;
max-height: 300px;
overflow-y: auto;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
#messages div {
padding: 8px 0;
border-bottom: 1px solid #eee;
font-size: 14px;
color: #333;
}
#messages div:last-child {
border-bottom: none;
}
.endpoint-selector {
margin-bottom: 10px;
}
.endpoint-selector label {
display: inline-block;
margin-right: 10px;
font-weight: bold;
}
.endpoint-selector select {
padding: 5px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="endpoint-selector">
<label for="endpointSelect">选择接口:</label>
<select id="endpointSelect">
<option value="stream/chatflux1">chatflux1 (deepseek ChatModel)</option>
<option value="stream/chatflux2" selected>chatflux2 (qwen ChatModel)</option>
<option value="stream/chatflux3">chatflux3 (deepseek ChatClient)</option>
<option value="stream/chatflux4">chatflux4 (qwen ChatClient)</option>
</select>
</div>
<textarea id="messageInput" rows="4" cols="50" placeholder="请输入你的问题..."></textarea><br>
<button onclick="sendMsg()">发送提问</button>
<div id="messages"></div>
<script> let currentEventSource = null;
function sendMsg() {
// 如果有正在运行的连接,先关闭它
if (currentEventSource) {
currentEventSource.close();
}
// 获取用户输入的消息
const message = document.getElementById('messageInput').value;
if (message == "") return false;
// 获取选中的接口
const endpoint = document.getElementById('endpointSelect').value;
//1 客户端使用 JavaScript 的 EventSource 对象连接到服务器上的一个特定端点(URL)
currentEventSource = new EventSource(endpoint + '?question=' + encodeURIComponent(message));
// 添加新消息容器
const messagesDiv = document.getElementById('messages');
const messageContainer = document.createElement('div');
messageContainer.innerHTML = '<strong>接口: ' + endpoint + '</strong><br>';
messagesDiv.appendChild(messageContainer);
//2 监听消息事件
currentEventSource.onmessage = function (event) {
// 获取流式返回的数据
const data = event.data;
// 将接收到的数据展示到页面上
messageContainer.innerHTML += data;
// 滚动到底部
messagesDiv.scrollTop = messagesDiv.scrollHeight;
};
//3 监听错误事件
currentEventSource.onerror = function (error) {
console.error('EventSource 发生错误:', error);
currentEventSource.close(); // 关闭连接
};
}
</script>
</body>
</html>
(2)存放位置

5.4.3测试
http://localhost:8004/index.html

六、提示词Prompt
6.1 DeepSeek提示词样例
https://api-docs.deepseek.com/zh-cn/prompt-library/

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调用说起


6.2.3再从源码Prompt说起
(1)String
最初的Prompt只是简单的文本字符串提问
(2)Message

enum MessageType:Prompt 中的四大角色(Role)

(3)Prompt

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返回的响应信息,定义为"助手角色"消息。用它可以确保上下文能够连贯的交互。
-
记忆对话,积累回答
-

(4)TOOL(value:"tool")
桥接外部服务,可以进行函数调用如,支付/数据查询等操作,类似调用第3方util工具类
6.4 开发步骤
6.4.1 新建子模块Module(springAI-05chat-Prompt)
springAI-05chat-Prompt

6.4.2 改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-05chat-Prompt</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springAI-05chat-Prompt</name>
<description>springAI-05chat-Prompt</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- <!–spring-ai-openai–>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
<version>1.0.0</version>
</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.34</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-core</artifactId>
<version>1.0.0.2</version>
<scope>compile</scope>
</dependency>-->
<!--spring-ai-alibaba dashscope-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${SpringAIAlibaba.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
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<String> chat(String question)
{
return deepseekChatClient.prompt()
// AI 能力边界
.system("你是一个法律助手,只回答法律问题,其它问题回复,我只能回答法律相关问题,其它无可奉告")
.user(question)
.stream()
.content();
}
}
- 测试

(3)Controller(ChatModel)
通过ChatModel实现:当然使用ChatModel等原子API可以为应用程序带来更多的灵活性,成本就是您需要编写大量样板代码。
java
@GetMapping("/prompt/chat2")
public Flux<ChatResponse> 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<String> 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());
}

(4)Controller(ASSISTANT)
-
ASSISTANT(value:"assistant")
-
Al返回的响应信息,定义为"助手角色"消息。用它可以确保上下文能够连贯的交互。
-
记忆对话,积累回答
-

-
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工具类
- 测试效果

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小总结

七、提示词Prompt Template
7.1 Prompt演化历程
7.1.1 简单纯字符串提问问题
最初的Prompt只是简单的文本字符串。
7.1.2 多角色信息
-
(1)将消息分为不同角色(如用户、助手、系统等),设置功能边界,增强交互的复杂性和上下文感知能力
-
(2)springai vs langchain4j vs spring ai alibaba
-
langchain4j
-
springai
-
spring ai alibaba
-
7.1.3 占位符(Prompt Template)
引入占位符(如 {占位符变量名} ) 以动态插入内容。
7.2 提示词模板是什么
7.2.1知识出处

7.2.2 模板
(1)入职邀请函模板

(2)短信模板
(3)邮件模板
7.3 开发步骤
7.3.1新建子模块Module(SAA-06PromptTemplate)
SAA-06PromptTemplate
7.3.2 改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-06PromptTemplate</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SAA-06PromptTemplate</name>
<description>SAA-06PromptTemplate</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-ai-alibaba dashscope-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${SpringAIAlibaba.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<source>21</source>
<target>21</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
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<String> 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();
}
}

(3)PromptTemplate读取模版文件实现模版功能
新建txt文件,作为模版文件
tex
讲一个关于{topic}的故事,并以{output_format}格式输出。

添加代码,读取模板配置:
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<String> 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();
}
}

(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();
}


(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; }
-
通过ChatClient实现
java
/**
* @Description: 人物角色设定,通过SystemMessage来实现人物设定,本案例用ChatClient实现
* 设定AI为"医疗专家"时,仅回答医学相关问题
* 设定AI为编程助手"时,专注于技术问题解答
* http://localhost:8006/prompttemplate/chat5?question=火锅
*/
@GetMapping("/prompttemplate/chat5")
public Flux<String> chat5(String question)
{
return deepseekChatClient.prompt()
.system("你是一个Java编程助手,拒绝回答非技术问题。")
.user(question)
.stream()
.content();
}

八、格式化输出(Structured Output)
8.1 是什么?
https://java2ai.com/docs/1.0.0.2/tutorials/basics/structured-output/

8.2 开发步骤
假设我们期望将模型输出转换为Record记录类结构体,不再是传统的String
8.2.1新建子模块Module(SAA-07StructuredOutput)
8.2.2 改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-07StructuredOutput</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SAA-07StructuredOutput</name>
<description>SAA-07StructuredOutput</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-ai-alibaba dashscope-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${SpringAIAlibaba.version}</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
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) { }

(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<ChatClient.PromptUserSpec>() {
@Override
public void accept(ChatClient.PromptUserSpec promptUserSpec)
{
promptUserSpec.text("学号1001,我叫{sname},大学专业是计算机科学与技术,邮箱{email}")
.param("sname",sname)
.param("email",email);
}
}).call().entity(StudentRecord.class);
}
}

(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聊天应用程序的对话上下文和历史的机制。

9.1.3 记忆类型

-
因大模型本身不存储数据,需将历史对话信息一次性提供给它以实现连续对话,不然服务一重启就什么都没了...所以,必须持久化
-
痛点2个
-
持久化媒介
-
消息对话窗口,聊天记录上限
-
9.2 持久化开发步骤
9.2.1 需求说明
- 将客户和大模型的对话问答保存进Redis进行持久化记忆留存
- 官网:https://java2ai.com/docs/1.0.0.2/tutorials/basics/memory/?spm=4347728f.4dc6f515.0.0.538b4305NobuzA

- 当然,开发者也可以自行实现ChatMemory基于类似于文件、Redis等方式进行上下文内容的存储和记录。
9.2.2 新建子模块(SAA-08Persistent)

9.2.3 改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-08Persistent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SAA-08Persistent</name>
<description>SAA-08Persistent</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-ai-alibaba dashscope-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${SpringAIAlibaba.version}</version>
</dependency>
<!--spring-ai-alibaba memory-redis-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-memory-redis</artifactId>
<version>${SpringAIAlibaba.version}</version>
</dependency>
<!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
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

- 接口ChatMemoryRepository

- RedisChatMemoryRepository源码

- 编码新建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

(3)顾问(Advisors)MessageChatMemoryAdvisor
官网:https://java2ai.com/docs/1.0.0.2/overview/?spm=4347728f.4dc6f515.0.0.273f43051xpgRX


官网:https://java2ai.com/ecosystem/spring-ai/reference/memory

(4)配置类SaaLLMConfig

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<ChatClient.AdvisorSpec>()
{
@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来进行区分,隔离不同用户之间的消息


十、文生图
10.1 阿里百炼文生图
官网:https://help.aliyun.com/zh/model-studio/text-to-image?s

10.2 通义万象-文生图V2版本API参考

10.3 新建子模块(SAA-09Text2image)

10.4 改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-09Text2image</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SAA-09Text2image</name>
<description>SAA-09Text2image</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-ai-alibaba dashscope-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${SpringAIAlibaba.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
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();
}
}
}

十一、文生音
11.1 阿里百炼文生音
11.1.1 语音合成-CosyVoice

11.1.2 语音合成-CosyVoice Java SDK

11.1.3SpeechSynthesizer类提供了语音合成的关键接口
提交文本后,服务端立即处理并返回完整的语音合成结果,整个过程是阻塞式的,客户端需要等待服务端完成处理后才能继续下一步操作,适合短文本语音合成场景

11.1.4 阿里内置接口一览

11.1.5 DashScopeSpeechSynthesisOptions
(1)SpeechSynthesisParam的链式方法配置模型、音色等参数


11.2 开发步骤
11.2.1 新建子模块(SAA-10Text2voice)

11.2.2 改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-10Text2voice</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SAA-10Text2voice</name>
<description>SAA-10Text2voice</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-ai-alibaba dashscope-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${SpringAIAlibaba.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
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


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;
}
}

11.2.7 FlowableTestVoiceController(使用官网SDK测试)

xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<version>2.21.8</version>
</dependency>
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<SpeechSynthesisResult> callback = new ResultCallback<SpeechSynthesisResult>() {
@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);
}
}

十二、向量化和向量数据库
12.1 向量
- Vector是向量或矢量的意思,向量是数学里的概念,而矢量是物理里的概念,但二者描述的是同一件事。
- 定义:向量是用于表示具有大小和方向的量。
- 向量可以在不同的维度空间中定义,最常见的是二维和三维空间中的向量,但理论上也可以有更高维的向量。
- 例如,在二维平面上的一个向量可以写作(xy),这里x和y分别表示该向量沿两个坐标轴方向上的分量;
- 而在三维空间里,则会有一个额外的z坐标,即(x,Y,z)
12.2 文本向量化
12.2.1 是什么

12.2.2 官网-嵌入模型 (Embedding Model)
https://java2ai.com/docs/1.0.0.2/tutorials/basics/embedding/?spm=5176.29160081.0.0.2856aa5cXggpMJ

12.2.3 案列

12.2.4 小总结

12.3 向量数据库
12.3.1 是什么
- 一种专门用于存储、管理和检索向量数据(即高维数值数组)的数据库系统。
- 其核心功能是通过高效的索引结构和相似性计算算法,支持大规模向量数据的快速查询与分析,向量数据库维度越高,查询精准度也越高,查询效果也越好。
官网:https://java2ai.com/docs/1.0.0.2/tutorials/basics/vectorstore/

12.3.2 下方是LangChain4J支持的向量数据库List清单
https://docs.langchain4j.dev/integrations/embedding-stores/
| Embedding Store:嵌入存储 | Storing Metadata:存储元数据 | Filtering by Metadata:按元数据过滤 | Removing Embeddings:移除嵌入 |
|---|---|---|---|
| In-memory | ✅ | ✅ | ✅ |
| AlloyDB for Postgres | ✅ | ✅ | ✅ |
| Astra DB | ✅ | ||
| Azure AI Search | ✅ | ✅ | ✅ |
| Azure CosmosDB Mongo vCore | ✅ | ||
| Azure CosmosDB NoSQL | ✅ | ||
| Cassandra | ✅ | ||
| Chroma | ✅ | ✅ | ✅ |
| ClickHouse | ✅ | ✅ | ✅ |
| Cloud SQL for Postgres | ✅ | ✅ | ✅ |
| Coherence | ✅ | ✅ | ✅ |
| Couchbase | ✅ | ✅ | |
| DuckDB | ✅ | ✅ | ✅ |
| Elasticsearch | ✅ | ✅ | ✅ |
| Infinispan | ✅ | ✅ | ✅ |
| JVector | ✅ | ||
| Mariadb | ✅ | ✅ | ✅ |
| Milvus | ✅ | ✅ | ✅ |
| MongoDB Atlas | ✅ | ✅ | ✅ |
| Neo4j | ✅ | ||
| OpenSearch | ✅ | ||
| Oracle | ✅ | ✅ | ✅ |
| PGVector | ✅ | ✅ | ✅ |
| Pinecone | ✅ | ✅ | ✅ |
| Qdrant | ✅ | ✅ | ✅ |
| Redis | ✅ | ✅ | |
| SQL Server | ✅ | ✅ | ✅ |
| Tablestore | ✅ | ✅ | ✅ |
| Vearch | ✅ | ||
| Vespa | |||
| Weaviate | ✅ | ✅ | |
| 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 - The Azure vector store.
Azure Vector Search - Azure 向量存储。 - Apache Cassandra - The Apache Cassandra vector store.
Apache Cassandra - Apache Cassandra 向量存储。 - Chroma Vector Store - The Chroma vector store.
Chroma 向量存储 - Chroma 向量存储。 - Elasticsearch Vector Store - The Elasticsearch vector store.
Elasticsearch 向量存储 - Elasticsearch 向量存储。 - GemFire Vector Store - The GemFire vector store.
GemFire 向量存储 - GemFire 向量存储。 - MariaDB Vector Store - The MariaDB vector store.
MariaDB 向量存储 - MariaDB 向量存储。 - Milvus Vector Store - The Milvus vector store.
Milvus 向量存储 - Milvus 向量存储。 - MongoDB Atlas Vector Store - The MongoDB Atlas vector store.
MongoDB Atlas 向量存储 - MongoDB Atlas 向量存储。 - Neo4j Vector Store - The Neo4j vector store.
Neo4j 向量存储 - Neo4j 向量存储。 - OpenSearch Vector Store - The OpenSearch vector store.
OpenSearch 向量存储 - OpenSearch 向量存储。 - Oracle Vector Store - The Oracle Database vector store.
Oracle 向量存储 - Oracle 数据库向量存储。 - PgVector Store - The PostgreSQL/PGVector vector store.
PgVector 存储 - PostgreSQL/PGVector 向量存储。 - Pinecone Vector Store - Pinecone vector store.
Pinecone 向量存储 - Pinecone 向量存储。 - Qdrant Vector Store - Qdrant vector store.
Qdrant 向量存储 - Qdrant 向量存储。 - Redis Vector Store - The Redis vector store.
Redis 向量存储 - Redis 向量存储。 - SAP Hana Vector Store - The SAP HANA vector store.
SAP Hana 向量存储 - SAP HANA 向量存储。 - Typesense Vector Store - The Typesense vector store.
Typesense 向量存储 - Typesense 向量存储。 - Weaviate Vector Store - The Weaviate vector store.
Weaviate Vector Store - Weaviate 向量存储。 - SimpleVectorStore - A simple implementation of persistent vector storage, good for educational purposes.
SimpleVectorStore - 一种简单的持久化向量存储实现,适合教育用途。
12.4能干嘛
- 捕捉复杂的词汇关系(如语义相似性、同义词、多义词)
- 向量嵌入为检索增强生成(RAG)应用程序提供支持
- 将文本映射到高维空间中的点,使语义相似的文本在这个空间中距离较近。
- 例如,"肯德基"和"麦当劳"的向量可能会比"肯德基"和"新疆大盘鸡"的向量更接近
12.5 开发步骤
12.5.1 建module(SAA-11Embed2vector)
12.5.2 改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-11Embed2vector</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SAA-11Embed2vector</name>
<description>SAA-11Embed2vector</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-ai-alibaba dashscope-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${SpringAIAlibaba.version}</version>
</dependency>
<!-- 添加 Redis 向量数据库依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-redis</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<source>21</source>
<target>21</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
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)阿里云百炼平台向量大模型

(2)配置参考信息来源和知识出处
https://docs.spring.io/spring-ai/reference/api/vectordbs/redis.html

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

(1)RedisStack是什么

Redis Stack 是 Redis Labs 推出的一个**"增强版 Redis",不是 Redis 的替代品,而是在原生 Redis 基
础上的功能扩展包**,专为构建现代实时应用而设计。
(2)RedisStack相比原生Redis 的优势

(3)RedisStack核心组件
- RediSearch::提供全文搜索能力,支持复杂的文本搜索、聚合和过滤,以及向量数据的存储和检索
- RedisJSON:原生支持JSON数据的存储、索引I和查询,可高效存储和操作嵌套的JSON文档。
- RedisGraph:支持图数据模型,使用Cypher查询语言进行图遍历查询。
- RedisBloom:支持 Bloom、Cuckoo、Count-Min Sketch等概率数据结构.

(4)RedisStack安装
linux下,命令:
bash
docker run -d --name redis-stack-server -p 6379:6379 redis/redis-stack-server
windows下安装步骤:
下载地址:Docker: Accelerated Container Application Development
安装步骤:Windows安装Docker(Docker Desktop)_docker-desktop-CSDN博客




json
{
"builder": {
"gc": {
"defaultKeepStorage": "20GB",
"enabled": true
}
},
"experimental": false,
"registry-mirrors": [
"https://docker.xuanyuan.me",
"https://docker.1ms.run"
]
}


12.5.6 业务类
(1)知识出处
官网:https://docs.spring.io/spring-ai/reference/api/vectordbs.html

(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<Document> 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<Document> list = vectorStore.similaritySearch(searchRequest);
System.out.println(list);
return list;
}
}
(3)测试
文本向量化展示:
http
http://localhost:8011/text2embed?msg=射雕英雄传

文本向量化添加: 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


文本向量化查询:越接近哪个,就会查出哪个
http
http://localhost:8011/embed2vector/get?msg=文本

12.6 知识图谱

十三、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


什么是幻觉?已读乱回、已读不回、似是而非
(2)springai
https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html

(3)springai alibaba
文档检索 (Document Retriever)
https://java2ai.com/docs/1.0.0.2/tutorials/basics/retriever/?spm=5176.29160081.0.0.2856aa5cXggpMl


13.2.2 核心设计概念
-
RAG技术就像给AI大模型装上了「实时百科大脑」,为了让大模型获取足够的上下文,以便获得更加广泛的信息源,通过先查资料后回答的机制,让AI摆脱传统模型的"知识遗忘和幻觉回复"困境
-
类似考试时有不懂的,给你准备了小抄,对大模型知识盲区的一种补充
13.3 能干嘛
通过引入外部知识源来增强LLM的输出能力,传统的LLM通常基于其训练数据生成响应,但这些数据可能过时或不够全面 。RAG允许型在生成答案之前,从特定的知识库中检索相关信息,从而提供更准确和上下文相关的回答
13.4 怎么玩
RAG流程分为两个不同的阶段:索引和检索



13.5 开发步骤
13.5.1 建module(SAA-12RAG4AiOps),改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-12RAG4AiOps</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SAA-12RAG4AiOps</name>
<description>SAA-12RAG4AiOps</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-ai-alibaba dashscope-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${SpringAIAlibaba.version}</version>
</dependency>
<!-- 添加 Redis 向量数据库依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-redis</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
13.5.2 写yml


官网:https://docs.spring.io/spring-ai/reference/api/vectordbs/redis.html


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);
}
}

13.5.4 业务类
(1)形成文档知识库
提供ErrorCode脚本让他存入向量数据库RedisStack,形成文档知识库
txt
00000 系统OK正确执行后的返回
A0001 用户端错误一级宏观错误码
A0100 用户注册错误二级宏观错误码
B1111 支付接口超时
C2222 Kafka消息解压严重

(2)SpringAI源代码接口

(3)用redis作为向量存储
bash
docker run -d -name redis-stack-server -p 6379:6379 redis/redis-stack-server


(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<Document> 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<String> 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

(7)第一版问题
重启服务后,发现重复数据写入问题需考虑,不然每次重启都要新增

(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<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactor)
{
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactor);
//设置key序列化方式string
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置value的序列化方式json,使用GenericJackson2JsonRedisSerializer替换默认序列化
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
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<String,String> redisTemplate;
@PostConstruct
public void init() {
// 1.读取文件
TextReader textReader = new TextReader(sqlFile);
textReader.setCharset(Charset.defaultCharset());
// 2.文件转换成向量(分词)
List<Document> 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("------向量初始化数据已经加载过,请不要重复操作");
}
}
}


十四、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

14.1.2 SpringAI Alibaba
https://java2ai.com/docs/1.0.0.2/tutorials/basics/tool-calling/?spm=5176.29160081.0.0.2856aa5cgvn0gm

https://java2ai.com/docs/frameworks/agent-framework/advanced/agent-tool#tool-calling

14.2 能干嘛?
14.2.1 访问实时数据

14.2.2 执行某种工具类/辅助类操作
-
大语言模型(LLMs)不仅仅是文本生成的能手
-
它们还能触发并调用第3方函数
-
比如:发邮件/查询微信/调用支付宝/查看顺丰快递单据号等等.
14.3 怎么玩?
工作流程如下:

14.4 开发步骤
14.4.1 新建子模块(SAA-13ToolCalling)

14.4.2 改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-13ToolCalling</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SAA-13ToolCalling</name>
<description>SAA-13ToolCalling</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-ai-alibaba dashscope-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${SpringAIAlibaba.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
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);
}
}

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<String> chat(@RequestParam(name = "msg",defaultValue = "你是谁现在几点") String msg)
{
return chatModel.stream(msg);
}
}
(3)测试
http://localhost:8013/notoolcall/chat

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();
}
}

(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

14.4.5.2.2 通过ChatClient实现
(1)ChatClient本身不会主动装配,直接定义无法使用

(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<String> chat2(@RequestParam(name = "msg",defaultValue = "你是谁现在几点") String msg)
{
return chatClient.prompt(msg)
.tools(new DateTimeTools())
.stream()
.content();
}
}
(4)测试
http://localhost:8013/toolcall/chat2





14.4.6 小总结
- 新建定义一个Too红具类
- ChatModel/ChatClient使用
- Tool Calling使用注意事项:ToolCalling使用的前提是大模型支持functioncall才能正常调用。
十五、MCP模型上下文协议(Model Context Protocol)
15.1 为什么会有MCP出现,之前痛点是什么?
(1)之前每个大模型(如DeepSeek、ChatGPT)需要为每个工具单独开发接口((FunctionCalling),导致重复劳动
(2)痛点:共用+数量

15.2 MCP入门概念
15.2.1 官网
(1)MCP自身协议官网:https://modelcontextprotocol.io/introduction

(2)SpringAI官网支持MCP:https://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html

(3)SpringAI Aibaba官网支持MCP:https://java2ai.com/integration/mcps/mcp-overview

15.2.2 是什么?能干嘛?怎么玩?
(1)是什么?
Java界的SpringCloud Openfeign,只不过Openfeign是用于微服务通讯的,而MCP用于大模型通讯 的,但它们都是为了通讯获取某项数据的一种机制
(2)能干嘛?
提供了一种标准化 的方式来连接LLMs需要的上下文,MCP就类似于一个Agent时代的Type-C协议,希望能将不同来源的数据、工具、服务统一起来供大模型调用

(3)怎么玩?
调用上万个通用的MCP:https://mcp.so/zh

15.3 MCP架构知识
15.3.1 MCP架构


15.3.2 通信协议-俩种模式


15.4 小总结
- TooICalling:工具类,为了让大模型使用Util工具
- RAG:知识库,为了让大模型获取足够的上下文
- MCP协议,为了让大模型之间的相互调用




15.5 本地MCP-开发步骤
15.5.1 MCP-Server服务端实现
(1)新建子模块SAA-14LocalMcpServer

(2)改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>
<artifactId>SAA-14LocalMcpServer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SAA-14LocalMcpServer</name>
<description>SAA-14LocalMcpServer</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!--注意事项(重要)
spring-ai-starter-mcp-server-webflux不能和<artifactId>spring-boot-starter-web</artifactId>依赖并存,
否则会使用tomcat启动,而不是Netty启动,从而导致mcpserver启动失败,但程序运行是正常的,mcp客户端连接不上。
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--mcp-server-webflux-->
<!-- Source: https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-mcp-server-webflux -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
<version>1.0.1</version>
<scope>compile</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
(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<String, String> 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)自启动作为服务器端等待客户端调用即可

15.5.2 MCP-Client客户端实现
(1)新建子模块SAA-15LocalMcpClient

(2)改pom
java
<?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>
<artifactId>SAA-15LocalMcpClient</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SAA-15LocalMcpClient</name>
<description>SAA-15LocalMcpClient</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-ai-alibaba dashscope-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${SpringAIAlibaba.version}</version>
</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>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
(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<String> chat(@RequestParam(name = "msg",defaultValue = "北京") String msg)
{
System.out.println("使用了mcp");
return chatClient.prompt(msg).stream().content();
}
@RequestMapping("/mcpclient/chat2")
public Flux<String> chat2(@RequestParam(name = "msg",defaultValue = "北京") String msg)
{
System.out.println("未使用mcp");
return chatModel.stream(msg);
}
}
(6)MCP-Client invoke MCP-Server测试
- 使用mcp

- 没有mcp支持,已读乱回

15.6 远程MCP增强案例-对接互联网通用MCP服务(百度地图)
15.6.1 调用上万个通用的MCP

15.6.2 对接互联网通用MCP服务(百度地图)
官网:https://mcp.so/zh/server/baidu-map/baidu-maps

15.6.3 环境配置
15.6.3.1 原理说明

15.6.3.2 下载最新版的NodeJS


15.6.3.3 注册百度地图账号+申请API+Key
(1)官网:https://lbsyun.baidu.com/

(2)创建应用-申请api+key



(3)测试连接:MCP Server 调试

15.6.3.4 nodejs配置编码-Typescript接入(Claude for Desktop)

参考文档: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

(2)下载Claude for Desktop

(3)配置apikey


(4)重启claude

15.6.4 新建子模块:SAA-16ChatMcpClientBaiduMcp

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"}
}
}
}

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);
}
}

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能力

(2)不具备mcp能力

15.7 MCP原理+源码分析
(1)原理

(2)下载到本地
bash
npm i -g @baidumap/mcp-server-baidu-map
npm config get prefix


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教程





