SpringAI 统一门面接口 ChatClient
ChatClient 通过 Fluent API 模式来统一设置提示语 / 提示语模板 / 模型参数 / 工具与 MCP / 记忆 Memory / Advisor 等,同时提供了同步和流式调用模式,通过类型转换器转换为标准 Java 实体等能力,方便的构建自己的 AI 程序。
创建 ChatClient 的两种方式
@Configuration
public class SpringAIConfig {
/**
* 第一种创建 ChatClient 的方式,使用默认的 builder 参数
*
* @param chatModel 该对象会根据配置自动生成
* @return ChatClient 对象。
*/
@Bean
public ChatClient chatClient(ChatModel chatModel) {
return ChatClient.create(chatModel);
}
/**
* 第二种创建 ChatClient 的方式,使用 builder 来设置自定义参数
*
* @return ChatClient 对象。
*/
@Bean
public ChatClient customChatClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultSystem("You are a helpful assistant.")
.build();
}
}说明:ChatModel 模型会根据配置自动注入。
启动 ChatClient Fluent API
ChatClient Fluent API 提供了以下三种 prompt 方法来启动 Fluent API:
方法定义:
ChatClientRequestSpec prompt();
ChatClientRequestSpec prompt(String content);
ChatClientRequestSpec prompt(Prompt prompt);使用:
@RestController
public class Controller {
@Resource
private ChatClient chatClient;
@RequestMapping("/1")
public String execute1(@RequestParam("userRequest") String userRequest) {
return chatClient.prompt().user(userRequest).call().content();
}
@RequestMapping("/2")
public String execute2(@RequestParam("userRequest") String userRequest) {
return chatClient.prompt(new Prompt(userRequest)).call().content();
}
@RequestMapping("/3")
public String execute3(@RequestParam("userRequest") String userRequest) {
return chatClient.prompt(userRequest).call().content();
}
}提示语模板 PromptTemplate
/**
* 使用默认提示语模板 StTemplateRenderer
*/
@RequestMapping("/100")
public String execute100() {
return chatClient.prompt()
.user(u -> u.text("Tell me the names of 5 movies those act by {actor}").param("actor", "周星驰"))
.call().content();
}
/**
* 定制提示语模板
*/
@RequestMapping("/101")
public String execute101() {
return chatClient.prompt()
.user(u -> u.text("Tell me the names of 5 movies those act by <actor>").param("actor", "刘德华"))
.templateRenderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build())
.call().content();
}说明:
- ChatClient 提供了 user 提示语模板和 system 提示语模板, 其中的变量可以在运行时被替换
- 默认使用 StTemplateRenderer 引擎,如果想要使用自定义的模板引擎,可以自己实现接口
TemplateRenderer - 默认使用
{作为参数占位符起始标识,使用}作为参数占位符结束标识,当发现这两个字符会被提示语或者参数占用时,可以定制提示语模板参数占位符分隔符,如上execute101()方法所示
ChatClient 响应
从返回方式上分为:同步(call())和流式(stream())。
同步响应
call() 响应类型有几种不同的选项:
String content():返回响应的字符串内容ChatResponse chatResponse():ChatResponse包含响应的元数据的对象,例如,token 的消耗ChatClientResponse chatClientResponse():ChatClientResponse包含ChatResponse对象和ChatClient执行上下文的对象,使您能够访问advisors执行期间使用的附加数据(例如,在RAG流中检索到的相关文档)ResponseEntity<?> responseEntity():ResponseEntity包含完整 HTTP 响应(包括状态码、标头和正文)的响应entity():返回 Java 类型entity(ParameterizedTypeReference<T> type):返回Collection实体类型entity(Class<T> type):返回特定的实体类型entity(StructuredOutputConverter<T> structuredOutputConverter):指定StructuredOutputConverter,将String为T类型。
说明:调用 call() 方法并不会真正触发 AI 模型的执行。它只是指示 Spring AI 是使用同步调用还是流式调用。实际的 AI 模型调用发生在调用 content()、chatResponse() 和 responseEntity() 等方法时。
/**
* 返回 String
*/
@RequestMapping("/1")
public String execute1(@RequestParam("userRequest") String userRequest) {
return chatClient.prompt().user(userRequest).call().content();
}
/**
* 返回 ChatResponse
* 包含 token 信息
*/
@RequestMapping("/4")
public ChatResponse execute4(@RequestParam("userRequest") String userRequest) {
return chatClient.prompt(userRequest).call().chatResponse();
}
/**
* 返回 ResponseEntity
*/
@RequestMapping("/40")
public org.springframework.ai.chat.client.ResponseEntity<ChatResponse, String> execute40() {
return chatClient.prompt("Tell me a joke").call().responseEntity(String.class);
}
/**
* 返回 Java 类
*/
@RequestMapping("/5")
public ActorFilms execute5() {
return chatClient.prompt("Generate the filmography for a random actor.").call().entity(ActorFilms.class);
}
/**
* 返回 Java 列表
*/
@RequestMapping("/6")
public List<ActorFilms> execute6() {
return chatClient.prompt("Generate the filmography of 5 movies for 周星驰 and 刘德华.").call().entity(new ParameterizedTypeReference<>() {
});Java 类如下:
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
@Data
@Accessors(chain = true)
public class ActorFilms {
private String actor;
private List<String> films;
}流式响应
stream() 响应类型有几种不同的选项:
Flux<String> content():返回响应的字符串内容Flux<ChatResponse> chatResponse():ChatResponse包含响应的元数据的对象Flux<ChatClientResponse> chatClientResponse():ChatClientResponse包含ChatResponse对象和ChatClient执行上下文的对象
说明:stream() 调用方式下,无法直接返回 Java 对象,需要借助类型转换器进行转换,见如下的代码操作。
/**
* 流式返回 String
* 包含 token 信息
*/
@RequestMapping("/7")
public Flux<String> execute7() {
return chatClient.prompt("Tell me a joke").stream().content();
}
/**
* 流式返回 ChatResponse
* 包含 token 信息
*/
@RequestMapping("/8")
public Flux<ChatResponse> execute8() {
return chatClient.prompt("Tell me a joke").stream().chatResponse();
}
/**
* 流式返回信息,转换为 Java 类
*/
@RequestMapping("/9")
public ActorFilms execute9() {
/*
* 创建 Converter
*/
BeanOutputConverter<ActorFilms> converter = new BeanOutputConverter<>(ActorFilms.class);
/*
* 流式调用
*/
Flux<String> flux = chatClient.prompt().user(
u -> u.text("""
Generate the filmography for a random actor.
{format}
""")
.param("format", converter.getFormat())
).stream().content();
String content = flux.collectList().block().stream().collect(Collectors.joining());
/*
* 转换
*/
return converter.convert(content);
}
/**
* 流式返回信息,转换为 Java 列表
*/
@RequestMapping("/10")
public List<ActorFilms> execute10() {
/*
* 创建 Converter
*/
BeanOutputConverter<List<ActorFilms>> converter = new BeanOutputConverter<>(new ParameterizedTypeReference<>() {
});
/*
* 流式调用
*/
Flux<String> flux = chatClient.prompt().user(
u -> u.text("""
Generate the filmography for a random actor.
{format}
""")
.param("format", converter.getFormat())
).stream().content();
String content = flux.collectList().block().stream().collect(Collectors.joining());
/*
* 转换
*/
return converter.convert(content);
}类型转换器会根据返回的不同的 Java 模型制定不同的 format 提示语。例如对于返回 ActorFilms,format 提示语如下(注意观察 schema 部分):
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" : {
"actor" : {
"type" : "string"
},
"films" : {
"type" : "array",
"items" : {
"type" : "string"
}
}
},
"additionalProperties" : false
}```对于返回 List<ActorFilms>,format 提示语如下(注意观察 schema 部分):
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" : "array",
"items" : {
"type" : "object",
"properties" : {
"actor" : {
"type" : "string"
},
"films" : {
"type" : "array",
"items" : {
"type" : "string"
}
}
},
"additionalProperties" : false
}
}```ChatClient 默认选项设置
使用 ChatClient.Builder 创建 ChatClient 实例时,可以全局指定以下的默认配置项来设置默认配置:
defaultOptions:核心是模型参数配置,例如温度,在模型介绍的章节会详细介绍defaultSystem:system 提示语,可以包含参数defaultUser:user 提示语,可以包含参数defaultTemplateRenderer:提示语模板defaultToolCallbacks/defaultTools/defaultToolContext/defaultToolNames:工具与 mcpdefaultAdvisors:advisors
可以在运行时使用不带前缀的相应方法覆盖这些默认值。
optionssystemusertemplateRenderertoolCallbacks/tools/toolContext/toolNamesadvisors
在运行时给带参数占位符的默认配置项设置参数值。
@Configuration
public class SpringAIConfig {
@Bean
public ChatClient customChatClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultSystem("You are a helpful assistant.{actor}")
.build();
}
}@RequestMapping("/1000")
public String execute1000() {
return chatClient.prompt()
.system(s -> s.param("actor", "周星驰")) // 直接设置参数
.user("tell me a joke").call().content();
}Advisors
Advisors 提供了一种灵活而强大的方法来拦截、修改和增强 Spring AI 应用程序。
例如,使用 user 提示语调用 AI 模型时的一个常见模式是 使用上下文数据扩充提示。 这些上下文数据可以是不同类型的。常见的类型包括:
- 企业私有数据:这是 AI 模型尚未训练过的数据
- 对话历史记录:聊天模型的 API 是无状态的。如果您告诉 AI 模型您的姓名,它不会在后续交互中记住它。每次请求都必须发送对话历史记录,以确保在生成响应时考虑到之前的交互
ChatClient 提供了一个 AdvisorSpec 接口用于配置 Advisor 的接口。该接口提供了添加参数、一次性设置多个参数以及将一个或多个 Advisors 添加到链中的方法。
interface AdvisorSpec {
AdvisorSpec param(String k, Object v);
AdvisorSpec params(Map<String, Object> p);
AdvisorSpec advisors(Advisor... advisors);
AdvisorSpec advisors(List<Advisor> advisors);
}关于 Advisors 更多细节,我们在后续的章节继续深入,这里我们以一个简单的日志记录为例,来了解其基础用法。
SimpleLoggerAdvisor 一个用于记录请求模型的 request 和模型返回的 response 数据的 Advisor。可以用于调试和监控 AI 交互。
application.properties 文件种新增如下配置,开启日志:
logging.level.org.springframework.ai.chat.client.advisor=DEBUG/**
* 使用默认的日志方式
*/
@RequestMapping("/10000")
public String execute10000() {
return chatClient.prompt()
.advisors(new SimpleLoggerAdvisor())
.user("tell me a joke")
.call().content();
}
/**
* 定制日志
*/
@RequestMapping("/10001")
public String execute10001() {
return chatClient.prompt()
.advisors(new SimpleLoggerAdvisor(
request -> "Custom request: " + request.prompt().getUserMessage(),
response -> "Custom response: " + response.getResult(),
0
))
.user("tell me a joke")
.call().content();
}注意:假设有多个 Advisors,每个 Advisor 添加到链中的顺序至关重要,因为它决定了它们的执行顺序。每个 Advisor 都会以某种方式修改提示或上下文,并且一个 Advisor 所做的更改会传递给链中的下一个 Advisor。
ChatMemory
ChatMemory 接口表示聊天对话内存存储。它提供了向对话添加消息、从对话中检索消息以及清除对话历史记录的方法;对话需要设置 conversationId,用于不同的对话区分。
接口定义如下:
public interface ChatMemory {
String DEFAULT_CONVERSATION_ID = "default";
/**
* The key to retrieve the chat memory conversation id from the context.
*/
String CONVERSATION_ID = "chat_memory_conversation_id";
/**
* Save the specified message in the chat memory for the specified conversation.
*/
default void add(String conversationId, Message message) {
Assert.hasText(conversationId, "conversationId cannot be null or empty");
Assert.notNull(message, "message cannot be null");
this.add(conversationId, List.of(message));
}
/**
* Save the specified messages in the chat memory for the specified conversation.
*/
void add(String conversationId, List<Message> messages);
/**
* Get the messages in the chat memory for the specified conversation.
*/
List<Message> get(String conversationId);
/**
* Clear the chat memory for the specified conversation.
*/
void clear(String conversationId);
}ChatMemory 有一个内置实现:MessageWindowChatMemory 维护一个消息窗口,窗口大小不超过指定的最大限制(默认值:20 条消息)。当消息数量超过此限制时,较旧的消息将被移除,但 system 消息将被保留。如果添加了新的 system 消息,所有先前的 system 消息都将从内存中删除。这确保了对话始终可以使用最新的上下文,同时保持内存使用量有限。
MessageWindowChatMemory 内部通过 ChatMemoryRepository 接口提供的内存实现来实现消息存储,ChatMemoryRepository 的实现包括 InMemoryChatMemoryRepository / JdbcChatMemoryRepository / CassandraChatMemoryRepository / Neo4jChatMemoryRepository 等,在 memory 章节我们会详细介绍。
ChatClient 是通过 Advisors 机制来使用 ChatMemory 的。
@Bean
public ChatMemory messageWindowChatMemory() {
return MessageWindowChatMemory.builder().maxMessages(10).build();
}@Resource
private ChatMemory messageWindowChatMemory;
@RequestMapping("/20000")
public String execute20000() {
return chatClient.prompt()
.advisors(
MessageChatMemoryAdvisor.builder(messageWindowChatMemory).build(),
new SimpleLoggerAdvisor())
.user("tell me a joke")
.call().content();
}文章的最后,如果您觉得本文对您有用,请打赏一杯咖啡!感谢!
