目录

1、介绍

2、官网地址

3、框架对比

4、工程搭建

(1)版本依赖

(2)接入阿里百炼平台的通义模型

1) 获得Api-key

​编辑2)获取模型名称

3)获得baseUrl

(3)创建父工程

1)创建SpringAIAlibabaProject父工程

2)修改pom文件

(4)创建子模块

1)创建SAA-01HelloWord子模块

2)修改pom文件

3)添加配置文件

4)配置ApiKey

5)主启动

6)编写配置类

7)编写controller

8)测试

9)切换其他模型

5、Ollama私有化部署

(1)介绍

(2)安装

1)下载地址

2)安装

3)修改模型存储目录

(3)安装大模型

(4)微服务对接模型

1)创建新的模块

2)修改pom文件

3)添加配置文件

4)编写control

5)测试

6)ollama与dashscope并存问题

6、ChatClient VS ChatModel

(1)简介

(2)编码案例

1)创建工程

2)修改pom文件

3)添加配置文件

4)配置类

5)只使用ChatModel

6)只使用ChatCilent

7)chatModel和chatClient混合使用

7、Stream流式输出及多模型共存

(1)流式输出介绍

(2)SSE

(3)代码实现多模型共存使用

1)创建模块

2)修改pom文件

3)添加配置文件

4)创建配置类ChatModel实现

5)创建controller类ChatModel实现

6)ChatModel测试

7)修改配置类ChatClient实现

8)修改controller类ChatClient实现

9)ChatClient测试

(4)结合前端代码

1)创建index.html页面

2)测试

8、提示词Prompt

(1)介绍

(2)四大角色

(3)业务测试

9、提示词模板

(1)介绍

(2)代码实现

1)基本使用

2)读取模板文件

3)多角色设定

4)人物设定

10、格式化输出

(1)介绍

(2)代码实现

1)创建实体类

2)编写controller

3)测试

11、连续对话保存和持久化

(1)介绍

(2)代码实现

1)创建工程

2)修改pom文件

3)添加配置文件

4)编写配置类

5)配置MessageWindowChatMemory

6)controller类

7)测试

12、文生图

13、文生音

14、向量数据库

(1)介绍

1)向量

2)嵌入模型

3)向量数据库

(2)redisStack向量数据库

1)介绍

2)RedisStack核心组件

3)安装

4)常用指令

(3)代码实现

1)创建模块

2)修改pom文件

3)添加配置文件

4)编写controller

5)测试

15、RAG

(1)介绍

1)LLM的缺陷

2)是什么

(2)代码实现

1)知识库

2)添加配置类

3)编写controller

4)测试

(3)向量数据库去重

1)修改配置类

2)测试

16、ToolCalling工具调用

(1)介绍

(2)代码结合

1)添加工具类

2)编写controller - chatModel实现

3)编写controller - chatClient实现

17、模型上下文协议MCP

(1)概述

(2)核心架构

(3)对比

(4)本地MCP开发

1)MCP服务端

1、新建模块

2、修改pom文件

3、添加配置文件

5、编写服务类

6、接口配置类

7、启动

2)MCP客户端

1、新建模块

2、修改pom文件

3、添加配置文件

4、编写配置类

5、编写controller

6、测试

(5)远程MCP增强

1)高德地图mcp

2)获取apiKey

3)客户端调用

1、新建模块

2、修改pom文件

3、添加配置文件

4、Typescript接入

5、新建配置类

6、新建controller

7、测试

18、云上RAG知识库

(1)阿里云上知识库搭建

(2)代码结合

1)创建模块

2)修改pom文件

3)添加配置文件

4)编写配置类

5)编写controller


1、介绍

        Spring Al Alibaba 项目的产生背景是生成式 AI与大模型在过去一年的快速发展,我们作为使用方、开发者,更关注的应该是如何为我们的应用接入生成式AI能力。

       在这样的背景下,Spring 官方开源了 Spring Al 框架,用来简化 Spring 开发者开发智能体应用的过程。随后阿里巴巴开源了 Spring Al Alibaba,它基于 Spring Al,同时与阿里云百炼大模型服务、通义系列大模型做了深度集成与最佳实践。基于 Spring Al Alibaba,Java 开发者可以非常方便的开发 AI智能体应用。

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

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

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

SpringAI官网地址https://spring.io/projects/spring-ai#learn

2、官网地址

Spring AI 官网:https://spring.io/projects/spring-ai#overview
Spring AI Alibaba 官网:https://java2ai.com
Spring AI Alibaba 仓库:https://github.com/alibaba/spring-ai-alibaba
Spring AI Alibaba 官方示例仓库:https://github.com/springaialibaba/spring-ai-alibaba-examples
Spring AI 1.0 GA 文章:https://java2ai.com/blog/spring-ai-100-ga-released
Spring AI 仓库:https://github.com/spring-projects/spring-ai

阿里云百炼平台:https://bailian.console.aliyun.com/console?tab=model#/model-market

3、框架对比

4、工程搭建

(1)版本依赖

技术文档https://java2ai.com/docs/1.0.0.2/faq/?spm=4347728f.6d9f13c1.0.0.17177187POpLHJ#%E6%80%8E%E4%B9%88%E7%A1%AE%E5%AE%9A-spring-ai-alibaba-%E4%B8%8E-spring-aispring-boot-%E7%89%88%E6%9C%AC%E7%9A%84%E5%85%BC%E5%AE%B9%E5%85%B3%E7%B3%BB

(2)接入阿里百炼平台的通义模型

阿里百炼平台https://bailian.console.aliyun.com/

1) 获得Api-key

2)获取模型名称

点击获取模型名称

3)获得baseUrl

点击api参考

(3)创建父工程

1)创建SpringAIAlibabaProject父工程

2)修改pom文件

管理版本依赖

文档https://java2ai.com/docs/1.0.0.2/tutorials/starters-and-quick-guide/?spm=5176.29160081.0.0.2856aa5c0l3sEA#%E4%BD%BF%E7%94%A8-bom-%E7%AE%A1%E7%90%86%E4%BE%9D%E8%B5%96%E7%89%88%E6%9C%AC

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.hk</groupId>
    <artifactId>springAIAlibabaProject</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <java.version>21</java.version>
        <!-- Spring Boot-->
        <spring-boot.version>3.5.5</spring-boot.version>
        <!-- Spring AI-->
        <spring-ai.version>1.0.0</spring-ai.version>
        <!-- Spring AI Alibaba-->
        <SpringAIAlibaba.version>1.0.0.2</SpringAIAlibaba.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- 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>
    </dependencyManagement>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
            </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>

(4)创建子模块

1)创建SAA-01HelloWord子模块

2)修改pom文件

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.hk</groupId>
        <artifactId>springAIAlibabaProject</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>SAA-01HelloWord</artifactId>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

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

3)添加配置文件

server.port=8001

#大模型对话中文乱码UTF8编码处理
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
server.servlet.encoding.charset=utf-8

spring.application.name=SSA-01HelloWord

# 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

4)配置ApiKey

ApiKey不可以明文,需配置进环境变量

5)主启动

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

6)编写配置类

读取ApiKey

方式一:

@Configuration
public class SaaLLMConfig {

    @Value("${spring.spring.ai.dashscope.api-key}")
    private String apiKey;
    
    @Bean
    public DashScopeApi dashScopeApi(){
        return DashScopeApi.builder().apiKey(apiKey).build();
    }
}

方式二:

@Configuration
public class SaaLLMConfig {

    @Bean
    public DashScopeApi dashScopeApi(){
        return DashScopeApi.builder()
                .apiKey(System.getenv("aliQwen-api"))
                .build();
    }
}

7)编写controller

@RestController
public class ChatHelloController {

    @Autowired
    private ChatModel chatModel;

    // 普通返回
    @GetMapping("/hello/v1")
    public String doChat(@RequestParam(value = "msg", defaultValue = "你是谁") String msg) {
        return chatModel.call(msg);
    }

    // 流式返回
    @GetMapping("/hello/v2")
    public Flux<String> streamChat(@RequestParam(value = "msg", defaultValue = "你是谁") String msg) {
        return chatModel.stream(msg);
    }
}

8)测试

9)切换其他模型

修改配置文件

# 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=deepseek-v3.2

5、Ollama私有化部署

(1)介绍

Ollama 是一个开源、跨平台的本地大语言模型(LLM)部署与管理工具,核心目标是让普通用户与开发者能在自己设备上一键运行各类开源大模型,无需复杂配置、不依赖云端服务,兼顾隐私与低延迟。

官网地址https://ollama.com/

(2)安装

1)下载地址

地址https://ollama.com/download

2)安装

  • OllamaSetup.exe复制到指定文件夹,例如: D:\mysoftware
  • 在该文件夹输入cmd打开黑窗口
  • 输入命令

OllamaSetup.exe   /DIR=要安装的目录

例如:OllamaSetup.exe /DIR=D:\mysoftware\Ollama

如果不指定目录,默认安装在C盘

3)修改模型存储目录

  • 新建一个环境变量

Ollama默认存储模型的位置是 C:\Users\HP\.ollama

  • 将里面的modes文件夹复制到D:\mysoftware\Ollama下面,然后将C盘的删除

  • 重启Ollama,打开CMD,输入Ollama list,查看大模型资源是否能够正常显示

(3)安装大模型

  • Ollama的默认端口为11434,查看Ollama是否安装成功 Ollama --version
  • 在Ollama上查找模型

  • 复制命令,在cmd窗口运行

找到需要下载的模型,复制命令,安装完成后用 ollama list查看安装的模型列表

(4)微服务对接模型

1)创建新的模块

2)修改pom文件

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.hk</groupId>
        <artifactId>springAIAlibabaProject</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>SAA-02Ollma</artifactId>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </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>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>

3)添加配置文件

server.port=8002

server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
server.servlet.encoding.charset=UTF-8

spring.application.name=SAA-02Ollama

# ====ollama Config=============
spring.ai.ollama.base-url=http://localhost:11434
spring.ai.ollama.chat.model=qwen:0.5b

4)编写control

@RestController
public class OllamaController {

    @Autowired
    private ChatModel chatModel;

    @GetMapping("/ollama/chat")
    public String chat(@RequestParam String msg) {
        return chatModel.call(msg);
    }
}

5)测试

6)ollama与dashscope并存问题

  • 修改pom
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.hk</groupId>
        <artifactId>springAIAlibabaProject</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>SSA-02Ollma</artifactId>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </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>
        </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>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>
  • 修改配置文件
server.port=8002

server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
server.servlet.encoding.charset=UTF-8

spring.application.name=SAA-02Ollama

# ====ollama Config=============
spring.ai.dashscope.api-key=${aliQwen-api}
spring.ai.ollama.base-url=http://localhost:11434
spring.ai.ollama.chat.model=qwen:0.5b
  • 添加配置类
@Configuration
public class SaaLLMConfig {

    @Bean
    public DashScopeApi dashScopeApi(){
        return DashScopeApi.builder()
                .apiKey(System.getenv("aliQwen-api"))
                .build();
    }
}
  • 启动服务

发现出现了异常,原因是出现了多个Model,程序并不知道调用哪一个model

  • 修改controller解决

方式一

@RestController
public class OllamaController {

    // - dashscopeChatModel
    //	- ollamaChatModel
    @Resource(name = "ollamaChatModel")
    private ChatModel chatModel;

    @GetMapping("/ollama/chat")
    public String chat(@RequestParam String msg) {
        return chatModel.call(msg);
    }
}

方式二

@RestController
public class OllamaController {

    // - dashscopeChatModel
    //	- ollamaChatModel
    @Resource
    @Qualifier("ollamaChatModel")
    private ChatModel chatModel;

    @GetMapping("/ollama/chat")
    public String chat(@RequestParam String msg) {
        return chatModel.call(msg);
    }
}

6、ChatClient VS ChatModel

(1)简介

ChatClient 提供了与 AI 模型通信的 Fluent API,它支持同步和反应式(Reactive)编程模型。与 ChatModelMessageChatMemory 等原子 API 相比,使用 ChatClient 可以将与 LLM 及其他组件交互的复杂性隐藏在背后,因为基于 LLM 的应用程序通常要多个组件协同工作(例如,提示词模板、聊天记忆、LLM Model、输出解析器、RAG 组件:嵌入模型和存储),并且通常涉及多个交互,因此协调它们会让编码变得繁琐。当然使用 ChatModel 等原子 API 可以为应用程序带来更多的灵活性,成本就是您需要编写大量样板代码。

ChatClient 类似于应用程序开发中的服务层,它为应用程序直接提供 AI 服务,开发者可以使用 ChatClient Fluent API 快速完成一整套 AI 交互流程的组装。

包括一些基础功能,如:

  • 定制和组装模型的输入(Prompt)
  • 格式化解析模型的输出(Structured Output)
  • 调整模型交互参数(ChatOptions)

还支持更多高级功能:

  • 聊天记忆(Chat Memory)
  • 工具/函数调用(Function Calling)
  • RAG

文档地址https://java2ai.com/docs/1.0.0.2/tutorials/basics/chat-client/?spm=5176.29160081.0.0.2856aa5cmUTyXCChatClient是高级封装,基于ChatModel构建,适合快速构建标准化复杂AI服务,支持同步和流式交互,集成多种高级功能。

(2)编码案例

1)创建工程

2)修改pom文件

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.hk</groupId>
        <artifactId>springAIAlibabaProject</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>SAA-03ChatClient</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </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>
        </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)添加配置文件

server.port=8003

server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
server.servlet.encoding.charset=UTF-8

spring.application.name=SAA-03ChatClient

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

# base-url 如果不配置,默认是https://dashscope.aliyuncs.com/compatible-mode/v1
# model 如果不配置,默认是 qwen-plus

4)配置类

@Configuration
public class SaaLLMConfig {

    @Bean
    public DashScopeApi dashScopeApi(){
        return DashScopeApi.builder()
                .apiKey(System.getenv("aliQwen-api"))
                .build();
    }

5)只使用ChatModel

controller类

@RestController
public class ChatModelController {

    @Autowired
    private ChatModel chatModel;

    @GetMapping("/chatmodel/chat")
    public String chat(@RequestParam(value = "msg", defaultValue = "你是谁") String msg){
        return chatModel.call(msg);
    }
}

6)只使用ChatCilent

如果直接自动注入ChatClient,会出现异常,所以ChatClient不支持自动注入,只能手动注入。

@RestController
public class ChatClientController {
    private final ChatClient chatClient;

    public ChatClientController(ChatModel chatModel) {
        this.chatClient = ChatClient.builder(chatModel).build();
    };

    @GetMapping("/chatclient/chat")
    public String chat(@RequestParam(value = "msg", defaultValue = "你是谁") String msg){
        return chatClient.prompt().user(msg).call().content();
    }
}

7)chatModel和chatClient混合使用

修改配置文件,将ChatClient使用配置类注入

@Configuration
public class SaaLLMConfig {

    @Bean
    public DashScopeApi dashScopeApi(){
        return DashScopeApi.builder()
                .apiKey(System.getenv("aliQwen-api"))
                .build();
    }
    
    @Bean
    public ChatClient chatClient(ChatModel chatModel) {
        return ChatClient.builder(chatModel).build();
    }
}

混合使用controller

@RestController
public class ChatClientModelController {

    @Resource
    private ChatModel chatModel;

    @Resource
    private ChatClient chatClient;


    @GetMapping("/chat/model")
    public Flux<String> chatM(@RequestParam String msg) {
        return chatModel.stream(msg);
    }

    @GetMapping("/chat/client")
    public Flux<String> chatC(@RequestParam String msg) {
        return chatClient.prompt().user(msg).stream().content();
    }
}

二者对比

7、Stream流式输出及多模型共存

(1)流式输出介绍

StreamingOutput(流式输出)是一种逐步返回大模型生成结果的技术,生成一点返回一点,允许服务器将响应内容分批次实时传输给客户端,而不是等待全部内容生成完毕后再一次性返回。 
这种机制能显著提升用户体验,尤其适用于大模型响应较慢的场景(如生成长文本或复杂推理结果)。

(2)SSE

        Server-Sent Events (SSE) 是一种允许服务端可以持续推送数据片段(如逐词或逐句)到前端的 Web 技术。通过单向的HTTP长连接,使用一个长期存在的连接,让服务器可以主动将数据"推"给客户端,SSE是轻量级的单向通信协议,适合AI对话这类服务端主导的场景
        SSE 的核心思想是:客户端发起一个请求,服务器保持这个连接打开并在有新数据时,通过这个连接将数据发送给客户端。这与传统的请求-响应模式(客户端请求一次,服务器响应一次,连接关闭)有本质区别。

(3)代码实现多模型共存使用

1)创建模块

2)修改pom文件

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.hk</groupId>
        <artifactId>springAIAlibabaProject</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>SAA-04StreamingOutput</artifactId>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </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>
        </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>

3)添加配置文件

server.port=8004

server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
server.servlet.encoding.charset=UTF-8

spring.application.name=SAA-04StreamingOutput

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

4)创建配置类ChatModel实现

@Configuration
public class SaaLLMConfig {

    private final String DEEPSEEK_MODEL = "deepseek-v3";
    private final String QWEN_MODEL = "qwen-plus";

    @Bean("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("qwen")
    public ChatModel qwen() {
        return DashScopeChatModel.builder()
                .dashScopeApi(DashScopeApi.builder().apiKey(System.getenv("aliQwen-api")).build())
                .defaultOptions(DashScopeChatOptions.builder().withModel(QWEN_MODEL).build())
                .build();
    }

}

5)创建controller类ChatModel实现

@RestController
public class ChatModelStreamOutputController {

    @Resource(name = "deepseek")
    private ChatModel deepseekChatModel;

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

    @GetMapping("/stream-v1")
    public Flux<String> streamDeepSeek(@RequestParam(value = "msg", defaultValue = "你是谁") String msg) {
        return deepseekChatModel.stream(msg);
    }

    @GetMapping("/stream-v2")
    public Flux<String> streamQWen(@RequestParam(value = "msg", defaultValue = "你是谁") String msg) {
        return qwenChatModel.stream(msg);
    }
}

6)ChatModel测试

7)修改配置类ChatClient实现

@Configuration
public class SaaLLMConfig {

    private final String DEEPSEEK_MODEL = "deepseek-v3";
    private final String QWEN_MODEL = "qwen-plus";

    @Bean("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("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("deepseekChatClient")
    public ChatClient deepSeekChatClient(@Qualifier("deepseek") ChatModel deepSeekChatModel) {
        return ChatClient.builder(deepSeekChatModel)
                .defaultOptions(ChatOptions.builder().model(DEEPSEEK_MODEL).build())
                .build();
    }

    @Bean("qwenChatClient")
    public ChatClient qwenChatClient(@Qualifier("qwen") ChatModel deepSeekChatModel) {
        return ChatClient.builder(deepSeekChatModel)
                .defaultOptions(ChatOptions.builder().model(QWEN_MODEL).build())
                .build();
    }

}

8)修改controller类ChatClient实现

@RestController
public class ChatModelStreamOutputController {

    @Resource(name = "deepseek")
    private ChatModel deepseekChatModel;

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

    @Resource(name = "deepseekChatClient")
    private ChatClient deepseekChatClient;

    @Resource(name = "qwenChatClient")
    private ChatClient qwenChatClient;


    @GetMapping("/stream-v1")
    public Flux<String> streamDeepSeek(@RequestParam(value = "msg", defaultValue = "你是谁") String msg) {
        return deepseekChatModel.stream(msg);
    }

    @GetMapping("/stream-v2")
    public Flux<String> streamQWen(@RequestParam(value = "msg", defaultValue = "你是谁") String msg) {
        return qwenChatModel.stream(msg);
    }

    @GetMapping("/stream-v3")
    public Flux<String> streamDeepSeekChatClient(@RequestParam(value = "msg", defaultValue = "你是谁") String msg) {
        return deepseekChatClient.prompt(msg).stream().content();
    }

    @GetMapping("/stream-v4")
    public Flux<String> streamQWenChatClient(@RequestParam(value = "msg", defaultValue = "你是谁") String msg) {
        return qwenChatClient.prompt(msg).stream().content();
    }
}

9)ChatClient测试

(4)结合前端代码

1)创建index.html页面

<!DOCTYPE html>
<html>
<head>
    <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;
        }
    </style>
</head>
<body>
<textarea id="messageInput" rows="4" cols="50" placeholder="请输入你的问题..."></textarea><br>
<button onclick="sendMsg()">发送提问</button>
<div id="messages"></div>
<script>
    function sendMsg() {
        // 获取用户输入的消息
        const message = document.getElementById('messageInput').value;
        if (message == "") return false;

        const messagesDiv = document.getElementById('messages');
        messagesDiv.innerHTML = "";
        //1 客户端使用 JavaScript 的 EventSource 对象连接到服务器上的一个特定端点(URL)
        const eventSource = new EventSource('stream-v1?msg=' + message);
        //2 监听消息事件
        eventSource.onmessage = function (event) {
            // 获取流式返回的数据
            const data = event.data;
            // 将接收到的数据展示到页面上
            messagesDiv.innerHTML += event.data;
        };

        //3 监听错误事件
        eventSource.onerror = function (error) {
            console.error('EventSource 发生错误:', error);
            eventSource.close(); // 关闭连接
        };
    }
</script>
</body>
</html>

2)测试

8、提示词Prompt

(1)介绍

文档地址https://java2ai.com/docs/1.0.0.2/tutorials/basics/prompt/?spm=5176.29160081.0.0.2856aa5cdeol7a

(2)四大角色

  • system:设定AI行为边界/角色/定位。指导AI的行为和响应方式,设置AI如何解释和回复输入的
  • user:用户原始提问输入。代表用户的输入他们向AI提出的问题、命令或陈述。
  • assistant:AI返回的响应信息,定义为”助手角色”消息。用它可以确保上下文能够连贯的交互。
  • tool:桥接外部服务,可以进行函数调用如,支付/数据查询等操作,类似调用第3方util工具类,后面章节详细介绍

(3)业务测试

1)system和user使用

@RestController
public class PromptController {

    @Resource(name = "qwenChatClient")
    private ChatClient chatClient;

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

    @GetMapping("/chat-v1")
    public Flux<String> v1(@RequestParam String msg) {
        return chatClient.prompt()
                .system("你是一个法律助手,只回答法律问题,其它问题回复,我只能回答法律相关问题,其它无可奉告")
                .user(msg)
                .stream()
                .content();
    }

    @GetMapping("/chat-v2")
    public Flux<String> v2(@RequestParam String msg) {
        SystemMessage systemMessage = new SystemMessage("你是一个法律助手,只回答法律问题,其它问题回复,我只能回答法律相关问题,其它无可奉告");
        UserMessage userMessage = new UserMessage(msg);
        Prompt prompt = new Prompt(systemMessage, userMessage);

        return chatModel.stream(prompt).map(response -> response.getResult().getOutput().getText());

    }
}

2)assistant使用

AI返回的响应信息,定义为”助手角色”消息。用它可以确保上下文能够连贯的交互。

    @GetMapping("/chat-v3")
    public String v3(@RequestParam String msg) {
        AssistantMessage assistantMessage = chatClient.prompt()
                .user(msg)
                .call()
                .chatResponse()
                .getResult()
                .getOutput();
        
        return assistantMessage.getText();
    }

3)tool使用

桥接外部服务,可以进行函数调用如,支付/数据查询等操作,类似调用第3方util工具类

@GetMapping("/chat-v4")
    public String v4(@RequestParam String msg) {
        String text = chatClient.prompt()
                .user(msg + "未来3天天气情况如何?")
                .call()
                .chatResponse()
                .getResult()
                .getOutput()
                .getText();

        ToolResponseMessage toolResponseMessage = new ToolResponseMessage(List.of(new ToolResponseMessage.ToolResponse("1", "获取天气", msg)));
        String messageText = toolResponseMessage.getText();

        return text + messageText;
    }

9、提示词模板

(1)介绍

Spring AI 中用于提示模板的关键组件是 PromptTemplate 类。该类使用 Terence Parr 开发的 OSS StringTemplate 引擎来构建和管理提示。PromptTemplate 类旨在促进结构化提示的创建,然后将其发送到 AI 模型进行处理

引入占位符(如{占位符变量名})以动态插入内容。

(2)代码实现

1)基本使用

@RestController
public class PromptTemplateController {

    @Resource(name = "qwenChatClient")
    private ChatClient chatClient;

    @GetMapping("/chatTemplate")
    public Flux<String> chatTemplate(String topic, String outputFormat, String wordCount) {
        PromptTemplate promptTemplate = new PromptTemplate("""
                将一个关于{topic}的故事
                并以{output_format}格式输出
                字数在{wordCount}左右
                """);
        Prompt prompt = promptTemplate.create(Map.of("topic", topic, "output_format", outputFormat, "wordCount", wordCount));

        return chatClient.prompt(prompt).stream().content();
    }
}

2)读取模板文件

    @Value("classpath:prompttemplate/test.txt")
    private org.springframework.core.io.Resource template;

    @GetMapping("/chatTemplate-2")
    public Flux<String> chatTemplate2(String topic, String outputFormat) {
        PromptTemplate promptTemplate = new PromptTemplate(template);
        Prompt prompt = promptTemplate.create(Map.of("topic", topic, "output_format", outputFormat));

        return chatClient.prompt(prompt).stream().content();
    }

3)多角色设定

   @GetMapping("/chatTemplate-3")
    public Flux<String> chatTemplate3(String systemTopic, String userTopic) {
        SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("你是{systemTopic}助手, 只回答{systemTopic}其他无可奉告。以html的格式输出");
        Message systemMessage = systemPromptTemplate.createMessage(Map.of("systemTopic", systemTopic));

        PromptTemplate promptTemplate = new PromptTemplate("解释一下{userTopic}");
        Message userMessage = promptTemplate.createMessage(Map.of("userTopic", userTopic));

        Prompt prompt = new Prompt(systemMessage, userMessage);

        return chatClient.prompt(prompt).stream().content();
    }

4)人物设定

    @GetMapping("/chatTemplate-4")
    public Flux<String> chatTemplate4(String msg) {
        SystemMessage systemMessage = new SystemMessage("你是法律助手,拒绝回答其他问题");
        UserMessage userMessage = new UserMessage(msg);
        Prompt prompt = new Prompt(systemMessage, userMessage);

        return chatModel.stream(prompt).map(res -> res.getResult().getOutput().getText());
    }

    @GetMapping("/chatTemplate-5")
    public Flux<String> chatTemplate5(String msg) {
        return chatClient.prompt()
                .system("你是法律助手,拒绝回答其他问题")
                .user(msg)
                .stream()
                .content();
    }

10、格式化输出

(1)介绍

        LLM 生成结构化输出的能力对于依赖可靠解析输出值的下游应用程序非常重要。开发人员希望快速将 AI 模型的结果转换为可以传递给其他应用程序函数和方法的数据类型,例如 JSON、XML 或 Java 类。Spring AI 结构化输出转换器有助于将 LLM 输出转换为结构化格式。

文档地址https://java2ai.com/docs/1.0.0.2/tutorials/basics/structured-output/?spm=5176.29160081.0.0.2856aa5cPJ9Ha8

(2)代码实现

1)创建实体类

public record StudentRecord(String id,String name,String major,String email ) {
}

2)编写controller

@RestController
public class StudentController {

    @Resource(name = "qwenChatClient")
    private ChatClient chatClient;

    @GetMapping("/getStudent")
    public StudentRecord chat(String name, String email) {
        return chatClient.prompt()
                .user(promptUserSpec ->
                        promptUserSpec.text("我是{name},学号10001,学习的专业是室内设计,我的邮箱是{email}")
                                .param("name", name)
                                .param("email", email))
                .call()
                .entity(StudentRecord.class);
    }
}

3)测试

11、连续对话保存和持久化

(1)介绍

对话记忆(Dialogue Memory/Conversation Memory),是对话系统(含大模型助手、客服机器人等)中存储、管理、复用对话上下文与用户历史交互信息的机制,核心目标是让系统能 “记住” 当前会话及跨会话的关键信息,从而理解上下文语义、维持对话连贯性、提供个性化响应。

文档地址https://java2ai.com/docs/1.0.0.2/tutorials/basics/memory/?spm=4347728f.4dc6f515.0.0.538b4305NobuzA

(2)代码实现

1)创建工程

2)修改pom文件

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.hk</groupId>
        <artifactId>springAIAlibabaProject</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>SAA-05Persistent</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </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>
        </dependency>
        <!--spring-ai-alibaba memory-redis-->
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-memory-redis</artifactId>
        </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>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>

3)添加配置文件

server.port=8008

# 设置响应的字符编码
server.servlet.encoding.charset=utf-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true

spring.application.name=SAA-08Persistent

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

# ==========redis config ===============
spring.data.redis.host=localhost
spring.data.redis.port=6379
spring.data.redis.database=10
spring.data.redis.password=12345
spring.data.redis.connect-timeout=3
spring.data.redis.timeout=2

4)编写配置类

        1、要实现SpringAI框架规定的ChatMemoryRepository接口,SpringAi的默认实现是InMemoryChatMemoryRepository

        2、引入了spring-ai-alibaba-starter-memory-redis坐标,ChatMemoryRepository的实现为InMemoryChatMemoryRepository和RedisChatMemoryRepository

        3、应为RedisChatMemoryRepository使用的是jedisPool,所以需要引入jedis坐标

        4、添加redis配置类

@Configuration
public class RedisMemoryConfig {

    @Value("${spring.data.redis.host}")
    private String host;

    @Value("${spring.data.redis.port}")
    private int port;

    @Value("${spring.data.redis.password}")
    private String password;

    @Bean
    public RedisChatMemoryRepository redisChatMemoryRepository(){
        return RedisChatMemoryRepository.builder()
                .host(host)
                .port(port)
                .password(password)
                .timeout(10)
                .build();
    }
}

5)配置MessageWindowChatMemory

MessageWindowChatMemory维护一个最大不限的消息窗口。当消息数量超过最大值时,旧消息被删除,同时保留系统消息。默认窗口大小为20条消息。

文档地址https://docs.spring.io/spring-ai/reference/api/chat-memory.html#_message_window_chat_memory

@Configuration
public class SaaLLMConfig {

    private final String QWEN_MODEL = "qwen-plus";

    @Bean("qwen")
    public ChatModel chatModel(){
        return DashScopeChatModel.builder()
                .dashScopeApi(DashScopeApi.builder().apiKey(System.getenv("aliQwen-api")).build())
                .defaultOptions(DashScopeChatOptions.builder().withModel(QWEN_MODEL).build())
                .build();
    }

    @Bean("qwenChatClient")
    public ChatClient qwenChatClient(@Qualifier("qwen") ChatModel qwenChatModel, 
                                     RedisChatMemoryRepository redisChatMemoryRepository) {
        MessageWindowChatMemory messageWindowChatMemory = MessageWindowChatMemory.builder()
                .chatMemoryRepository(redisChatMemoryRepository)
                .maxMessages(10)
                .build();
        return ChatClient.builder(qwenChatModel)
                .defaultOptions(ChatOptions.builder().model(QWEN_MODEL).build())
                .defaultAdvisors(MessageChatMemoryAdvisor.builder(messageWindowChatMemory).build())
                .build();
    }
}

6)controller类

@RestController
public class ChatMemoryController {

    @Resource(name = "qwenChatClient")
    private ChatClient chatClient;

    @GetMapping
    public String chat(String msg, String userId) {
        return chatClient.prompt(msg)
                .advisors(advisorSpec -> advisorSpec.param(CONVERSATION_ID, userId))
                .call()
                .content();
    }
}

7)测试

12、文生图

通过语言描述生成需要的图片

阿里文档地址https://help.aliyun.com/zh/model-studio/text-to-image-v2-api-reference?spm=a2c4g.11186623.help-menu-2400256.d_2_2_4.665a6968Yua49P

@RestController
public class Text2ImageController {

    @Resource
    private ImageModel imageModel;

    @GetMapping("image")
    public String image(String msg){
        return imageModel.call(
                new ImagePrompt(msg,
                        DashScopeImageOptions.builder()
                                .withWidth(1920)
                                .withHeight(1080)
                                .withModel("wan2.5-t2i-preview")
                                .build()
                )
        ).getResult()
                .getOutput()
                .getUrl();
    }

13、文生音

将文字转成语音

模型参数设置https://help.aliyun.com/zh/model-studio/cosyvoice-java-sdk#2e9a9a89aclc8音色列表https://help.aliyun.com/zh/model-studio/cosyvoice-voice-list?spm=a2c4g.11186623.0.0.2a74740fnnjgNw

@RestController
public class Text2VoiceController {

    @Resource
    private SpeechSynthesisModel speechSynthesisModel;

    @GetMapping("/voice")
    public String voice() {
        String path = "d:/" + UUID.randomUUID() + ".mp3";

        DashScopeSpeechSynthesisOptions dashScopeSpeechSynthesisOptions = DashScopeSpeechSynthesisOptions.builder()
                // 音色
                .voice("longxiaoxia_v2")
                .model("cosyvoice-v2")
                .build();
        String msg = "支付宝到账,100元";
        SpeechSynthesisResponse response = speechSynthesisModel.call(new SpeechSynthesisPrompt(msg, dashScopeSpeechSynthesisOptions));
        ByteBuffer buffer = response.getResult().getOutput().getAudio();

        // 输出
        try(FileOutputStream fileOutputStream = new FileOutputStream(path)) {
            fileOutputStream.write(buffer.array());
            fileOutputStream.flush();
        } catch (Exception e){

        }
        return path;
    }
}

14、向量数据库

(1)介绍

1)向量

        向量是将非结构化数据(文本、图像、音频等)转化为机器可理解的数字形式的核心载体,也被称为特征向量嵌入向量(Embedding)。它不再局限于数学 / 物理中的 “大小 + 方向” 几何定义,而是延伸为高维空间中的一个坐标点,通过向量间的数学关系(距离、相似度)来量化数据的语义、特征或行为关联。

2)嵌入模型

文档地址https://java2ai.com/docs/1.0.0.2/tutorials/basics/embedding/?spm=5176.29160081.0.0.2856aa5cXggpMJ

嵌入(Embedding)的工作原理是将文本、图像和视频转换为称为向量(Vectors)的浮点数数组。这些向量旨在捕捉文本、图像和视频的含义。嵌入数组的长度称为向量的维度(Dimensionality)。

嵌入模型(EmbeddingModel)是嵌入过程中采用的模型。当前EmbeddingModel的接口主要用于将文本转换为数值向量,接口的设计主要围绕这两个目标展开:

  • 可移植性:该接口确保在各种嵌入模型之间的轻松适配。它允许开发者在不同的嵌入技术或模型之间切换,所需的代码更改最小化。这一设计与 Spring 模块化和互换性的理念一致。
  • 简单性:嵌入模型简化了文本转换为嵌入的过程。通过提供如embed(String text)embed(Document document)这样简单的方法,它去除了处理原始文本数据和嵌入算法的复杂性。这个设计选择使开发者,尤其是那些初次接触 AI 的开发者,更容易在他们的应用程序中使用嵌入,而无需深入了解其底层机制。

嵌入模型是一类专门将非结构化数据(文本、图像、音频等) 映射到低维稠密向量空间的机器学习模型,其输出的向量(称为嵌入向量 / Embedding)能够保留原始数据的语义关联、特征属性或相似性关系。它是 AI 领域实现 “数据数字化理解” 的核心工具,也是连接原始数据与向量数据库、大模型等下游应用的桥梁。

简单来说:嵌入模型的作用,就是给每一份数据生成唯一的 “语义身份证”(向量)

3)向量数据库

文档地址https://java2ai.com/docs/1.0.0.2/tutorials/basics/vectorstore/?spm=5176.29160081.0.0.2856aa5cXggpMJ        向量存储(VectorStore)是一种用于存储和检索高维向量数据的数据库或存储解决方案,它特别适用于处理那些经过嵌入模型转化后的数据。在 VectorStore 中,查询与传统关系数据库不同。它们执行相似性搜索,而不是精确匹配。当给定一个向量作为查询时,VectorStore 返回与查询向量“相似”的向量。

        其核心功能是通过高效的索引结构和相似性计算算法,支持大规模向量数据的快速查询与分析,向量数据库维度越高,查询精准度也越高,查询效果也越好。

例如:LangChain4J支持的向量数据库

LangChain4J支持的向量数据库

SpringAI支持的向量数据库

总结:就是将文本、图像和视频转换为向量(Vectors)的浮点数数组在 VectorStore中,查询与传统关系数据库不同。它们执行相似性搜索,而不是精确匹配。当给定一个向量作为查询时,VectorStore 返回与查询向量“相似”的向量

(2)redisStack向量数据库

1)介绍

        redisStack是redis的增强版,不是redis的替代品,而是在原生的redis基础上的功能扩展,专为构建现代实时应用而设计。

2)RedisStack核心组件

  • RediSearch

        提供全文搜索能力,支持复杂的文本搜索、聚合和过滤,以及向量数据的存储和检索

  • RedisJSON

        原生支持JSON数据的存储、索引I和查询,可高效存储和操作嵌套的JSON文档。

  • RedisGraph

        支持图数据模型,使用Cypher查询语言进行图遍历查询。

  • RedisBloom

        支持 Bloom、Cuckoo、Count-Min Sketch等概率数据结构。

RedisStack = 原生Redis + 搜索 + 图 + 时间序列 + JSON + 概率结构 + 可视化工具 + 开发框架支持

3)安装

docker安装:

docker run -d --name redis-stack-server -p 6379:6379 redis/redis-stack-server

4)常用指令

JSON.SET - 设置JSON值

JSON.GET - 获取JSON值

JSON.DEL - 删除JOSN值

JSON.MGET - 批量获取多个键的JSON值

JSON.TYPE - 获取JOSN值的类型

JSON.NUMINCRBY - 对JSON中的数字进行增量操作

JSON.STRAPPEND - 追加字符串到JSON字符串

JSON.STRLEN - 获取JOSN字符串的长度

(3)代码实现

1)创建模块

2)修改pom文件

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.hk</groupId>
        <artifactId>springAIAlibabaProject</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>SAA-06Vector</artifactId>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </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>
        </dependency>
        <!-- 添加 Redis 向量数据库依赖 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-vector-store-redis</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>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>

3)添加配置文件

本次使用阿里云百炼平台向量大模型 text-embedding-v3

模型地址https://bailian.console.aliyun.com/?tab=api#/api/?type=model&url=https%3A%2F%2Fhelp.aliyun.com%2Fdocument_detail%2F2712515.html

server.port=8006

# 设置响应的字符编码
server.servlet.encoding.charset=utf-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true

spring.application.name=SAA-06Vector

# ====SpringAIAlibaba Config=============
spring.ai.dashscope.api-key=${aliQwen-api}
spring.ai.dashscope.chat.options.model=qwen-plus
spring.ai.dashscope.embedding.options.model=text-embedding-v3


# =======Redis Stack==========
spring.data.redis.host=192.168.10.40
spring.data.redis.port=6379
spring.data.redis.username=default
spring.data.redis.password=
spring.ai.vectorstore.redis.initialize-schema=true
spring.ai.vectorstore.redis.index-name=custom-index
spring.ai.vectorstore.redis.prefix=custom-prefix

参数说明:

参数出处https://docs.spring.io/spring-ai/reference/api/vectordbs/redis.html

4)编写controller

@RestController
public class VectorController {

    @Resource
    private EmbeddingModel embeddingModel;

    @Resource
    private VectorStore vectorStore;

    // 文本转为向量
    @GetMapping("/text2embed")
    public EmbeddingResponse text2embed(String msg) {
//        embeddingModel.call(new EmbeddingRequest(List.of(msg), null));
        EmbeddingResponse embeddingResponse = embeddingModel.call(new EmbeddingRequest(List.of(msg), DashScopeEmbeddingOptions.builder().withModel("text-embedding-v3").build()));
        return  embeddingResponse;
    }

    // 将文本添加到向量数据库
    @GetMapping("/add")
    public void add() {
        List<Document> documents = List.of(
                new Document("i study java"),
                new Document("i study javaScript"),
                new Document("i study python"),
                new Document("i study jquery")
        );

        vectorStore.add(documents);
    }

    @GetMapping("get")
    public List getAll(String msg) {
        SearchRequest searchRequest = SearchRequest.builder()
                .query(msg)
                .topK(2)
                .build();
        List<Document> documents = vectorStore.similaritySearch(searchRequest);
        return documents;
    }
}

5)测试

测试1:

测试2:

测试3:

15、RAG

(1)介绍

1)LLM的缺陷

  • LLM的知识不是实时的,不具备知识更新.
  • LLM可能不知道你私有的领域/业务知识.
  • LLM有时会在回答中生成看似合理但实际上是错误的信息

2)是什么

RAG 的全称是 Retrieval-Augmented Generation,中文译为检索增强生成,是一种提升大语言模型(LLM)回答准确性、时效性和可信度的核心技术方案。它的核心逻辑是:让大模型在生成回答前,先从外部知识库中检索相关的权威信息,再结合检索到的内容生成回答,以此解决大模型 “幻觉”、知识过时、领域知识不足等痛点。

        简单来说,RAG 相当于给大模型加装了一个可随时更新的 “外挂大脑” —— 大模型不再只依赖训练时的固有知识,而是可以调取最新、最专业的外部数据来辅助回答。        

SpringAI对RAG的支持https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html        通过引入外部知识源来增强LLM的输出能力,传统的LLM通常基于其训练数据生成响应,但这些数据可能过时或不够全面。RAG允许模型在生成答案之前,从特定的知识库中检索相关信息,从而提供更准确和上下文相关的回答

(2)代码实现

1)知识库

提供ErrorCode脚本让存入向量数据库RedisStack,形成文档知识库

2)添加配置类

  • LLMConfig配置类
@Configuration
public class SaaLLMConfig {

    @Bean
    public ChatModel chatModel() {
        return DashScopeChatModel.builder()
                .dashScopeApi(
                        DashScopeApi.builder().apiKey(System.getenv("aliQwen-api")).build()
                )
                .defaultOptions(
                        DashScopeChatOptions.builder().withModel("qwen-plus").build()
                )
                .build();
    }

    @Bean
    public ChatClient chatClient(ChatModel chatModel) {
        return ChatClient.builder(chatModel)
                .defaultOptions(ChatOptions.builder().model("qwen-plus").build())
                .build();
    }
}
  • 初始化配置,将文件存入向量数据库
@Configuration
public class InitVectorDatabaseConfig {

    @Autowired
    private VectorStore vectorStore;

    @Value("classpath:ops.txt")
    private Resource resource;

    @PostConstruct
    public void init() {
        // 读取文件内容
        TextReader textReader = new TextReader(resource);
        textReader.setCharset(Charset.defaultCharset());
        // 转成向量文档
        List<Document> documentList = new TokenTextSplitter().transform(textReader.read());
        // 写入数据库(问题:每次启动都会重新写入数据库一份,同样的文件数据库存在多份)
        vectorStore.add(documentList);
    }
}

3)编写controller

@RestController
public class RagController {

    @Resource
    private ChatClient client;

    @Resource
    private VectorStore vectorStore;

    // 没有加增强的效果
    @GetMapping("/rag")
    public Flux<String> rag(String msg) {
        String systemInfo = """
                你是一个运维工程师,按照给出的编码给出对应故障解释,否则回复找不到信息。
                """;

        return client.prompt()
                .system(systemInfo)
                .user(msg)
                .stream()
                .content();
    }

    // 加增强的效果
    @GetMapping("/rag-v1")
    public Flux<String> ragv1(String msg) {
        String systemInfo = """
                你是一个运维工程师,按照给出的编码给出对应故障解释,否则回复找不到信息。
                """;
        RetrievalAugmentationAdvisor augmentationAdvisor = RetrievalAugmentationAdvisor.builder()
                .documentRetriever(
                        VectorStoreDocumentRetriever.builder()
                                .vectorStore(vectorStore)
                                .build()
                )
                .build();

        return client.prompt()
                .system(systemInfo)
                .user(msg)
                .advisors(augmentationAdvisor)
                .stream()
                .content();
    }
}

4)测试

没有RAG的效果

添加RAG的效果

(3)向量数据库去重

1)修改配置类

上述配置每次重启项目都会向数据库存入一份数据,如何去重:

@Configuration
public class InitVectorDatabaseConfig {

    @Autowired
    private VectorStore vectorStore;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Value("classpath:ops.txt")
    private Resource resource;

    @PostConstruct
    public void init() {
        // 读取文件内容
        TextReader textReader = new TextReader(resource);
        textReader.setCharset(Charset.defaultCharset());
        // 转成向量文档
        List<Document> documentList = new TokenTextSplitter().transform(textReader.read());

        // 数据库去重
        String source = (String) textReader.getCustomMetadata().get("source");
        String textHash = SecureUtil.md5(source);
        String key = "vector-hk:" + textHash;
        // 判断是否存入过,redisKey如果可以成功插入表示以前没有过,可以假如向量数据
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(key, "1");
        if (ifAbsent) {
            //键不存在,首次插入,可以保存进向量数据库
            vectorStore.add(documentList);
        } else {
            System.out.println("------向量初始化数据已经加载过,请不要重复操作");
        }
    }
}

2)测试

多次重启发现数据库只有一份

16、ToolCalling工具调用

例如在LLM询问当前时间,此时他无法给我们回复,他无法获取最新的数据。

(1)介绍

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

SpringAIhttps://docs.spring.io/spring-ai/reference/api/tools.htmlSpringAIAlibabahttps://java2ai.com/docs/1.0.0.2/tutorials/basics/tool-calling/?spm=5176.29160081.0.0.2856aa5cgvn0gm

(2)代码结合

结合上一讲测试在不使用外部工具时的结果

@RestController
public class ToolCallingController {

    @Autowired
    private ChatClient client;

    @GetMapping("getTime")
    public Flux<String> getTime(@RequestParam(value = "msg", defaultValue = "当前时间几点") String msg){
        return client.prompt(msg).stream().content();
    }
}

1)添加工具类

public class DateTool {

    /**
     * returnDirect
     *    true = tool直接返回不走大模型,直接给客户
     *    false = 拿到tool返回的结果,给大模型,最后由大模型回复
     */
    @Tool(description = "获取当前时间", returnDirect = false)
    public String getTime() {
        return LocalDateTime.now().toString();
    }
}

2)编写controller - chatModel实现

@RestController
public class ToolChatModelController {

    @Resource
    private ChatModel chatModel;

    @GetMapping("getTime_v1")
    public String getTime(@RequestParam(value = "msg", defaultValue = "当前时间几点") String msg){
        // 工具注册到工具集合里
        ToolCallback[] callbacks = ToolCallbacks.from(new DateTool());
        // 将工具集配置进ChatOptions对象
        ChatOptions options = ToolCallingChatOptions.builder().toolCallbacks(callbacks).build();
        // 构建提示词
        Prompt prompt = new Prompt(msg, options);
        return chatModel.call(prompt).getResult().getOutput().getText();
    }
}

3)编写controller - chatClient实现

@RestController
public class ToolChatClientController {

    @Resource(name = "qwen")
    private ChatClient chatClient;

    @GetMapping("getTime_v2")
    public String getTime(@RequestParam(value = "msg", defaultValue = "当前时间几点") String msg){
        return chatClient.prompt(msg)
                .tools(new DateTool())
                .call()
                .content();
    }
}

17、模型上下文协议MCP

(1)概述

之前我们需要为每个 API / 工具编写定制化适配代码,上下文传递混乱,无统一规范

        MCP(Model Context Protocol,模型上下文协议) 是由 Anthropic 公司于 2024 年 11 月推出的开放标准协议,被誉为 AI 领域的 “USB-C 接口”,核心目标是标准化大型语言模型(LLM)与外部工具、数据源和服务的双向通信机制,解决传统 AI 集成中 “工具碎片化、适配成本高、安全可控难” 的三大痛点。    

文档地址https://modelcontextprotocol.io/docs/getting-started/intro    

模型地址https://mcp.so/zh

SpringAI Aibaba官网支持MCPhttps://java2ai.com/docs/1.0.0.2/tutorials/basics/model-context-protocol/?spm=5176.29160081.0.0.2856aa5ccBJ7XESpringAI官网支持MCPhttps://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html

(2)核心架构

MCP 主机(MCP Hosts):发起请求的 AI 应用程序,比如聊天机器人、AI 驱动的 IDE 等。

MCP 客户端(MCP Clients):在主机程序内部,与 MCP 服务器保持 1:1 的连接。

MCP 服务器(MCP Servers):为 MCP 客户端提供上下文、工具和提示信息。

本地资源(Local Resources):本地计算机中可供 MCP 服务器安全访问的资源,如文件、数据库。

远程资源(Remote Resources):MCP 服务器可以连接到的远程资源,如通过 API 提供的数据

(3)对比

ToolCalling:工具类,为了让大模型使用Util工具

RAG:知识库,为了让大模型获取足够的上下文

MCP:协议,为了让大模型之间的相互调用

        之前每个大模型(如DeepSeek、ChatGPT)需要为每个工具单独开发接口(FunctionCalling),导致重复劳动,而现在开发者只需写一次MCP服务端,所有兼容MCP协议的模型都能调用,MCP让大模型从"被动应答”变为”主动调用工具”;调用一个MCP服务器就等价调用一个带有多个功能的Utils工具类。

(4)本地MCP开发

1)MCP服务端

1、新建模块

2、修改pom文件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.hk</groupId>
        <artifactId>springAIAlibabaProject</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>SAA-07MCPService</artifactId>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </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-->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-server-webflux</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>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>
3、添加配置文件
server.port=8007

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

spring.application.name=SAA-07McpServer


# ====mcp-server Config=============
spring.ai.mcp.server.type=async
spring.ai.mcp.server.name=customer-define-mcp-server
spring.ai.mcp.server.version=1.0.0
5、编写服务类
@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, "抱歉:未查询到对应城市!");
    }
    
}
6、接口配置类

将工具方法暴露给外部 mcp client 调用

@Configuration
public class McpServerConfig {

    // 将工具方法暴露给外部 mcp client 调用
    @Bean
    public ToolCallbackProvider weatherTools (WeatherService weatherService) {
        return MethodToolCallbackProvider.builder()
                .toolObjects(weatherService)
                .build();
    }
}
7、启动

2)MCP客户端

1、新建模块

2、修改pom文件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.hk</groupId>
        <artifactId>springAIAlibabaProject</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>SAA-08MCPClient</artifactId>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </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>
        </dependency>
        <!-- 2.mcp-clent 依赖 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-client</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>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>
3、添加配置文件
server.port=8008

# 设置全局编码格式
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=============
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:8007
#spring.ai.mcp.client.sse.connections.mcp-server2.url=http://localhost:8009
4、编写配置类
@Configuration
public class SaaLLMConfig {
    
    @Bean
    public ChatClient chatClient(ChatModel chatModel, ToolCallbackProvider provider){
        return ChatClient.builder(chatModel)
                .defaultToolCallbacks(provider.getToolCallbacks())
                .build();
    }
}
5、编写controller
@RestController
public class McpClientController {

    @Resource
    private ChatClient chatClient;

    @Resource
    private ChatModel chatModel;

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

    @GetMapping("/v2")
    public Flux<String> chat2(@RequestParam(name = "msg", defaultValue = "上海") String msg) {
        System.out.println("没有使用了mcp....");
        return chatModel.stream(msg);
    }
}
6、测试

使用mcp

没有使用mcp

(5)远程MCP增强

1)高德地图mcp

mcp地址https://mcp.so/zh/server/amap-maps/amap?tab=tools

2)获取apiKey

登录高德开发平台获取apikey

3)客户端调用

1、新建模块

2、修改pom文件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.hk</groupId>
        <artifactId>springAIAlibabaProject</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>SAA-09AMapMCP</artifactId>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <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>
        </dependency>
        <!-- 2.mcp-clent 依赖 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-client</artifactId>
        </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>
            <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>

</project>
3、添加配置文件
server.port=8009

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

spring.application.name=springAI-09amapmcp

# ====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
4、Typescript接入

需要先安装nodejs

新建mcp-server.json5文件

{
  "mcpServers": {
    "amap-maps": {
      "command": "cmd",
      "args": [
        "/c",
        "npx",
        "-y",
        "@amap/amap-maps-mcp-server"
      ],
      "env": {
        "AMAP_MAPS_API_KEY": "a0***"
      }
    }
  }
}
// 构建McpTransport协议

//  cmd:启动 Windows 命令行解释器。
//  /c:告诉 cmd 执行完后面的命令后关闭自身。
//  npx:npx = npm execute package,Node.js 的一个工具,用于执行 npm 包中的可执行文件。
//  -y 或 --yes:自动确认操作(类似于默认接受所有提示)。
//  @amap/amap-maps-mcp-server:要通过 npx 执行的 npm 包名
//  AMAP_MAPS_API_KEY 是访问高德地图开放平台API的AK
5、新建配置类
@Configuration
public class SaaLLMConfig {

    @Bean
    public ChatClient chatClient(ChatModel chatModel, ToolCallbackProvider provider) {
        return ChatClient.builder(chatModel)
                .defaultToolCallbacks(provider.getToolCallbacks())
                .build();
    }
}
6、新建controller
@RestController
public class AMapController {

    @Resource
    private ChatClient client;

    @Resource
    private ChatModel chatModel;

    @GetMapping("/v1")
    public Flux<String> chatV1(String msg) {
        System.out.println("使用了mcp....");
        return client.prompt(msg)
                .stream().content();
    }

    @GetMapping("/v2")
    public Flux<String> chatV2(String msg) {
        System.out.println("没有使用了mcp....");
        return chatModel.stream(msg);
    }
}
7、测试

使用mcp

不使用mcp

18、云上RAG知识库

(1)阿里云上知识库搭建

阿里云地址https://bailian.console.aliyun.com/

(2)代码结合

1)创建模块

2)修改pom文件

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.hk</groupId>
        <artifactId>springAIAlibabaProject</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>SAA-10YunRAG</artifactId>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </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>
        </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>

3)添加配置文件

server.port=8010

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

spring.application.name=SAA-10YunRAG


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

4)编写配置类

@Configuration
public class DashScopeConfig {

    @Bean
    public DashScopeApi dashScopeApi(){
        return DashScopeApi.builder()
                .apiKey(System.getenv("aliQwen-api"))
                .workSpaceId("llm-***")
                .build();
    }
    
    @Bean
    public ChatClient client(ChatModel chatModel) {
        return ChatClient.builder(chatModel).build();
    }
}

5)编写controller

@RestController
public class RagController {

    @Resource
    private DashScopeApi dashScopeApi;

    @Resource
    private ChatClient client;

    @GetMapping("/chat")
    public Flux<String> chat(String msg){
        DashScopeDocumentRetrieverOptions options = DashScopeDocumentRetrieverOptions.builder()
                .withIndexName("测试-学习-01") // 知识库名称
                .build();

        DashScopeDocumentRetriever retriever = new DashScopeDocumentRetriever(dashScopeApi, options);

        return client.prompt()
                .user(msg)
                .advisors(new DocumentRetrievalAdvisor(retriever))
                .stream()
                .content();
    }
}

6)测试

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐