AI基础概念学习
1.1 模型调用快速入门
注册链接:创建API Key
我们的调用模型的时候需要使用API Key进行身份验证。所以需要参加一个API Key。创建完后避免暴露。

创建好后,复制对应的key

1.1.2 接口调用
使用ApiFox调用模型
我们先使用接口的调用下模型测试下key是否正常可用。
同时也是也是让大家原始的模型调用。后面我们在使用Spring AI Alibaba的时候也更容易理解。
每个模型都有自己的API文档。我们去看下智谱AI的API文档。(可以先在官网上测试接口)
智普Api文档
xml
curl -X POST "https://open.bigmodel.cn/api/paas/v4/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"model": "glm-5",
"messages": [
{
"role": "system",
"content": "你是一个有用的AI助手。"
},
{
"role": "user",
"content": "你好,请介绍一下自己。"
}
],
"temperature": 1.0,
"stream": true
}'
把curl复制到apifox,然后把自己的API Key替换上去

然后点击发送即可
由于上面请求的stream是true。所以结果是流式输出。

1.2 相关概念
-
LLM - LLM是大型语言模型(Large Language Model)的缩写。是一种通过海量文本数据训练,能够理解和生成人类语言的人工智能系统
-
token - AI大模型调用中,模型处理文本的基本单位。各家算法切字逻辑,但是大致一个token约等
于一个汉字左右。 具体可以看各家厂商提供的切词可视化工具
(OpenAI:https://platform.openai.com/tokenizer 文心一言:
https://console.bce.baidu.com/support/#/tokenizer 阿里千问:
"stream": true
}'
-
模型参数 - 调用模式时可以设置的一些参数,每个模型支持的参数会有一些参数,都是大部分参数
都是相同的。常见参数:
-
Temperature (温度):控制生成文本的随机性。值越低(如0.2),输出越确定、保守和专注;值越高(如0.9),输出越具有创造性和多样性,但也可能更不连贯
-
Top-p (核采样):与Temperature类似,用于控制采样的多样性。它从累积概率超过阈值p的最可能候选词中随机选择。较低的值(如0.5)限制选择范围,输出更可预测;较高的值(如0.9)则扩大选择范围
-
Max Tokens (最大生成长度):限制模型单次响应所能生成的最大token数量。设置过短可能导致回答被截断。
-
流式响应 - 类似于我们使用Deepseek时AI给我们一个字一个字的回答的效果。 目前流式响应主要是使用SSE技术实现的。
-
message - 目前主要是4种消息角色
- system(系统消息,用于设定AI的行为和角色)
- user(用户消息,来自用户的输入)
- assistant(助手消息,来自AI的回复)
- tool(工具调用消息)
-
prompt - Prompt(提示词) 是您传递给AI模型的指令或问题。简单来说,Prompt是你用来告诉AI"做什么"和"怎么做"的话。
2.SpringAIAlibaba概述
它本质上是一个基于 Spring AI 抽象层的"增强插件集",其核心价值在于提供了统一、便捷的接口,让你可以灵活地选择和切换底层模型。并不是使用了SpringAIAlibaba就必须和阿里千问模型等深度绑定。我们去学习它最主要的一个核心原因是 Graph:用于编排多智能体与工作(Multi-Agent/Workflow)

官方文档:https://java2ai.com/docs/1.0.0.2/overview/?
spm=4347728f.6476bf87.0.0.a3c1556b0YIohP
3.快速入门
3.1 目标
使用Spring AI Alibaba 实现模型调用。
调用/zhipuai/simple 接口 传入一个问题 就能调用实现AI模型的调用,把模型的答案返回
3.2 准备工作
3.2.1 把API Key写入环境变量
为了避免把我们的API Key明文写入代码或者配置文件中。我们可以把API Key存储到系统的环境变量中。然后工程中去引用对应的环境变量。
| 系统 | 写入方式 |
|---|---|
| windows | 通过系统属性 GUI 操作,添加环境变量 ZHIPU_KEY 值为 你的API Key |
3.2.2 创建父工程
为了便于我们管理依赖。我们先创建个父工程。
版本说明:
JDK:17 (官方最低要求17)
SpringAI :1.0.0
SpringBoot : 3.4.0
Spring AI Alibaba : 1.0.0.4
pom.xml
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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sangeng</groupId>
<artifactId>sangeng-springai-examples</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<project.version>1.0-SNAPSHOT</project.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-ai.version>1.0.0</spring-ai.version>
<spring-ai-alibaba.version>1.0.0.4</spring-ai-alibaba.version>
<!-- Spring Boot -->
<spring-boot.version>3.4.0</spring-boot.version>
<!-- maven plugin -->
<maven-deploy-plugin.version>3.1.1</maven-deploy-plugin.version>
<flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-bom</artifactId>
<version>${spring-ai-alibaba.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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>${maven-deploy-plugin.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<release>17</release>
<compilerArgs>
<compilerArg>-parameters</compilerArg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>${flatten-maven-plugin.version}</version>
<inherited>true</inherited>
<executions>
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>ossrh</flattenMode>
<pomElements>
<distributionManagement>remove</distributionManagement>
<dependencyManagement>remove</dependencyManagement>
<repositories>remove</repositories>
<scm>keep</scm>
<url>keep</url>
<organization>resolve</organization>
</pomElements>
</configuration>
</execution>
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
3.2.3 创建子工程
01_springai-alibaba-quick-start
3.2.3.1 添加依赖
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-zhipuai</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
由于我们要调用的是智谱AI,所以需要引入spring-ai-starter-model-zhipuai。如果用的是别的AI模型引入
对于的启动器即可
- deepseek - spring-ai-starter-model-deepseek
- 千问 - spring-ai-alibaba-starter-dashscope
- OpenAI - spring-ai-starter-model-openai
3.2.3.2 修改配置
application.yaml
yml
server:
port: 8080
servlet:
encoding:
charset: utf-8
enabled: true
force: true
spring:
ai:
zhipuai:
api-key: ${zhipu} # 配置 API Key
base-url: "https://open.bigmodel.cn/api/paas" # 配置 模型地址
chat:
options:
model: glm-4.5
http:
client:
read-timeout: 1000000
3.2.3.3 创建SpringBoot启动类
java
package com.sangeng;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ZhipuAIApplication {
public static void main(String[] args) {
SpringApplication.run(ZhipuAIApplication.class, args);
}
}
3.3 代码编写
java
@RestController
@RequestMapping("/zhipuai")
public class ZhipuChatController {
private final ChatModel chatModel;
// 通过构造器注入 ChatModel
public ZhipuChatController(ChatModel chatModel) {
this.chatModel = chatModel;
}
@GetMapping("/simple")
public String simpleChat(@RequestParam(name = "query") String query) {
// 调用ChatModel 的call方法传入问题完成模型调用
return chatModel.call(query);
}
}
3.4 测试
http://localhost:8080/zhipuai/simple?query=你是谁

3.5 原理初探
我们可以在org.springframework.ai.zhipuai.api.ZhiPuAiApi#chatCompletionEntity 打上断点进行调试
4.核心API
4.1 Message

我们来看请求中的这部分内容。messages是一个数组。
其中的每个对象都有role和content两个属性。 这个对象在SpringAI中就被抽象成Message。
我们先来看下智谱的文档中对messages的介绍:

再来看下SpringAI中的设计的Message的继承体系:

从这个继承关系中我们可以看到AbstractMessage相对于把我们接口中看到的role字段抽象成messageType属性。content字段抽象成textContent属性。
MessageType定义的枚举源码如下:
java
public enum MessageType {
USER("user"),
ASSISTANT("assistant"),
SYSTEM("system"),
TOOL("tool");
}
也是和我们目前role可以使用的4种值对应。
这个时候我们回过头去看call方法的支持的参数类型:

你就能理解为什么它可以支持传Message的可变参的含义了。 相对于就是传入了一个Message列表。这个列表本质上会转化为请求中的messages。
所以如果想使改造入门案例的代码和下面的curl调用相同的效果应该怎么实现呢?
json
curl -X POST "https://open.bigmodel.cn/api/paas/v4/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"model": "glm-4.5",
"messages": [
{
"role": "system",
"content": "你是一个有用的AI助手。"
},
{
"role": "user",
"content": "你好,请介绍一下自己。"
}
]
}'
java
@GetMapping("/message")
public String message(@RequestParam(name = "query") String query) {
SystemMessage systemMessage = new SystemMessage("你是一个有用的AI助手。");
UserMessage userMessage = new UserMessage(query);
// 调用模型
return chatModel.call(systemMessage,userMessage);
}
4.2 Prompt
Prompt 提示词 是引导 AI 模型生成特定输出的输入格式,Prompt 的设计和措辞会显著影响模型的响应。

在SpringAI中,是把模型参数和消息列表的组合抽象为Prompt。
所以Prompt中主要是有以下两个属性

这个时候我们再看下call方法支持的参数类型中就有Prompt类型的就更容易理解了。

接下去我们聊下ChatOptions。
我们前面讲过不同厂商的模型支持的模型参数不同,当然也有些参数上普遍都支持的。所以SpringAI在设计的时候把共有的模型参数都设计到了ChatOptions中。而每个模型厂商可以创建自己的ChatOptions实现定义厂商支持的模型参数。所以我们看下当前项目中ChatOptions的实现有哪些。就发现了ZhiPuAiChatOptions。

ZhiPuAiChatOptions对象的创建
java
ZhiPuAiChatOptions zhiPuAiChatOptions = new ZhiPuAiChatOptions();
zhiPuAiChatOptions.setModel("glm-4.5");
zhiPuAiChatOptions.setTemperature(0.0);
zhiPuAiChatOptions.setMaxTokens(15536);
另外一种创建CharOptions的方式
java
ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
.model("glm-4.5")
.maxTokens(15536)
.temperature(0.0)
.build();
所以如果想使改造入门案例的代码和下面的curl调用相同的效果应该怎么实现呢?
json
curl -X POST "https://open.bigmodel.cn/api/paas/v4/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"model": "glm-4.5",
"messages": [
{
"role": "system",
"content": "你是一个有用的AI助手。"
},
{
"role": "user",
"content": "你好,请介绍一下自己。"
}
],
"temperature": 0.0,
"maxTokens":15536
}
java
@GetMapping("/chatOptions")
public ChatResponse chatOptions(@RequestParam(name = "query") String query)
{
SystemMessage systemMessage = new SystemMessage("你是一个有 用的AI助手。");
UserMessage userMessage = new UserMessage(query);
ZhiPuAiChatOptions zhiPuAiChatOptions = new ZhiPuAiChatOptions();
zhiPuAiChatOptions.setModel("glm-4.5");
zhiPuAiChatOptions.setTemperature(0.0);
zhiPuAiChatOptions.setMaxTokens(15536);
// 调用模型
return chatModel.call(new
Prompt(List.of(systemMessage,userMessage),zhiPuAiChatOptions));
}
4.3 ChatModel

从图中可以看出ChatModel API 让应用开发者可以非常方便的与 AI 模型进行文本交互,它抽象了应用与模型交互的过程,包括使用 Prompt作为输入,使用ChatResponse作为输出等。
4.3.1 对象获取
引入模型的启动器后直接从容器中注入即可
java
@RestController
@RequestMapping("/zhipuai")
public class ZhipuChatController {
private final ChatModel chatModel;
// 通过构造器注入 ChatModel
public ZhipuChatController(ChatModel chatModel) {
this.chatModel = chatModel;
}
}
4.3.2 模型调用
同步响应
java
@GetMapping("/simple")
public String simpleChat(@RequestParam(name = "query") String query) {
return chatModel.call(query);
}
流式响应
java
@GetMapping("/stream/chat")
public Flux<String> stream(@RequestParam(name = "query") String query) {
return chatModel.stream(query);
}
乱码问题解决:
java
server:
servlet:
encoding:
charset: UTF-8
enabled: true
force: true
4.4 ChatClient
4.4.1 概述
虽然我们可以使用ChatModel去调用模型。但是它的使用方法还是有点繁琐的。
SpringAI为我们提供了ChatClient来让我们的使用更加简便。
基础功能:
- 定制和组装模型的输入(Prompt)
- 格式化解析模型的输出(Structured Output)
- 调整模型交互参数(ChatOptions)
- ...
并且它还提供了更多的高级功能:
- 聊天记忆(Chat Memory)
- 工具/函数调用(Function Calling)
- RAG
- ...
4.4.2 创建 ChatClient
直接从容器中注入ChatClient.Builder
java
@RestController
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
}
自己创建ChatClient.Builder来构造ChatClient
java
@RestController
@RequestMapping("/chatclient")
public class ZhipuChatClientController {
private final ChatClient chatClient;
public ZhipuChatClientController(ChatModel chatModel) {
this.chatClient = ChatClient.builder(chatModel)
.build();
}
}
4.4.2 调用模型并处理响应
4.4.2.1 返回字符串
java
@RestController
@RequestMapping("/chatclient")
public class ZhipuChatClientController {
private final ChatClient chatClient;
public ZhipuChatClientController(ChatModel chatModel) {
this.chatClient = ChatClient.builder(chatModel)
.build();
}
@GetMapping("/simple")
public String simpleChat(@RequestParam(name = "query") String query) {
ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
.maxTokens(15536)
.temperature(0.0)
.model("glm-4.5")
.build();
return chatClient.prompt()
.system("你是一个有用的AI助手。")
.user("你好,请介绍一下自己。")
.options(chatOptions)
.call().content();
}
}
4.4.2.2 返回 ChatResponse
java
ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
.maxTokens(15536)
.temperature(0.0)
.model("glm-4.5")
.build();
ChatResponse chatResponse = chatClient.prompt()
.system("你是一个有用的AI助手。")
.user("你好,请介绍一下自己。")
.options(chatOptions)
.call()
.chatResponse();
4.4.2.3 响应数据转化成实体
我们在实际开发过程中会更希望模型返回的数据是结构话的数据,这样我们才更好对数据去处理。目前我们最熟悉的结构化数据就是json格式。并且我们希望能把json直接转化成bean对象更方便我们使用。
例如,我们需要让AI随机生成一本书,要有书名和作者。并且我们希望结果能直接转化成一个Book对象供我们使用。那么我们只需要先定义好Book类
java
@Data
public class Book {
private String name;
private String author;
}
然后在使用chatClient调用完call方法后调用entity方法传入要转化类的字节码对象即可。
java
@GetMapping("/response")
public Book response() {
return chatClient.prompt()
.user("给我随机生成一本书,要求书名和作者都是中文")
.call().entity(Book.class);
}
结果测试

但是我们也要理解他的原理。
前面我们讲过模型调用的本质就是调用模型的接口。所以想知道它是怎么实现的只要看下他在给模型发
送请求的时候究竟发送了什么就知道了。
我们可以在ZhiPuAiApi的chatCompletionEntity方法中打上断点。

我们可以看到本质上他其实就是使用RestClient发送请求。

从这里我们可以看到请求中包含了一个UserMessage,这个UserMessage的实际内容上:这个内容就是在我们的设置的message的基础上又加了一段提示词。这个提示词主要就是告诉模型,要求回复的格式是JSON格式。并且还告诉了模型我们的Json要求有哪些字段,字段的类型是什么。
json
给我随机生成一本书,要求书名和作者都是中文
Your response should be in JSON format.
Do not include any explanations, only provide a RFC8259 compliant JSON response
following this format without deviation.
Do not include markdown code blocks in your response.
Remove the ```json markdown from the output.
Here is the JSON Schema instance your output must adhere to:
```{
"$schema" : "https://json-schema.org/draft/2020-12/schema",
"type" : "object",
"properties" : {
"author" : {
"type" : "string"
},
"name" : {
"type" : "string"
}
},
"additionalProperties" : false
}
4.4.2.4 响应数据流式返回
使用stream方法调用模型
java
@GetMapping(value = "/stream")
public Flux<String> stream() {
return chatClient.prompt()
.user("给我随机生成一本书,要求书名和作者都是中文")
.stream().content();
}
4.4.3 Advisors
4.4.3.1 概述
Spring AI Advisors 是 Spring AI 框架中的核心拦截器组件,专门用于处理和增强 AI 应用程序中的请求与响应流。其设计于过滤器拦截器非常类似。
优势:
- 将常见的生成式 AI 模式(如对话记忆、敏感词过滤、RAG 检索)打包成可重用单元,简化开发流程
- 创建可跨不同模型和用例工作的可重用转换组件,提升代码灵活性
Advisor增强流程如下:

继承体系

如果我们需要对同步调用进行增强可以使用CallAdvisor。如果是对流式调用(响应式调用)进行增强可以使用StreamAdvisor。
4.4.3.2 快速入门
添加两个自定义的Advisor进行增强
我们只需要实现CallAdvisor接口重写其中的方法即可。
adviseCall方法中的chatClientRequest 是封装了AI请求的对象,我们可以在Advisor方法中对其进行增强。
adviseCall方法中的callAdvisorChain可以让我们对当前的AI请求进行放行,并且返回ChatClientResponse。我们可以增强ChatClientResponse后再返回。
java
package com.sangeng.advisor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
@Slf4j
public class SGCallAdvisor1 implements CallAdvisor {
/**
*
* @param chatClientRequest 请求
* @param callAdvisorChain 增强链,可以用来放行AI请求到下一个Advisor
* @return
*/
@Override
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest,
CallAdvisorChain callAdvisorChain) {
log.info("SGCallAdvisor1 请求");
ChatClientResponse chatClientResponse =
callAdvisorChain.nextCall(chatClientRequest);
log.info("SGCallAdvisor1 响应");
return chatClientResponse;
}
@Override
public String getName() {
return "SGCallAdvisor1";
}
@Override
public int getOrder() {
return 0;
}
}
java
package com.sangeng.advisor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
@Slf4j
public class SGCallAdvisor2 implements CallAdvisor {
/**
*
* @param chatClientRequest 请求
* @param callAdvisorChain 增强链,可以用来放行AI请求到下一个Advisor
* @return
*/
@Override
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest,
CallAdvisorChain callAdvisorChain) {
log.info("SGCallAdvisor2 请求");
ChatClientResponse chatClientResponse =
callAdvisorChain.nextCall(chatClientRequest);
log.info("SGCallAdvisor2 响应");
return chatClientResponse;
}
@Override
public String getName() {
return "SGCallAdvisor2";
}
@Override
public int getOrder() {
return 0;
}
}
给ChatClient添加Advisor
java
package com.sangeng.controller;
import com.sangeng.advisor.SGCallAdvisor1;
import com.sangeng.advisor.SGCallAdvisor2;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/advisor")
public class AdvisorController {
private final ChatClient chatClient;
public AdvisorController(ChatModel chatModel) {
this.chatClient = ChatClient.builder(chatModel)
.build();
}
@GetMapping("/simple")
public String simpleChat(@RequestParam(name = "query") String query) {
ZhiPuAiChatOptions chatOptions = ZhiPuAiChatOptions.builder()
.maxTokens(15536)
.temperature(0.0)
.model("glm-4.5")
.build();
return chatClient.prompt()
.system("你是一个有用的AI助手。")
.user(query)
.advisors(new SGCallAdvisor1(),new SGCallAdvisor2())
.options(chatOptions)
.call().content();
}
}
调用接口我们可以看到如下的打印信息

4.4.3.3 调整Advisor顺序
如果我们想调整Advisor在Advisor链中的顺序只需要修改getOrder的返回值即可。
getOrder的返回值越小在Advisor链中的顺序越靠前,也就是越早处理请求。
所以如果我们希望把SGCallAdvisor2的getOrder返回值设置为1。打印的结果就变成了

4.4.3.4 案例-自定义SimpleMessageChatMemoryAdvisor
我们通过一个简化版的MessageChatMemoryAdvisor来学习并理解如何自定义Advisor
需求:我们希望能在能够进行多轮对话,模型能知道我们之前聊过什么内容。
设计:1. 把对话记录(用户发送的消息和AI回复的消息)存储到内存中 2.每个会话有自己的会话id来判断自己的消息记录是哪些 3.给模型的请求中需要携带之前的聊天记录
定义Advisor
java
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.AdvisorChain;
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.prompt.Prompt;
import java.util.*;
@Slf4j
public class SimpleMessageChatMemoryAdvisor implements BaseAdvisor {
// 本案例主要是为了学习和理解如何写Advisor所以这里选用最简单的实现。实际肯定不能这样存储消息
private static Map<String, List<Message>> chatMemory = new HashMap<>();
@Override
public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
// 通过会话id查询之前的对话记录
String conversationId = chatClientRequest.context().get("conversationId").toString();
List<Message> messages = chatMemory.get(conversationId);
if (messages == null) {
messages = new ArrayList<>();
}
// 把这次请求的消息添加到对话记录中
List<Message> requestMessageList = chatClientRequest.prompt()
.getInstructions();
messages.addAll(requestMessageList);
chatMemory.put(conversationId, messages);
// 把添加后记录的List<Message> 放入请求中
Prompt oldPrompt = chatClientRequest.prompt();
Prompt newPrompt = oldPrompt.mutate()
.messages(messages)
.build();
ChatClientRequest request = chatClientRequest.mutate()
.prompt(newPrompt)
.build();
return request;
}
@Override
public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
// 通过会话id查询之前的对话记录
String conversationId = chatClientResponse.context().get("conversationId").toString();
List<Message> hisMessages = chatMemory.get(conversationId);
if (hisMessages == null) {
hisMessages = new ArrayList<>();
}
// 获取response中ai的消息 添加到对话记录中
if(Objects.isNull(chatClientResponse)){
return chatClientResponse;
}
AssistantMessage assistantMessage = chatClientResponse.chatResponse()
.getResult()
.getOutput();
hisMessages.add(assistantMessage);
chatMemory.put(conversationId, hisMessages);
return chatClientResponse;
}
@Override
public int getOrder() {
return 0;
}
}
使用Advisor
java
@GetMapping("/simpleChatMemory")
public String simpleChatMemory(@RequestParam(name = "msg") String
msg,@RequestParam(name = "conversationId") String conversationId) {
return chatClient.prompt()
.user(msg)
.advisors(advisorSpec ->
advisorSpec.param("conversationId",conversationId))
.advisors(new SimpleMessageChatMemoryAdvisor())
.call().content();
}
4.4.3.5 使用官方的Advisor实现对话记录存储-如何统一设置advisors
java
//构造器注入
public ZhipuChatMemoryController(ChatClient.Builder builder) {
// 创建 MessageWindowChatMemory
MessageWindowChatMemory windowChatMemory =
MessageWindowChatMemory.builder()
.build();
//创建 MessageChatMemoryAdvisor
MessageChatMemoryAdvisor chatMemoryAdvisor =
MessageChatMemoryAdvisor.builder(windowChatMemory)
.build();
this.chatClient = builder
.defaultAdvisors(chatMemoryAdvisor)
.build();
}
@GetMapping("/messageChatMemoryAdvisor")
public String messageChatMemoryAdvisor(@RequestParam(name = "query") String
query,
@RequestParam(name =
"conversationId") String conversationId) {
return chatClient.prompt()
.user(query)
.advisors(advisorSpec ->
advisorSpec.param(ChatMemory.CONVERSATION_ID,conversationId))
.call()
.content();
}