聊天客户端 API
ChatClient
提供了一个流畅的 API 用于与 AI 模型通信。它支持同步和反应式编程模型。
流畅的 API 具有用于构建传递给 AI 模型作为输入的 提示 的组成部分的方法。提示
包含指导 AI 模型输出和行为的指令文本。从 API 的角度来看,提示由一组消息组成。
AI 模型处理两种主要类型的消息:用户消息,即来自用户的直接输入;系统消息,即由系统生成以指导对话。
这些消息通常包含占位符,这些占位符在运行时根据用户输入进行替换,以根据用户输入自定义 AI 模型的响应。
还可以指定提示选项,例如要使用的 AI 模型的名称以及控制生成的输出的随机性或创造性的温度设置。
创建 ChatClient
ChatClient
是使用 ChatClient.Builder
对象创建的。您可以为任何 ChatModel Spring Boot 自动配置获取自动配置的 ChatClient.Builder
实例,或者以编程方式创建一个。
使用自动配置的 ChatClient.Builder
在最简单的用例中,Spring AI 提供 Spring Boot 自动配置,为您创建原型 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 模型发送请求,context 方法将 AI 模型的响应作为字符串返回。
以编程方式创建 ChatClient
您可以通过设置属性 spring.ai.chat.client.enabled=false
来禁用 ChatClient.Builder
自动配置。这在使用多个聊天模型时很有用。然后,为每个 ChatModel
以编程方式创建一个 ChatClient.Builder
实例。
ChatModel myChatModel = ... // usually autowired
ChatClient.Builder builder = ChatClient.builder(myChatModel);
// or create a ChatClient with the default builder settings:
ChatClient chatClient = ChatClient.create(myChatModel);
ChatClient 响应
ChatClient API 提供了几种格式化来自 AI 模型的响应的方法。
返回 ChatResponse
来自 AI 模型的响应是一个由类型 ChatResponse 定义的丰富结构。它包含有关响应生成方式的元数据,还可以包含多个响应,称为 Generation,每个响应都有自己的元数据。元数据包括用于创建响应的令牌数量(每个令牌大约是 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 of 5 movies for Tom Hanks and Bill Murray.")
.call()
.entity(new ParameterizedTypeReference<List<ActorsFilms>>() {
});
流式响应
stream
允许您获得异步响应,如下所示
Flux<String> output = chatClient.prompt()
.user("Tell me a joke")
.stream()
.content();
您还可以使用 Flux<ChatResponse> chatResponse()
方法流式传输 ChatResponse
。
在 1.0.0 M2 中,我们将提供一个方便的方法,允许您使用响应式 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", converter.getFormat()))
.stream()
.content();
String content = flux.collectList().block().stream().collect(Collectors.joining());
List<ActorFilms> actorFilms = converter.convert(content);
call() 返回值
在 ChatClient
上指定 call
方法后,响应类型有几个不同的选项。
-
String content()
:返回响应的字符串内容 -
ChatResponse chatResponse()
:返回包含多个生成以及有关响应的元数据的ChatResponse
对象,例如用于创建响应的令牌数量。 -
entity
用于返回 Java 类型。-
entity(ParameterizedTypeReference<T> type): 用于返回实体类型的集合。
-
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", chatClient.prompt().user(message).call().content());
}
}
通过 curl 调用它,结果是
❯ curl localhost:8080/ai/simple
{"generation":"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",
chatClient.prompt()
.system(sp -> sp.param("voice", voice))
.user(message)
.call()
.content());
}
}
响应是
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(RequestResponseAdvisor… advisor)
: 顾问允许修改用于创建Prompt
的数据。QuestionAnswerAdvisor
实现通过将提示附加与用户文本相关的上下文信息来启用Retrieval Augmented Generation
模式。 -
defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer)
: 此方法允许您定义一个Consumer
来使用AdvisorSpec
配置多个顾问。顾问可以修改用于创建最终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(RequestResponseAdvisor… advisor)
-
advisors(Consumer<AdvisorSpec> advisorSpecConsumer)
顾问
在使用用户文本调用 AI 模型时,一种常见的模式是在提示中附加或增强上下文数据。
这种上下文数据可以是不同类型的。常见的类型包括
-
您自己的数据:这是 AI 模型未经训练的数据。即使模型已经见过类似的数据,附加的上下文数据在生成响应时优先考虑。
-
对话历史:聊天模型的 API 是无状态的。如果您告诉 AI 模型您的姓名,它不会在后续交互中记住它。对话历史必须与每个请求一起发送,以确保在生成响应时考虑之前的交互。
检索增强生成
向量数据库存储 AI 模型不知道的数据。当用户问题发送到 AI 模型时,QuestionAnswerAdvisor
查询向量数据库以查找与用户问题相关的文档。
向量数据库的响应附加到用户文本,为 AI 模型提供上下文以生成响应。
假设您已经将数据加载到 VectorStore
中,您可以通过向 ChatClient
提供 QuestionAnswerAdvisor
实例来执行检索增强生成 (RAG)。
ChatResponse response = ChatClient.builder(chatModel)
.build().prompt()
.advisors(new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()))
.user(userText)
.call()
.chatResponse();
在这个示例中,SearchRequest.defaults()
将对向量数据库中的所有文档执行相似性搜索。为了限制搜索的文档类型,SearchRequest
接受一个类似 SQL 的过滤器表达式,该表达式在所有 VectorStores
中都是可移植的。
聊天记忆
接口 ChatMemory
代表聊天对话历史的存储。它提供方法将消息添加到对话中,从对话中检索消息以及清除对话历史。
有一个实现 InMemoryChatMemory
提供聊天对话历史的内存存储。
两个顾问实现使用 ChatMemory
接口来建议使用对话历史的提示,它们在将内存添加到提示的细节方面有所不同
-
MessageChatMemoryAdvisor
:内存被检索并作为消息集合添加到提示中 -
PromptChatMemoryAdvisor
:内存被检索并添加到提示的系统文本中。 -
VectorStoreChatMemoryAdvisor
: 构造函数 ` VectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConversationId, int chatHistoryWindowSize)` 允许您指定用于检索聊天历史记录的 VectorStore、唯一的对话 ID 以及要检索的聊天历史记录的大小(以令牌大小为单位)。
下面显示了一个使用多个顾问的示例 @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 PromptChatMemoryAdvisor(chatMemory),
// new MessageChatMemoryAdvisor(chatMemory), // CHAT MEMORY
new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()),
new LoggingAdvisor()) // RAG
.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();
}
}