聊天客户端 API
这ChatClient
提供用于与 AI 模型通信的 Fluent API。
它支持同步和流式编程模型。
Fluent API 具有构建 Prompt 的组成部分的方法,这些部分作为输入传递给 AI 模型。
这Prompt
包含指导 AI 模型的输出和行为的说明文本。从 API 的角度来看,提示由一组消息组成。
AI 模型处理两种主要类型的消息:用户消息(来自用户的直接输入)和系统消息(由系统生成以指导对话)。
这些消息通常包含占位符,这些占位符在运行时根据用户输入进行替换,以自定义 AI 模型对用户输入的响应。
还有一些可以指定的 Prompt 选项,例如要使用的 AI 模型的名称以及控制生成输出的随机性或创造性的温度设置。
创建 ChatClient
这ChatClient
是使用ChatClient.Builder
对象。
您可以获取自动配置的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()
method 向 AI 模型发送请求,并且content()
方法将 AI 模型的响应作为String
.
以编程方式创建 ChatClient
您可以禁用ChatClient.Builder
autoconfiguration 通过设置属性spring.ai.chat.client.enabled=false
.
如果同时使用多个聊天模型,这非常有用。
然后,创建一个ChatClient.Builder
实例ChatModel
你需要:
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 Fluent API
这ChatClient
Fluent API 允许您使用重载的prompt
启动 Fluent API 的方法:
-
prompt()
:这种不带参数的方法允许您开始使用 Fluent API,从而允许您构建 user、system 和 prompt的其他部分。 -
prompt(Prompt prompt)
:此方法接受Prompt
参数,允许您传入Prompt
实例。 -
prompt(String content)
:这是一种类似于前面的重载的便捷方法。它获取用户的文本内容。
ChatClient 响应
这ChatClient
API 提供了多种使用 Fluent API 格式化来自 AI 模型的响应的方法。
返回 ChatResponse
来自 AI 模型的响应是由类型定义的丰富结构ChatResponse
.
它包括有关响应生成方式的元数据,还可以包含多个响应,称为 Generations,每个响应都有自己的元数据。
元数据包括用于创建响应的标记数(每个标记大约是一个单词的 3/4)。
此信息非常重要,因为托管 AI 模型根据每个请求使用的令牌数量收费。
返回ChatResponse
对象通过调用chatResponse()
在call()
方法。
ChatResponse chatResponse = chatClient.prompt()
.user("Tell me a joke")
.call()
.chatResponse();
返回实体
您通常希望返回一个实体类,该实体类是从返回的String
.
这entity()
method 提供此功能。
例如,给定 Java 记录:
record ActorFilms(String actor, List<String> movies) {}
您可以使用entity()
方法,如下所示:
ActorFilms actorFilms = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorFilms.class);
还有一个 overloadedentity
方法,签名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()
method 允许你获得异步响应,如下所示:
Flux<String> output = chatClient.prompt()
.user("Tell me a joke")
.stream()
.content();
您还可以流式传输ChatResponse
使用方法Flux<ChatResponse> chatResponse()
.
将来,我们将提供一个便捷的方法,让你返回一个带有响应式stream()
方法。
同时,您应该使用 Structured Output Converter 转换聚合响应显式,如下所示。
这也演示了 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() 返回值
指定call()
method 开启ChatClient
,则响应类型有几种不同的选项。
-
String content()
:返回响应的 String 内容 -
ChatResponse chatResponse()
: 返回ChatResponse
对象,其中包含多个代以及有关响应的元数据,例如用于创建响应的令牌数。 -
entity()
返回 Java 类型-
entity(ParameterizedTypeReference<T> type)
:用于返回Collection
的实体类型。 -
entity(Class<T> type)
:用于返回特定实体类型。 -
entity(StructuredOutputConverter<T> structuredOutputConverter)
:用于指定StructuredOutputConverter
要将String
设置为实体类型。
-
您还可以调用stream()
method 而不是call()
.
stream() 返回值
指定stream()
method 开启ChatClient
,则响应类型有几个选项:
-
Flux<String> content()
:返回Flux
由 AI 模型生成的字符串。 -
Flux<ChatResponse> chatResponse()
:返回Flux
的ChatResponse
对象,其中包含有关响应的其他元数据。
使用默认值
创建ChatClient
在@Configuration
类简化了运行时代码。
通过设置默认值,您只需在调用ChatClient
,无需为运行时代码路径中的每个请求设置系统文本。
默认系统文本
在以下示例中,我们将系统文本配置为始终以海盗的声音回复。
为了避免在运行时代码中重复系统文本,我们将创建一个ChatClient
实例中@Configuration
类。
@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
implementations,请参阅 JavaDocs。 -
defaultFunction(String name, String description, java.util.function.Function<I, O> function)
:这name
用于在用户文本中引用函数。这description
解释函数的用途,并帮助 AI 模型选择正确的函数以获得准确的响应。这function
argument 是模型将在必要时执行的 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
implementation 启用Retrieval Augmented Generation
通过在 Prompt 中附加与用户文本相关的上下文信息。 -
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(Advisor… advisor)
-
advisors(Consumer<AdvisorSpec> advisorSpecConsumer)
顾问
Advisors API 提供了一种灵活而强大的方法来拦截、修改和增强 Spring 应用程序中的 AI 驱动的交互。
使用用户文本调用 AI 模型时,一种常见模式是使用上下文数据附加或增强提示。
此上下文数据可以是不同的类型。常见类型包括:
-
您自己的数据:这是 AI 模型尚未训练的数据。即使模型看到了类似的数据,附加的上下文数据在生成响应时也优先。
-
对话历史记录:聊天模型的 API 是无状态的。如果您告诉 AI 模型您的名字,它将在后续交互中不会记住它。必须随每个请求发送对话历史记录,以确保在生成响应时考虑以前的交互。
ChatClient 中的 Advisor 配置
ChatClient Fluent API 提供了一个AdvisorSpec
用于配置 advisor 的接口。此接口提供了添加参数、一次设置多个参数以及将一个或多个 advisor 添加到链的方法。
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 都以某种方式修改 prompt 或 context,并且一个 advisor 所做的更改将传递给链中的下一个 advisor。 |
ChatClient.builder(chatModel)
.build()
.prompt()
.advisors(
new MessageChatMemoryAdvisor(chatMemory),
new QuestionAnswerAdvisor(vectorStore)
)
.user(userText)
.call()
.content();
在此配置中,MessageChatMemoryAdvisor
将首先执行,并将对话历史记录添加到提示符中。然后,QuestionAnswerAdvisor
将根据用户的问题和添加的对话历史记录执行搜索,从而可能提供更相关的结果。
检索增强一代
请参阅 Retrieval Augmented Generation 指南。
聊天记忆
界面ChatMemory
表示聊天对话历史记录的存储。它提供了向对话添加消息、从对话中检索消息以及清除对话历史记录的方法。
目前有两种实现:InMemoryChatMemory
和CassandraChatMemory
,它们为聊天对话历史记录提供存储,内存和time-to-live
相应。
要创建CassandraChatMemory
跟time-to-live
:
CassandraChatMemory.create(CassandraChatMemoryConfig.builder().withTimeToLive(Duration.ofDays(1)).build());
以下 advisor 实现使用ChatMemory
与带有对话历史记录的提示通知的接口,这些对话历史记录在如何将内存添加到提示的细节上有所不同
-
MessageChatMemoryAdvisor
:检索内存并将其作为消息集合添加到提示符中 -
PromptChatMemoryAdvisor
:检索内存并将其添加到提示的系统文本中。 -
VectorStoreChatMemoryAdvisor
:构造函数VectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConversationId, int chatHistoryWindowSize, int order)
此构造函数允许您:-
指定用于管理和查询文档的 VectorStore 实例。
-
设置在上下文中未提供任何内容时使用的默认对话 ID。
-
根据令牌大小定义聊天历史记录检索的窗口大小。
-
提供用于 Chat Advisor 系统的系统文本建议。
-
设置此 advisor 在链中的优先顺序。
-
这VectorStoreChatMemoryAdvisor.builder()
method 允许您指定默认对话 ID、聊天历史记录窗口大小以及要检索的聊天历史记录的顺序。
示例@Service
使用多个 advisor 的实现如下所示。
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();
}
}
Logging
The SimpleLoggerAdvisor
is an advisor that logs the request
and response
data of the ChatClient
.
This can be useful for debugging and monitoring your AI interactions.
Spring AI supports observability for LLM and vector store interactions. Refer to the Observability guide for more information.
To enable logging, add the SimpleLoggerAdvisor
to the advisor chain when creating your ChatClient.
It’s recommended to add it toward the end of the chain:
ChatResponse response = ChatClient.create(chatModel).prompt()
.advisors(new SimpleLoggerAdvisor())
.user("Tell me a joke?")
.call()
.chatResponse();
To see the logs, set the logging level for the advisor package to DEBUG
:
logging.level.org.springframework.ai.chat.client.advisor=DEBUG
Add this to your application.properties
or application.yaml
file.
You can customize what data from AdvisedRequest
and ChatResponse
is logged by using the following constructor:
SimpleLoggerAdvisor(
Function<AdvisedRequest, String> requestToString,
Function<ChatResponse, String> responseToString
)
Example usage:
SimpleLoggerAdvisor customLogger = new SimpleLoggerAdvisor(
request -> "Custom request: " + request.userText,
response -> "Custom response: " + response.getResult()
);
This allows you to tailor the logged information to your specific needs.
Be cautious about logging sensitive information in production environments.