一、前言

随着大模型应用落地越来越广泛,传统硬编码对接各大 LLM 接口的方式,存在适配复杂、底层逻辑冗余、多模型切换成本高等问题。而 Spring AI Alibaba 基于 Spring 生态打造,提供了统一、流畅的 ChatClient 流式 API,屏蔽了不同大模型的底层通信细节,让 Java 开发者可以像写普通 Spring 业务代码一样快速开发 AI 应用,聚焦业务逻辑而非接口适配。

本文基于 Spring AI Alibaba 核心组件 ChatClient,从基础概念、适用场景出发,结合结构化实体返回、动态消息角色、对话记忆(内存 / Redis 持久化) 三大高频实战场景,附上完整可运行代码,帮助大家快速上手企业级 AI 聊天应用开发。

二、什么是 ChatClient?

1. 核心概念

ChatClient 是 Spring AI 体系中面向大模型对话的核心流式客户端,设计风格对标 Spring 生态的 WebClient,采用链式调用写法。

它统一封装了各类大模型的请求构造、参数传递、结果解析、流式响应等底层能力,抹平不同 LLM 厂商的接口差异。开发者无需关心 HTTP 请求头、请求体拼装、响应解析等底层工作,只需要通过链式 API 组织对话内容,极大提升 LLM 应用的开发效率。

2. 主要适用场景

  1. 基础对话问答:通用聊天机器人、智能客服、知识问答等场景;
  2. 结构化数据输出:要求大模型返回固定格式数据(对象、数组、枚举),替代手动解析 JSON;
  3. 角色定制对话:动态切换 AI 身份、语气、人设,如模仿不同风格回复、专属职业助手;
  4. 多轮连续对话:需要上下文记忆的聊天场景(聊天机器人、智能陪伴、交互式问答);
  5. 流式输出:SSE 流式返回回答内容,实现类似 ChatGPT 打字机效果。

三、实战一:大模型结果直接返回实体类(结构化输出)

在实际业务中,我们经常需要大模型按照固定格式返回数据,而非纯文本。Spring AI 的 entity() 方法可以直接将大模型返回内容映射为 Java 实体(Record/POJO),自动完成序列化,彻底告别手动 JSON 解析。

1. 定义接收实体

使用 Java Record 定义结构化返回实体(简洁高效,适合数据载体):

// 接收演员+对应影视作品的结构化实体
public record ActorMovies(String actor, List<String> movies) {
}

2. 接口开发

通过 chatClient.prompt().user().call().entity(实体类.class) 直接绑定返回类型:

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/movies")
public class MovieAiController {

    private final ChatClient chatClient;

    // 构造器注入ChatClient(Spring AI自动装配)
    public MovieAiController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    @RequestMapping
    public ActorMovies movies(@RequestParam(value = "message") String message){
        // 链式调用:传入用户提问,直接映射为ActorMovies实体
        return this.chatClient.prompt()
                .user(message)
                .call()
                .entity(ActorMovies.class);
    }
}

使用说明:调用接口后,大模型会自动按照实体结构返回数据,框架内部完成格式校验与映射,适用于报表、信息提取、数据整理等强结构化场景。

四、实战二:设置消息角色 & 动态参数

大模型的系统角色(System Message) 是控制 AI 行为、语气、身份的核心。ChatClient 支持全局默认角色 + 接口动态角色参数,灵活实现人设切换。

完整控制器代码

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/ai")
public class AiController {

    private final ChatClient chatClient;

    // 全局配置默认系统角色,预留动态参数 {voice}
    public AiController(ChatClient.Builder chatClient){
        this.chatClient = chatClient
                .defaultSystem("你是一个聊天机器人,回答问题请用{voice}的语气回答我")
                .build();
    }

    @RequestMapping
    public String chat(
            @RequestParam("message") String message,
            @RequestParam("voice") String voice
    ){
        return this.chatClient.prompt()
                // 动态替换系统角色中的 {voice} 参数
                .system(sp -> sp.param("voice", voice))
                // 用户提问
                .user(message)
                // 同步调用,返回纯文本内容
                .call()
                .content();
    }
}

核心要点

  1. defaultSystem():全局预设系统提示词,定义 AI 基础身份,支持占位符 {参数名}
  2. .system(sp -> sp.param()):接口层动态传递参数,替换占位符,实现运行时切换语气 / 人设
  3. 支持角色区分:系统角色(AI 人设)、用户角色(用户提问),符合 LLM 标准对话协议。

测试示例:传入 message="讲个笑话"voice="可爱俏皮",AI 就会以可爱风格回复内容。

五、实战三:实现大模型多轮对话记忆(内存 + Redis 两种方案)

默认情况下,ChatClient 每次请求都是单次独立对话,无法记住上文内容。想要实现多轮连续对话,需要借助 Spring AI 提供的 ChatMemory 对话记忆组件,搭配 MessageChatMemoryAdvisor 实现上下文存储。

主流分为两种方案:

  • 内存存储(InMemory):数据存服务内存,重启丢失,适合测试、单机临时对话;
  • Redis 存储:数据持久化到 Redis,服务重启不丢失,支持分布式部署,生产环境首选

前置依赖

使用 Redis 记忆需要引入 Spring AI Alibaba Redis 记忆 依赖,本文基于阿里云 Spring AI 生态组件。


方案 1:基于内存的对话记忆(单机临时会话)

核心组件:

  • MessageWindowChatMemory:窗口式对话记忆,限制最大消息条数,防止上下文过长;
  • InMemoryChatMemoryRepository:内存仓库;
  • MessageChatMemoryAdvisor:对话记忆增强器,自动拼接历史上下文。

完整代码:

import jakarta.servlet.http.HttpServletResponse;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
@RequestMapping("memory")
public class MemoryController {

    private ChatClient chatClient;

    public MemoryController(ChatModel chatModel) {
        // 1. 构建内存对话记忆:最大保存100条消息
        ChatMemory chatMemory = MessageWindowChatMemory.builder()
                .chatMemoryRepository(new InMemoryChatMemoryRepository())
                .maxMessages(100)
                .build();

        // 2. 构建记忆增强器
        MessageChatMemoryAdvisor messageChatMemoryAdvisor = MessageChatMemoryAdvisor.builder(chatMemory)
                .build();

        // 3. 初始化ChatClient,绑定默认系统角色和记忆增强器
        this.chatClient = ChatClient.builder(chatModel)
                .defaultSystem("你是一个旅游规划导师,请根据用户的需求提供旅游规划建议。")
                .defaultAdvisors(messageChatMemoryAdvisor)
                .build();
    }

    // 流式接口(SSE)返回对话内容
    @RequestMapping ("/in-memory")
    public Flux<String> memory(
            @RequestParam("prompt") String prompt,
            @RequestParam("chatId") String chatId,
            HttpServletResponse response
    ) {
        response.setCharacterEncoding("UTF-8");
        // 传入会话ID,区分不同用户的对话上下文
        return chatClient.prompt(prompt)
                .advisors(a -> a
                        .param(ChatMemory.CONVERSATION_ID, chatId) // 会话唯一标识
                        .param("TOP_K", 50)
                )
                .stream() // 开启流式输出
                .content();
    }
}

特点

  • 依靠 chatId 区分不同会话,不同用户 / 对话互不干扰;
  • 数据保存在 JVM 内存,服务重启、集群部署后记忆失效
  • 适合本地测试、演示场景。

方案 2:基于 Redis 的持久化对话记忆(生产推荐)

使用 RedisChatMemoryRepository 将对话历史持久化到 Redis,支持分布式、服务重启数据不丢失,是线上项目标准方案。

加入依赖

        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-memory-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.12.0</version>
        </dependency>

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>5.2.0</version>
        </dependency>

完整代码:

import com.alibaba.cloud.ai.memory.redis.RedisChatMemoryRepository;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
@RequestMapping("/redis")
public class MemoryRedisController {

    private final ChatClient chatClient;

    // 注入Redis记忆仓库 + ChatClient构建器
    public MemoryRedisController(ChatClient.Builder builder,
                                 RedisChatMemoryRepository redisChatMemoryRepository ) {
        // 1. 构建Redis持久化对话记忆
        ChatMemory redisChatMemory = MessageWindowChatMemory.builder()
                .chatMemoryRepository(redisChatMemoryRepository)
                .maxMessages(100)
                .build();

        // 2. 构建记忆增强器
        MessageChatMemoryAdvisor redisAdvisor = MessageChatMemoryAdvisor.builder(redisChatMemory).build();

        // 3. 初始化ChatClient
        this.chatClient = builder.defaultSystem("你是一个旅游规划导师,请根据用户的需求提供旅游规划建议。")
                .defaultAdvisors(redisAdvisor)
                .build();
    }

    @RequestMapping("/chat")
    public Flux<String> chat(
            @RequestParam("prompt") String prompt,
            @RequestParam("chatId") String chatId,
            HttpServletResponse response
    ) {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/event-stream;charset=UTF-8");
        // 绑定会话ID,读取Redis中历史对话
        return chatClient.prompt(prompt)
                .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, chatId).param("TOP_K", 50))
                .stream()
                .content();
    }
}

RedisChatMemoryRepository需要手动装配,Spring AI Alibaba 扩展组件,并非 Spring AI 原生组件,官方没有提供全局自动配置类。

package org.example.ai_demo.config;

import com.alibaba.cloud.ai.memory.redis.RedisChatMemoryRepository;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedisConfig {
    @Value("${spring.data.redis.host:localhost}")
    private String host;

    @Value("${spring.data.redis.port:6379}")
    private int port;
    @Bean
    public RedisChatMemoryRepository redisChatMemoryRepository(){
        return RedisChatMemoryRepository.builder().host(host).port(port).build();
    }
}

特点

  1. 对话历史存入 Redis,支持分布式集群、服务重启数据不丢失;
  2. 依旧通过 chatId 隔离不同用户会话;
  3. 搭配 stream() 实现 SSE 流式输出,前端可实现打字机效果;
  4. 企业级生产环境首选方案

两种记忆方案对比

表格

存储方案 底层仓库 优缺点 适用场景
内存记忆 InMemoryChatMemoryRepository 简单、无中间件依赖;重启丢失、不支持集群 本地测试、单机演示
Redis 记忆 RedisChatMemoryRepository 持久化、支持分布式;依赖 Redis 中间件 线上生产、集群项目

六、总结

  1. ChatClient 定位:Spring AI Alibaba 核心对话客户端,流式链式 API,屏蔽 LLM 底层细节,让开发者专注业务;
  2. 结构化输出:借助 entity() 方法,一键将大模型返回映射为 Java 实体,告别手动 JSON 解析;
  3. 动态角色:通过系统提示词 + 动态参数,灵活切换 AI 语气、人设,满足多样化对话需求;
  4. 对话记忆:依靠 ChatMemory + Advisor 实现多轮对话,测试用内存、生产用 Redis 是最佳实践;

Spring AI Alibaba 深度融合 Spring Boot 生态,学习成本低、上手快,以上四大能力基本覆盖了聊天机器人、智能助手、交互式问答等主流 LLM 应用场景。大家可以基于本文代码快速搭建基础 AI 对话服务,再结合 RAG、函数调用等能力进一步拓展业务功能。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。