介绍
ChatClient 提供了一个流畅的 API 用于与 AI 模型通信。它同时支持同步和流式的编程模型。
ChatClient
提供了一个流畅的 API 用于与 AI 模型通信。它同时支持同步和流式的编程模型。
这个流畅的 API 提供了方法来构建作为输入传递给 AI 模型的 Prompt 的各个组成部分。Prompt
包含了用于指导 AI 模型输出和行为的指令文本。从 API 的角度来看,prompt 由一系列消息组成。
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
方法以三种不同的方式创建 prompt 来启动流畅 API:
prompt()
: 这个无参数方法让你开始使用流畅 API,允许你构建用户、系统和 prompt 的其他部分。prompt(Prompt prompt)
: 这个方法接受一个Prompt
参数,让你传入使用 Prompt 的非流畅 API 创建的Prompt
实例。prompt(String content)
: 这是一个类似于前一个重载的便捷方法。它接受用户的文本内容。
ChatClient 响应
ChatClient API 提供了几种使用流畅 API 格式化 AI 模型响应的方法。
返回 ChatResponse
AI 模型的响应是由 ChatResponse
类型定义的丰富结构。它包含了关于响应如何生成的元数据,也可以包含多个响应(称为 Generations),每个都有自己的元数据。元数据包括用于创建响应的令牌数量(每个令牌大约是 3/4 个词)。这个信息很重要,因为托管的 AI 模型根据每个请求使用的令牌数量收费。
通过调用 call()
方法后调用 chatResponse()
可以返回包含元数据的 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
等类型:
ActorFilms actorFilms = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(new ParameterizedTypeReference<ActorFilms>() {});
流式响应
stream()
方法让你获得异步响应。
Flux<String> output = chatClient.prompt()
.user("Tell me a joke")
.stream()
.content();
你可以使用 ChatResponse
方法流式传输 Flux<ChatResponse>
。
在未来,我们将提供一个方便的方法,让你可以通过反应式 stream()
方法返回一个 Java 实体。在此期间,你应该使用结构化输出转换器显式地将聚合响应转换,如下所示。这还展示了将在文档的后续部分详细讨论的 fluent 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
对象,例如用于创建响应的令牌数量。entity()
返回 Java 类型:entity(ParameterizedTypeReference<T> type)
: 用于返回实体类型的Collection
entity(Class<T> type)
: 用于返回特定实体类型entity(StructuredOutputConverter<T> structuredOutputConverter)
: 用于指定StructuredOutputConverter
实例来将String
转换为实体类型
你也可以调用 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$2 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$6 Okay, here's a joke for ya: Why couldn't the bicycle stand up by itself$7 Because it was two tired! Classic, right$8"
}
其他默认值
在 ChatClient.Builder
级别,你可以指定默认的 prompt 配置。
defaultOptions(ChatOptions chatOptions)
: 可以传递在 ChatOptions 类中定义的便携选项或特定于模型的选项,例如 OpenAiChatOptions 中的选项。有关特定于模型的 ChatOptions 实现的更多信息,请参阅 JavaDocs。defaultFunction(String name, String description, java.util.function.Function<I, O> function)
: 用于引用用户文本中的函数。 name 解释函数的目的,并帮助 AI 模型选择正确的函数以获得准确的响应。 description 参数是 Java 函数实例,当需要时模型将执行该实例。defaultFunctions(String… functionNames)
: 作为java.util.Function
定义在应用上下文中的 bean 名称。defaultUser(String text)
,defaultUser(Resource text)
,defaultUser(Consumer<UserSpec> userSpecConsumer)
: 这些方法让你定义用户文本。Consumer<UserSpec>
允许你使用 lambda 来指定用户文本和任何默认参数。defaultAdvisors(Advisor… advisor)
: 顾问允许修改用于创建 Prompt 的数据。 QuestionAnswerAdvisor 实现通过在提示中附加与用户文本相关的内容信息来启用 Retrieval Augmented Generation 模式。defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer)
: 该方法允许您使用 Consumer 配置多个顾问。顾问可以修改用于创建最终 Prompt 的数据。Consumer<AdvisorSpec>
允许您指定一个 lambda 以添加顾问,例如 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)
Advisors API 提供了一种灵活而强大的方式来拦截、修改和增强 Spring 应用程序中的 AI 驱动交互。
调用 AI 模型时,如果使用用户文本,一个常见的模式是附加或扩展提示以包含上下文数据。
这些上下文数据可以是不同类型的。常见类型包括:
- 您自己的数据:这是 AI 模型未训练的数据。即使模型见过类似的数据,附加的上下文数据在生成响应时优先。
- 聊天模型的 API 是无状态的。如果你告诉 AI 模型你的名字,它在后续交互中不会记住。每次请求时必须发送会话历史,以确保之前的交互被考虑在内时生成响应。
ChatClient 中的 Advisor 配置
ChatClient 流畅 API 提供了一个 AdvisorSpec
接口用于配置顾问。该接口提供了添加参数、一次性设置多个参数以及向链中添加一个或多个顾问的方法。
interface AdvisorSpec {
AdvisorSpec param(String k, Object v);
AdvisorSpec params(Map<String, Object> p);
AdvisorSpec advisors(Advisor... advisors);
AdvisorSpec advisors(List<Advisor> advisors);
}
顾问添加到链中的顺序至关重要,因为它决定了它们的执行顺序。每个顾问以某种方式修改提示或上下文,一个顾问所做的更改会传递给链中的下一个顾问。
ChatClient.builder(chatModel)
.build()
.prompt()
.advisors(
new MessageChatMemoryAdvisor(chatMemory),
new QuestionAnswerAdvisor(vectorStore)
)
.user(userText)
.call()
.content();
在该配置中, MessageChatMemoryAdvisor
将首先执行,将对话历史添加到提示中。然后, QuestionAnswerAdvisor
将根据用户的问题和添加的对话历史进行搜索,可能会提供更相关的结果。
检索增强生成(RAG)
参考 Retrieval Augmented Generation 指南。
聊天记忆
ChatMemory
接口表示聊天对话历史的存储。它提供了向对话添加消息、从对话检索消息和清除对话历史的方法。
目前有两个实现 InMemoryChatMemory
和 CassandraChatMemory
,分别提供聊天对话历史的内存存储和使用 time-to-live
持久化存储。
创建一个 CassandraChatMemory
与 time-to-live
:
CassandraChatMemory.create(CassandraChatMemoryConfig.builder().withTimeToLive(Duration.ofDays(1)).build());
以下顾问实现使用 ChatMemory 接口根据对话历史对提示进行建议,它们在将记忆添加到提示的细节上有所不同:
MessageChatMemoryAdvisor
: 内存被检索并作为消息集合添加到提示中。PromptChatMemoryAdvisor
: 内存被检索并添加到提示的系统文本中。VectorStoreChatMemoryAdvisor
: 这个构造函数允许你:- 指定用于管理和查询文档的 VectorStore 实例。
- 设置一个默认对话 ID,如果上下文中没有提供对话 ID,则使用该 ID。
- 定义用于聊天历史检索的窗口大小,以令牌数量为单位。
- 提供用于聊天顾问系统的文本建议。
- 设置此顾问在链中的优先级顺序。
VectorStoreChatMemoryAdvisor.builder()
方法允许您指定默认对话 ID、聊天历史窗口大小以及要检索的聊天历史的顺序。
一个使用了多个顾问的示例实现如下。
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
数据的顾问。这对于调试和监控你的 AI 交互很有用。
Spring AI 支持 LLM 和向量存储的可观测性。更多信息请参阅可观测性指南。
要启用日志记录,在创建 ChatClient 时将 SimpleLoggerAdvisor
添加到顾问链中。建议将其添加到链的末尾。
ChatResponse response = ChatClient.create(chatModel).prompt()
.advisors(new SimpleLoggerAdvisor())
.user("Tell me a joke$10")
.call()
.chatResponse();
要查看日志,请将顾问包的日志级别设置为 DEBUG
:
logging.level.org.springframework.ai.chat.client.advisor=DEBUG
将此添加到你的 application.properties
或 application.yaml
文件中。
SimpleLoggerAdvisor(
Function<AdvisedRequest, String> requestToString,
Function<ChatResponse, String> responseToString
)
示例用法:
SimpleLoggerAdvisor customLogger = new SimpleLoggerAdvisor(
request -> "Custom request: " + request.userText,
response -> "Custom response: " + response.getResult()
);
这允许你根据具体需求定制记录的信息。
注意:在生产环境中要谨慎记录敏感信息。