SpringAI - Models(一)
Model
AI模型
Model
是SpringAI对AI模型的抽象。学习中一直使用的ChatModel
是对聊天模型的抽象。SpringAI除了聊天模型
ChatModel
,还抽象了比如嵌入模型、图像、音频模型等等。下面是官方的模型层次图:StreamingModel
本质是与Model
一致的,只是对支持流式响应的模型的抽象。
ChatModel
ChatModel
是Model
以及StreamingModel
的子接口,是SpringAI专门对聊天模型的抽象。下面两个方法是
ChatModel
接口的核心方法。1
2
3
4
5
ChatResponse call(Prompt prompt);
Flux<ChatResponse> stream(Prompt prompt);从参数及返回上看输入是提示
Prompt
,返回是AI的响应ChatResponse
。(这两块前面都有学习记录到)前面学习记录的案例中使用到的
DeepSeekChatModel
以及OpenAiChatModel
都是其子类。除了上面提到的两个,SpringAI还封装了其他聊天模型的实现。下面是官方给出的实现图(现在应该更多了)
由此可见
ChatModel
是与AI模型交互的核心接口。那么开发者也可以通过实现其来对接SpringAI未提供的聊天模型。下面模拟一个自行实现的聊天模型。
大致需求:
将DIFY平台视为一个聊天模型来模拟自行实现一个
DifyChatModel
。当然DIFY并不是一个AI模型,只是模拟当成聊天模型来使用一下。
用到的API是一个聊天助手。是以前学习遗留下来的。
Code
创建
DifyChatModel
,实现ChatModel
接口,并实现了构造者模式。这里的DifyApiClient
是以前写好的调用DIFY平台的客户端,直接引入用一下。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52public class DifyChatModel implements ChatModel {
/** 这是作者以前写的一个调用 Dify 的客户端,直接引入用一用 */
private DifyApiClient difyApiClient;
/** apiKey */
private String difyApiKey;
private String conversationId = "";
public DifyChatModel(Builder builder) {
this.difyApiClient = builder.difyApiClient;
this.difyApiKey = builder.difyApiKey;
}
public ChatResponse call(Prompt prompt) {
return null;
}
public Flux<ChatResponse> stream(Prompt prompt) {
return ChatModel.super.stream(prompt);
}
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private DifyApiClient difyApiClient;
private String difyApiKey;
private Builder() {
}
public Builder difyApiClient(DifyApiClient difyApiClient) {
this.difyApiClient = difyApiClient;
return this;
}
public Builder difyApiKey(String difyApiKey) {
this.difyApiKey = difyApiKey;
return this;
}
public DifyChatModel build() {
return new DifyChatModel(this);
}
}
}这里只模拟一下
.call()
方法的具体实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public ChatResponse call(Prompt prompt) {
DifyClientContext<ChatMessagesRequest> requestContext = createRequestContext(prompt);
ChatCompletionResponse sync = difyApiClient.onChatMessagesRequest(requestContext).sync();
return ChatResponse.builder()
.metadata(createMetadata(sync))
.generations(createGeneration(sync))
.build();
}
/**
* 将Dify返回的元数据转换
* @param response
* @return
*/
private ChatResponseMetadata createMetadata(ChatCompletionResponse response) {
conversationId = response.getConversationId();
return ChatResponseMetadata.builder()
.id(response.getMessageId())
.usage(new DefaultUsage(
response.getMetadata().getUsage().getPromptTokens(),
response.getMetadata().getUsage().getCompletionTokens(),
response.getMetadata().getUsage().getTotalTokens())
).build();
}
/**
* 将Dify返回的消息转换
* @param response
* @return
*/
private List<Generation> createGeneration(ChatCompletionResponse response) {
AssistantMessage assistantMessage = new AssistantMessage(response.getAnswer());
return List.of(new Generation(assistantMessage, ChatGenerationMetadata.builder().build()));
}
/**
* 将提示转换成Dify的请求
* @param prompt
* @return
*/
private DifyClientContext<ChatMessagesRequest> createRequestContext(Prompt prompt) {
ChatMessagesRequest request = new ChatMessagesRequest();
request.setQuery(prompt.getContents());
request.setUser("123456");
request.setConversationId(conversationId);
return new DifyClientContext<>(difyApiKey, request);
}将
DifyChatModel
注册到容器1
2
3
4
5
6
7
8
9
10
private String difyApiKey;
public DifyChatModel difyChatModel(DifyApiClient difyApiClient) {
return DifyChatModel.builder()
.difyApiKey(difyApiKey)
.difyApiClient(difyApiClient)
.build();
}DifyApiClient
相关的配置。需要开启加载和配置调用API的KEY。1
2
3
4
5# 开启 DifyApiClient 的加载
dify:
enable: true
api:
key: ${DIFY_API_KEY}直接使用
ChatModel
调用。1
2
3
4
5
6private final DifyChatModel difyChatModel;
public void modelExample() {
String call = difyChatModel.call("你好啊!");
log.info("\n modelExample call -> \n{}", call);
}调用结果。可以看到是正常调用的。
1
2
3
42025-07-17T18:53:11.893+08:00 INFO 57364 --- [spring-ai-example] [ main] c.s.ai.example.models.DifyModelExample :
modelExample call ->
(●'◡'●)嗨~我家主人这会正在开会呢,我是他的小助手微微~有什么好玩的事可以先跟我说说呀~使用
ChatClient
来调用。并使用LogExampleAdvisor
做日志输出。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public ChatClient difyChatClient(DifyChatModel difyChatModel) {
return ChatClient.builder(difyChatModel)
// 添加 Advisors
.defaultAdvisors(spec -> {
spec.params(Map.of(
// 放置客户端名称
ContextKeys.ClientName.name(), "difyChatClient"
));
spec.advisors(
new LogExampleAdvisor() // Log
);
})
.build();
}1
2
3
4
5
6
7
8private final ChatClient difyChatClient;
public void clientExample() {
difyChatClient.prompt()
.user("你谁啊?")
.call()
.chatResponse();
}调用结果。也是完全没问题的。
1
2
3
4
5
6
7
8
9
10
112025-07-21T18:42:47.039+08:00 INFO 90099 --- [spring-ai-example] [ main] c.s.a.e.advisor.three.LogExampleAdvisor :
Chat client request to AI
prompt text -> 你谁啊?
context -> {
"ClientName" : "difyChatClient"
}
2025-07-21T18:42:56.629+08:00 INFO 90099 --- [spring-ai-example] [ main] c.s.a.e.advisor.three.LogExampleAdvisor :
Chat client response from AI
output text -> (≧▽≦) 哈喽~我是主人的小助手微微呀!主人现在正在开会忙得团团转呢,特意让我先来陪您聊天~有什么想说的都可以告诉我哦!
context -> {ClientName=difyChatClient}
整个模拟的
DifyChatModel
并不复杂,下面继续围绕其做一些优化。
ChatResponse
ChatResponse
ChatResponse
在前面的学习中经常用到过,这里再进一步了解其内部结构及作用。ChatResponse
是SpringAI封装 AI 模型输出的响应,从源码上看是实现了ModelResponse
接口。且定义了
chatResponseMetadata
、generations
两个属性ChatResponseMetadata chatResponseMetadata
:用来封装 AI 模型响应的元数据。List<Generation> generations
:用来封装 AI 模型响应的输出消息。
Generation
Generation
实现了ModelResult
接口。主要用于封装模型输出(Assistant 消息)及相关元数据。其内部有
assistantMessage
、chatGenerationMetadata
两个属性AssistantMessage assistantMessage
:Assistant 助手消息。ChatGenerationMetadata chatGenerationMetadata
:用来封装相关元数据。
将上面案例优化一下,把
DifyChatModel
中调用Dify返回的conversationId
(会话ID)封装到元数据中。1
2
3
4
5
6
7private List<Generation> createGeneration(ChatCompletionResponse response) {
AssistantMessage assistantMessage = new AssistantMessage(response.getAnswer());
ChatGenerationMetadata chatGenerationMetadata = ChatGenerationMetadata.builder()
.metadata("CONVERSATION_ID", response.getConversationId())
.build();
return List.of(new Generation(assistantMessage, chatGenerationMetadata));
}可以在返回中获取
1
2
3
4
5ChatResponse chatResponse = difyChatClient.prompt()
.user("你谁啊?")
.call()
.chatResponse();
chatResponse.getResult().getMetadata().get("CONVERSATION_ID");
ChatOptions
ChatOptions
接口是用来在运行时向ChatModel
传递AI模型的选项参数。其定义如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public interface ChatOptions extends ModelOptions {
String getModel();
Double getFrequencyPenalty();
Integer getMaxTokens();
Double getPresencePenalty();
List<String> getStopSequences();
Double getTemperature();
Integer getTopK();
Double getTopP();
}ChatOptions
是ModelOptions
的子接口。说明ChatOptions
是专门用于聊天模型的。基于上面的
DifyChatModel
定义一个DifyChatOptions
,用来传递几个Dify的参数。需求:
- 将ApiKey、会话ID、用户唯一标识都定义在
DifyChatOptions
- 将ApiKey、会话ID、用户唯一标识都定义在
Code:
定义DifyChatOptions
。这里模拟将ApiKey视为Model,
ChatOptions`中其他定义暂不实现了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55public class DifyChatOptions implements ChatOptions {
// 模拟将 difyApiKey 视为Model,在Dify中一个key对应一个应用。
private String difyApiKey;
/** 回话ID **/
private String conversationId;
/** 用户唯一标识 **/
private String user;
public String getModel() {
// 模拟将 difyApiKey 视为Model
return difyApiKey;
}
public static DifyChatOptions.Builder builder() {
return new DifyChatOptions.Builder();
}
public static class Builder {
protected DifyChatOptions options;
public Builder() {
this.options = new DifyChatOptions();
}
public Builder(DifyChatOptions options) {
this.options = options;
}
public DifyChatOptions.Builder difyApiKey(String difyApiKey) {
this.options.difyApiKey = difyApiKey;
return this;
}
public DifyChatOptions.Builder conversationId(String conversationId) {
this.options.conversationId = conversationId;
return this;
}
public DifyChatOptions.Builder user(String user) {
this.options.user = user;
return this;
}
public DifyChatOptions build() {
return this.options;
}
}
}将创建请求Dify的参数改造一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14ChatMessagesRequest request = new ChatMessagesRequest();
request.setQuery(prompt.getContents());
request.setUser("123456");
request.setConversationId("");
ChatOptions options = prompt.getOptions();
// 使用传递的选项参数
if (options instanceof DifyChatOptions difyChatOptions) {
request.setUser(difyChatOptions.getUser());
request.setConversationId(difyChatOptions.getConversationId());
return new DifyClientContext<>(StringUtils.isBlank(difyChatOptions.getModel()) ? difyApiKey : difyChatOptions.getModel(), request);
}
return new DifyClientContext<>(difyApiKey, request);调用案例改造
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public void clientExample() {
String user = "DEFAULT-USER";
ChatResponse chatResponse = difyChatClient.prompt()
// 初次交互没有回话ID
.options(DifyChatOptions.builder()
.user(user)
.build())
.user("你谁啊?")
.call()
.chatResponse();
Object conversationId = chatResponse.getResult().getMetadata().get("CONVERSATION_ID");
chatResponse = difyChatClient.prompt()
.options(DifyChatOptions.builder()
// 使用返回的回话ID
.conversationId(conversationId.toString())
.user(user)
.build())
.user("你主人在干什么啊?")
.call()
.chatResponse();
}日志输出结果
Dify平台中可以看到,两个消息是同一会话中的。
最后再看看
ChatOptions
的子类,基本可以看到每实现一个AI模型ChatModel
就会对应有一个ChatOptions
的实现
总结
- 在SpringAI中
Model
是对AI模型的抽象。 - 其中
ChatModel
是对聊天模型的抽象。当然还包含其他多种模型,后面会学习到。 - 开发者可以通过实现
ChatModel
接口来与自定义SpringAI未实现的模型来进行交互扩展。 - 在
ChatModel
中与模型交互后通过ChatResponse
封装模型返回的响应。Generation
主要用于封装模型输出(Assistant 消息)及相关元数据(额外数据)。ChatResponseMetadata
主要用于封装模型返回的一些元数据(一些量化数据)。
- 可以通过
ChatOptions
在运行时向ChatModel
传递AI模型的选项参数,以便灵活开发。
最后
- 最近两周发生了一些小插曲,导致没怎么学习。后续努力补课吧。
- 下一篇大概学习一下SpringAI封装的Deepseek模型或者OpenAI模型。(主要是封装的配置以及模型的特性如何使用)
- 所有案例的源码,都会提交在GitHub上。本次案例包:
com.spring.ai.example.models.one