Skip to content

fix(redis): Implement ChatMemoryRepository interface and fix test connectivity #2991

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: redis/semantic-caching-advisor-and-chat-memory
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?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.ai</groupId>
<artifactId>spring-ai-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../../../../pom.xml</relativePath>
</parent>
<artifactId>spring-ai-autoconfigure-model-chat-memory-redis</artifactId>
<packaging>jar</packaging>
<name>Spring AI Redis Chat Memory Auto Configuration</name>
<description>Spring AI Redis Chat Memory Auto Configuration</description>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-model-chat-memory-redis</artifactId>
<version>${project.version}</version>
</dependency>

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

<!-- Optional dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.redis</groupId>
<artifactId>testcontainers-redis</artifactId>
<version>2.2.0</version>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model.chat.memory.redis.autoconfigure;

import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.ChatMemoryRepository;
import org.springframework.ai.chat.memory.redis.RedisChatMemory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.util.StringUtils;

import redis.clients.jedis.JedisPooled;

/**
* Auto-configuration for Redis-based chat memory implementation.
*
* @author Brian Sam-Bodden
*/
@AutoConfiguration(after = RedisAutoConfiguration.class)
@ConditionalOnClass({ RedisChatMemory.class, JedisPooled.class })
@EnableConfigurationProperties(RedisChatMemoryProperties.class)
public class RedisChatMemoryAutoConfiguration {

@Bean
@ConditionalOnMissingBean
public JedisPooled jedisClient(RedisChatMemoryProperties properties) {
return new JedisPooled(properties.getHost(), properties.getPort());
}

@Bean
@ConditionalOnMissingBean({ RedisChatMemory.class, ChatMemory.class, ChatMemoryRepository.class })
public RedisChatMemory redisChatMemory(JedisPooled jedisClient, RedisChatMemoryProperties properties) {
RedisChatMemory.Builder builder = RedisChatMemory.builder().jedisClient(jedisClient);

// Apply configuration if provided
if (StringUtils.hasText(properties.getIndexName())) {
builder.indexName(properties.getIndexName());
}

if (StringUtils.hasText(properties.getKeyPrefix())) {
builder.keyPrefix(properties.getKeyPrefix());
}

if (properties.getTimeToLive() != null && properties.getTimeToLive().toSeconds() > 0) {
builder.timeToLive(properties.getTimeToLive());
}

if (properties.getInitializeSchema() != null) {
builder.initializeSchema(properties.getInitializeSchema());
}

if (properties.getMaxConversationIds() != null) {
builder.maxConversationIds(properties.getMaxConversationIds());
}

if (properties.getMaxMessagesPerConversation() != null) {
builder.maxMessagesPerConversation(properties.getMaxMessagesPerConversation());
}

if (properties.getMetadataFields() != null && !properties.getMetadataFields().isEmpty()) {
builder.metadataFields(properties.getMetadataFields());
}

return builder.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model.chat.memory.redis.autoconfigure;

import java.time.Duration;
import java.util.List;
import java.util.Map;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.ai.chat.memory.redis.RedisChatMemoryConfig;

/**
* Configuration properties for Redis-based chat memory.
*
* @author Brian Sam-Bodden
*/
@ConfigurationProperties(prefix = "spring.ai.chat.memory.redis")
public class RedisChatMemoryProperties {

/**
* Redis server host.
*/
private String host = "localhost";

/**
* Redis server port.
*/
private int port = 6379;

/**
* Name of the Redis search index.
*/
private String indexName = RedisChatMemoryConfig.DEFAULT_INDEX_NAME;

/**
* Key prefix for Redis chat memory entries.
*/
private String keyPrefix = RedisChatMemoryConfig.DEFAULT_KEY_PREFIX;

/**
* Time to live for chat memory entries. Default is no expiration.
*/
private Duration timeToLive;

/**
* Whether to initialize the Redis schema. Default is true.
*/
private Boolean initializeSchema = true;

/**
* Maximum number of conversation IDs to return (defaults to 1000).
*/
private Integer maxConversationIds = RedisChatMemoryConfig.DEFAULT_MAX_RESULTS;

/**
* Maximum number of messages to return per conversation (defaults to 1000).
*/
private Integer maxMessagesPerConversation = RedisChatMemoryConfig.DEFAULT_MAX_RESULTS;

/**
* Metadata field definitions for proper indexing. Compatible with RedisVL schema
* format. Example: <pre>
* spring.ai.chat.memory.redis.metadata-fields[0].name=priority
* spring.ai.chat.memory.redis.metadata-fields[0].type=tag
* spring.ai.chat.memory.redis.metadata-fields[1].name=score
* spring.ai.chat.memory.redis.metadata-fields[1].type=numeric
* </pre>
*/
private List<Map<String, String>> metadataFields;

public String getHost() {
return host;
}

public void setHost(String host) {
this.host = host;
}

public int getPort() {
return port;
}

public void setPort(int port) {
this.port = port;
}

public String getIndexName() {
return indexName;
}

public void setIndexName(String indexName) {
this.indexName = indexName;
}

public String getKeyPrefix() {
return keyPrefix;
}

public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}

public Duration getTimeToLive() {
return timeToLive;
}

public void setTimeToLive(Duration timeToLive) {
this.timeToLive = timeToLive;
}

public Boolean getInitializeSchema() {
return initializeSchema;
}

public void setInitializeSchema(Boolean initializeSchema) {
this.initializeSchema = initializeSchema;
}

public Integer getMaxConversationIds() {
return maxConversationIds;
}

public void setMaxConversationIds(Integer maxConversationIds) {
this.maxConversationIds = maxConversationIds;
}

public Integer getMaxMessagesPerConversation() {
return maxMessagesPerConversation;
}

public void setMaxMessagesPerConversation(Integer maxMessagesPerConversation) {
this.maxMessagesPerConversation = maxMessagesPerConversation;
}

public List<Map<String, String>> getMetadataFields() {
return metadataFields;
}

public void setMetadataFields(List<Map<String, String>> metadataFields) {
this.metadataFields = metadataFields;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.springframework.ai.model.chat.memory.redis.autoconfigure.RedisChatMemoryAutoConfiguration
Loading