Bug description deserialization. Currently, ChatMemory only implements a memory-based dialogue cache storage. I wanted to implement a Redis-based cache for Memory in my project, but I encountered a problem: the implementation classes of the Memory interface do not implement the Serializable interface, which causes failures in accessing Redis.
Environment Spring AI 1.0.0-SNAPSHOT Steps to reproduce step1 : `@Slf4j public class RedisChatMemory implements ChatMemory {
private static final String PREFIX = "chat:";
@Override
public void add(String conversationId, List<Message> messages) {
messages.forEach(message -> RedisUtils.lSet(PREFIX + conversationId, message, CommonConst.CHAT_EXPIRE_TIME));
}
@Override
public List<Message> get(String conversationId, int lastN) {
List<Message> messages = RedisUtils.lGet(PREFIX + conversationId, -lastN, -1);
log.info("history conversation :{}", JSONObject.toJSONString(messages));
return messages;
}
@Override
public void clear(String conversationId) {
RedisUtils.del(PREFIX + conversationId);
}
}` Step2: Runtime error
Expected behavior Support for Redis-based session storage
Comment From: canonxu
i also got this issue
Comment From: csterwa
@lvchzh @canonxu has this issue been resolved in the current milestone release of Spring AI?
Comment From: markpollack
Hi. There is an implementation of the ChatMemory interface for Cassandra and there is a PR for pgvector. That said, we would like to provide an impelmentation that delgates to Spring's Cache abstraction along the lines of what was done in llamaindex. I'll create a new issue and post it back here. I don't see an implementation where having ChatMemory implement serializable is needed, entries of the memory may or may not need to be serializable depending upon the storage approach.
Comment From: Joaonic
I've encountered the same issue with implementing a Redis-based cache for ChatMemory
. To address this, I implemented a workaround using Jackson and mixins to handle serialization and deserialization with Redis. This solution is currently functioning correctly in version M4.
Implementation Details
- Define Message Mixins
```java import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.SystemMessage; import org.springframework.ai.chat.messages.ToolResponseMessage; import org.springframework.ai.chat.messages.UserMessage;
@JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "messageType", visible = true ) @JsonSubTypes({ @JsonSubTypes.Type(value = AssistantMessage.class, name = "ASSISTANT"), @JsonSubTypes.Type(value = SystemMessage.class, name = "SYSTEM"), @JsonSubTypes.Type(value = UserMessage.class, name = "USER"), @JsonSubTypes.Type(value = ToolResponseMessage.class, name = "TOOL") }) public abstract class MessageMixin { } ```
- Configure Redis Template with Jackson Serializer
```java import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.ai.chat.messages.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration public class RedisConfig {
@Bean
public RedisTemplate<String, Message> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Message> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
StringRedisSerializer stringSerializer = new StringRedisSerializer();
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(Message.class, MessageMixin.class);
objectMapper.addMixIn(UserMessage.class, UserMessageMixin.class);
objectMapper.addMixIn(AssistantMessage.class, AssistantMessageMixin.class);
objectMapper.addMixIn(SystemMessage.class, SystemMessageMixin.class);
objectMapper.addMixIn(ToolResponseMessage.class, ToolResponseMessageMixin.class);
Jackson2JsonRedisSerializer<Message> jsonSerializer = new Jackson2JsonRedisSerializer<>(Message.class);
jsonSerializer.setObjectMapper(objectMapper);
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
template.afterPropertiesSet();
return template;
}
} ```
- Define Specific Mixins
```java import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.springframework.ai.model.Media;
import java.util.Collection; import java.util.Map;
public abstract class UserMessageMixin {
@JsonCreator
public UserMessageMixin(
@JsonProperty("content") String textContent,
@JsonProperty("media") Collection<Media> media,
@JsonProperty("metadata") Map<String, Object> metadata
) {
}
} ```
Note: Ensure that all mixin classes (e.g., AssistantMessageMixin
, SystemMessageMixin
, etc.) are properly defined similar to UserMessageMixin
to cover all message types.
Comment From: poo0054
Moreover, Message has not yet implemented Serializable. Now ChatMemory can only be in memory. Can you provide a persistence method so that historical information can be queried normally even if the service is restarted or distributed?
Comment From: hardikSinghBehl
Used the workaround suggested by @Joaonic in a POC. Was able to persist chat memory across application sessions.
Comment From: poo0054
Thank you very much. In future versions, it will be directly supported without having to rely on Jackson serialization to be compatible with this problem. If I use other persistence, it won't work.