Java后端硬核实战:用Spring AI Alibaba+Redis给LLM装上“超强记忆中枢”

告别无状态对话,用Spring AI Alibaba+Redis打造智能体记忆中枢

手写一个带记忆的AI客服,让你的大模型真正"记住"你说过的话

现在AI应用遍地开花,但你会发现:大多数Demo级别的AI对话,问完一句就忘了上一句。你跟它说"我叫张三",下一轮问"我叫什么",它一脸懵地回答"你还没有告诉我你的名字"。这就是典型的无状态AI,毫无实用价值。

真正能落地的AI应用,比如智能客服、角色陪伴、多轮对话Agent,核心挑战从来不是"怎么调API",而是状态管理------如何高效、低成本地管理多轮对话的上下文记忆、控制Token消耗、实现历史截断。

而这恰恰是Java后端工程师的主场。今天,我们就用Spring AI Alibaba + Redis,手把手打造一个带记忆的AI客服,让你的LLM应用拥有"超强大脑"。

一、问题揭示:无状态AI有多尴尬?

先来看一个典型"无状态"实现的伪代码:

java 复制代码
@PostMapping("/chat")
public String chat(String userMessage) {
    // 每次调用都只发当前消息,不带历史
    return chatClient.call(userMessage);
}

效果是这样的:

用户:我叫张三。

AI:好的,张三。

用户:我刚才说我的名字是什么?

AI:抱歉,我不知道你的名字,你还没有告诉我。

更真实一点的场景:用户问"我家空调不制冷了怎么办",AI给出了一系列排查步骤,然后用户追问"第二步说的过滤网在哪个位置",AI完全不知道"第二步"指的是什么。

没有记忆的AI,就像一个金鱼------每次对话都是"全新的开始"。 在业务场景中,这种断片式的交互体验用户根本无法接受。

二、核心方案:四种记忆存储方案对比

在给AI加记忆之前,先明确我们要存储什么:

  • 对话历史:用户每轮说的话、AI的回复。
  • 会话元数据:会话ID、用户ID、创建时间、最后活跃时间等。
  • Token消耗:用于计费和截断策略。

常见的存储方案及优缺点:

方案 优点 缺点 适用场景
客户端存储(LocalStorage) 零服务端成本 不安全、不可跨端、数据易丢失 纯前端演示
内存缓存(ConcurrentHashMap) 实现简单、极快 无法集群共享、重启丢失、内存不可控 单机原型
Redis 高性能、支持集群、过期策略、数据结构丰富 需要额外组件 生产级首选
数据库(MySQL) 持久化、可复杂查询 性能较低、不适合高频读写 需要长期存档+审计

对于大多数对话场景,Redis是最佳平衡点。尤其是在生产环境中,我们需要多节点集群共享记忆------用户请求可能被负载均衡到任意一台服务器,如果某台机器把对话记忆存在本地内存里,换一台机器就全丢了。而Redis作为集中式存储,天然支持跨节点共享。本文基于Spring AI Alibaba框架 + Redis,实现一个支持滑动窗口、自动截断的生产级记忆方案。

为什么选择Spring AI Alibaba?

Spring AI Alibaba是阿里云推出的面向Java开发者的AI接入方案,构建在Spring AI基础之上,对接了阿里云的DashScope平台(通义千问系列模型)。它与Spring Boot紧密集成,符合传统Java编程习惯,封装了复杂的底层通信逻辑,支持Prompt模板、多轮对话、RAG检索增强等能力。在国内Java生态中使用广泛,特别是需要对接通义大模型或阿里百炼平台的项目。

三、实战代码:Spring AI Alibaba + Redis 实现滑动窗口记忆

3.1 技术栈

  • Spring Boot 3.x
  • Spring AI Alibaba 1.1.2.0+
  • Redis(推荐使用Redis 6.x+)
  • 阿里云DashScope(通义千问模型)

3.2 项目搭建与依赖配置

首先在pom.xml中添加Spring AI Alibaba的依赖

xml 复制代码
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.10</version>
</parent>

<properties>
    <java.version>17</java.version>
</properties>

<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring AI Alibaba DashScope Starter -->
    <dependency>
        <groupId>com.alibaba.spring</groupId>
        <artifactId>spring-ai-alibaba-starter</artifactId>
        <version>1.1.2.0</version>
    </dependency>

    <!-- Redis 整合 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- Jackson JSON 序列化 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>

    <!-- Commons Pool 连接池 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
</dependencies>

接着配置API Key和Redis连接信息:

yaml 复制代码
# application.yml
spring:
  data:
    redis:
      host: localhost
      port: 6379
      timeout: 5000ms
      lettuce:
        pool:
          max-active: 8
          max-idle: 8

  ai:
    dashscope:
      api-key: sk-xxx-your-real-key

注意 :API Key可以在阿里云DashScope控制台获取

3.3 核心数据结构设计

我们用Redis的List结构存储某个会话的对话历史,每条记录包含角色、内容和时间戳:

java 复制代码
// 对话消息实体
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatMessage {
    private String role;        // "user" 或 "assistant"
    private String content;     // 消息内容
    private Long timestamp;     // 时间戳,用于排序和过期判断
}

Redis Key命名规则:chat:history:{sessionId}

3.4 实现基于Redis的ChatMemoryRepository

Spring AI Alibaba提供了ChatMemoryRepository接口作为存储抽象层,我们需要实现一个Redis版本,负责消息的持久化存储和检索

java 复制代码
@Component
public class RedisChatMemoryRepository implements ChatMemoryRepository {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String KEY_PREFIX = "chat:history:";
    private static final int MAX_HISTORY_SIZE = 20;  // 最大历史消息数,超过则滑动窗口截断
    
    @Override
    public List<Message> findByConversationId(String conversationId) {
        String key = KEY_PREFIX + conversationId;
        List<Object> range = redisTemplate.opsForList().range(key, 0, -1);
        if (range == null || range.isEmpty()) {
            return new ArrayList<>();
        }
        // 将存储的ChatMessage转换为Spring AI的Message对象
        return range.stream()
                .map(obj -> {
                    ChatMessage msg = (ChatMessage) obj;
                    return new Message(msg.getRole(), msg.getContent());
                })
                .collect(Collectors.toList());
    }
    
    @Override
    public void saveAll(String conversationId, List<Message> messages) {
        String key = KEY_PREFIX + conversationId;
        // 将Message转换为ChatMessage并存储
        for (Message msg : messages) {
            ChatMessage chatMsg = new ChatMessage(
                msg.getRole(), 
                msg.getContent(), 
                System.currentTimeMillis()
            );
            redisTemplate.opsForList().rightPush(key, chatMsg);
        }
        // 限制队列长度,实现滑动窗口
        Long size = redisTemplate.opsForList().size(key);
        if (size != null && size > MAX_HISTORY_SIZE) {
            // 弹出超出部分的老消息
            for (int i = 0; i < size - MAX_HISTORY_SIZE; i++) {
                redisTemplate.opsForList().leftPop(key);
            }
        }
        // 设置会话过期时间(30分钟无活动则自动清理)
        redisTemplate.expire(key, 30, TimeUnit.MINUTES);
    }
    
    @Override
    public void deleteByConversationId(String conversationId) {
        String key = KEY_PREFIX + conversationId;
        redisTemplate.delete(key);
    }
    
    @Override
    public List<String> findConversationIds() {
        // 简化实现,生产环境可通过SCAN命令遍历
        throw new UnsupportedOperationException("如需遍历会话ID,请使用KeysCommand或SCAN命令");
    }
}

3.5 配置ChatMemory

通过配置类,将我们的Redis存储库绑定到滑动窗口记忆策略上

java 复制代码
@Configuration
public class ChatMemoryConfig {
    
    @Bean
    public ChatMemoryRepository chatMemoryRepository() {
        // 使用我们实现的Redis版本
        return new RedisChatMemoryRepository();
    }
    
    @Bean
    public ChatMemory chatMemory(ChatMemoryRepository repository) {
        // MessageWindowChatMemory是Spring AI提供的内置实现,维护一个固定大小的消息窗口
        // 当消息数量超过maxMessages时,会自动移除最老的消息,同时保留系统消息[reference:6][reference:7]
        return MessageWindowChatMemory.builder()
                .chatMemoryRepository(repository)
                .maxMessages(20)      // 最大保留20条消息(约10轮对话)
                .build();
    }
}

3.6 将ChatMemory集成到ChatClient

Spring AI Alibaba通过Advisor(顾问)机制 来为ChatClient添加增强功能。MessageChatMemoryAdvisor会在每次请求时自动从记忆库加载历史消息,并附加到当前Prompt中

java 复制代码
@Configuration
public class ChatClientConfig {
    
    @Bean
    public ChatClient chatClient(ChatMemory chatMemory) {
        return ChatClient.builder()
                // 添加记忆增强顾问,自动注入历史对话
                .build(advisor -> advisor
                        .with(MessageChatMemoryAdvisor.class, advisorSpec -> advisorSpec
                                .chatMemory(chatMemory))
                );
    }
}

3.7 完整的Controller:带记忆的聊天接口

java 复制代码
@RestController
@RequestMapping("/api/chat")
public class ChatController {
    
    @Autowired
    private ChatClient chatClient;
    
    @PostMapping("/send")
    public ResponseEntity<ChatResponse> sendMessage(@RequestBody ChatRequest request) {
        String sessionId = request.getSessionId();   // 由前端传入,如UUID
        String userMessage = request.getMessage();
        
        // chatClient会自动根据conversationId参数加载历史记忆
        // Spring AI的MessageChatMemoryAdvisor会识别conversationId参数,
        // 自动从对应的记忆存储中加载历史消息,并注入到本次请求中[reference:9]
        String aiReply = chatClient.prompt()
                .user(userMessage)
                .withSystemParam("conversationId", sessionId)  // 关键:绑定会话ID,实现会话隔离
                .call()
                .content();
        
        return ResponseEntity.ok(new ChatResponse(aiReply, sessionId));
    }
}

注意 :Spring AI的MessageChatMemoryAdvisor基于AOP实现环绕增强逻辑。当我们在请求中传入conversationId参数后,Advisor会自动完成两件事:请求前 从记忆库加载历史消息合并到Prompt中;请求后 将本轮问答结果自动保存回记忆库。开发者无需手动维护任何存储逻辑。

3.8 效果演示

启动项目后,用curl测试:

bash 复制代码
curl -X POST http://localhost:8080/api/chat/send \
  -H "Content-Type: application/json" \
  -d '{"sessionId": "user-001", "message": "我叫张三"}'

# 返回: "你好张三!很高兴认识你。有什么我可以帮你的吗?"

curl -X POST http://localhost:8080/api/chat/send \
  -H "Content-Type: application/json" \
  -d '{"sessionId": "user-001", "message": "我刚才说我叫什么?"}'

# 返回: "你刚才说你叫张三。"

同一个sessionId下,AI记住了之前的对话。换一个sessionId,对话历史完全独立,互不干扰------这就是会话隔离

四、进阶优化:Token消耗控制与智能截断

真实场景下,对话越长,发送给LLM的token就越多,费用和延迟都会增加。我们需要更精细的控制。

4.1 滑动窗口内存修剪

我们已经在RedisChatMemoryRepository.saveAll()中实现了滑动窗口机制,通过限制MAX_HISTORY_SIZE来控制记忆长度。需要调整窗口大小时,直接修改配置即可。对于更精细的控制,Spring AI的MessageWindowChatMemory支持自定义消息窗口大小

4.2 按Token数智能截断

如果单纯按消息数量截断不够精细(有的消息短,有的消息长),可以考虑实现按Token数量截断 。借助阿里百炼平台提供的tiktoken-java库:

java 复制代码
public List<Message> getRecentWithinTokenLimit(String conversationId, int maxTokens) {
    List<Message> all = chatMemory.get(conversationId);
    List<Message> result = new ArrayList<>();
    int currentTokens = 0;
    for (int i = all.size() - 1; i >= 0; i--) {
        Message msg = all.get(i);
        int tokens = encoding.encode(msg.getContent()).size();
        if (currentTokens + tokens > maxTokens) break;
        currentTokens += tokens;
        result.add(0, msg);
    }
    return result;
}

4.3 长对话摘要压缩

当对话轮次超过阈值后,可以触发后台任务,使用LLM本身将旧对话总结为摘要,替换原始历史。Spring AI Alibaba支持通过ReactAgent的上下文工程(Context Engineering)技术实现这一能力,通过消息修剪(Message Trimming)和摘要生成(Summarization) 来管理长对话中的上下文过载问题

五、进阶思路:向量数据库与阿里百炼长期记忆

以上方案使用Redis存储短期记忆(最近N轮对话),适合会话级多轮交互。但如果要实现"永久记忆"------比如AI能记住用户一年前说过的喜好、历史订单、过往投诉------就需要长期记忆方案。

5.1 向量数据库方案

通过向量数据库(如Milvus、Qdrant、PgVector)配合嵌入模型,可以为记忆建立语义索引:

  1. Embedding:把每轮对话内容用嵌入模型转为向量。
  2. 存储:把向量连同元数据存入向量数据库。
  3. 检索:新消息到来时,同样转为向量,查询最相似的K条历史记忆,注入到Prompt中。

Spring AI Alibaba已提供VectorStore、Retriever、DocumentReader等模块化组件,支持ElasticSearch、Redis、PGVector等后端

5.2 阿里百炼"记忆库"功能

更便捷的选择是利用阿里百炼平台提供的记忆库(Memory Vault) 功能。该功能于2026年4月正式上线,让Agent具备跨会话的长期记忆能力,真正实现"越聊越懂用户"的个性化体验。

记忆库系统内置了 "提取-存储-检索-注入" 四大模块:用户每次与AI Agent对话结束后,系统可根据配置的记忆规则自动提取关键信息并存储;当用户再次提问时,系统会触发语义检索召回相关记忆并附加至上下文中,实现个性化回答。

开发者可通过API直接调用,同时Spring AI Alibaba已深度集成阿里百炼平台,支持DashScope Agent与记忆系统的无缝对接

六、生产环境最佳实践与踩坑指南

6.1 不同用户/会话的隔离

在调用时,务必通过withSystemParam("conversationId", sessionId)为不同用户传入不同的会话标识。同一会话ID自动共享记忆,不同会话ID之间完全隔离,避免"串话"问题

6.2 Redis连接池配置

生产环境中,Redis的高并发至关重要,建议:

yaml 复制代码
spring:
  data:
    redis:
      lettuce:
        pool:
          max-active: 20
          max-idle: 10
          min-idle: 5
          max-wait: 10000ms

6.3 记忆过期策略管理

对于不再活跃的会话,应自动清理避免Redis内存膨胀。可以在RedisChatMemoryRepository.saveAll()中添加过期时间:

java 复制代码
// 设置会话30分钟无活动则自动过期
redisTemplate.expire(key, 30, TimeUnit.MINUTES);

根据业务场景灵活调整TTL时长:高频对话场景可设为1小时,离线场景可设为7天。

6.4 工具调用结果的记忆回写

如果你的Agent调用了外部工具(如查询订单、获取天气),记得将工具返回结果以系统消息形式回写到记忆。否则,后续轮次中Agent不知道它自己之前查到了什么信息

java 复制代码
// 工具执行后,将结果追加到记忆
String toolResult = orderService.query(userId, lastWeek);
memory.add("system", "工具返回:" + toolResult);

七、总结:你的AI应用离"智能"只差一个记忆中枢

今天我们从一个痛点出发,逐步实现了一个生产级的AI对话记忆方案:

  • 问题:无状态LLM毫无价值,无法支撑真实业务场景。
  • 方案对比:Redis是生产最优解,支持分布式部署和会话隔离。
  • 实战:用Spring AI Alibaba + Redis实现滑动窗口记忆,代码可直接复用。
  • 框架优势 :Spring AI Alibaba提供ChatMemoryRepository存储抽象、MessageWindowChatMemory滑动窗口实现、以及MessageChatMemoryAdvisor自动增强机制------开发者只需专注业务,框架负责复杂的记忆管理逻辑。
  • 优化:Token截断、摘要压缩。
  • 进阶:向量数据库实现长期记忆、阿里百炼"记忆库"功能(限时免费,可直接通过API调用)。

下一步你可以做什么?

  1. 按照本文代码,10分钟内跑通一个带记忆的AI客服Demo。
  2. 根据业务需求调整记忆窗口大小和会话过期时间。
  3. 在阿里百炼控制台探索"记忆库"功能,为你的应用加上长期记忆。
  4. 阅读Spring AI Alibaba官方文档中关于ChatMemory和Agent框架的更多内容

如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、评论。下期我会写《Java工程师如何用向量数据库为AI构建长期记忆》,届时也会结合阿里百炼的最佳实践,敬请期待。

相关推荐
oo哦哦8 小时前
星链引擎矩阵系统深度解析:AI驱动下的全域智能营销SaaS新范式
大数据·人工智能·矩阵
oo哦哦8 小时前
轻量化内容中台如何破解企业矩阵运营困局?以星链引擎为例的技术解析
大数据·人工智能·矩阵
我爱cope9 小时前
【Agent智能体6 | 智能体AI评估】
人工智能·职场和发展
Raink老师9 小时前
【AI面试临阵磨枪-68】设计一个端侧(手机 / 浏览器)轻量化 AI Agent 系统
人工智能·面试·智能手机
wuxinyan1239 小时前
工业级大模型学习之路021:LangChain零基础入门教程(第四篇):文档加载与文本分块技术
人工智能·python·学习·langchain
oo哦哦9 小时前
企业级矩阵管理中台:从“人海战术“到“AI智能增长“的架构演进与实践解析
人工智能·矩阵·架构·轻量化中台
tedcloud1239 小时前
academic-research-skills部署教程:构建AI辅助科研环境
服务器·人工智能·word·excel·dreamweaver
IT_陈寒9 小时前
Java的Optional差点让我掉坑里,这几个坑你别踩
前端·人工智能·后端
编码时空的诗意行者9 小时前
那些全新的Prompt范式(新提示词工程新思维)
人工智能·prompt·ai编程