SpringAi学习之ChatClient API
写在前面
本篇开始学习学习Spring Ai提供的Chat Client Api,通过它可以很轻松的与大模型进行交互。
ChatClient API
**ChatClient**为与AI模型进行通信,提供了一个流畅的API。它同时支持同步和流式编程模型。
流畅的API具有构建提示词组成部分的方法,该提示词会作为输入传递给AI模型。Prompt包含用于指导AI模型输出和行为的指令性文本。从API的角度来看,提示词由一系列消息组成。
AI模型处理两种主要类型的消息:
- 用户消息,即用户的直接输入。
- 系统消息,由系统生成以指导对话。
这些消息通常包含占位符,这些占位符在运行时会根据用户输入进行替换,以自定义AI模型对用户输入的响应。
还可以指定一些提示选项,如:
- AI模型的名称,即要使用的AI模型的名称。
- 温度设置,控制生成输出的随机性或创造性。
这些功能使得ChatClient成为一个强大的工具,允许开发者以灵活的方式与AI模型进行交互,并通过定制化的提示和消息来优化AI模型的响应。
创建ChatClient
ChatClient是使用ChatClient.Builder对象创建的。你可以为任何ChatModel Spring Boot自动配置获取自动配置的ChatClient.Builder 实例,或者通过编程方式创建一个。
使用自动配置的ChatClient.Builder
Spring AI提供Spring Boot自动配置,会创建一个原型<font style="color:rgb(25, 30, 30);">ChatClient.Builder</font>bean,供开发者注入到自己的类中。
以下是一个简单的示例,用于检索对简单用户请求的字符串响应。新建一个名为controller的包,并在里面定义一个名为MyController的类:
1 | @RestController |
运行项目,访问对应地址,返回信息如下:

用户传入了消息,之后调用call()方法向 AI 模型发送请求,content()方法将AI模型的响应作为字符串返回。
手动创建ChatClient
默认情况下,Spring AI会自动配置一个ChatClient.Builder bean。不过,当你的应用程序中需要使用多个聊天模型时,就需要手动创建ChatClient。首先必须通过设置属性spring.ai.chat.client.enabled=false来禁用ChatClient.Builder的自动配置,之后才允许开发者手动创建多个ChatClient实例。
单一模型类型的多个****ChatClient
一个常见的使用场景,比如开发者需要创建多个ChatClient实例,但这些实例都使用相同的基础模型类型,只是配置不同:
1 | ChatModel myChatModel = ... // 通常通过自动装配获得 |
不同模型类型的****ChatClient
当你需要使用多个AI模型时,可以为每个模型定义单独的<font style="color:rgb(25, 30, 30);">ChatClient</font> bean:
1 | import org.springframework.ai.chat.ChatClient; |
之后开发者就可以使用@Qualifier注解将这些bean注入到自己的应用组件中:
1 | @Configuration |
多个兼容OpenAI的API端点
OpenAiApi和OpenAiChatModel类提供了一个mutate()方法,该方法允许开发者创建具有不同属性的现有实例的变体。当需要使用多个与OpenAI兼容的API时,这一点特别有用:
1 | @Service |
ChatClient流畅API(ChatClient Fluent API)
<font style="color:rgb(25, 30, 30);">ChatClient</font>流畅 PI允许开发者通过重载的<font style="color:rgb(25, 30, 30);">prompt</font>方法来启动流畅 API,从而以三种不同的方式创建提示词:
<font style="color:rgb(25, 30, 30);">prompt()</font>:此无参数方法可让您开始使用流式 API,从而构建提示词的用户部分、系统部分和其他部分。<font style="color:rgb(25, 30, 30);">prompt(Prompt prompt)</font>:此方法接受一个<font style="color:rgb(25, 30, 30);">Prompt</font>参数,允许你传入一个使用Prompt的非流式API创建的<font style="color:rgb(25, 30, 30);">Prompt</font>实例。<font style="color:rgb(25, 30, 30);">prompt(String content)</font>:这是一个与之前的重载类似的便捷方法。它接收用户的文本内容。
ChatClient响应
ChatClient API 提供了多种使用流式API 来格式化 AI 模型响应的方法。
返回ChatResponse
AI模型的响应是一种由ChatResponse类型定义的丰富结构。它包含关于响应生成方式的元数据,还可以包含多个响应(称为Generation),每个响应都有自己的元数据。元数据包括用于生成响应的令牌数量(每个令牌大约相当于一个单词的3/4)。这一信息很重要,因为托管的人工智能模型会根据每个请求所使用的令牌数量来收费。
下面展示了一个在调用call()方法后,通过调用chatResponse()来返回包含元数据的ChatResponse对象的示例:
1 | ChatResponse chatResponse = chatClient.prompt() |
返回实体
开发者如果希望返回一个从返回的String映射而来的实体类。entity()方法就提供了这一功能。
举个例子,给定以下Java记录:
1 | record ActorFilms(String actor, List<String> movies) {} |
开发者可以使用entity()方法轻松地将AI模型的输出映射到此记录,如下所示:
1 | ActorFilms actorFilms = chatClient.prompt() |
还有一个重载的entity方法,其签名为entity(ParameterizedTypeReference<T> type),可让你指定诸如泛型列表之类的类型:
1 | List<ActorFilms> actorFilms = chatClient.prompt() |
流式响应
<font style="color:rgb(25, 30, 30);">strea</font>m()方法可让你获取异步响应,如下所示:
1 | Flux<String> output = chatClient.prompt() |
您也可以使用<font style="color:rgb(25, 30, 30);">Flux<ChatResponse> chatResponse()</font>方法来流式传输<font style="color:rgb(25, 30, 30);">ChatResponse</font>。
未来,我们将提供一种便捷方法,让您能够使用响应式的<font style="color:rgb(25, 30, 30);">stream()</font>方法返回Java实体。在此期间,您应该像下面所示的那样,使用结构化输出转换器来显式转换聚合响应。如下所示。这也展示了流畅 API 中参数的使用,我们将在文档的后续部分详细讨论。
1 | var converter = new BeanOutputConverter<>( |
提示模板(Prompt Templates)
ChatClient的流畅API允许开发者将用户和系统文本作为带有变量的模板提供,这些变量会在运行时被替换:
1 | String answer = ChatClient.create(chatModel).prompt() |
在内部,ChatClient 使用PromptTemplate类来处理用户和系统文本,并依靠给定的TemplateRenderer实现,在运行时用提供的值替换变量。默认情况下,Spring AI 使用StTemplateRenderer实现,该实现基于 Terence Parr 开发的开源StringTemplate引擎。
Spring AI 还提供了一个 NoOpTemplateRenderer,用于不需要模板处理的情况。
直接在<font style="color:rgb(25, 30, 30);">ChatClient</font>上配置(通过<font style="color:rgb(25, 30, 30);">.templateRenderer()</font>)的<font style="color:rgb(25, 30, 30);">TemplateRenderer</font>仅适用于在<font style="color:rgb(25, 30, 30);">ChatClient</font>构建器链中直接定义的提示内容(例如,通过<font style="color:rgb(25, 30, 30);">.user()</font>、<font style="color:rgb(25, 30, 30);">.system()</font>)。它不会影响Advisors(如<font style="color:rgb(25, 30, 30);">QuestionAnswerAdvisor</font>)内部使用的模板,这些模板有自己的模板自定义机制(参见 Custom Advisor Templates)。 |
|---|
如果您更愿意使用不同的模板引擎,可以直接向ChatClient提供<font style="color:rgb(25, 30, 30);">TemplateRenderer</font>接口的自定义实现。您也可以继续使用默认的<font style="color:rgb(25, 30, 30);">StTemplateRenderer</font>,但需采用自定义配置。
例如,默认情况下,模板变量通过{}语法来标识。如果您计划在提示词中包含JSON,可能需要使用不同的语法以避免与JSON语法冲突。比如使用<和>分隔符。
1 | String answer = ChatClient.create(chatModel).prompt() |
Call()返回值
在<font style="color:rgb(25, 30, 30);">ChatClient</font>上指定<font style="color:rgb(25, 30, 30);">call()</font>方法后,响应类型有几种不同的选项:
<font style="color:rgb(25, 30, 30);">String content()</font>:返回响应的字符串内容。<font style="color:rgb(25, 30, 30);">ChatResponse chatResponse()</font>:返回包含多个生成内容以及响应元数据(例如创建响应所用的令牌数量)的<font style="color:rgb(25, 30, 30);">ChatResponse</font>对象。<font style="color:rgb(25, 30, 30);">ChatClientResponse chatClientResponse()</font>:返回一个<font style="color:rgb(25, 30, 30);">ChatClientResponse</font>对象,该对象包含<font style="color:rgb(25, 30, 30);">ChatResponse</font>对象和ChatClient的执行上下文,使您能够访问顾问执行期间使用的额外数据(例如,在RAG流程中检索到的相关文档)。<font style="color:rgb(25, 30, 30);">ResponseEntity<?> responseEntity()</font>:返回一个包含完整HTTP响应的<font style="color:rgb(25, 30, 30);">ResponseEntity</font>,包括状态码、头部和正文。当你需要访问响应的低级HTTP细节时,这很有用。<font style="color:rgb(25, 30, 30);">entity()</font>返回一个Java类型。<font style="color:rgb(25, 30, 30);">entity(ParameterizedTypeReference<T> type)</font>:用于返回实体类型的<font style="color:rgb(25, 30, 30);">Collection</font>。<font style="color:rgb(25, 30, 30);">entity(Class<T> type)</font>:用于返回特定的实体类型。<font style="color:rgb(25, 30, 30);">entity(StructuredOutputConverter<T> structuredOutputConverter)</font>:用于指定一个<font style="color:rgb(25, 30, 30);">StructuredOutputConverter</font>实例,将<font style="color:rgb(25, 30, 30);">String</font>转换为实体类型。
开发者也可以调用<font style="color:rgb(25, 30, 30);">stream()</font>方法来替代<font style="color:rgb(25, 30, 30);">call()</font>方法。
调用
call()方法并不会实际触发AI模型的执行。相反,它只是指示Spring AI是使用同步调用还是流式调用。实际的AI模型调用会在调用content()、chatResponse()和responseEntity()等方法时发生。
stream()返回值
在<font style="color:rgb(25, 30, 30);">ChatClient</font>上指定<font style="color:rgb(25, 30, 30);">stream()</font>方法后,响应类型有几个选项:
<font style="color:rgb(25, 30, 30);">Flux<String> content()</font>:返回由AI模型生成的字符串的<font style="color:rgb(25, 30, 30);">Flux</font>。<font style="color:rgb(25, 30, 30);">Flux<ChatResponse> chatResponse()</font>:返回<font style="color:rgb(25, 30, 30);">Flux</font>类型的<font style="color:rgb(25, 30, 30);">ChatResponse</font>对象,该对象包含有关响应的附加元数据。<font style="color:rgb(25, 30, 30);">Flux<ChatClientResponse> chatClientResponse()</font>:返回一个<font style="color:rgb(25, 30, 30);">Flux</font>类型的<font style="color:rgb(25, 30, 30);">ChatClientResponse</font>对象,该对象包含<font style="color:rgb(25, 30, 30);">ChatResponse</font>对象和ChatClient的执行上下文,使你能够访问在顾问执行过程中使用的额外数据(例如,在RAG流程中检索到的相关文档)。
使用默认值(using defaults)
在@Configuration类中创建带有默认系统文本的ChatClient,可以简化运行时代码。通过设置默认值,调用ChatClient时只需指定用户文本,无需在运行时代码路径中为每个请求设置系统文本。
默认系统文本
在下面的示例中,我们将把系统文本配置为始终以海盗的语气回复。为避免在运行时代码中重复系统文本,我们将在一个@Configuration类中创建一个ChatClient实例。
1 | @Configuration |
一个@RestController来调用它:
1 | @RestController |
通过curl调用应用程序端点时,结果如下:
1 | ❯ curl localhost:8080/ai/simple |
带参数的默认系统文本
在下面的示例中,我们将在系统文本中使用占位符,以在运行时而非设计时指定补全内容的语气:
1 | @Configuration |
1 | @RestController |
使用httpie调用应用程序端点时,结果如下:
1 | http localhost:8080/ai voice=='海盗' |
其他默认值
在<font style="color:rgb(25, 30, 30);">ChatClient.Builder</font>级别,您可以指定默认的提示配置。
<font style="color:rgb(25, 30, 30);">defaultOptions(ChatOptions chatOptions)</font>:传入ChatOptions类中定义的可移植选项,或特定于模型的选项(如OpenAiChatOptions中的选项)。<font style="color:rgb(25, 30, 30);">defaultFunction(String name, String description, java.util.function.Function<I, O> function)</font>:<font style="color:rgb(25, 30, 30);">name</font>用于在用户文本中引用该函数。<font style="color:rgb(25, 30, 30);">description</font>解释函数的用途,并帮助AI模型选择正确的函数以获得准确的响应。<font style="color:rgb(25, 30, 30);">function</font>参数是模型在必要时将执行的Java函数实例。<font style="color:rgb(25, 30, 30);">defaultFunctions(String… functionNames)</font>:在应用上下文中定义的java.util.Function的Bean名称。<font style="color:rgb(25, 30, 30);">defaultUser(String text)</font>、<font style="color:rgb(25, 30, 30);">defaultUser(Resource text)</font>、<font style="color:rgb(25, 30, 30);">defaultUser(Consumer<UserSpec> userSpecConsumer)</font>:这些方法允许你定义用户文本。<font style="color:rgb(25, 30, 30);">Consumer<UserSpec></font>支持你使用lambda来指定用户文本和任何默认参数。<font style="color:rgb(25, 30, 30);">defaultAdvisors(Advisor… advisor)</font>:Advisor程序允许修改用于创建<font style="color:rgb(25, 30, 30);">Prompt</font>的数据。<font style="color:rgb(25, 30, 30);">QuestionAnswerAdvisor</font>实现通过在提示词后附加与用户文本相关的上下文信息,支持<font style="color:rgb(25, 30, 30);">Retrieval Augmented Generation</font>模式。<font style="color:rgb(25, 30, 30);">defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer)</font>:此方法允许您定义一个<font style="color:rgb(25, 30, 30);">Consumer</font>,以使用<font style="color:rgb(25, 30, 30);">AdvisorSpec</font>配置多个Advisor。Advisor可以修改用于创建最终<font style="color:rgb(25, 30, 30);">Prompt</font>的数据。<font style="color:rgb(25, 30, 30);">Consumer<AdvisorSpec></font>允许您指定一个lambda来添加Advisor,例如QuestionAnswerAdvisor,它通过根据用户文本附加相关上下文信息来支持<font style="color:rgb(25, 30, 30);">Retrieval Augmented Generation</font>。
你也可以在运行时使用相应的方法(不带<font style="color:rgb(25, 30, 30);">default</font>前缀)来覆盖这些默认值:
<font style="color:rgb(25, 30, 30);">options(ChatOptions chatOptions)</font><font style="color:rgb(25, 30, 30);">function(String name, String description, java.util.function.Function<I, O> function)</font><font style="color:rgb(25, 30, 30);">functions(String… functionNames)</font><font style="color:rgb(25, 30, 30);">user(String text)</font>,<font style="color:rgb(25, 30, 30);">user(Resource text)</font>,<font style="color:rgb(25, 30, 30);">user(Consumer<UserSpec> userSpecConsumer)</font><font style="color:rgb(25, 30, 30);">advisors(Advisor… advisor)</font><font style="color:rgb(25, 30, 30);">advisors(Consumer<AdvisorSpec> advisorSpecConsumer)</font>
顾问(Advisors)
Advisors API 提供了一种灵活而强大的方式,用于拦截、修改和增强Spring应用程序中的 AI 驱动交互。
在使用用户文本调用人工智能模型时,一种常见模式是在提示词中添加或补充上下文数据。
这些上下文数据可以有不同的类型。常见类型包括:
- 您自己的数据:这是AI模型未经过训练的数据。即便模型见过类似的数据,附加的上下文数据在生成响应时也具有优先性。
- 对话历史记录:聊天模型的API是无状态的。如果你告诉AI模型你的名字,它在后续交互中不会记住这个名字。每次请求都必须发送对话历史,以确保生成响应时会考虑之前的交互。
ChatClient中的顾问配置
ChatClient流畅API 提供了一个 AdvisorSpec 接口,用于配置顾问。该接口提供了添加参数、一次性设置多个参数以及向链中添加一个或多个顾问的方法。
1 | interface AdvisorSpec { |
向链中添加顾问的顺序至关重要,因为它决定了顾问的执行顺序。每个顾问以某种方式修改提示或上下文,一个顾问所做的更改会传递给链中的下一个。
1 | ChatClient.builder(chatModel) |
在上述配置下,MessageChatMemoryAdvisor将首先执行,把对话历史添加到提示词中。然后,QuestionAnswerAdvisor会根据用户的问题和添加的对话历史进行搜索,有可能提供更相关的结果。
检索增强生成
向量数据库存储了 AI 模型不知道的数据。当用户问题发送到 AI 模型时,QuestionAnswerAdvisor 会为与用户问题相关的文档查询向量数据库。向量数据库的响应被追加到用户文本中,为 AI 模型生成响应提供上下文。
假设您已经将数据加载到 VectorStore 中,您可以通过向ChatClient提供QuestionAnswerAdvisor实例来执行检索增强生成(RAG):
1 | ChatResponse response = ChatClient.builder(chatModel) |
在此示例中,SearchRequest.defaults() 将在 Vector Database 中对所有文档执行相似性搜索。要限制搜索的文档类型,SearchRequest 接受一个 SQL 样式的过滤表达式,该表达式在所有 VectorStores 中都是可移植的。
动态过滤表达式
使用 FILTER_EXPRESSION 顾问上下文参数在运行时更新 SearchRequest 过滤表达式:
1 | ChatClient chatClient = ChatClient.builder(chatModel) |
FILTER_EXPRESSION 参数允许开发者根据提供的表达式动态过滤搜索结果。
日志记录
SimpleLoggerAdvisor是一个记录器顾问,它会记录ChatClient的request和response数据。这对于调试和监控开发者的人工智能交互很有用。
Spring AI支持对大语言模型和向量存储交互的可观测性。
要启用日志记录,需要在创建ChatClient时将SimpleLoggerAdvisor添加到顾问链中。建议将其添加到链的末尾:
1 | ChatResponse response = ChatClient.create(chatModel).prompt() |
要查看日志,请将advisor包的日志级别设置为<font style="color:rgb(25, 30, 30);">DEBUG</font>:
1 | logging.level.org.springframework.ai.chat.client.advisor=DEBUG |
将此添加到您的application.properties或application.yaml文件中。
开发者可以使用以下构造函数来自定义要记录的来自AdvisedRequest和ChatResponse的数据:
1 | SimpleLoggerAdvisor( |
举个例子,如下所示:
1 | SimpleLoggerAdvisor customLogger = new SimpleLoggerAdvisor( |
这样开发者就能够根据自己的特定需求定制记录的信息。
在生产环境中记录敏感信息时要谨慎。
聊天记忆(ChatMemory)
<font style="color:rgb(25, 30, 30);">ChatMemory</font>接口代表聊天对话记忆的存储。它提供了向对话添加消息、从对话检索消息以及清除对话历史的方法。
目前有一个内置实现:<font style="color:rgb(25, 30, 30);">MessageWindowChatMemory</font>。<font style="color:rgb(25, 30, 30);">MessageWindowChatMemory</font>是一种聊天记忆实现方式,它会维持一个消息窗口,消息数量最多为指定的最大规模(默认值:20条消息)。当消息数量超过这一限制时,较早的消息会被移除,但系统消息会被保留。如果添加了新的系统消息,所有之前的系统消息都会从记忆中删除。这确保了对话中始终能获取最新的上下文,同时控制了记忆的使用量。
<font style="color:rgb(25, 30, 30);">MessageWindowChatMemory</font>由<font style="color:rgb(25, 30, 30);">ChatMemoryRepository</font>抽象提供支持,该抽象为聊天对话记忆提供了存储实现。目前有多种可用的实现,包括<font style="color:rgb(25, 30, 30);">InMemoryChatMemoryRepository</font>、<font style="color:rgb(25, 30, 30);">JdbcChatMemoryRepository</font>、<font style="color:rgb(25, 30, 30);">CassandraChatMemoryRepository</font> 和 <font style="color:rgb(25, 30, 30);">Neo4jChatMemoryRepository</font>。
实现说明
在<font style="color:rgb(25, 30, 30);">ChatClient</font>中,命令式编程模型和响应式编程模型的结合使用是该API的一个独特之处。通常,一个应用程序要么采用响应式编程,要么采用命令式编程,而不会两者兼而有之。
- 在自定义模型实现的HTTP客户端交互时,必须同时配置RestClient和WebClient。
由于Spring Boot3.4中存在一个漏洞,必须设置“spring.http.client.factory=jdk”属性。否则,该属性会默认设为“reactor”,这会破坏某些AI工作流,如ImageModel。
- 流处理仅通过响应式栈支持。因此,命令式应用程序必须包含响应式栈(例如,spring-boot-starter-webflux)。
- 非流式处理仅通过Servlet栈提供支持。因此,响应式应用程序必须包含Servlet栈(例如spring-boot-starter-web),并需接受一些调用会是阻塞式的。
- 工具调用是必要的,但会导致工作流阻塞。这还会造成Micrometer观测的部分中断(例如,ChatClient跨度和工具调用跨度未连接,因此前者仍不完整)。
- 内置的顾问对标准调用执行阻塞操作,对流式调用执行非阻塞操作。可通过每个顾问类上的构建器配置用于顾问流式调用的Reactor调度器。
