Spring AI 第一讲 之 Chat Client API

疼死老夫了 2024-07-05 12:01:02 阅读 59

 Chat Client API

ChatClient 提供了与人工智能模型通信的流畅 API。它同时支持同步和反应编程模型。

流畅的应用程序接口(API)提供了一些方法,用于构建作为输入传递给人工智能模型的 "提示"(Prompt)的各个组成部分。提示包含指导人工智能模型输出和行为的指示文本。从应用程序接口的角度来看,提示由一系列信息组成。

人工智能模型主要处理两类信息:用户信息和系统信息,前者是用户的直接输入,后者则由系统生成,用于引导对话。

这些信息通常包含占位符,在运行时根据用户输入进行替换,以定制人工智能模型对用户输入的响应。

此外,还可以指定提示选项,如要使用的人工智能模型名称和控制生成输出的随机性或创造性的温度设置。

创建聊天客户端

ChatClient 是使用 ChatClient.Builder 对象创建的。您可以为任何 ChatModel Spring Boot 自动配置获取一个自动配置的 ChatClient.Builder 实例,也可以通过编程创建一个。

使用自动配置的 ChatClient.Builder

在最简单的使用案例中,Spring AI 提供了 Spring Boot 自动配置功能,创建了一个 ChatClient.Builder Bean 原型,供您注入到您的类中。下面是一个对简单用户请求检索字符串响应的简单示例。

<code>@RestController

public 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();

}

}

在这个简单的例子中,用户输入设置了用户信息的内容。调用方法向人工智能模型发送请求,而上下文方法则以字符串形式返回人工智能模型的响应。

以编程方式创建聊天客户端

通过设置属性 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 API 提供了几种格式化 AI 模型响应的方法。

返回聊天响应

人工智能模型的响应是一种丰富的结构,由 ChatResponse 类型定义。它包括关于如何生成回复的元数据,也可以包含多个回复,即所谓的 "代",每个 "代 "都有自己的元数据。元数据包括用于创建回复的标记数(每个标记约为一个单词的 3/4)。这一信息非常重要,因为托管人工智能模型是根据每个请求所使用的标记数来收费的。

下面是在 call() 方法后调用 chatResponse() 返回包含元数据的 ChatResponse 对象的示例。

ChatResponse chatResponse = chatClient.prompt()

.user("Tell me a joke")

.call()

.chatResponse();

返回实体

您经常希望返回一个实体类,该实体类由返回的字符串映射而成。实体方法提供了这种功能。

例如,给定 Java record:

record ActorFilms(String actor, List<String> movies) {

}

您可以使用实体方法轻松地将人工智能模型的输出映射到该记录,如下所示:

ActorFilms actorFilms = chatClient.prompt()

.user("Generate the filmography for a random actor.")

.call()

.entity(ActorFilms.class);

还有一个重载的实体方法,其签名为 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>>() {

});

流式响应

流可让您获得异步响应,如下所示(目前市面上都采用此方法:前后端交互【见下期案例】)

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() return values

在指定 ChatClient 的调用方法后,有几种不同的响应类型可供选择。

字符串 content():返回响应的字符串内容ChatResponse chatResponse():返回 ChatResponse 对象,该对象包含多代内容和有关响应的元数据,例如创建响应时使用了多少标记。实体返回 Java 类型

entity(ParameterizedTypeReference<T> type):用于返回实体类型集合。entity(Class<T> type):用于返回特定的实体类型。entity(StructuredOutputConverter<T>结构化输出转换器):用于指定结构化输出转换器的实例,以将字符串转换为实体类型。

您还可以调用流方法,而不是调用和

stream() return values

在 ChatClient 上指定流方法后,有几个响应类型选项:

Flux<String> content(): 返回人工智能模型生成的字符串的 Flux。Flux<ChatResponse> chatResponse():返回人工智能模型生成的字符串的 Flux: 返回 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): 名称用于在用户文本中引用函数。描述解释了函数的用途,并帮助人工智能模型选择正确的函数以做出准确的响应。函数参数是一个 Java 函数实例,模型将在必要时执行该函数。defaultFunctions(String... functionNames): 应用程序上下文中定义的 `java.util.Function`s 的 bean 名称。defaultUser(String text)、defaultUser(Resource text)、defaultUser(Consumer<UserSpec> userSpecConsumer): 通过这些方法可以定义用户文本。Consumer<UserSpec> 允许您使用 lambda 来指定用户文本和任何默认参数。defaultAdvisors(RequestResponseAdvisor...advisor): 顾问允许修改用于创建提示的数据。QuestionAnswerAdvisor 实现通过在提示中附加与用户文本相关的上下文信息来启用检索增强生成模式。defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer): 该方法允许您定义一个消费者,以便使用 AdvisorSpec 配置多个顾问。顾问可以修改用于创建最终提示的数据。Consumer<AdvisorSpec> 可让您指定一个 lambda 来添加顾问,例如 QuestionAnswerAdvisor,它支持检索增强生成(Retrieval Augmented Generation),可根据用户文本在提示中附加相关上下文信息。

您可以在运行时使用不带默认前缀的相应方法覆盖这些默认值。

options(聊天选项 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)advisor(RequestResponseAdvisor... advisor)advisor(Consumer<AdvisorSpec> advisorSpecConsumer)

Advisors

在使用用户文本调用人工智能模型时,一种常见的模式是使用上下文数据附加或增强提示。

这种上下文数据可以是不同类型的。常见类型包括

你自己的数据: 这是人工智能模型尚未训练过的数据。即使模型看过类似的数据,在生成回复时也会优先使用附加的上下文数据。对话历史: 聊天模型的 API 是无状态的。如果您告诉人工智能模型您的名字,它不会在随后的交互中记住您的名字。必须在每次请求时发送对话历史记录,以确保在生成响应时考虑到之前的交互。

检索增强生成

向量数据库存储了人工智能模型不知道的数据。当用户向人工智能模型发送问题时,QuestionAnswerAdvisor 会查询向量数据库中与用户问题相关的文档。

向量数据库中的回复会附加到用户文本中,为人工智能模型生成回复提供上下文。

假设您已经将数据加载到向量存储中,您可以通过向聊天客户端提供一个 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 实现示例如下所示

@Service

public class CustomerSupportAssistant {

private final ChatClient chatClient;

public CustomerSupportAssistant(ChatClient.Builder builder, VectorStore vectorStore, ChatMemory chatMemory) {

this.chatClient = builderh

.defaultSystem("""

您是一家名为 "Funnair "的航空公司的客户聊天支持人员、

乐于助人。

在提供预订信息或取消预订之前,您必须始终

从用户处获取以下信息:预订号、客户名和姓。

在更改预订之前,必须确保条款允许更改预订。

如果更改需要收费,则必须在征得用户同意后方可进行。

""")

.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();

}

}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。