聊天客户端 API
ChatClient
提供了一个流畅的 API 用于与 AI 模型进行通信。它支持同步和流式编程模型。
流利的 API 提供了用于构建 Prompt 组成部分的方法,这些部分作为输入传递给 AI 模型。Prompt
包含指导 AI 模型输出和行为的指令文本。从 API 的角度来看,提示由一系列消息组成。
AI 模型处理两种主要类型的消息:用户消息,即用户直接输入的内容;以及系统消息,即系统生成的用于引导对话的内容。
这些消息通常包含占位符,这些占位符在运行时根据用户输入进行替换,以定制 AI 模型对用户输入的响应。
还有一些可以指定的 Prompt 选项,例如要使用的 AI 模型的名称以及控制生成输出随机性或创造性的温度设置。
创建 ChatClient
ChatClient
是通过 ChatClient.Builder
对象创建的。你可以为任何 ChatModel Spring Boot 自动配置获取一个自动配置的 ChatClient.Builder
实例,或者通过编程方式创建一个。
使用自动配置的 ChatClient.Builder
在最简单的使用场景中,Spring AI 提供了 Spring Boot 自动配置,为你创建了一个原型 ChatClient.Builder
bean,以便你可以将其注入到你的类中。以下是一个简单的示例,展示了如何获取对用户简单请求的 String
响应。
@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 模型的响应作为 String
返回。
以编程方式创建 ChatClient
你可以通过设置属性 spring.ai.chat.client.enabled=false
来禁用 ChatClient.Builder
的自动配置。这在多个聊天模型一起使用时非常有用。然后,为每个你需要的 ChatModel
以编程方式创建一个 ChatClient.Builder
实例:
ChatModel myChatModel = ... // usually autowired
ChatClient.Builder builder = ChatClient.builder(this.myChatModel);
// or create a ChatClient with the default builder settings:
ChatClient chatClient = ChatClient.create(this.myChatModel);
ChatClient 流式 API
ChatClient
的流式 API 允许你通过重载的 prompt
方法以三种不同的方式创建提示,从而启动流式 API:
-
prompt()
: 此方法无需参数,允许你开始使用流畅的 API,从而构建用户、系统和其他部分的提示。 -
prompt(Prompt prompt)
: 此方法接受一个Prompt
参数,允许你传入一个使用 Prompt 的非流畅 API 创建的Prompt
实例。 -
prompt(String content)
: 这是一个便捷方法,类似于前面的重载方法。它接受用户的文本内容。
ChatClient 响应
ChatClient
API 提供了多种方式,使用流畅的 API 来格式化 AI 模型的响应。
返回一个 ChatResponse
AI 模型的响应是一个由 [ChatResponse](../chatmodel/index.md#ChatResponse)
类型定义的丰富结构。它包含了关于响应生成方式的元数据,并且还可以包含多个响应,称为 [Generation](../chatmodel/index.md#Generation)
,每个响应都有其自己的元数据。元数据包括用于生成响应的 token 数量(每个 token 大约相当于 3/4 个单词)。这些信息非常重要,因为托管 AI 模型根据每个请求使用的 token 数量收费。
一个返回包含元数据的 ChatResponse
对象的示例如下所示,通过在 call()
方法之后调用 chatResponse()
来实现。
ChatResponse chatResponse = chatClient.prompt()
.user("Tell me a joke")
.call()
.chatResponse();
返回一个实体
你通常希望返回一个从 String
映射而来的实体类。entity()
方法提供了这一功能。
例如,给定以下 Java 记录:
record ActorFilms(String actor, List<String> movies) {}
你可以使用 entity()
方法轻松地将 AI 模型的输出映射到此记录,如下所示:
ActorFilms actorFilms = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorFilms.class);
还有一个重载的 entity
方法,其签名为 entity(ParameterizedTypeReference<T> type)
,它允许你指定诸如泛型 List
之类的类型:
List<ActorFilms> actorFilms = chatClient.prompt()
.user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
.call()
.entity(new ParameterizedTypeReference<List<ActorFilms>>() {});
流式响应
stream()
方法允许你获取异步响应,如下所示:
Flux<String> output = chatClient.prompt()
.user("Tell me a joke")
.stream()
.content();
你也可以通过方法 Flux<ChatResponse> chatResponse()
来流式传输 ChatResponse
。
未来,我们将提供一个便捷方法,让你能够通过响应式的 stream()
方法返回一个 Java 实体。目前,你应该使用 结构化输出转换器 来显式地转换聚合响应,如下所示。这也展示了在流式 API 中使用参数的方式,文档的后续部分将对此进行更详细的讨论。
var converter = new BeanOutputConverter<>(new ParameterizedTypeReference<List<ActorsFilms>>() {});
Flux<String> flux = this.chatClient.prompt()
.user(u -> u.text("""
Generate the filmography for a random actor.
{format}
""")
.param("format", this.converter.getFormat()))
.stream()
.content();
String content = this.flux.collectList().block().stream().collect(Collectors.joining());
List<ActorFilms> actorFilms = this.converter.convert(this.content);
call() 返回值
在指定 ChatClient
上的 call()
方法后,响应类型有几种不同的选项。
-
String content()
: 返回响应的字符串内容。 -
ChatResponse chatResponse()
: 返回包含多个生成结果以及响应元数据的ChatResponse
对象,例如用于创建响应的 token 数量。 -
entity()
用于返回 Java 类型-
entity(ParameterizedTypeReference<T> type)
: 用于返回实体类型的集合。 -
entity(Class<T> type)
: 用于返回特定的实体类型。 -
entity(StructuredOutputConverter<T> structuredOutputConverter)
: 用于指定StructuredOutputConverter
的实例,以将字符串转换为实体类型。
-
你也可以调用 stream()
方法,而不是 call()
。
stream() 返回值
在 ChatClient
上指定 stream()
方法后,响应类型有几种选项:
-
Flux<String> content()
: 返回由 AI 模型生成的字符串的Flux
。 -
Flux<ChatResponse> chatResponse()
: 返回ChatResponse
对象的Flux
,该对象包含有关响应的附加元数据。
使用默认值
在 @Configuration
类中创建一个带有默认系统文本的 ChatClient
可以简化运行时代码。通过设置默认值,你只需要在调用 ChatClient
时指定用户文本,从而无需在运行时代码路径中为每个请求设置系统文本。
默认系统文本
在下面的示例中,我们将配置系统文本始终以海盗的口吻回复。为了避免在运行时代码中重复系统文本,我们将在 @Configuration
类中创建一个 ChatClient
实例。
@Configuration
class Config {
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder.defaultSystem("You are a friendly chat bot that answers question in the voice of a Pirate")
.build();
}
}
以及一个 @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 = "Tell me a joke") String message) {
return Map.of("completion", this.chatClient.prompt().user(message).call().content());
}
}
通过 curl 调用应用程序端点时,结果是:
❯ curl localhost:8080/ai/simple
{"completion":"Why did the pirate go to the comedy club? To hear some arrr-rated jokes! Arrr, matey!"}
带参数的默认系统文本
在以下示例中,我们将在系统文本中使用占位符,以在运行时而非设计时指定补全的声音。
@Configuration
class Config {
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder.defaultSystem("You are a friendly chat bot that answers question in the voice of a {voice}")
.build();
}
}
@RestController
class AIController {
private final ChatClient chatClient;
AIController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/ai")
Map<String, String> completion(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message, String voice) {
return Map.of("completion",
this.chatClient.prompt()
.system(sp -> sp.param("voice", voice))
.user(message)
.call()
.content());
}
}
通过 httpie 调用应用程序端点时,结果是:
http localhost:8080/ai voice=='Robert DeNiro'
{
"completion": "You talkin' to me? Okay, here's a joke for ya: Why couldn't the bicycle stand up by itself? Because it was two tired! Classic, right?"
}
其他默认设置
在 ChatClient.Builder
级别,您可以指定默认的提示配置。
-
defaultOptions(ChatOptions chatOptions)
:传入在ChatOptions
类中定义的便携选项或特定模型的选项,例如OpenAiChatOptions
中的选项。有关特定模型的ChatOptions
实现的更多信息,请参阅 JavaDocs。 -
defaultFunction(String name, String description, java.util.function.Function<I, O> function)
:name
用于在用户文本中引用该函数。description
解释了函数的用途,并帮助 AI 模型选择正确的函数以生成准确的响应。function
参数是一个 Java 函数实例,模型将在必要时执行该函数。 -
defaultFunctions(String… functionNames)
:应用程序上下文中定义的java.util.Function
的 bean 名称。 -
defaultUser(String text)
,defaultUser(Resource text)
,defaultUser(Consumer<UserSpec> userSpecConsumer)
:这些方法允许你定义用户文本。Consumer<UserSpec>
允许你使用 lambda 表达式来指定用户文本和任何默认参数。 -
defaultAdvisors(Advisor… advisor)
:Advisor 允许修改用于创建Prompt
的数据。QuestionAnswerAdvisor
实现通过在提示中附加与用户文本相关的上下文信息来支持Retrieval Augmented Generation
模式。 -
defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer)
:此方法允许你定义一个Consumer
来使用AdvisorSpec
配置多个 Advisor。Advisor 可以修改用于创建最终Prompt
的数据。Consumer<AdvisorSpec>
允许你指定一个 lambda 表达式来添加 Advisor,例如QuestionAnswerAdvisor
,它通过在提示中附加与用户文本相关的上下文信息来支持Retrieval Augmented Generation
。
你可以在运行时使用不带 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<UserSpec> userSpecConsumer)
-
advisors(Advisor… advisor)
-
advisors(Consumer<AdvisorSpec> advisorSpecConsumer)
顾问
Advisors API 提供了一种灵活而强大的方式,用于拦截、修改和增强 Spring 应用程序中基于 AI 的交互。
在调用 AI 模型处理用户文本时,一种常见的模式是将上下文数据附加或增强到提示(prompt)中。
这个上下文数据可以是不同类型的。常见的类型包括:
-
您自己的数据:这是 AI 模型未训练过的数据。即使模型已经见过类似的数据,附加的上下文数据在生成响应时具有优先权。
-
对话历史:聊天模型的 API 是无状态的。如果您告诉 AI 模型您的名字,它不会在后续的交互中记住。每次请求时必须发送对话历史,以确保在生成响应时考虑之前的交互。
ChatClient 中的 Advisor 配置
ChatClient 的流式 API 提供了一个 AdvisorSpec
接口,用于配置 advisors。该接口提供了添加参数、一次性设置多个参数以及向链中添加一个或多个 advisors 的方法。
interface AdvisorSpec {
AdvisorSpec param(String k, Object v);
AdvisorSpec params(Map<String, Object> p);
AdvisorSpec advisors(Advisor... advisors);
AdvisorSpec advisors(List<Advisor> advisors);
}
将 advisor 添加到链中的顺序至关重要,因为它决定了它们的执行顺序。每个 advisor 都会以某种方式修改提示或上下文,并且一个 advisor 所做的更改会传递给链中的下一个 advisor。
ChatClient.builder(chatModel)
.build()
.prompt()
.advisors(
new MessageChatMemoryAdvisor(chatMemory),
new QuestionAnswerAdvisor(vectorStore)
)
.user(userText)
.call()
.content();
在此配置中,MessageChatMemoryAdvisor
将首先执行,将对话历史添加到提示中。然后,QuestionAnswerAdvisor
将基于用户的问题和添加的对话历史执行其搜索,可能会提供更相关的结果。
检索增强生成
请参考 检索增强生成 指南。
聊天记忆
接口 ChatMemory
代表聊天对话历史的存储。它提供了向对话中添加消息、从对话中检索消息以及清除对话历史的方法。
目前有两个实现,InMemoryChatMemory
和 CassandraChatMemory
,它们分别提供了内存中和持久化的聊天对话历史存储,后者带有 time-to-live
(生存时间)特性。
要创建一个带有 time-to-live
的 CassandraChatMemory
:
CassandraChatMemory.create(CassandraChatMemoryConfig.builder().withTimeToLive(Duration.ofDays(1)).build());
以下顾问实现使用 ChatMemory
接口来通过对话历史为提示提供建议,它们在如何将记忆添加到提示中的细节上有所不同。
-
MessageChatMemoryAdvisor
:从记忆中检索信息并将其作为消息集合添加到提示中。 -
PromptChatMemoryAdvisor
:从记忆中检索信息并将其添加到提示的系统文本中。 -
VectorStoreChatMemoryAdvisor
:构造函数VectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConversationId, int chatHistoryWindowSize, int order)
允许你:-
指定用于管理和查询文档的 VectorStore 实例。
-
设置一个默认的对话 ID,如果在上下文中没有提供对话 ID 时使用。
-
定义聊天历史记录的检索窗口大小(以 token 大小为基准)。
-
提供用于聊天顾问系统的系统文本建议。
-
设置该顾问在链中的优先级顺序。
-
VectorStoreChatMemoryAdvisor.builder()
方法允许你指定默认的对话 ID、聊天历史窗口大小以及要检索的聊天历史的顺序。
下面展示了一个使用多个顾问(advisors)的 @Service
实现示例。
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY;
@Service
public class CustomerSupportAssistant {
private final ChatClient chatClient;
public CustomerSupportAssistant(ChatClient.Builder builder, VectorStore vectorStore, ChatMemory chatMemory) {
this.chatClient = builder
.defaultSystem("""
You are a customer chat support agent of an airline named "Funnair". Respond in a friendly,
helpful, and joyful manner.
Before providing information about a booking or cancelling a booking, you MUST always
get the following information from the user: booking number, customer first name and last name.
Before changing a booking you MUST ensure it is permitted by the terms.
If there is a charge for the change, you MUST ask the user to consent before proceeding.
""")
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory), // CHAT MEMORY
new QuestionAnswerAdvisor(vectorStore), // RAG
new SimpleLoggerAdvisor())
.defaultFunctions("getBookingDetails", "changeBooking", "cancelBooking") // FUNCTION CALLING
.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)
.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100))
.stream().content();
}
}
日志记录
SimpleLoggerAdvisor
是一个用于记录 ChatClient
的 request
和 response
数据的 advisor。这对于调试和监控 AI 交互非常有用。
Spring AI 支持对 LLM 和向量存储交互的可观测性。更多信息请参考可观测性指南。
要启用日志记录,请在创建 ChatClient
时将 SimpleLoggerAdvisor
添加到 advisor 链中。建议将其添加到链的末尾:
ChatResponse response = ChatClient.create(chatModel).prompt()
.advisors(new SimpleLoggerAdvisor())
.user("Tell me a joke?")
.call()
.chatResponse();
要查看日志,请将 advisor 包的日志级别设置为 DEBUG
:
logging.level.org.springframework.ai.chat.client.advisor=DEBUG
将此添加到你的 application.properties
或 application.yaml
文件中。
你可以通过使用以下构造函数来自定义从 AdvisedRequest
和 ChatResponse
中记录的数据:
SimpleLoggerAdvisor(
Function<AdvisedRequest, String> requestToString,
Function<ChatResponse, String> responseToString
)
示例用法:
SimpleLoggerAdvisor customLogger = new SimpleLoggerAdvisor(
request -> "Custom request: " + request.userText,
response -> "Custom response: " + response.getResult()
);
这使您能够根据特定需求定制记录的信息。
在生产环境中记录敏感信息时要谨慎。
章节摘要
📄️ Advisors API
Spring AI Advisors API 提供了一种灵活且强大的方式来拦截、修改和增强 Spring 应用程序中 AI 驱动的交互。通过利用 Advisors API,开发者可以创建更加复杂、可重用且易于维护的 AI 组件。