Persisting Spring AI Alibaba Memory to Redis: A Hands‑On Guide
This guide walks through configuring Spring AI Alibaba 1.1.2.0 to persist an agent's short‑term memory in Redis, covering environment setup, core concepts like Checkpointer and StateSerializer, step‑by‑step Maven project creation, code snippets, common pitfalls, and verification of multi‑turn conversation state across sessions.
Introduction
When building an intelligent conversational agent, memory is a core capability. Spring AI Alibaba provides a flexible short‑term memory mechanism that can checkpoint an agent’s state—including dialogue history—to external storage. Using Redis as the storage medium enables memory sharing across multiple service instances and prevents data loss on application restart.
Environment Requirements
JDK 17+
Spring Boot 3.2.5
Spring AI Alibaba 1.1.2.0
Redis 6.x/7.x (Windows: Memurai or WSL2)
Maven 3.6+
DashScope API Key (for model calls)
Core Concepts
Short‑term Memory
Short‑term memory lets an agent remember previous interactions within the same session identified by threadId. In Spring AI Alibaba, the memory is stored as an OverAllState object in the graph execution context.
Checkpointer
The Checkpointer persists the agent state to external storage. Two implementations are provided: MemorySaver: in‑memory storage for development and debugging. RedisSaver: Redis storage suitable for production.
StateSerializer
The state serializer converts Java objects to byte arrays for Redis. The framework defaults to SpringAIJacksonStateSerializer, which uses Jackson for JSON serialization.
Message Trimming
To keep the conversation within the LLM’s context window, MessagesModelHook can trim the message list (e.g., keep the first and the most recent N messages) or apply summarization.
Implementation Steps
1. Create Maven Project
Define pom.xml and manage the Jackson version to avoid NoSuchMethodError caused by version mismatches.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/>
</parent>
<groupId>com.example.ai</groupId>
<artifactId>spring-ai-redis-memory-demo</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
<spring-ai-alibaba.version>1.1.2.0</spring-ai-alibaba.version>
<jackson.version>2.17.2</jackson.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>
<!-- DashScope starter for model access -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>
<!-- Jackson components (managed by BOM) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<!-- Redisson client -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.52.0</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>2. Configuration File
server:
port: 885
spring:
ai:
dashscope:
api-key: ${DASHSCOPE_API_KEY}
redis:
host: localhost
port: 6379
timeout: 50003. Agent Configuration (using RedisSaver)
package com.badao.ai.config;
import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.checkpoint.savers.redis.RedisSaver;
import com.alibaba.cloud.ai.graph.serializer.StateSerializer;
import com.alibaba.cloud.ai.graph.serializer.plain_text.jackson.SpringAIJacksonStateSerializer;
import com.alibaba.cloud.ai.graph.state.AgentStateFactory;
import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy;
import com.badao.ai.hook.MessageTrimmingHook;
import org.redisson.api.RedissonClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AgentConfig {
@Bean
public ReactAgent reactAgent(ChatModel chatModel, RedissonClient redissonClient) {
// 1. Create state factory
AgentStateFactory<OverAllState> stateFactory = (inputs) -> {
OverAllState state = new OverAllState();
state.registerKeyAndStrategy("messages", new ReplaceStrategy());
state.input(inputs);
return state;
};
// 2. Create serializer
StateSerializer stateSerializer = new SpringAIJacksonStateSerializer(stateFactory);
// 3. Build RedisSaver (method name is redisson, not redissonClient)
RedisSaver redisSaver = RedisSaver.builder()
.redisson(redissonClient)
.stateSerializer(stateSerializer)
.build();
// 4. Build ReactAgent
return ReactAgent.builder()
.name("redis_memory_agent")
.model(chatModel)
.saver(redisSaver)
.hooks(new MessageTrimmingHook()) // optional trimming
.build();
}
}4. Message‑Trimming Hook (optional)
package com.badao.ai.hook;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.hook.HookPosition;
import com.alibaba.cloud.ai.graph.agent.hook.HookPositions;
import com.alibaba.cloud.ai.graph.agent.hook.messages.AgentCommand;
import com.alibaba.cloud.ai.graph.agent.hook.messages.MessagesModelHook;
import com.alibaba.cloud.ai.graph.agent.hook.messages.UpdatePolicy;
import org.springframework.ai.chat.messages.Message;
import java.util.ArrayList;
import java.util.List;
@HookPositions({HookPosition.BEFORE_MODEL})
public class MessageTrimmingHook extends MessagesModelHook {
private static final int MAX_MESSAGES = 5;
@Override
public String getName() {
return "message_trimming";
}
@Override
public AgentCommand beforeModel(List<Message> previousMessages, RunnableConfig config) {
if (previousMessages.size() <= MAX_MESSAGES) {
return new AgentCommand(previousMessages);
}
// Keep the first message and the last three
Message firstMsg = previousMessages.get(0);
List<Message> recent = previousMessages.subList(previousMessages.size() - 3, previousMessages.size());
List<Message> trimmed = new ArrayList<>();
trimmed.add(firstMsg);
trimmed.addAll(recent);
return new AgentCommand(trimmed, UpdatePolicy.REPLACE);
}
}5. Service and Controller
package com.badao.ai.service;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.stereotype.Service;
@Service
public class AgentService {
private final ReactAgent reactAgent;
public AgentService(ReactAgent reactAgent) { this.reactAgent = reactAgent; }
public String chat(String userMessage, String sessionId) {
RunnableConfig config = RunnableConfig.builder()
.threadId(sessionId) // session isolation
.build();
AssistantMessage response = reactAgent.call(userMessage, config);
return response.getText();
}
} package com.badao.ai.controller;
import com.badao.ai.service.AgentService;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/agent")
public class AgentController {
private final AgentService agentService;
public AgentController(AgentService agentService) { this.agentService = agentService; }
@PostMapping("/chat/session")
public Map<String, Object> chatWithSession(@RequestParam String message, @RequestParam String sessionId) {
String response = agentService.chat(message, sessionId);
return Map.of("success", true, "response", response, "sessionId", sessionId);
}
}Common Issues and Solutions
RedisSaver constructor access
Attempting to instantiate new RedisSaver(...) fails because the constructor is protected. Use the builder pattern:
RedisSaver.builder().redisson(...).stateSerializer(...).build().
Method name mismatch
The builder method is redisson, not redissonClient. Replace calls accordingly.
Jackson version conflict
Inconsistent Jackson versions cause NoSuchMethodError. Resolve by unifying the version via dependencyManagement to 2.17.2 (or the version matching Spring Boot).
Timeout parsing error
Specifying timeout: 5000ms in application.yml leads to NumberFormatException because the field expects an int. Remove the ms suffix and use timeout: 5000.
Memory not taking effect
Possible causes: missing saver registration, inconsistent threadId, or unregistered StateSerializer. Ensure the agent builder includes .saver(redisSaver), use a consistent sessionId (mapped to threadId), and verify that state data appears in Redis (e.g., redis-cli KEYS '*').
Verification Tests
Start Redis and Spring Boot
cd C:\Redis
redis-server.exe redis.windows.conf
export DASHSCOPE_API_KEY="your_api_key"
mvn spring-boot:runMulti‑turn conversation
# First turn: introduce name
curl -X POST "http://localhost:885/api/agent/chat/session?message=你好,我叫张三&sessionId=test-001"
# Second turn: ask name (should be remembered)
curl -X POST "http://localhost:885/api/agent/chat/session?message=我叫什么名字?&sessionId=test-001"
# Third turn: new session, memory isolated
curl -X POST "http://localhost:885/api/agent/chat/session?message=我叫什么名字?&sessionId=test-002"Inspect Redis state
redis-cli
127.0.0.1:6379> KEYS *
# e.g., agent:test-001:state
127.0.0.1:6379> GET "agent:test-001:state"Conclusion
The guide demonstrates how to use RedisSaver to persist an agent’s short‑term memory, ensuring context continuity across multi‑turn dialogues and supporting production‑grade deployments with shared memory and resilience to restarts. Key takeaways include building the Checkpointer with the correct builder method ( redisson), unifying Jackson versions to avoid serialization errors, isolating sessions via threadId, and optionally applying MessagesModelHook for context trimming. Future extensions could incorporate summarization or long‑term memory backed by vector databases.
References
Spring AI Alibaba official documentation
RedisSaver source examples
DashScope model integration guide
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
The Dominant Programmer
Resources and tutorials for programmers' advanced learning journey. Advanced tracks in Java, Python, and C#. Blog: https://blog.csdn.net/badao_liumang_qizhi
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
