聊天客户端 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, SearchRequest.defaults())
)
.user(userText)
.call()
.content();
在此配置中,MessageChatMemoryAdvisor
将首先执行,并将对话历史记录添加到提示符中。然后,QuestionAnswerAdvisor
将根据用户的问题和添加的对话历史记录执行搜索,从而可能提供更相关的结果。
检索增强一代
矢量数据库存储 AI 模型不知道的数据。
将用户问题发送到 AI 模型时,QuestionAnswerAdvisor
查询 Vector 数据库中与用户问题相关的文档。
来自向量数据库的响应将附加到用户文本中,以便为 AI 模型生成响应提供上下文。
假设您已经将数据加载到VectorStore
中,您可以通过提供QuestionAnswerAdvisor
到ChatClient
.
ChatResponse response = ChatClient.builder(chatModel)
.build().prompt()
.advisors(new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()))
.user(userText)
.call()
.chatResponse();
在此示例中,SearchRequest.defaults()
将对 Vector Database 中的所有文献执行相似性检索。
要限制搜索的文档类型,SearchRequest
采用可跨所有VectorStores
.
动态筛选表达式
更新SearchRequest
filter 表达式在FILTER_EXPRESSION
advisor context 参数:
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()))
.build();
// Update filter expression at runtime
String content = this.chatClient.prompt()
.user("Please answer my question XYZ")
.advisors(a -> a.param(QuestionAnswerAdvisor.FILTER_EXPRESSION, "type == 'Spring'"))
.call()
.content();
这FILTER_EXPRESSION
parameter 允许您根据提供的表达式动态筛选搜索结果。
聊天记忆
界面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, SearchRequest.defaults()), // 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
这SimpleLoggerAdvisor
是一个 advisor,它会记录request
和response
的数据ChatClient
.
这对于调试和监控 AI 交互非常有用。
Spring AI 支持 LLM 和向量存储交互的可观察性。有关更多信息,请参阅 可观测性指南 。 |
要启用日志记录,请添加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()
);
这允许您根据自己的特定需求定制记录的信息。
在生产环境中记录敏感信息时要小心。 |