Spring AI Alibaba 个人学习笔记

一、Spring Al Alibaba之理论概述

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

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

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

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

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

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

(1)Before

(2)After

2. 是什么?

2.1 什么是Spring AI Alibaba?

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

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

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

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

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

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

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

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

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

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

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

2.2 SAA公式化一句话表达

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

2.3 官网知识出处

2.3.1 Spring Ai 官网

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

2.3.2 Spring Al Alibaba 1.0 GA 正式发布

https://java2ai.com/

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

2.3.3阿里云百炼平台

[https://bailian.console.aliyun.com/console?tab=model#/model-market

2.3.4 能干嘛

​ Spring Al Alibaba基于Spring Al 构建,因此SAA继承了SpringAl 的所有原子能力抽象 并在此基础上扩充丰富了模型、向量存储、记忆、RAG等核心组件适配,让其能够接入阿里云的AI生态。

2.3.5 去哪下?

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管理依赖版本

https://java2ai.com/docs/1.0.0.2/tutorials/starters-and-quick-guide/?spm=5176.29160081.0.0.2856aa5c0l3sEA#使��om-管理依赖版本、

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)
  • 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)测试
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玩模型

官网:https://ollama.com/

​ Ollama是一个功能强大的开源框架,旨在简化在Docker容器中部署和管理大型语言模型(LLM)的过程。它帮助用户快速在本地运行大模型,通过简单的安装指令,用户可以执行一条命令就在本地运行开源大型语言模型,如Llama 2。

​ Ollama极大地简化了在Docker容器内部署和管理LLM的过程,它优化了设置和配置细节,包括GPU使用情况,并

将模型权重、配置和数据捆绑到一个包中,定义成Modelfile。此外,Ollama还提供了多种大型语言模型的开源仓

库,用户可以通过简单的命令行操作来下载和运行这些模型。

​ 总的来说,Ollama是一个用于在本地高效运行大型语言模型的工具,为开发者和研究人员提供了极大的便利。

(2)能干嘛?

产品定位:

(3)去哪下?

https://ollama.com/download

(4)怎么玩?

3.1.2安装Ollama

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

    • 新建环境变量
  • 复制转移大模型存储目录

3.1.3安装通义千问大模型

  • 验证是否安装成功:

    shell 复制代码
    netstat -ano |findstr 11434
    shell 复制代码
    ollama --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/docs/1.0.0.2/tutorials/basics/chat-model/?spm=4347728f.13601988.0.0.654f6001MkbUyJ

https://java2ai.com/ecosystem/spring-ai/reference/chat-model

  • 对话模型(Chat Model)接收一系列消息(Message)作为输入,与模型LLM服务进行交互,并接收返回的聊天消息(ChatMessage)作为输出。相比于普通的程序输入,模型的输入与输出消息(Message)不止支持纯字符文本,还支持包括语音、图片、视频等作为输入输出。同时,在SpringAlAlibaba中,消息中还支持包含不同的角色,帮助底层模型区分来自模型、用户和系统指令等的不同消息。
  • SpringAlAlibaba复用了SpringAl抽象的Model APl,并与通义系列大模型服务进行适配(如通义千问、通义万相等),目前支持纯文本聊天、文生图、文生语音、语音转文本等。以下是框架定义的几个
  • 核心API:
    • ChatModel,文本聊天交互模型,支持纯文本格式作为输入,并将模型的输出以格式化文本形式返回。
    • mageModel,接收用户文本输入,并将模型生成的图片作为输出返回。
    • AudioModel,接收用户文本输入,并将模型合成的语音作为输出返回。
  • Spring Al Alibaba支持以上Model抽象与通义系列模型的适配,并通过spring-ai-alibaba-starter AutoConfiguration自动初始化了默认实例,因此我们可以在应用程序中直接注入ChatModel、ImageModel等bean,当然在需要的时候也可以自定义Model实例。

4.2.2 说明

  • 对话模型(ChatModel)是底层接口,直接与具体大语言模型交互,
  • 提供call()和stream()方法,适合简单大模型交互场景

4.3 ChatClient()

4.3.1 官网

https://java2ai.com/docs/1.0.0.2/tutorials/basics/chat-client/?spm=4347728f.13601988.0.0.654f6001MkbUyJ

https://java2ai.com/ecosystem/spring-ai/reference/chat-client

  • ChatClient提供了与AI模型通信的Fluent API,它支持同步和反应式(Reactive)编程模型。与ChatModel、Message、ChatMemory等原子API相比,使用ChatClient可以将与 LLM及其他组件交互的复杂性隐藏在背后,因为基于LLM的应用程序通常要多个组件协同工作(例如,提示词模板、聊天记忆、LLM Model、输出解析器、RAG组件:嵌入模型和存储),并且通常涉及多个交互,因此协调它们会让编码变得繁琐。当然使用ChatModel等原子API可以为应用程序带来更多的灵活性,成本就是您需要编写大量样板代码。
  • ChatClient类似于应用程序开发中的服务层,它为应用程序直接提供AI服务,开发者可以使用ChatClient Fluent API快速完成一整套Al交互流程的组装。
  • 包括一些基础功能,如:
    • 定制和组装模型的输入(Prompt)
    • 格式化解析模型的输出(Structured Output)
    • 调整模型交互参数(ChatOptions)
  • 还支持更多高级功能:
    • 聊天记忆(Chat Memory)
    • 工具/函数调用(Function Calling)
    • RAG

4.3.2 样板代码?ChatClient对ChatModel吐槽

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

B站教学视频:https://www.bilibili.com/video/BV1Es4y1q7Bf?spm_id_from=333.788.videopod.episodes\&vd_source=f3f60f7acbef49d38b97c4d660d439fc\&p=110

响应式编程官网介绍: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

    java 复制代码
    package com.zsh.test.saa04.config;
    
    import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
    import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
    import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
    import org.springframework.ai.chat.model.ChatModel;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * @author ZhaoShuhao
     * @data 2025/11/26 23:05
     */
    @Configuration
    public class SaaLLMConfig
    {
        // 模型名称常量定义
        private final String DEEPSEEK_MODEL = "deepseek-v3";
        private final String QWEN_MODEL = "qwen-plus";
    
        @Bean(name = "deepseek")
        public ChatModel deepSeek()
        {
            return DashScopeChatModel.builder()
                    .dashScopeApi(DashScopeApi.builder()
                            .apiKey(System.getenv("aliQwen-api"))
                            .build())
                    .defaultOptions(
                            DashScopeChatOptions.builder().withModel(DEEPSEEK_MODEL).build()
                    )
                    .build();
        }
    
        @Bean(name = "qwen")
        public ChatModel qwen()
        {
            return DashScopeChatModel.builder().dashScopeApi(DashScopeApi.builder()
                            .apiKey(System.getenv("aliQwen-api"))
                            .build())
                    .defaultOptions(
                            DashScopeChatOptions.builder()
                                    .withModel(QWEN_MODEL)
                                    .build()
                    )
                    .build();
        }
    }
  • controller第1版

java 复制代码
package com.zsh.test.saa04.controller;

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

/**
 * @author ZhaoShuhao
 * @data 2025/11/26 23:06
 */
@RestController
public class StreamOutputController
{
    //V1 通过ChatModel实现stream实现流式输出
    @Resource(name = "deepseek")
    private ChatModel deepseekChatModel;

    @Resource(name = "qwen")
    private ChatModel qwenChatModel;


    @GetMapping(value = "/stream/chatflux1")
    public Flux<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>
    <!--    &lt;!&ndash;spring-ai-openai&ndash;&gt;
        <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知识出处

https://java2ai.com/docs/1.0.0.2/tutorials/basics/prompt/?spm=4347728f.4dc6f515.0.0.538b4305NobuzA#prompt-template

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

学习资料:https://www.bilibili.com/video/BV1PY411e7J6?spm_id_from=333.788.videopod.episodes\&vd_source=f3f60f7acbef49d38b97c4d660d439fc\&p=199

(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 需求说明

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/blog/spring-ai-100-ga-released/?spm=4347728f.45ed3faa.0.0.451949faPdHUBx#顾问advisors

官网: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参考

官网:https://help.aliyun.com/zh/model-studio/text-to-image-v2-api-reference?spm=a2c4g.11186623.help-menu-2400256.d_2_2_3.25127c3b02eD93\&scm=20140722.H_2862677._.OR_help-T_cn\~zh-V_1

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

官网:https://help.aliyun.com/zh/model-studio/cosyvoice-large-model-for-speech-synthesis/?spm=a2c4g.11186623.help-menu-2400256.d_2_6_0.2a7474473XyDNE\&scm=20140722.H_2817551...OR_help-T_cn\~zh-V_1

11.1.2 语音合成-CosyVoice Java SDK

11.1.3SpeechSynthesizer类提供了语音合成的关键接口

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

11.1.4 阿里内置接口一览

11.1.5 DashScopeSpeechSynthesisOptions

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

官网:https://help.aliyun.com/zh/model-studio/cosyvoice-java-sdk?spm=a2c4g.11186623.help-menu-2400256.d_2_6_0_0.1aa4383a1fw0oG#adcb5e9bddbyq


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 接口的可用实现:

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

官网:https://mcp.so/zh

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

官网: https://nodejs.org/zh-cn

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

相关推荐
钓了猫的鱼儿1 分钟前
基于深度学习+AI的棉叶蝉目标检测与预警系统(Python源码+数据集+UI可视化界面+YOLOv11训练结果)
人工智能·深度学习·目标检测
Resistance丶未来1 小时前
管控用量,降本增效,MAI Gateway:助力企业搭建 Tokens 统一管理体系
人工智能·大模型·api·claude·ai安全·魔芋ai·maigateway
GIS数据转换器1 小时前
无人机车载巡检系统
大数据·数据库·人工智能·数据挖掘·数据分析·无人机
逸模8 小时前
告别熬夜手工整理台账,逸模智能归集实现项目数据自动化存档
大数据·运维·人工智能·笔记·其他·信息可视化·自动化
weixin_397574099 小时前
生产管理和设备管理:制造执行层的AI痛点
人工智能·制造
冬奇Lab9 小时前
Agent 系列(16):工具链设计——让 LLM 用对工具的五个原则
人工智能·llm·agent
xqqxqxxq9 小时前
哈希表(HashMap)技术学习笔记
笔记·学习·散列表
生而为虫9 小时前
[学习记录] 幼儿学习拼音html游戏
学习·游戏
冬奇Lab9 小时前
每日一个开源项目(第125篇):taste-skill - 给 AI 装上审美,让前端不再千篇一律
人工智能·开源·agent
AOwhisky9 小时前
MySQL 学习笔记(第四期):SQL 语言之多表查询
linux·运维·网络·数据库·笔记·学习·mysql