依赖配置可以参考这篇文章:
https://blog.youkuaiyun.com/m0_73679567/article/details/148357643
❓ChatClient和ChatModel的区别是什么?
ChatClient
提供了与 AI 模型通信的 Fluent API,它支持同步和反应式(Reactive)编程模型。与ChatModel
、Message
、ChatMemory
等原子 API 相比,使用 ChatClient 可以将与 LLM 及其他组件交互的复杂性隐藏在背后,因为基于 LLM 的应用程序通常要多个组件协同工作(例如,提示词模板、聊天记忆、LLM Model、输出解析器、RAG 组件:嵌入模型和存储),并且通常涉及多个交互,因此协调它们会让编码变得繁琐。当然使用ChatModel
等原子 API 可以为应用程序带来更多的灵活性,成本就是您需要编写大量样板代码。
ChatClient 类似于应用程序开发中的服务层,它为应用程序直接提供 AI 服务
,开发者可以使用 ChatClient Fluent API 快速完成一整套 AI 交互流程的组装。
应用场景
ChatModel适用于定制化、灵活性更高的场景,如:对话过程中需要不断调整模型参数。
ChatClient适用于在应用中不需要对ChatModel做频繁调整的场景。
创建
自动注入
适用场景,简单的应用,不需要多个模型协作。
private final ChatClient chatClient;
public ChatClientDemoController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
建造者模式
适用场景,复杂的应用,需要实例化多个模型,需要多个模型协作。
- 关闭ChatClient.Builder的自动注入
spring:
application:
name: super-ai-agent
ai:
dashscope:
api-key: your-api-key
chat:
client:
enabled: false #禁止ChatClient.Builder的自动装配
- 通过
ChatClientConfig
定义多个Client
import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ChatClientConfig {
@Value("${spring.ai.dashscope.api-key}")
public String apiKey;
@Bean
public ChatClient qwenMaxClient() {
return ChatClient.builder(new DashScopeChatModel(
new DashScopeApi(apiKey),
DashScopeChatOptions.builder()
.withModel("qwen-max")
.build()
)).build();
}
@Bean
public ChatClient deepseekR1Client(){
return ChatClient.builder(new DashScopeChatModel(
new DashScopeApi(apiKey),
DashScopeChatOptions.builder()
.withModel("deepseek-r1")
.build()
)).build();
}
}
测试
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/client")
public class ChatClientDemoController {
@Resource
private ChatClient deepseekR1Client;
@Resource
private ChatClient qwenMaxClient;
@GetMapping("/chat1")
public String chat1(String input) {
return qwenMaxClient.prompt()
.user(input)
.call()
.content();
}
@GetMapping("/chat2")
public String chat2(String input) {
return deepseekR1Client.prompt()
.user(input)
.call()
.content();
}
}
运行结果
通义:
deepseek:
Client响应
返回ChatResponse
AI 模型的响应是一种由
ChatResponse
类型定义的丰富结构。它包含响应生成相关的元数据,同时它还可以包含多个子响应(称为Generation
),每个子响应都有自己的元数据。元数据包括用于创建响应的令牌(token
)数量信息(在英文中,每个令牌大约为一个单词的 3/4),了解令牌信息很重要,因为 AI 模型根据每个请求使用的令牌数量收费。
测试
@GetMapping("/chat3")
public String chat3(String input) {
ChatResponse chatResponse = qwenMaxClient.prompt()
.user(input)
.call()
.chatResponse();
assert chatResponse != null;
ChatResponseMetadata metadata = chatResponse.getMetadata();
System.out.println("总消耗token:"+metadata.getUsage().getTotalTokens());
System.out.println("使用的模型:"+metadata.getModel());
return chatResponse.getResult().getOutput().getText();
}
运行结果
格式化输出返回实体类(entity)
您经常希望返回一个预先定义好的实体类型响应,Spring AI 框架可以自动替我们完成从 String 到
实体类
的转换,调用entity()
方法可完成响应数据转换。
介绍
如果您想从 LLM 接收结构化输出,
Structured Output
可以协助将ChatModel/ChatClient
方法的返回类型从 String 更改为其他类型。
在 LLM 调用之前,转换器会将期望的输出格式(output format instruction)附加到 prompt 中,为模型提供生成所需输出结构的明确指导,这些指令充当蓝图,塑造模型的响应以符合指定的格式。
如:
您的响应应该是JSON格式。
JSON的数据结构应该与Java类Java.util.util.HashMap匹配
不包括任何解释,只提供符合RFC8259的JSON响应,遵循此格式,没有偏差。
在 LLM 调用之后,转换器获取模型的输出文本并将其转换为结构化类型的实例,此转换过程涉及解析原始文本输出并将其映射到相应的结构化数据表示,例如 JSON、XML 或特定于域的数据结构。
定义实体类
import lombok.Data;
import java.util.List;
@Data
public class ActorFilms {
private String actorName;
private List<String> filmography; // 电影/电视剧作品列表
private int totalMovies; // 可选字段:总作品数量
private String nationality; // 可选字段:演员国籍
}
测试
@GetMapping("/chat4")
public void chat4(){
ActorFilms actorFilms = qwenMaxClient.prompt()
.user("为随机演员生成电影目录")
.call()
.entity(ActorFilms.class);
System.out.println(actorFilms);
}
运行结果
补充
entity
还有一种带有参数的重载方法entity(ParameterizedTypeReference<T> type
),可让您指定如泛型List
等类型:
代码:
@GetMapping("/chat5")
public void chat5(){
List<ActorFilms> actorFilms = qwenMaxClient.prompt()
.user("为汤姆·汉克斯和比尔·默里制作5部电影的电影目录")
.call()
.entity(new ParameterizedTypeReference<List<ActorFilms>>() {
});
System.out.println(actorFilms);
}
运行结果:
[ActorFilms(actorName=汤姆·汉克斯, filmography=[阿甘正传, 拯救大兵瑞恩, 荒岛余生, 费城故事, 西雅图未眠夜], totalMovies=5, nationality=美国), ActorFilms(actorName=比尔·默里, filmography=[土拨鼠之日, 迷失东京, 捉鬼敢死队, 月升王国, 布达佩斯大饭店], totalMovies=5, nationality=美国)]
流式输出
测试
@GetMapping("/chat6")
public Flux<String> chat6(HttpServletResponse response){
response.setCharacterEncoding("UTF-8");
return qwenMaxClient.prompt()
.user("介绍下你自己")
.stream()
.content();
}
运行结果
PromotTemplate(提示词模板)
修改qwenMaxClient
@Bean
public ChatClient qwenMaxClient() {
return ChatClient.builder(new DashScopeChatModel(
new DashScopeApi(apiKey),
DashScopeChatOptions.builder()
.withModel("qwen-max")
.build()
)).defaultSystem("以{style}风格回答所有问题").build();
}
测试
@GetMapping("/chat7")
public String chat7(@RequestParam(value = "message", defaultValue = "讲个笑话")
String message, String style){
return qwenMaxClient.prompt()
.system(sp ->sp.param("style",style))
.user(message)
.call()
.content();
}
运行结果
其它默认配置
参考官方文档:
https://java2ai.com/docs/1.0.0-M6.1/tutorials/chat-client/?spm=5176.29160081.0.0.2856aa5cmQF5SZ#%E5%85%B6%E4%BB%96%E9%BB%98%E8%AE%A4%E8%AE%BE%E7%BD%AE
Advisors
Spring AI Advisors API 提供了一种灵活而强大的方法来拦截、修改和增强 Spring 应用程序中的 AI 驱动的交互。通过利用 Advisors API,开发人员可以创建更复杂、可重用和可维护的 AI 组件。
类似于拦截器
的功能,允许我们在接收用户请求后,将请求发送给ChatModel前做一些处理,响应也是一样的。
这些用于扩充 Prompt 的上下文数据可以是不同类型的,常见类型包括:
- 您自己的数据:这是 AI 模型尚未训练过的数据,如特定领域知识、产品文档等,即使模型已经看到过类似的数据,附加的上下文数据也会优先生成响应。
- 对话历史记录:聊天模型的 API 是无状态的,如果您告诉 AI 模型您的姓名,它不会在后续交互中记住它,每次请求都必须发送对话历史记录,以确保在生成响应时考虑到先前的交互。
日志输出
yml配置
logging:
level:
org.springframework.ai.chat.client.advisor: DEBUG
测试
@GetMapping("/chat8")
public String chat8(){
return deepseekR1Client.prompt()
.advisors(new SimpleLoggerAdvisor(
request -> "Custom request: " + request.userText(),
response -> "Custom response: " + response.getResult(),
0 //表示在advisors调用顺序,责任链模式
))
.user("你和豆包那个强?")
.call()
.content();
}
运行结果
更多内容请参考spring-ai官方文档:
https://docs.spring.io/spring-ai/reference/api/chatclient.html