1.Chat Model
对话模型是一种利用人工智能技术,能够生成类似人类对话响应的工具。通过向预训练语言模型(如 GPT
等)发送提示词或部分对话内容,模型依据自身训练数据及对自然语言模式的理解,生成对话的延续或完整回复,并返回给应用程序。应用程序可以将其呈现给用户或用于进一步处理。
Spring AI Chat Model API
设计目标为简单且可移植的接口,用于与各种人工智能模型进行交互,使开发人员能够在不同模型之间进行切换,且只需进行最少的代码更改。这种设计符合 Spring
的模块化和可互换性理念。同时,借助 Prompt
(用于输入封装)和 ChatResponse
(用于输出处理)等辅助类,Chat Model API
统一了与人工智能模型的通信。
2.ChatClient
ChatClient``对ChatModel与
StreamingChatModel
进行了封装,采用了 Fluent API
的风格,可以进行链式调用。相比如ChatModel
原子类API
,ChatClient
屏蔽了与AI
大模型的交互的复杂性,但是ChatModel API
更灵活,尤其当系统中要接入多个大模型并且有个性化配置时。
Fluent API 是一种编程风格的 API 设计,其主要特点是通过方法链的方式来实现操作的连贯性和可读性
ChatModel
类似于JDK
中的核心JDBC
库。ChatClient
可以比作JdbcClient
,它构建在ChatModel
之上,并通过 Advisor
提供更高级的功能,提供上下文记忆,上下文提示词扩充等功能。
3.接口详解
上图所示,ChatModel
接口继承了StreamingChatModel
接口,所以ChatModel
具备流式处理响应的能力。同时ChatClient
接口是对话模型更上高一层次的抽象。在上一章介绍过自动注入ChatClient
无需关注ChatModel
,Spring AI
会自动加载所配置的AI模型
。当我们需要集成多个AI模型时,这时就需要使用ChatModel
接口,如下代码所示在同一个项目中创建2个不同对话模型。
java
@GetMapping("/ai/zhipu")
public ChatResponse zhipu(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
var zhiPuAiApi = new ZhiPuAiApi(System.getenv("ZHIPU_AI_API_KEY"));
var chatModel = new ZhiPuAiChatModel(zhiPuAiApi, ZhiPuAiChatOptions.builder()
.model(ZhiPuAiApi.ChatModel.GLM_3_Turbo.getValue())
.temperature(0.4)
.maxTokens(200)
.build());
ChatResponse response = chatModel.call(
new Prompt("Generate the names of 5 famous pirates."));
return response;
}
@GetMapping("/ai/openapi")
public ChatResponse openapi(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
var openAiApi = new OpenAiApi(System.getenv("OPENAI_API_KEY"));
var openAiChatOptions = OpenAiChatOptions.builder()
.model("gpt-3.5-turbo")
.temperature(0.4)
.maxTokens(200)
.build();
var chatModel = new OpenAiChatModel(openAiApi, openAiChatOptions);
ChatResponse response = chatModel.call(
new Prompt("Generate the names of 5 famous pirates."));
return response;
}
// 流式响应
Flux<ChatResponse> streamResponse = chatModel.stream(
new Prompt("Generate the names of 5 famous pirates."));
3.1ChatClient接口
3.1.1 创建ChatClient方式
自动配置
Spring AI 提供了 Spring Boot 自动配置,创建一个 ChatClient.Builder Bean,以便注入到使用的类中,快速入门中使用的是此方式。
java
@RestController
class AiController {
private final ChatClient chatClient;
public AiController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@GetMapping("/ai")
String generation(String userInput) {
return this.chatClient.prompt()
.user(userInput)
.call()
.content();
}
}
代码创建
可以通过设置属性 spring.ai.chat.client.enabled=false 来禁用 ChatClient.Builder 的自动配置。如果多个对话模型一起使用,这很有用。然后,为每个你需要的 ChatModel 以编程方式创建一个 ChatClient.Builder 实例。
java
/**
* Spring AI supports Spring Boot 3.2.x and 3.3.x
* JDK 17
* zhipuai
*/
@RestController
public class AiController {
private final ChatClient chatClient;
//注入ZhiPuAi ChatModel
public AiController(ZhiPuAiChatModel zhiPuAiChatModel) {
//编码创建ChatClient对象
this.chatClient= ChatClient.create(zhiPuAiChatModel);
}
@GetMapping("/ai")
String generation(String msg) {
//用户输入的信息提交给大模型,使用的是ChatClient与大模型交互。
return this.chatClient.prompt()
.user(msg)
.call()
.content();
}
}
3.1.2 核心API
ChatClient
对象提供了三个prompt()重载方法,启动Fluent API。三个方法如下:
1.prompt()
: 不带参数的方法可以构建用户、系统以及提示词的其他部分。
java
chatClient.prompt()
.user(("你给我说一个笑话")
.call()
.content();
2.prompt(Prompt prompt)
: 该方法接受一个Prompt
参数,创建一个带Prompt的Fluent API。
scss
chatClient.prompt(new Prompt("你给我说一个笑话"))
.call()
.content();
3.prompt(String content)
: 该方法和之前的重载方法类似。它接收用户输入的文本内容。
java
chatClient.prompt("你给我说一个笑话")
.call()
.content();
3.1.3 响应结果
ChatClient API
提供了3种使用FluentAPI
格式化AI
模型响应的方法。
- 返回
ChatResponse
java
//同步返回ChatResponse
@GetMapping("/chatResponse")
ChatResponse chatResponse() {
//用户输入的信息提交给大模型,使用的是ChatClient与大模型交互。
return this.chatClient.prompt()
.user("给我讲一个笑话")
.call()
.chatResponse();
}
//流式返回ChatResponse
@GetMapping("/fluxChatResponse")
Flux<ChatResponse> fluxChatResponse() {
Flux<ChatResponse> output = chatClient.prompt()
.user("给我讲一个笑话")
.stream()
.chatResponse();
return output;
}
- 返回实体对象(
Entity
)
java
import java.util.List;
record ActorFilms(String actor, List<String> movies) {}
//同步返回实体
@GetMapping("/entity")
ActorFilms entity() {
ActorFilms actorFilms = chatClient.prompt()
.user("随机生成周星驰的5部电影作品")
.call()
.entity(ActorFilms.class);
return actorFilms;
}
- 返回
String
java
@GetMapping("/fluxString")
Flux<String> fluxString() {
Flux<String> output = chatClient.prompt()
.user("给我讲一个笑话")
.stream()
.content();
return output;
}
java
//同步返回字符串
@GetMapping("/ai")
String generation(String msg) {
return this.chatClient.prompt()
.user(msg)
.call()
.content();//直接返回字符串
}
大型语言模型(LLM
)生成结构化输出的能力对于下游系统使用大模型能力非常重要。开发人员希望快速将人工智能模型的结果转换为可以传递给其他应用程序函数和方法的数据类型,例如 JSON
、XML
或 Java
类。后面的内容结构化输出
章节会详细的介绍使用。
3.1.3 默认设置
ChatClient
通过设置默认值,在调用时只需要指定用户文本,无需在运行时代码中为每个请求设置系统文本。
-
默认设置
下面的示例中,配置系统文本"你是一个会讲笑话的智能助手,可以讲不同风格的笑话"。为了避免在运行时代码中重复设置系统文本,在
@Configuration
配置类中创建一个ChatClient
实例。
java
@Configuration
class Config {
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder.defaultSystem("你是一个会讲笑话的智能助手,用周星驰幽默的风格讲笑话。")
.build();
}
}
java
@RestController
class AIController {
//直接构造器注入,在Config已经创建了对象
private final ChatClient chatClient;
AIController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/ai/defaults")
public Map<String, String> completion(@RequestParam(value = "message", defaultValue = "给我讲个笑话") String message) {
return Map.of("completion", this.chatClient.prompt().user(message).call().content());
}
}
shell
❯ curl http://localhost:8080/ai/defaults?message=给我讲个笑话
{
"completion": "有一天,唐僧师徒四人行至火焰山,见一老翁躺在地上,气息奄奄。唐僧说:"悟空,快去救救这位老翁。"悟空便将老翁扶起,问道:"老翁,你怎么了?"老翁答道:"唉,我被这火焰山烤得受不了了,想喝口水。"悟空便去取水,途中遇到一只乌鸦,乌鸦对悟空说:"悟空,你为何那么辛苦?我有一个办法,能让老翁马上凉爽。"悟空好奇地问:"哦?什么办法?"乌鸦说:"你把老翁放在我背上,我带你飞到北极,那里凉快极了!"悟空心想:"这办法不错,但我不能让唐僧担心。"于是,悟空带着乌鸦回到原地,对唐僧说:"师傅,我有个办法能让老翁凉爽,但需要你配合。"唐僧问:"什么办法?"悟空答:"你把老翁放在我背上,我带你飞到北极。"唐僧疑惑地问:"那你怎么办?"悟空笑着说:"放心吧,我会在乌鸦的背上飞。"\n\n这个笑话有点周星驰式的幽默,希望您喜欢!"
}
- 带参数的系统设置
java
@Configuration
class Config {
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder.defaultSystem("你是一个会讲笑话的智能助手,用{style}幽默的风格讲笑话。")
.build();
}
}
java
@GetMapping("/ai/defaultsParameters")
public Map<String, String> defaultsParameters(@RequestParam(value = "message", defaultValue = "给我讲个笑话") String message,
@RequestParam(value = "style") String style) {
return Map.of("completion",
this.chatClient.prompt()
.system(sp -> sp.param("style", style))
.user(message)
.call()
.content());
}
这个例子展示了,动态填充系统默认参数的方式,笑话的风格用户动态传入。
shell
❯ curl http://localhost:8080/ai/defaultsParameters?message=给我讲个笑话&style=赵本山
{
"completion": "
那好,我给你讲一个有关于我和老张头的笑话。\n\n这天,我和老张头去赶集,正巧碰见一个卖耗子药的。卖耗子药的哥们儿在那儿吆喝:"我这耗子药,一吃就死,绝无仅有,无效退款!"老张头听了,好奇心起,就买了一包。\n\n回家的路上,老张头突然发现耗子药不见了,吓得他出了一身冷汗,连忙问我:"哎呀,本山啊,我这耗子药咋不见了?这可咋整啊?"\n\n我一看他紧张的样子,就逗他:"老张头,你是不是把耗子药揣裤兜里了?说不定是你自己忘带了。"\n\n老张头一拍大腿:"哎呦,还真是!我咋就忘了这茬呢!"说完,他赶紧掏出耗子药,小心翼翼地揣回裤兜里。\n\n到了家,老张头把耗子药放在老鼠出没的地方。没过多久,一只老鼠跑过来,闻了闻耗子药,然后夹着尾巴跑了。老张头一看,高兴地跟我说:"本山,你看,这耗子药真灵,老鼠一闻就跑了!"\n\n我笑了笑,说:"老张头,那是因为老鼠知道你这人太实在,它害怕吃了你的耗子药,回头还得给你送回来,说'这耗子药味道不好,退款吧!
"
}
3.2 ChatModel接口
ChatModel
接口定义如下:
java
public interface ChatModel extends Model<Prompt, ChatResponse>, StreamingChatModel {
default String call(String message) {
...
}
default String call(Message... messages) {
...
}
@Override
ChatResponse call(Prompt prompt);
}
call(String message)
创建一个UserMessage
发送给AI模型,执行调用返回String
。
call(Message... messages)
将多个Message
发送给AI
模型,执行调用返回String
。
call(Prompt prompt)
发送一个Prompt
提示词给AI模型,执行调用返回返回ChatResponse
。
Message
接口有多种实现,对应 AI 模型可以处理的消息类别,后面章节内容详细介绍。
3.3 StreamingChatModel接口
StreamingChatModel
接口定义如下:
java
public interface StreamingChatModel extends StreamingModel<Prompt, ChatResponse> {
default Flux<String> stream(String message) {
....
}
default Flux<String> stream(Message... messages) {
....
}
@Override
Flux<ChatResponse> stream(Prompt prompt);
}
stream(String message)
创建一个Message
发送给AI模型,执行调用返回Flux String
。
stream(Message... messages)
将多个Message
发送给AI
模型,执行调用返回Flux String
。
stream(Prompt prompt)
发送一个Prompt
提示词给AI模型,执行调用返回返回Flux ChatResponse
。
stream()
方法采用与 ChatModel
类似的 String
或 Prompt
参数,但它使用反应式 Flux API
传输响应(流式)。
Spring
中实现非阻塞的响应式编程模型使用的是Reactor
框架,Reactor
提供了两种主要的异步序列API
:Flux
和Mono
。Flux
用于处理0到N个元素的异步序列,而Mono
则用于处理0到1个元素的异步序列。
3.4 ChatOptions接口
ChatOptions
接口继承自 ModelOptions
,定义了一些可传递给 AI 模型的选项,如模型名称、频率、最大令牌数等。每个特定模型的实现可以有自己的选项,启动时可设置默认配置,运行时可通过 Prompt
请求覆盖。
接口定义如下:
scss
public interface ChatOptions extends ModelOptions {
String getModel();
Float getFrequencyPenalty();
Integer getMaxTokens();
Float getPresencePenalty();
List<String> getStopSequences();
Float getTemperature();
Integer getTopK();
Float getTopP();
ChatOptions copy();
}
对于每个特定模型的 ChatModel
/StreamingChatModel
实现都可以有自己的选项,这些选项可以传递给 AI
模型。例如,OpenAI Chat
模型有自己的选项,如 logitBias
、seed
和 user
。
scss
var openAiChatOptions = OpenAiChatOptions.builder()
.seed(1024).
user("user")
.logitBias(new HashMap<>())
.build();
4.总结
本章主要围绕 Spring AI
中的对话模型相关组件展开介绍,涵盖 Chat Model
、ChatClient
以及多个关联接口,详细阐述了它们的功能、设计目标、使用方式及相互关系。Spring AI
通过这些精心设计的对话模型组件及接口,为开发人员提供了一套功能丰富、灵活易用且高度模块化的工具集,助力在不同场景下高效开发与人工智能模型交互的应用程序,满足多样化的业务需求。