简介
本文旨在记录学习和实践 Spring AI Alibaba 提供的 ChatClient
组件的过程。ChatClient
是 Spring AI 中用于与大语言模型(LLM)进行交互的高级 API,它通过流畅(Fluent)的编程接口,极大地简化了构建聊天应用程序的复杂度。相比直接使用底层的 ChatModel
,ChatClient
封装了提示词构建、响应处理、结构化输出、流式响应以及与 RAG、聊天记忆等高级功能的集成。
通过本文的学习,我们将掌握:
ChatClient
的核心概念和优势。如何创建和配置 ChatClient
实例。使用 ChatClient
处理不同类型的 AI 响应(文本、完整响应对象、结构化实体、流式响应)。结合 Spring Boot 快速搭建一个可交互的聊天后端服务。了解 Server-Sent Events (SSE) 在流式响应中的应用。我们将从官方文档入手,结合代码实践,逐步深入理解 ChatClient
的使用方法。
ChatClient相关原理
核心知识点:ChatClient
in Spring AI Alibaba
ChatClient
是 Spring AI Alibaba 提供的一个更高级别的 API,用于与 AI 模型进行交互。它旨在简化开发流程,特别是当应用程序需要组合多个组件(如提示词模板、聊天记忆、模型本身、输出解析器、RAG 组件等)时。
1. ChatClient
简介
- 目的: 提供一个 Fluent API (流畅 API) 与 AI 模型通信,支持同步和反应式 (Reactive) 编程模型。优势:
- 隐藏复杂性: 将与 LLM (Large Language Model) 及其他组件(提示词模板、聊天记忆、RAG 等)交互的复杂性封装起来。减少样板代码: 相比直接使用
ChatModel
、Message
等原子 API,ChatClient
减少了需要编写的重复性代码。类似服务层: 在应用程序中扮演类似服务层 (Service Layer) 的角色,直接为应用提供 AI 服务。快速组装交互流程: 使用 Fluent API 可以快速地组装一个完整的 AI 交互流程。- 定制和组装模型的输入 (
Prompt
)。格式化解析模型的输出 (结构化输出 Structured Output
)。调整模型交互参数 (ChatOptions
)。- 聊天记忆 (
Chat Memory
)。工具/函数调用 (Function Calling
)。检索增强生成 (RAG
)。2. 创建 ChatClient
创建 ChatClient
实例需要使用 ChatClient.Builder
对象。有两种主要方式获取 ChatClient
:
- 方式一:使用自动配置的
ChatClient.Builder
(推荐)- Spring Boot 会根据你的依赖和配置自动创建一个默认的
ChatClient.Builder
Bean。你只需要在你的类中注入这个 ChatClient.Builder
,然后调用 build()
方法即可获得 ChatClient
实例。示例代码 (带详细注释):import org.springframework.ai.chat.client.ChatClient; // 导入 ChatClient 类import org.springframework.web.bind.annotation.GetMapping; // 导入 GetMapping 注解,用于处理 HTTP GET 请求import org.springframework.web.bind.annotation.RequestParam; // 导入 RequestParam 注解,用于获取请求参数import org.springframework.web.bind.annotation.RestController; // 导入 RestController 注解,标识这是一个 RESTful 控制器@RestController // 声明这是一个 Spring MVC 的 REST 控制器,其方法默认返回 JSON 或其他指定格式的数据public class ChatController { // 声明一个 final 的 ChatClient 成员变量,用于与 AI 模型交互 private final ChatClient chatClient; // 控制器的构造函数 // Spring Boot 会自动查找并注入一个 ChatClient.Builder 类型的 Bean public ChatController(ChatClient.Builder builder) { // 使用注入的 builder 构建 ChatClient 实例,并赋值给成员变量 // builder 会使用自动配置好的底层 ChatModel (例如通义千问模型) 和其他默认设置 this.chatClient = builder.build(); } @GetMapping("/chat") // 将 HTTP GET 请求映射到 /chat 路径 // @RequestParam("input") String input 表示从请求参数中获取名为 "input" 的值,并赋给 input 变量 public String chat(@RequestParam("input") String input) { // 使用 chatClient 的 Fluent API 开始构建一个请求 return this.chatClient.prompt() // 1. 调用 prompt() 方法开始构建一个 Prompt (提示) .user(input) // 2. 调用 user() 方法设置用户角色的消息内容,内容为传入的 input 字符串 .call() // 3. 调用 call() 方法,执行与 AI 模型的同步调用 (发送请求并等待响应) .content(); // 4. 调用 content() 方法,从 AI 模型的响应 (ChatResponse) 中提取文本内容并返回 }}
- 方式二:以编程方式创建
ChatClient
- 禁用自动配置: 如果你想完全控制
ChatClient
的创建过程,或者需要使用多个不同的 ChatModel
实例,可以先禁用 ChatClient.Builder
的自动配置。在 application.properties
或 application.yml
中设置:spring.ai.chat.client.enabled=false
手动创建: 然后,你可以手动创建 ChatClient.Builder
,并传入你想要使用的 ChatModel
实例。示例代码 (带详细注释):import org.springframework.ai.chat.client.ChatClient; // 导入 ChatClient 类import org.springframework.ai.chat.model.ChatModel; // 导入 ChatModel 接口// ... 其他 importpublic class MyService { private final ChatClient customChatClient; // 假设 myChatModel 是你通过其他方式配置或注入的特定 ChatModel 实例 public MyService(ChatModel myChatModel) { // 方法一:使用 ChatClient.builder() 静态方法,并传入 ChatModel 实例 ChatClient.Builder builder = ChatClient.builder(myChatModel); // 这里可以继续使用 builder 配置其他选项,例如 .defaultOptions(), .defaultSystem(), etc. this.customChatClient = builder.build(); // 构建 ChatClient 实例 // 方法二:使用 ChatClient.create() 静态方法,这是一个更简洁的方式 // 它会使用传入的 ChatModel 和默认的 Builder 设置来创建 ChatClient // this.customChatClient = ChatClient.create(myChatModel); } public String askSomething(String question) { // 使用手动创建的 customChatClient 进行交互 return customChatClient.prompt() .user(question) .call() .content(); }}
3. 处理 ChatClient
响应
ChatClient
API 提供了多种方式来处理和格式化来自 AI 模型的响应:
返回 ChatResponse
:
call()
或 stream()
方法默认返回 ChatResponse
对象。ChatResponse
是一个丰富的结构,包含:- AI 生成的实际结果 (
Generation
)。与响应生成相关的元数据 (例如模型名称、token 使用情况等)。可能包含多个子响应结果 (如果模型支持,例如返回多个候选答案)。ChatResponse
中获取详细信息。示例: (在上面的 /chat
接口示例中,.call()
返回的就是 ChatResponse
,之后 .content()
是从中提取内容)返回实体类 (Entity) - 结构化输出:
ChatClient
支持将 AI 模型的文本输出自动映射为你定义的 Java POJO (Plain Old Java Object)。这对于需要固定格式输出的场景非常有用。使用 .entity(YourClass.class)
方法来指定期望的输出类型。示例代码 (带详细注释):import org.springframework.ai.chat.client.ChatClient; // 导入 ChatClient 类import org.springframework.web.bind.annotation.GetMapping; // 导入 GetMapping 注解import org.springframework.web.bind.annotation.RequestParam; // 导入 RequestParam 注解import org.springframework.web.bind.annotation.RestController; // 导入 RestController 注解@RestControllerpublic class StructuredOutputController { private final ChatClient chatClient; // 定义一个简单的 POJO 类,用于接收结构化输出 static class ActorFilms { public String actor; // 演员姓名 public List<String> movies; // 电影列表 } public StructuredOutputController(ChatClient.Builder builder) { this.chatClient = builder.build(); } @GetMapping("/actor-films") // 将 HTTP GET 请求映射到 /actor-films 路径 public ActorFilms getActorFilms(@RequestParam("actor") String actorName) { // 使用 chatClient 的 Fluent API return this.chatClient.prompt() // 设置用户消息,要求模型列出指定演员的电影,并明确要求 JSON 格式 .user("Generate a list of films for the actor " + actorName + ". Respond in JSON format with keys 'actor' and 'movies'.") .call() // 执行与 AI 模型的同步调用 // 调用 entity() 方法,并传入 ActorFilms.class // ChatClient 会尝试将 AI 返回的文本内容 (预期是 JSON 字符串) // 解析并映射到 ActorFilms 类的实例中 .entity(ActorFilms.class); }}
- 流式响应 (Streaming):
- 对于需要实时显示或处理部分结果的场景 (例如聊天机器人),可以使用流式响应。调用
.stream()
方法代替 .call()
。.stream()
方法返回一个 Flux<ChatResponse>
(如果使用了 Reactive 库) 或支持类似的流式处理机制。你可以订阅这个流来接收模型逐步生成的内容块。示例代码 (概念性,具体实现依赖 Reactive 库如 Project Reactor):import org.springframework.ai.chat.client.ChatClient; // 导入 ChatClient 类import org.springframework.http.MediaType; // 导入 MediaType 类import org.springframework.web.bind.annotation.GetMapping; // 导入 GetMapping 注解import org.springframework.web.bind.annotation.RequestParam; // 导入 RequestParam 注解import org.springframework.web.bind.annotation.RestController; // 导入 RestController 注解import reactor.core.publisher.Flux; // 导入 Flux 类 (来自 Project Reactor)import org.springframework.ai.chat.model.ChatResponse; // 导入 ChatResponse 类@RestControllerpublic class StreamingChatController { private final ChatClient chatClient; public StreamingChatController(ChatClient.Builder builder) { this.chatClient = builder.build(); } // produces = MediaType.TEXT_EVENT_STREAM_VALUE 指定响应类型为 Server-Sent Events (SSE) @GetMapping(value = "/stream-chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> streamChat(@RequestParam("input") String input) { // 使用 chatClient 的 Fluent API return this.chatClient.prompt() .user(input) // 设置用户消息 .stream() // 1. 调用 stream() 方法,启动与 AI 模型的流式交互,返回 Flux<ChatResponse> .content(); // 2. 调用 content() 方法 (针对 Flux),将 ChatResponse 流映射为 String 内容流 // 每次模型生成一部分内容,就会在这个 Flux 中发布一个 String 片段 } // 如果需要更详细的流信息,可以直接处理 Flux<ChatResponse> @GetMapping(value = "/stream-chat-response", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<ChatResponse> streamChatResponse(@RequestParam("input") String input) { return this.chatClient.prompt() .user(input) .stream() // 直接返回 ChatResponse 的流 .chatResponse(); // 获取原始的 ChatResponse 流 }}
4. 定制 ChatClient
默认值
可以在创建 ChatClient
时设置一些默认行为,这些默认值会应用于该 ChatClient
实例发出的所有请求,除非在单次请求中被覆盖。
- 设置默认系统消息 (System Message):
- 系统消息通常用于给 AI 模型设定角色、提供指令或上下文背景。在
ChatClient.Builder
上调用 .defaultSystem(...)
方法。示例:ChatClient chatClient = ChatClient.builder(chatModel) // 设置默认的系统消息,告诉 AI 它是一个乐于助人的 AI 助手 .defaultSystem("You are a helpful AI assistant.") .build();// 后续使用这个 chatClient 发送请求时,会自动带上这个系统消息chatClient.prompt().user("What is the capital of France?").call().content();
- 其他默认设置:
- 默认用户消息:
.defaultUser(...)
默认模型选项: .defaultOptions(...)
,可以设置温度 (temperature)、最大 token 数 (maxTokens) 等模型参数。默认函数: .defaultFunctions(...)
(用于 Function Calling)默认头信息: .defaultHeaders(...)
(可能用于特定模型的 API 调用)5. Advisors (增强器/顾问)
- Advisors 是一种强大的机制,用于向
ChatClient
的请求/响应流程中添加额外的功能,类似于中间件 (Middleware) 或 AOP (Aspect-Oriented Programming) 中的切面。它们可以在请求发送前对其进行修改,或者在收到响应后对其进行处理。通过 ChatClient.Builder
的 .defaultAdvisors(...)
方法添加。常见的 Advisors 应用场景:- 检索增强生成 (RAG): 在发送用户问题给 LLM 之前,先从知识库 (Vector Store) 中检索相关文档,并将文档内容添加到提示词中。
QuestionAnswerAdvisor
是一个常见的 RAG Advisor。聊天记忆 (Chat Memory): 在发送请求前,从 ChatMemory
组件中加载历史对话记录,并将其添加到提示词中;在收到响应后,将当前的问答对保存到 ChatMemory
中。ChatMemoryAdvisor
用于此目的。日志记录: 记录请求和响应的详细信息。// 假设 vectorStore 和 chatMemory 是已经配置好的 BeanVectorStore vectorStore = ...;ChatMemory chatMemory = ...;ChatClient chatClient = ChatClient.builder(chatModel) // 添加一个 RAG Advisor,使用指定的 VectorStore 进行检索 .defaultAdvisors(new QuestionAnswerAdvisor(vectorStore)) // 添加一个聊天记忆 Advisor .defaultAdvisors(new ChatMemoryAdvisor(chatMemory)) .build();// 现在使用这个 chatClient 时,会自动进行 RAG 检索和聊天记忆管理chatClient.prompt().user("Tell me about Spring AI based on my previous questions.").call().content();
总结:
ChatClient
是 Spring AI Alibaba 中进行 AI 模型交互的核心组件之一。它通过 Fluent API 极大地简化了与 AI 模型的通信,隐藏了底层实现的复杂性,并提供了强大的功能,如结构化输出、流式响应和通过 Advisors 实现的 RAG、聊天记忆等高级特性。掌握 ChatClient
的使用对于在 Spring 应用中高效地集成 AI 功能至关重要。
实践记录
配置项目
<dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter</artifactId> <version>1.0.0-M6.1</version></dependency>
为了实践ChatClient的使用,我们创建了一个简单的Spring Boot项目,在pom.xml文件中引入spring-ai-alibaba-starter
依赖。
server: port: 10001spring: application: name: spring-ai-alibaba-dashscope-chat-example ai: dashscope: api-key: ${AI_DASHSCOPE_API_KEY}
在application.yml文件中,配置spring.ai.dashscope.api-key
,用于设置Dashscope的API密钥,使其从环境变量中读取。
调用content方法
这段代码展示了如何使用 Spring AI Alibaba 快速构建一个简单的聊天接口。 通过 Spring Boot 的 @RestController
注解,我们创建了一个 RESTful 控制器 ChatClientController
,它依赖于 Spring AI 提供的 ChatClient
组件。 构造函数中,我们利用 ChatClient.Builder
构建了一个 ChatClient
实例,Spring AI 负责自动注入这个 Builder。 核心逻辑在 chat()
方法中,这个方法通过 @GetMapping("/chat")
注解映射到 HTTP GET 请求。 当用户访问 /chat?input=你的消息
时,chat()
方法会被调用,它会接收 URL 中的 input
参数作为用户输入,并使用 ChatClient
向配置好的 AI 模型发送请求,最终将 AI 模型的响应文本内容返回给客户端。 这个简单的接口可以轻松集成到前端应用中,实现基本的对话功能,是快速体验和构建 AI 应用的理想起点。注释后的代码如下所示。
package com.miku.springaialibabalearn.controller;import org.springframework.ai.chat.client.ChatClient; // 导入 Spring AI 提供的 ChatClient 接口import org.springframework.web.bind.annotation.GetMapping; // 导入 Spring MVC 的 GetMapping 注解,用于处理 GET 请求import org.springframework.web.bind.annotation.RestController; // 导入 Spring MVC 的 RestController 注解,表明这是一个 RESTful 控制器/** * RestController 注解表明这是一个 RESTful 控制器,用于处理 HTTP 请求并返回数据。 * 此类负责处理与 ChatClient 交互的 HTTP 请求。 */@RestControllerpublic class ChatClientController { private final ChatClient chatClient; // 声明一个私有 final 的 ChatClient 类型的成员变量,用于进行聊天交互 /** * 构造器注入 ChatClient.Builder,并通过 builder 构建 ChatClient 实例。 * Spring 会自动解析 ChatClient.Builder 并注入到这里。 * * @param builder ChatClient 的构建器,由 Spring AI 自动提供。 */ public ChatClientController( ChatClient.Builder builder ) { // 使用 ChatClient.Builder 构建 ChatClient 实例并赋值给成员变量 chatClient this.chatClient = builder.build(); } /** * GetMapping 注解将 HTTP GET 请求 "/chat" 映射到 chat() 方法。 * 当接收到 "/chat" 的 GET 请求时,此方法将被调用。 * * @param input 请求参数 input,从 URL 的 query parameter 中获取,例如 /chat?input=你好。 * Spring MVC 会自动将 URL 中的 input 参数值绑定到此方法参数。 * @return String 返回 AI 聊天模型的响应内容,以纯文本形式返回。 */ @GetMapping("/chat") // 将 HTTP GET 请求映射到 /chat 路径 public String chat( @RequestParam("input") String input ) { // @RequestParam("input") String input 表示从请求参数中获取名为 "input" 的值,并赋给 input 变量 // 使用 chatClient 的 Fluent API 开始构建一个请求 return this.chatClient.prompt() // 1. 调用 prompt() 方法开始构建一个 Prompt (提示) .user(input) // 2. 调用 user() 方法设置用户角色的消息内容,内容为传入的 input 字符串 .call() // 3. 调用 call() 方法,执行与 AI 模型的同步调用 (发送请求并等待响应) .content(); // 4. 调用 content() 方法,从 AI 模型的响应 (ChatResponse) 中提取文本内容并返回 }}
启动Application.java
。
使用Postman发送GET类型的HTTP请求:http://localhost:10001/chat?input=你好,介绍依赖注入的原理
。可以看到成功获得了dashscope模型的响应。
调用chatResponse方法
/**AI 模型的响应是一种由ChatResponse类型定义的丰富结构。它包含响应生成相关的元数据,同时它还可以包含多个子响应(称为Generation),每个子响应都有自己的元数据。元数据包括用于创建响应的令牌(token)数量信息(在英文中,每个令牌大约为一个单词的 3/4),了解令牌信息很重要,因为 AI 模型根据每个请求使用的令牌数量收费。 */@GetMapping( "/response" ) public ChatResponse response(String input ) { return this.chatClient.prompt() .user(input) // 设置了用户消息的内容 .call() // call 方法向 AI 模型发送请求 .chatResponse(); // 调用 chatResponse() 返回 ChatResponse }
在浏览器使用http://localhost:10001/response?input=你好
发起http请求,返回 ChatResponse。
调用entity方法
// 定义一个 HTTP GET 请求处理方法,映射到 "/singer-songs" 路径@GetMapping("/singer-songs")// 返回类型为 SingerSongs,表示该方法会返回一个 SingerSongs 类型的对象,Spring MVC 会自动将其转换为 JSON 格式返回给客户端public SingerSongs chatEntity(@RequestParam("name") String name) { // 使用 @RequestParam("name") 注解将 HTTP 请求参数 "name" 绑定到方法参数 String name 上 // 使用 chatClient 的 Fluent API 构建 Prompt 并调用 AI 模型 return this.chatClient.prompt() // 1. 开始构建 Prompt (提示) 对象,prompt() 方法是 Fluent API 的起始点 .user("Generate a list of songs of the singer " + name + ". Respond in JSON format with keys 'singer' and 'songs'. ") // 2. 设置用户消息 (User Message),作为 Prompt 的内容 // - user() 方法指定消息的角色为用户 (User) // - 消息内容是动态生成的字符串,包含用户输入的歌手名字 (name) // - 指示 AI 模型生成指定歌手的歌曲列表,并明确要求以 JSON 格式返回 // - JSON 格式要求包含 "singer" 和 "songs" 两个键 .call() // 3. 调用 call() 方法,执行与 AI 模型的同步请求并等待响应 // - call() 方法会将之前构建的 Prompt 发送给配置好的 AI 模型服务 // - 它会返回一个 ChatResponse 对象,包含了 AI 模型的响应结果 .entity(SingerSongs.class); // 4. 调用 entity(SingerSongs.class) 方法,将 AI 模型的响应结果映射为 SingerSongs 类型的对象 // - entity() 方法假设 AI 模型返回的响应是 JSON 格式的字符串 // - 它会自动将 JSON 字符串反序列化 (解析) 成 SingerSongs 类的实例 // - SingerSongs.class 指定了目标实体类的类型,Spring AI 会根据这个类型进行映射 // 整个方法流程: // 1. 接收 HTTP GET 请求,并从请求参数中获取歌手名字 (name)。 // 2. 构建一个 Prompt,包含指示 AI 模型生成歌手歌曲列表的指令,并要求 JSON 格式输出。 // 3. 使用 ChatClient 调用 AI 模型,发送 Prompt 并获取 ChatResponse。 // 4. 将 ChatResponse 中的 JSON 格式内容映射为 SingerSongs 对象。 // 5. 返回 SingerSongs 对象,Spring MVC 会自动将其转换为 JSON 格式返回给客户端。}
package com.miku.springaialibabalearn.entity;import java.util.List;public class SingerSongs { private String singer; // 歌手名字,对应 JSON 中的 "singer" 键 private List<String> songs; // 歌曲列表,对应 JSON 中的 "songs" 键 // 必须要有默认的无参构造器,以便 Jackson (或其他 JSON 库) 可以进行反序列化 public SingerSongs() { } // Getter 和 Setter 方法 (建议添加,虽然在这个例子中可能不是必须的,但良好的 Java 实践) public String getSinger() { return singer; } public void setSinger(String singer) { this.singer = singer; } public List<String> getSongs() { return songs; } public void setSongs(List<String> songs) { this.songs = songs; }}
在浏览器使用http://localhost:10001/singer-songs?name=初音未来
发起http请求,返回JSON格式的singer
和songs
的键值对数据。
调用stream方法
@GetMapping( value = "/stream-chat")// 使用 @GetMapping 注解,将 HTTP GET 请求路径 "/stream-chat" 映射到 streamChat 方法// value = "/stream-chat" 指定了请求的 URL 路径public Flux<String> streamChat(@RequestParam("input") String input) { // 定义名为 streamChat 的方法,处理 "/stream-chat" 的 GET 请求 // 方法的返回类型是 Flux<String>,表示一个包含多个 String 元素的异步数据流 (Reactive Stream) // 使用 @RequestParam("input") 注解,将 HTTP 请求参数名为 "input" 的值绑定到方法的 input 参数上,类型为 String return this.chatClient.prompt() // 1. 使用 chatClient 的 Fluent API 开始构建 Prompt (提示) 对象 // prompt() 方法开始构建一个新的 Prompt,它是与 AI 模型交互的输入 .user(input) // 2. 设置用户消息 (User Message) 到 Prompt 中 // user(input) 方法指定消息的角色为 "user",消息内容为方法的参数 input (用户输入) .stream() // 3. 调用 stream() 方法,执行与 AI 模型的流式交互 // stream() 方法会将构建好的 Prompt 发送给配置的 AI 模型服务,并启动流式响应 // 返回值是一个 Flux<ChatResponse>,表示 ChatResponse 对象的流 .content(); // 4. 调用 content() 方法,从 Flux<ChatResponse> 中提取内容并转换为 Flux<String> // content() 方法将 Flux<ChatResponse> 转换为 Flux<String>,只保留每个 ChatResponse 中的文本内容 (getContent()) // 最终返回 Flux<String>,每个 String 元素代表模型流式输出的一部分文本 // 整个 streamChat 方法的流程: // 1.接收用户通过 HTTP GET 请求传递的 "input" 参数作为用户输入 // 2.构建一个 Prompt,包含用户输入的消息 // 3.使用 ChatClient 发起流式聊天请求,将 Prompt 发送给 AI 模型 // 4.从模型返回的流式 ChatResponse 中提取文本内容,并作为 Flux<String> 返回 // 5.Spring WebFlux 会将返回的 Flux<String> 作为 Server-Sent Events (SSE) 流式响应返回给客户端}@GetMapping( value = "/stream-chat-response")// 使用 @GetMapping 注解,将 HTTP GET 请求路径 "/stream-chat-response" 映射到 streamChatResponse 方法// value = "/stream-chat-response" 指定了请求的 URL 路径public Flux<ChatResponse> streamChatResponse(@RequestParam("input") String input) { // 定义名为 streamChatResponse 的方法,处理 "/stream-chat-response" 的 GET 请求 // 方法的返回类型是 Flux<ChatResponse>,表示一个包含多个 ChatResponse 对象的异步数据流 // 使用 @RequestParam("input") 注解,将 HTTP 请求参数名为 "input" 的值绑定到方法的 input 参数上,类型为 String return this.chatClient.prompt() // 1. 使用 chatClient 的 Fluent API 开始构建 Prompt (提示) 对象 // prompt() 方法开始构建一个新的 Prompt,它是与 AI 模型交互的输入 .user(input) // 2. 设置用户消息 (User Message) 到 Prompt 中 // user(input) 方法指定消息的角色为 "user",消息内容为方法的参数 input (用户输入) .stream() // 3. 调用 stream() 方法,执行与 AI 模型的流式交互 // stream() 方法会将构建好的 Prompt 发送给配置的 AI 模型服务,并启动流式响应 // 返回值是一个 Flux<ChatResponse>,表示 ChatResponse 对象的流 .chatResponse(); // 4. 调用 chatResponse() 方法,直接返回 Flux<ChatResponse> 对象流 // chatResponse() 方法直接返回 stream() 方法返回的 Flux<ChatResponse>,不做任何转换 // 最终返回 Flux<ChatResponse>,每个 ChatResponse 对象包含更完整的模型响应信息 (包括文本内容和元数据等) // 整个 streamChatResponse 方法的流程: // 1.接收用户通过 HTTP GET 请求传递的 "input" 参数作为用户输入 // 2.构建一个 Prompt,包含用户输入的消息 // 3.使用 ChatClient 发起流式聊天请求,将 Prompt 发送给 AI 模型 // 4.直接将模型返回的流式 ChatResponse 对象作为 Flux<ChatResponse> 返回 // 5.Spring WebFlux 会将返回的 Flux<ChatResponse> 作为 Server-Sent Events (SSE) 流式响应返回给客户端 // 6.客户端可以接收到完整的 ChatResponse 对象,包含更详细的响应信息}
在浏览器使用http://localhost:10001/stream-chat?input=介绍初音未来
发起http请求,得到字符串的流式响应。
在浏览器使用http://localhost:10001/stream-chat-response?input=介绍初音未来
发起http请求,得到ChatResponse类型的流式响应。
Server-Sent Events (SSE)
在我们的 Spring AI 应用中,为了实现 AI 回复的流式效果(即内容逐步显示而非一次性返回),我们利用了 Server-Sent Events (SSE) 技术。这通过在 Controller 方法上添加特定的 @GetMapping
注解来实现。
让我们深入理解这个关键注解:
@GetMapping( value = "/stream-chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
解读如下:
@GetMapping
与 value
属性@GetMapping
: 这是 Spring MVC 中用于处理 HTTP GET 请求的常用注解。value = "/stream-chat"
: 这个属性定义了该方法处理的 URL 路径。当客户端向 /stream-chat
发送 GET 请求时,Spring 会调用这个方法。produces = MediaType.TEXT_EVENT_STREAM_VALUE
属性
这是实现流式响应的核心部分。
produces
属性: 指定了方法返回的响应内容的 MIME 类型 (Media Type)。MediaType.TEXT_EVENT_STREAM_VALUE
: 这个常量代表 MIME 类型 text/event-stream
。当浏览器或客户端看到这个响应类型时,它就知道服务器将使用 Server-Sent Events (SSE) 协议来发送数据。什么是 Server-Sent Events (SSE)?
SSE 是一种允许服务器向客户端单向推送更新的技术。
- 工作方式: 客户端发起一个 HTTP 请求,服务器保持该连接打开,并可以持续地向客户端发送数据流(事件),而无需客户端再次请求。特点:
- 单向通信: 数据仅从服务器流向客户端。基于 HTTP: 使用标准 HTTP 协议,易于实现和部署。文本协议: 数据以简单的文本格式发送,易于解析。自动重连: 浏览器通常会自动处理断线重连。
总结
@GetMapping(value = "/stream-chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
注解的整体含义是:
创建一个处理
/stream-chat
路径 GET 请求的端点,该端点将使用 Server-Sent Events (SSE) 技术,以text/event-stream
格式向客户端持续推送数据流。
在我们的 Spring AI 聊天应用中,这意味着当客户端请求 /stream-chat
时,后端不会一次性返回完整的 AI 回复,而是将 AI 模型逐步生成的文本块作为 SSE 事件流式地发送给客户端,从而在前端实现打字机般的显示效果。
编写前端界面
用Trae辅助简单编写了一个初音未来网页,点击右上角“AI聊天”,进入对话界面。
发送消息,前端向后端的streamChat方法发起请求,可以看到成功实现了流式对话。
总结与展望
通过本次学习和实践,我们深入了解了 Spring AI Alibaba 中 ChatClient
的核心用法。我们掌握了:
ChatClient
的创建与配置: 包括依赖引入、API Key 配置以及通过 ChatClient.Builder
获取实例。Fluent API 的便捷性: 使用链式调用 .prompt().user().call()/stream().content()/entity()/chatResponse()
可以清晰、简洁地构建与 AI 模型的交互逻辑。多样化的响应处理: 学习了如何获取纯文本内容 (.content()
)、完整的响应对象 (.chatResponse()
)、自动映射的 Java 实体 (.entity()
) 以及实现实时效果的流式响应 (.stream()
)。SSE 的应用: 理解了如何通过设置 produces = MediaType.TEXT_EVENT_STREAM_VALUE
结合 Flux
来实现 Server-Sent Events,为前端提供流式数据。ChatClient
作为 Spring AI 的高级抽象,有效地降低了在 Spring 应用中集成 LLM 的门槛。它不仅简化了基础的对话交互,还为集成 RAG(通过 QuestionAnswerAdvisor
)、聊天记忆(通过 ChatMemoryAdvisor
)等高级功能提供了便利的扩展点 (Advisors)。
后续可以探索的方向包括:
- 深入研究
ChatMemoryAdvisor
实现多轮对话记忆。结合 VectorStore
和 QuestionAnswerAdvisor
构建 RAG 应用,让 AI 能基于私有知识库回答问题。探索 Function Calling/Tools 功能,让 AI 能够调用外部 API 或执行特定任务。学习更复杂的提示词工程(Prompt Engineering)技巧。完善前端聊天界面,增加用户体验。总而言之,ChatClient
是 Spring AI 生态中一个非常实用的工具,掌握它将为我们构建更智能、更强大的 Java 应用打下坚实的基础。