ChatClient 提供了一个流畅的API,用于与AI模型进行通信。它同时支持同步和流式编程模型。 该流畅的API提供了构建Prompt(提示词)各组成部分的方法,这些Prompt作为输入传递给AI模型。Prompt包含指导AI模型输出及行为的指令文本。从API视角来看,Prompt由一系列消息(Message)组成。
AI模型处理两种主要类型的消息:
用户消息(User Messages) :用户的直接输入内容; 系统消息(System Messages) :由系统生成以引导对话流程的消息。 这些消息通常包含占位符(Placeholders),这些占位符会在运行时根据用户输入动态替换,从而实现AI模型对用户输入的定制化响应。
此外,Prompt还可指定以下选项: AI模型名称:指定使用的具体模型; 温度参数(Temperature):控制生成结果的随机性或创造性(值越高,输出越随机)
创建一个ChatClient
ChatClient通过ChatClient.Builder对象创建。你可以通过Spring Boot自动配置获取任意ChatModel对应的自动配置ChatClient.Builder实例,或通过编程方式创建
使用自动配置的ChatClient.Builder
在最简单的用例中,Spring AI通过Spring Boot自动配置机制,注入一个原型作用域(prototype)的ChatClient.Builder Bean 。以下是一个获取简单用户请求字符串响应的示例
@RestController
class MyController {
private final ChatClient chatClient;
public MyController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@GetMapping("/ai")
String generation(String userInput) {
return this.chatClient.prompt()
.user(userInput)
.call()
.content();
}
}
在以上简单示例中,用户输入用于设置用户消息的内容。call()方法向AI模型发送请求,而content()方法以字符串形式返回AI模型的响应
以编程方式创建 ChatClient
你可以通过设置属性 spring.ai.chat.client.enabled=false 禁用 ChatClient.Builder 的自动配置功能。当需要同时使用多个聊天模型时,此配置非常有用。随后,你可以为每个需要的 ChatModel 手动创建 ChatClient.Builder 实例:
ChatModel myChatModel = ... // 通常通过依赖注入(@Autowired)获取
// 通过模型创建自定义配置的Builder
ChatClient.Builder builder = ChatClient.builder(this.myChatModel);
// 或使用默认配置直接创建ChatClient实例
ChatClient chatClient = ChatClient.create(this.myChatModel);
ChatClient Fluent API
ChatClient Fluent API 通过重载的 prompt 方法提供了三种创建提示(Prompt)的方式,用于与 AI 模型交互:
1、prompt() 无参数方法,允许通过Fluent API 逐步构建用户消息(user)、系统消息(system)及其他提示内容。例如:
Prompt prompt = Prompt.builder()
.user("用户输入内容")
.system("系统指令")
.build();
2、prompt(Prompt prompt) 接受一个 Prompt 实例作为参数,适用于通过非流畅 API(如直接构造 Prompt 对象)创建的提示。例如:
Prompt predefinedPrompt = new Prompt(...); // 预定义Prompt
ChatResponse response = chatClient.prompt(predefinedPrompt).call();
3、prompt(String content) 便捷方法,直接接收用户输入的文本内容(等效于 prompt(Prompt.builder().user(content).build()))。例如:
String response = chatClient.prompt("用户问题").call().content();
ChatClient 响应
ChatClient API 提供了多种通过fluent API格式化AI模型响应的方式。
返回一个ChatResponse(Returning a ChatResponse)
AI模型的响应通过ChatResponse对象返回,该对象是一个包含丰富结构的数据类型。它不仅包含生成响应的元数据(如使用的令牌数量),还可能包含多个生成结果(Generations),每个结果包含独立的内容和元数据,适用于需要对比或筛选的场景每个结果都有独立的元数据,元数据(Metadata) 包含生成过程的详细信息,例如使用的令牌数量(每个令牌约等于3/4个单词)。托管型AI模型通常按请求的令牌数量计费,因此此数据对成本控制至关重要。
以下示例通过chatResponse()方法获取包含元数据的完整响应对象:
ChatResponse chatResponse = chatClient.prompt()
.user("Tell me a joke")
.call()
.chatResponse();
返回一个实体类(Returning an Entity)
通常需要将AI模型返回的字符串映射到一个实体类(Entity Class)。entity()方法提供了此功能,支持将响应内容自动转换为指定的Java对象
示例:基础实体类映射 给定一个Java record类定义:
record ActorFilms(String actor, List<String> movies) {}
可通过entity()方法将AI生成的文本直接映射到该实体类:
ActorFilms actorFilms = chatClient.prompt()
.user("为一位随机演员生成电影作品列表")
.call()
.entity(ActorFilms.class); // 映射到ActorFilms实体类
示例:泛型集合映射
若需返回泛型集合(如List
List<ActorFilms> actorFilms = chatClient.prompt()
.user("生成Tom Hanks和Bill Murray各5部电影的作品列表")
.call()
.entity(new ParameterizedTypeReference<List<ActorFilms>>() {}); // 支持泛型类型 [[3]][[4]]
流式响应(Streaming Responses)
通过 stream() 方法可获取异步响应流,示例如下:
Flux<String> output = chatClient.prompt()
.user("讲个笑话")
.stream()
.content(); // 返回包含字符串的响应流
你也可以通过 Flux
未来版本将提供便捷方法,支持直接通过响应式 stream() 方法返回Java实体类。当前可通过结构化输出转换器(Structured Output Converter) 显式转换聚合后的响应,示例如下:
// 定义转换器(支持泛型类型)
var converter = new BeanOutputConverter<>(new ParameterizedTypeReference<List<ActorFilms>>() {});
// 流式请求:通过参数动态替换占位符
Flux<String> flux = chatClient.prompt()
.user(u -> u.text("""
为随机演员生成电影作品列表
{format} // 动态替换格式参数
""")
.param("format", converter.getFormat())) // 注入格式参数 [[3]][[5]]
.stream()
.content();
// 聚合流式响应并转换为实体类
String content = flux.collectList().block().stream().collect(Collectors.joining());
List<ActorFilms> actorFilms = converter.convert(content); // 转换为List<ActorFilms> [[1]][[6]]
call() 返回值
在 ChatClient 上指定 call() 方法后,有以下几种不同的响应类型选项 1、String content() 返回响应的字符串内容。例如:
String response = chatClient.prompt("问题").call().content();
2、ChatResponse chatResponse() 返回包含多个生成结果(Generations)及元数据(如生成响应所用的令牌数量)的 ChatResponse 对象。例如:
ChatResponse response = chatClient.prompt("问题").call().chatResponse();
3、entity() 方法(返回 Java 类型)
- entity(Class
type) 用于返回特定实体类型。例如:
ActorFilms films = chatClient.prompt("生成演员作品").call().entity(ActorFilms.class);
- entity(ParameterizedTypeReference
type) 用于返回泛型集合(如 List )。例如:
List<ActorFilms> films = chatClient.prompt("生成多个演员作品")
.call()
.entity(new ParameterizedTypeReference<List<ActorFilms>>() {});
- entity(StructuredOutputConverter
converter) 通过自定义转换器将字符串响应映射到实体类。例如:
var converter = new BeanOutputConverter<>(ActorFilms.class);
ActorFilms films = chatClient.prompt("生成演员作品").call().entity(converter);
4、stream() 方法(替代 call())
通过 stream() 方法可获取异步响应流(如 Flux
Flux<String> stream = chatClient.prompt("问题").stream().content();
stream() 返回值
在 ChatClient 上指定 stream() 方法后,响应类型有以下选项:
1、Flux
Flux<String> stream = chatClient.prompt("问题").stream().content();
2、Flux
Flux<ChatResponse> responseStream = chatClient.prompt("问题").stream().chatResponse();
使用默认值(Using Defaults)
在 @Configuration 类中创建带有默认系统文本的 ChatClient,可简化运行时代码。通过设置默认值,调用 ChatClient 时只需指定用户文本,无需在运行时代码路径中为每个请求重复设置系统文本
默认系统文本(Default System Text)
以下示例将配置系统文本,使AI始终以海盗的口吻回答。为避免在运行时代码中重复设置系统文本,我们通过 @Configuration 类创建预配置的 ChatClient 实例
1、配置类(@Configuration)
@Configuration
class Config {
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem("你是一个友好的聊天机器人,以海盗的口吻回答问题")
.build();
}
}
作用 :通过 defaultSystem() 方法设置全局系统提示,后续调用时无需重复指定。
2、控制器(@RestController)
@RestController
class AIController {
private final ChatClient chatClient;
AIController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/ai/simple")
public Map<String, String> completion(
@RequestParam(value = "message", defaultValue = "讲个笑话") String message) {
return Map.of("completion",
chatClient.prompt().user(message).call().content());
}
}
简化调用 :仅需传递用户输入(message),系统文本自动应用。
3、调用示例 通过curl请求端点时,响应会遵循预设的海盗风格:
❯ curl localhost:8080/ai/simple
{"completion":"海盗为什么去喜剧俱乐部?为了听些‘啊’级笑话!啊,伙计!"}
带参数的默认系统文本
以下示例通过占位符({voice})在系统文本中动态指定回复风格,实现运行时配置而非设计时固定。
1、配置类(@Configuration)
@Configuration
class Config {
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder.defaultSystem("你是一个友好的聊天机器人,以{voice}的口吻回答问题")
.build();
}
}
占位符机制:{voice} 在运行时通过参数替换,实现动态风格控制。
2、 控制器(@RestController)
@RestController
class AIController {
private final ChatClient chatClient;
AIController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/ai")
Map<String, String> completion(
@RequestParam(value = "message", defaultValue = "讲个笑话") String message,
@RequestParam("voice") String voice) { // 接收风格参数
return Map.of("completion",
chatClient.prompt()
.system(sp -> sp.param("voice", voice)) // 替换占位符
.user(message)
.call()
.content());
}
}
动态替换:通过 system().param() 方法将请求参数 voice 绑定到占位符。
3、调用示例 通过HTTP请求传递 voice 参数,响应会根据参数值调整风格:
http localhost:8080/ai voice=='Robert DeNiro'
{
"completion": "你在跟我说话吗?好吧,给你讲个笑话:为什么自行车不能自己站起来?因为它太累了!经典吧?"
}
其他默认配置
在 ChatClient.Builder 级别,您可以指定默认的提示(Prompt)配置。
1、defaultOptions(ChatOptions chatOptions)传入在 ChatOptions 类中定义的可移植选项,或特定于模型的选项(如 OpenAiChatOptions)。有关特定模型的 ChatOptions 实现,请参考 JavaDocs。
2、defaultFunction(String name, String description, java.util.function.Function<I, O> function)
- name :用户文本中引用该函数的名称。
- description :描述函数用途,帮助AI模型选择正确的函数以生成准确响应。
- function :当需要执行时,AI模型调用的Java函数实例。
3、defaultFunctions(String… functionNames)指定应用上下文中定义的 java.util.Function Bean名称列表。
4、defaultUser 方法族定义用户文本的默认值:
- defaultUser(String text):直接传入文本。
- defaultUser(Resource text):从资源文件加载文本。
- defaultUser(Consumer
userSpecConsumer):通过Lambda表达式定义用户文本及默认参数。
5、defaultAdvisors 方法族
- defaultAdvisors(Advisor… advisor) :添加默认顾问(Advisor),用于修改生成Prompt的数据。例如,QuestionAnswerAdvisor 通过附加与用户文本相关的上下文信息,实现检索增强生成(RAG)模式。
- defaultAdvisors(Consumer
advisorSpecConsumer) :通过 AdvisorSpec 配置多个顾问。例如,使用Lambda表达式添加 QuestionAnswerAdvisor,基于用户文本动态附加上下文。
覆盖默认值
运行时可通过以下不带 default 前缀的方法 覆盖默认配置:
- options(ChatOptions chatOptions)
- function(String name, String description, java.util.function.Function<I, O> function)
- functions(String… functionNames)
- user(String text), user(Resource text), user(Consumer
userSpecConsumer) - advisors(Advisor… advisor)
- advisors(Consumer
advisorSpecConsumer)
Advisors
Advisors API 提供了一种灵活而强大的方式,用于在 Spring 应用程序中拦截、修改和增强 AI 驱动的交互。
当通过用户文本调用 AI 模型时,一种常见模式是通过附加或增强提示(Prompt)来引入上下文数据 。此类上下文数据可包含以下类型:
1、自有数据(Your own data) 这是 AI 模型未接受过训练的数据。即使模型接触过类似数据,附加的上下文数据在生成响应时仍会优先生效 。
2、对话历史(Conversational history) 聊天模型的 API 是无状态的 。例如,若用户告知模型自己的姓名,模型不会在后续交互中记住。为确保生成响应时考虑之前的交互,必须在每次请求中附带对话历史
ChatClient中的Advisor配置
ChatClient的fluent API提供了一个AdvisorSpec接口用于配置Advisor。该接口提供了添加参数、批量设置参数以及向链中添加一个或多个顾问的方法
interface AdvisorSpec {
AdvisorSpec param(String k, Object v); // 添加单个参数
AdvisorSpec params(Map<String, Object> p); // 批量添加参数
AdvisorSpec advisors(Advisor... advisors); // 添加可变参数Advisor
AdvisorSpec advisors(List<Advisor> advisors); // 添加Advisor列表
}
Advisor链中添加的顺序至关重要,因为这决定了它们的执行顺序。每个Advisor会以某种方式修改提示(Prompt)或上下文,前一个Advisor的修改结果会传递给下一个Advisor
ChatClient.builder(chatModel)
.build()
.prompt()
.advisors(
new MessageChatMemoryAdvisor(chatMemory), // 对话历史Advisor
new QuestionAnswerAdvisor(vectorStore) // 问答增强Advisor
)
.user(userText)
.call()
.content();
在此配置中: MessageChatMemoryAdvisor 会首先执行,将对话历史附加到提示中。 QuestionAnswerAdvisor 随后基于用户问题和对话历史执行检索,提供更相关的上下文信息。
Advisor执行顺序与作用
1、顺序敏感性:Advisor链的执行顺序由添加顺序决定,类似Spring AOP的拦截链。 2、上下文增强:
- MessageChatMemoryAdvisor通过附加历史对话记录,确保模型具备上下文感知能力。
- QuestionAnswerAdvisor结合外部数据源(如向量数据库),实现检索增强生成(RAG)。
关于 QuestionAnswerAdvisor QuestionAnswerAdvisor 是一种特殊Advisor,用于在生成响应前从外部数据源(如向量库)检索与用户问题相关的上下文信息,并将其附加到提示中。其核心流程如下:
- 分析用户输入,提取关键词或嵌入向量。
- 从向量库中检索最相关的文档片段。
- 将检索到的上下文与原始用户输入合并,形成增强后的提示。
Retrieval Augmented Generation
Refer to the Retrieval Augmented Generation guide.
聊天记忆(Chat Memory)
ChatMemory 接口表示聊天对话历史记录的存储,提供以下方法:
- 向对话中添加消息
- 从对话中检索消息
- 清除对话历史记录
目前有两种实现:
- InMemoryChatMemory :基于内存的存储,适合临时会话。
- CassandraChatMemory :支持持久化存储并可通过 time-to-live 设置数据存活时间。
创建带存活时间的 CassandraChatMemory
CassandraChatMemory.create(
CassandraChatMemoryConfig.builder()
.withTimeToLive(Duration.ofDays(1)) // 设置存活时间为1天
.build()
);
使用ChatMemory的Advisors
以下顾问实现通过 ChatMemory 接口将对话历史附加到提示(Prompt)中,区别在于附加方式:
1、MessageChatMemoryAdvisor 将历史记录作为消息集合直接附加到提示中。
2、PromptChatMemoryAdvisor 将历史记录合并到提示的系统文本(System Text)中。
3、VectorStoreChatMemoryAdvisor 通过向量存储(Vector Store)管理对话历史,支持更复杂的检索逻辑。构造函数参数包括:
- VectorStore:向量存储实例(如数据库)
- defaultConversationId:默认对话ID
- chatHistoryWindowSize:历史记录窗口大小(以令牌数计)
- order:Advisor链中的执行顺序。
示例:客服助手服务 以下 @Service 类结合了多种顾问实现:
@Service
public class CustomerSupportAssistant {
private final ChatClient chatClient;
public CustomerSupportAssistant(
ChatClient.Builder builder,
VectorStore vectorStore,
ChatMemory chatMemory) {
this.chatClient = builder
.defaultSystem("""
你是名为"Funnair"的航空公司的客服代理,需以友好、乐于助人且愉快的方式回应。
在提供预订信息或取消预订前,必须获取用户的预订号、姓名。
修改预订前需确认是否符合条款,若涉及费用需先获得用户同意。
""")
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory), // 聊天记忆 [[9]]
new QuestionAnswerAdvisor(vectorStore), // 检索增强生成(RAG)[[3]][[8]]
new SimpleLoggerAdvisor() // 日志记录
)
.defaultFunctions("getBookingDetails", "changeBooking", "cancelBooking") // 函数调用 [[7]]
.build();
}
public Flux<String> chat(String chatId, String userMessageContent) {
return this.chatClient.prompt()
.user(userMessageContent)
.advisors(a -> a
.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId) // 绑定对话ID [[4]]
.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100)) // 历史记录窗口大小 [[8]]
.stream()
.content();
}
}
关键点说明
- 对话历史管理 :通过 MessageChatMemoryAdvisor 附加历史记录,确保上下文连贯性。
- 检索增强生成(RAG) :QuestionAnswerAdvisor 结合向量存储检索外部数据,提升响应准确性。
- 函数调用 :defaultFunctions 注册的函数(如 getBookingDetails)允许AI执行业务逻辑。
日志记录(Logging)
SimpleLoggerAdvisor 是一个用于记录 ChatClient 请求与响应数据的顾问,可用于调试和监控AI交互。 Spring AI 支持对大语言模型(LLM)和向量存储交互的可观测性。更多细节请参考《可观测性指南》。
1、启用日志记录
在创建 ChatClient 时,将 SimpleLoggerAdvisor 添加到顾问链中以启用日志记录。建议将其置于链的末尾:
ChatResponse response = ChatClient.create(chatModel).prompt()
.advisors(new SimpleLoggerAdvisor()) // 添加日志顾问 [[9]]
.user("讲个笑话?")
.call()
.chatResponse();
在 application.properties 或 application.yml 中设置日志级别为 DEBUG 以查看日志:
logging.level.org.springframework.ai.chat.client.advisor=DEBUG
2、自定义日志内容
通过构造函数可自定义记录的请求(AdvisedRequest
)和响应(ChatResponse
)数据:
SimpleLoggerAdvisor customLogger = new SimpleLoggerAdvisor(
request -> "自定义请求: " + request.getUserText(), // 自定义请求日志格式
response -> "自定义响应: " + response.getResult() // 自定义响应日志格式
);
此功能允许根据需求调整日志内容,例如仅记录关键字段或添加标记。