上一课:Spring AI API介绍
ChatClient,它提供了一个流畅的 API,用于与 AI 模型进行通信。 它支持同步编程模型和响应式编程模型。
Fluent API 具有用于构建包含提示语组成部分的方法,这些提示语作为输入传递给 AI 模型。 包含用于指导 AI 模型的输出和行为的说明文本。从 API 的角度来看,提示语由消息集合组成。
AI 模型处理两种主要类型的消息:用户消息(来自用户的直接输入)和系统消息(由系统生成以引导对话)。
这些消息通常包含占位符,这些占位符在运行时根据用户输入进行替换,以自定义 AI 模型对用户输入的响应。
还可以指定提示选项,例如要使用的 AI 模型的名称以及控制生成输出的随机性或创造力的温度设置。
ChatClient源码分析请移步到此处Spring AI之ChatClient源码分析
创建 ChatClient
您可以获取任何 ChatModel Spring Boot 自动配置的自动配置实例,也可以以编程方式创建一个实例。
ChatClientChatClient.Builder
使用自动配置的 ChatClient.Builder
在最简单的用例中,Spring AI 提供 Spring Boot 自动配置,创建一个原型 Bean 供您注入类中。 下面是检索对简单用户请求的 String 响应。
ChatClient.Builder
@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 方法以 String 形式返回 AI 模型的响应。
以编程方式创建 ChatClient
您可以通过设置属性来禁用自动配置。 如果同时使用多个聊天模型,这将非常有用。 然后以编程方式为每个创建一个实例:ChatClient.Builderspring.ai.chat.client.enabled=falseChatClient.BuilderChatModel
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 类型定义的丰富结构。 它包括有关如何生成响应的元数据,还可以包含多个响应(称为第 s 代),每个响应都有自己的元数据。 元数据包括用于创建响应的标记数(每个标记大约为一个单词的 3/4)。 此信息很重要,因为托管 AI 模型根据每个请求使用的令牌数量收费。
下面通过调用该方法来返回包含元数据的对象的示例。ChatResponsechatResponse()call()
ChatResponse chatResponse = chatClient.prompt() .user("Tell me a joke") .call() .chatResponse();
返回实体
您通常希望返回从返回的实体类映射的实体类。 该方法提供此功能。Stringentity
例如,给定 Java 记录:
record ActorFilms(String actor, List<String> movies) { }
您可以使用以下方法轻松地将 AI 模型的输出映射到此记录,如下所示:entity
ActorFilms actorFilms = chatClient.prompt() .user("Generate the filmography for a random actor.") .call() .entity(ActorFilms.class);
还有一个带有签名的重载方法,可用于指定泛型列表等类型:entityentity(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();
您还可以使用方法流式传输 。ChatResponseFlux<ChatResponse> chatResponse()
在 1.0.0 M2 中,我们将提供一种方便的方法,让您使用响应式方法返回 Java 实体。 同时,应使用结构化输出转换器来转换聚合响应显式,如下所示。 这也演示了 Fluent API 中参数的使用,这将在本文档的后面部分中更详细地讨论。stream()
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() 返回值
指定方法后,响应类型有几个不同的选项。callChatClient
String content():返回响应的 String 内容
ChatResponse chatResponse():返回包含多代的对象以及有关响应的元数据,例如,用于创建响应的令牌数。ChatResponse
entity返回 Java 类型
entity(ParameterizedTypeReference<T> type):用于返回实体类型的集合。
entity(Class<T> type):用于返回特定的实体类型。
entity(StructuredOutputConverter<T> structuredOutputConverter):用于指定 a 的实例以将 a 转换为实体类型。StructuredOutputConverterString
您还可以调用该方法,而不是 和streamcall
stream() 返回值
指定 on 的方法后,响应类型有几个选项:streamChatClient
Flux<String> content():返回 AI 模型生成的字符串的 Flux。
Flux<ChatResponse> chatResponse():返回对象的 Flux,其中包含有关响应的其他元数据。ChatResponse
使用默认值
在类中使用默认系统文本创建 ChatClient 可简化运行时代码。 通过设置默认值,您只需要在调用时指定用户文本,而无需为运行时代码路径中的每个请求设置系统文本。@ConfigurationChatClient
默认系统文本
在以下示例中,我们将系统文本配置为始终以盗版者的声音回复。 为了避免在运行时代码中重复系统文本,我们将在类中创建一个实例。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(); } }
和 an 来调用它@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):传入类中定义的可移植选项或特定于模型的选项,例如 中的选项。有关特定于模型的实现的更多信息,请参阅 JavaDocs。ChatOptionsOpenAiChatOptionsChatOptions
defaultFunction(String name, String description, java.util.function.Function<I, O> function):用于在用户文本中引用函数。它解释了函数的用途,并帮助 AI 模型选择正确的函数以获得准确的响应。该参数是模型在必要时执行的 Java 函数实例。namedescriptionfunction
defaultFunctions(String… functionNames):在应用程序上下文中定义的 'java.util.Function' 的 Bean 名称。
defaultUser(String text)、 、 : 这些方法允许您定义用户文本。这允许您使用 lambda 来指定用户文本和任何默认参数。defaultUser(Resource text)defaultUser(Consumer<UserSpec> userSpecConsumer)Consumer<UserSpec>
defaultAdvisors(RequestResponseAdvisor… advisor):顾问程序允许修改用于创建 .该实现通过在提示后附加与用户文本相关的上下文信息来实现模式。PromptQuestionAnswerAdvisorRetrieval Augmented Generation
defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer):此方法允许您定义一个以使用 .顾问可以修改用于创建最终 .它允许您指定一个 lambda 来添加顾问,例如 ,它通过根据用户文本在提示后附加相关上下文信息来支持。ConsumerAdvisorSpecPromptConsumer<AdvisorSpec>QuestionAnswerAdvisorRetrieval Augmented Generation
您可以在运行时使用不带前缀的相应方法覆盖这些默认值。default
options(ChatOptions chatOptions)
function(String name, String description, java.util.function.Function<I, O> function)
'functions(字符串...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 模型生成响应提供上下文。
假设您已经将数据加载到 ,您可以通过提供 的实例来执行检索增强生成 (RAG)。VectorStoreQuestionAnswerAdvisorChatClient
ChatResponse response = ChatClient.builder(chatModel) .build().prompt() .advisors(new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults())) .user(userText) .call() .chatResponse();
在此示例中,将对矢量数据库中的所有文档执行相似性搜索。 为了限制搜索的文档类型,需要一个类似 SQL 的过滤器表达式,该表达式可移植到所有 .SearchRequest.defaults()SearchRequestVectorStores
聊天记忆
该接口表示聊天对话历史记录的存储。它提供了将消息添加到 * 对话,从对话中检索消息,并清除对话历史记录。ChatMemory
有一种实现为聊天对话历史记录提供内存中存储。InMemoryChatMemory
两个顾问实现使用该接口向提示提供对话历史记录的建议,它们在如何将内存添加到提示的详细信息上有所不同ChatMemory
MessageChatMemoryAdvisor:检索内存作为消息集合添加到提示符中
PromptChatMemoryAdvisor:检索到的内存将添加到提示的系统文本中。
VectorStoreChatMemoryAdvisor:构造函数 'VectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConversationId, int chatHistoryWindowSize)' 允许您指定要从中检索聊天记录的 VectorStore、unqiue 会话 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();
}
}