基于SpringAI实现简易聊天对话

简介

本文旨在记录学习和实践 Spring AI Alibaba 提供的 ChatClient 组件的过程。ChatClient 是 Spring AI 中用于与大语言模型(LLM)进行交互的高级 API,它通过流畅(Fluent)的编程接口,极大地简化了构建聊天应用程序的复杂度。相比直接使用底层的 ChatModelChatClient 封装了提示词构建、响应处理、结构化输出、流式响应以及与 RAG、聊天记忆等高级功能的集成。

通过本文的学习,我们将掌握:

  • ChatClient 的核心概念和优势。
  • 如何创建和配置 ChatClient 实例。
  • 使用 ChatClient 处理不同类型的 AI 响应(文本、完整响应对象、结构化实体、流式响应)。
  • 结合 Spring Boot 快速搭建一个可交互的聊天后端服务。
  • 了解 Server-Sent Events (SSE) 在流式响应中的应用。

我们将从官方文档入手,结合代码实践,逐步深入理解 ChatClient 的使用方法。


ChatClient相关原理

Spring AI Alibaba 文档

核心知识点:ChatClient in Spring AI Alibaba

ChatClient 是 Spring AI Alibaba 提供的一个更高级别的 API,用于与 AI 模型进行交互。它旨在简化开发流程,特别是当应用程序需要组合多个组件(如提示词模板、聊天记忆、模型本身、输出解析器、RAG 组件等)时。

1. ChatClient 简介

  • 目的: 提供一个 Fluent API (流畅 API) 与 AI 模型通信,支持同步和反应式 (Reactive) 编程模型。
  • 优势:
    • 隐藏复杂性: 将与 LLM (Large Language Model) 及其他组件(提示词模板、聊天记忆、RAG 等)交互的复杂性封装起来。
    • 减少样板代码: 相比直接使用 ChatModelMessage 等原子 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 实例。
    • 示例代码 (带详细注释):
java 复制代码
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.propertiesapplication.yml 中设置:

      properties 复制代码
      spring.ai.chat.client.enabled=false
    • 手动创建: 然后,你可以手动创建 ChatClient.Builder,并传入你想要使用的 ChatModel 实例。

    • 示例代码 (带详细注释):

java 复制代码
import org.springframework.ai.chat.client.ChatClient; // 导入 ChatClient 类
import org.springframework.ai.chat.model.ChatModel;   // 导入 ChatModel 接口

// ... 其他 import

public 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) 方法来指定期望的输出类型。
    • 示例代码 (带详细注释):
java 复制代码
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 注解

@RestController
public 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):
java 复制代码
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 类

@RestController
public 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(...) 方法。
    • 示例:
java 复制代码
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 用于此目的。
    • 日志记录: 记录请求和响应的详细信息。
  • 示例 (概念性):
java 复制代码
// 假设 vectorStore 和 chatMemory 是已经配置好的 Bean
VectorStore 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 功能至关重要。


实践记录

配置项目

xml 复制代码
<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依赖。

yaml 复制代码
server:
  port: 10001

spring:
  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 应用的理想起点。注释后的代码如下所示。

java 复制代码
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 请求。
 */
@RestController
public 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方法

java 复制代码
/**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方法

java 复制代码
// 定义一个 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 格式返回给客户端。
}
java 复制代码
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格式的singersongs的键值对数据。


调用stream方法

java 复制代码
@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 注解来实现。

让我们深入理解这个关键注解:

java 复制代码
@GetMapping( value = "/stream-chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)

解读如下:

  1. @GetMappingvalue 属性
  • @GetMapping: 这是 Spring MVC 中用于处理 HTTP GET 请求的常用注解。
  • value = "/stream-chat": 这个属性定义了该方法处理的 URL 路径。当客户端向 /stream-chat 发送 GET 请求时,Spring 会调用这个方法。
  1. produces = MediaType.TEXT_EVENT_STREAM_VALUE 属性

    这是实现流式响应的核心部分。

  • produces 属性: 指定了方法返回的响应内容的 MIME 类型 (Media Type)。
  • MediaType.TEXT_EVENT_STREAM_VALUE: 这个常量代表 MIME 类型 text/event-stream。当浏览器或客户端看到这个响应类型时,它就知道服务器将使用 Server-Sent Events (SSE) 协议来发送数据。
  1. 什么是 Server-Sent Events (SSE)?

    SSE 是一种允许服务器向客户端单向推送更新的技术。

  • 工作方式: 客户端发起一个 HTTP 请求,服务器保持该连接打开,并可以持续地向客户端发送数据流(事件),而无需客户端再次请求。
  • 特点:
    • 单向通信: 数据仅从服务器流向客户端。
    • 基于 HTTP: 使用标准 HTTP 协议,易于实现和部署。
    • 文本协议: 数据以简单的文本格式发送,易于解析。
    • 自动重连: 浏览器通常会自动处理断线重连。
  • 与传统 HTTP 的区别: 传统 HTTP 是请求-响应模式,服务器发送一次响应后连接即关闭。SSE 保持连接打开以持续推送。
  • 与 WebSocket 的区别: WebSocket 提供双向通信,允许客户端和服务器互相发送消息,协议也不同于 HTTP。SSE 更轻量,适用于服务器单方面推送数据的场景。
  • 应用场景: 实时通知、股票行情、状态更新、以及我们这里的流式 AI 对话响应

总结

@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 实现多轮对话记忆。
  • 结合 VectorStoreQuestionAnswerAdvisor 构建 RAG 应用,让 AI 能基于私有知识库回答问题。
  • 探索 Function Calling/Tools 功能,让 AI 能够调用外部 API 或执行特定任务。
  • 学习更复杂的提示词工程(Prompt Engineering)技巧。
  • 完善前端聊天界面,增加用户体验。

总而言之,ChatClient 是 Spring AI 生态中一个非常实用的工具,掌握它将为我们构建更智能、更强大的 Java 应用打下坚实的基础。

相关推荐
南客先生2 分钟前
多级缓存架构设计与实践经验
java·面试·多级缓存·缓存架构
anqi274 分钟前
如何在 IntelliJ IDEA 中编写 Speak 程序
java·大数据·开发语言·spark·intellij-idea
m0_7401546711 分钟前
maven相关概念深入介绍
java·maven
用户40993225021212 分钟前
异步IO与Tortoise-ORM的数据库
后端·ai编程·trae
fanTuanye24 分钟前
Spring-全面详解(学习总结)
java·spring·ssm框架
Best_Liu~25 分钟前
TransactionTemplate 与@Transactional 注解的使用
java·开发语言·spring boot·后端
胡斌附体1 小时前
idea启动springboot方式及web调用
java·spring boot·intellij-idea
她和夏天一样热1 小时前
【Java面试题04】MySQL 篇
java·mysql·adb
曹天骄1 小时前
设计并实现一个基于 Java + Spring Boot + MySQL 的通用多租户权限系统
java·spring boot·mysql