diff --git a/.gitignore b/.gitignore
index c2fb8fc27..19db9000b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,4 +38,6 @@ package.json
shell.log
-.profiler
\ No newline at end of file
+.profiler
+/spring-ai-spring-boot-autoconfigure/nbproject/
+/vector-stores/spring-ai-cassandra-store/nbproject/
diff --git a/pom.xml b/pom.xml
index ce1bc39a4..84744dc02 100644
--- a/pom.xml
+++ b/pom.xml
@@ -170,7 +170,7 @@
3.25.2
- 4.18.0
+ 4.18.1
3.0.1
0.1.4
2.20.11
diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/ChatMemory.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/ChatMemory.java
index 9e1cad6f3..6d383c0c4 100644
--- a/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/ChatMemory.java
+++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/ChatMemory.java
@@ -26,7 +26,7 @@ import org.springframework.ai.chat.messages.Message;
* conversation, and clear the conversation history.
*
* @author Christian Tzolov
- * @since 1.0.0 M1
+ * @since 1.0.0
*/
public interface ChatMemory {
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc
index eb2887a46..fa494278c 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc
@@ -369,7 +369,13 @@ The `FILTER_EXPRESSION` parameter allows you to dynamically filter the search re
The interface `ChatMemory` represents a storage for chat conversation history. It provides methods to add messages to a
* conversation, retrieve messages from a conversation, and clear the conversation history.
-There is one implementation `InMemoryChatMemory` that provides in-memory storage for chat conversation history.
+There are two implementations `InMemoryChatMemory` and `CassandraChatMemory` that provides storage for chat conversation history, in-memory and persisted with time-to-live correspondingly.
+
+To create a CassandraChatMemory with time-to-live
+[source,java]
+----
+CassandraChatMemory.create(CassandraChatMemoryConfig.builder().withTimeToLive(Duration.ofDays(1)).build());
+----
Two advisor implementations use the `ChatMemory` interface to advice the prompt with conversation history which differ in the details of how the memory is added to the prompt
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/apache-cassandra.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/apache-cassandra.adoc
index 6e1b407e1..e064f2649 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/apache-cassandra.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/apache-cassandra.adoc
@@ -20,14 +20,11 @@ This Spring AI Vector Store is designed to work for both brand-new RAG applicati
The store can also be used for non-RAG use-cases in an existing database, e.g. semantic searches, geo-proximity searches, etc.
-The vector store implementation can initialize the requisite schema for you, but you must opt-in by specifying the `initializeSchema` boolean in the appropriate constructor or by setting `...initialize-schema=true` in the `application.properties` file.
-
-NOTE: this is a breaking change! In earlier versions of Spring AI, this schema initialization happened by default.
-
-
-
The store will automatically create, or enhance, the schema as needed according to its configuration. If you don't want the schema modifications, configure the store with `disallowSchemaChanges`.
+When using spring-boot-autoconfigure `disallowSchemaChanges` defaults to true, per Spring Boot standards, and you must opt-in to schema creation/modifications by specifying the `initializeSchema` boolean in the appropriate constructor or by setting `...initialize-schema=true` in the `application.properties` file.
+
+
== What is JVector ?
link:https://github.com/jbellis/jvector[JVector] is a pure Java embedded vector search engine.
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/chat/memory/CommonChatMemoryProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/chat/memory/CommonChatMemoryProperties.java
new file mode 100644
index 000000000..a635c70f6
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/chat/memory/CommonChatMemoryProperties.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2024 - 2024 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.autoconfigure.chat.memory;
+
+/**
+ * @author Mick Semb Wever
+ * @since 1.0.0
+ */
+public class CommonChatMemoryProperties {
+
+ private boolean initializeSchema = true;
+
+ public boolean isInitializeSchema() {
+ return initializeSchema;
+ }
+
+ public void setInitializeSchema(boolean initializeSchema) {
+ this.initializeSchema = initializeSchema;
+ }
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/chat/memory/cassandra/CassandraChatMemoryAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/chat/memory/cassandra/CassandraChatMemoryAutoConfiguration.java
new file mode 100644
index 000000000..b84011dfe
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/chat/memory/cassandra/CassandraChatMemoryAutoConfiguration.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 - 2024 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.autoconfigure.chat.memory.cassandra;
+
+import com.datastax.oss.driver.api.core.CqlSession;
+import org.springframework.ai.chat.memory.CassandraChatMemory;
+import org.springframework.ai.chat.memory.CassandraChatMemoryConfig;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * @author Mick Semb Wever
+ * @since 1.0.0
+ */
+@AutoConfiguration(after = CassandraAutoConfiguration.class)
+@ConditionalOnClass({ CassandraChatMemory.class, CqlSession.class })
+@EnableConfigurationProperties(CassandraChatMemoryProperties.class)
+public class CassandraChatMemoryAutoConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public CassandraChatMemory chatMemory(CassandraChatMemoryProperties properties, CqlSession cqlSession) {
+
+ var builder = CassandraChatMemoryConfig.builder().withCqlSession(cqlSession);
+
+ builder = builder.withKeyspaceName(properties.getKeyspace())
+ .withTableName(properties.getTable())
+ .withAssistantColumnName(properties.getAssistantColumn())
+ .withUserColumnName(properties.getUserColumn());
+
+ if (!properties.isInitializeSchema()) {
+ builder = builder.disallowSchemaChanges();
+ }
+ if (null != properties.getTimeToLiveSeconds()) {
+ builder = builder.withTimeToLive(properties.getTimeToLiveSeconds());
+ }
+
+ return CassandraChatMemory.create(builder.build());
+ }
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/chat/memory/cassandra/CassandraChatMemoryProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/chat/memory/cassandra/CassandraChatMemoryProperties.java
new file mode 100644
index 000000000..91d2252bc
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/chat/memory/cassandra/CassandraChatMemoryProperties.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2024 - 2024 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.autoconfigure.chat.memory.cassandra;
+
+import java.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.ai.autoconfigure.chat.memory.CommonChatMemoryProperties;
+import org.springframework.ai.chat.memory.CassandraChatMemoryConfig;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.lang.Nullable;
+
+/**
+ * @author Mick Semb Wever
+ * @since 1.0.0
+ */
+@ConfigurationProperties(CassandraChatMemoryProperties.CONFIG_PREFIX)
+public class CassandraChatMemoryProperties extends CommonChatMemoryProperties {
+
+ public static final String CONFIG_PREFIX = "spring.ai.chat.memory.cassandra";
+
+ private static final Logger logger = LoggerFactory.getLogger(CassandraChatMemoryProperties.class);
+
+ private String keyspace = CassandraChatMemoryConfig.DEFAULT_KEYSPACE_NAME;
+
+ private String table = CassandraChatMemoryConfig.DEFAULT_TABLE_NAME;
+
+ private String assistantColumn = CassandraChatMemoryConfig.DEFAULT_ASSISTANT_COLUMN_NAME;
+
+ private String userColumn = CassandraChatMemoryConfig.DEFAULT_USER_COLUMN_NAME;
+
+ private Duration timeToLiveSeconds = null;
+
+ public String getKeyspace() {
+ return this.keyspace;
+ }
+
+ public void setKeyspace(String keyspace) {
+ this.keyspace = keyspace;
+ }
+
+ public String getTable() {
+ return this.table;
+ }
+
+ public void setTable(String table) {
+ this.table = table;
+ }
+
+ public String getAssistantColumn() {
+ return assistantColumn;
+ }
+
+ public void setAssistantColumn(String assistantColumn) {
+ this.assistantColumn = assistantColumn;
+ }
+
+ public String getUserColumn() {
+ return userColumn;
+ }
+
+ public void setUserColumn(String userColumn) {
+ this.userColumn = userColumn;
+ }
+
+ @Nullable
+ public Duration getTimeToLiveSeconds() {
+ return timeToLiveSeconds;
+ }
+
+ public void setTimeToLiveSeconds(Duration timeToLiveSeconds) {
+ this.timeToLiveSeconds = timeToLiveSeconds;
+ }
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/CommonVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/CommonVectorStoreProperties.java
similarity index 83%
rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/CommonVectorStoreProperties.java
rename to spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/CommonVectorStoreProperties.java
index 8dcf45b23..cf429fe1a 100644
--- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/CommonVectorStoreProperties.java
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/CommonVectorStoreProperties.java
@@ -1,4 +1,4 @@
-package org.springframework.ai.autoconfigure;
+package org.springframework.ai.autoconfigure.vectorstore;
/**
* @author Josh Long
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/azure/AzureVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/azure/AzureVectorStoreProperties.java
index d2ad4a8ea..6de3a5730 100644
--- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/azure/AzureVectorStoreProperties.java
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/azure/AzureVectorStoreProperties.java
@@ -15,7 +15,7 @@
*/
package org.springframework.ai.autoconfigure.vectorstore.azure;
-import org.springframework.ai.autoconfigure.CommonVectorStoreProperties;
+import org.springframework.ai.autoconfigure.vectorstore.CommonVectorStoreProperties;
import org.springframework.ai.vectorstore.azure.AzureVectorStore;
import org.springframework.boot.context.properties.ConfigurationProperties;
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/cassandra/CassandraVectorStoreAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/cassandra/CassandraVectorStoreAutoConfiguration.java
index eb053338f..536ae5154 100644
--- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/cassandra/CassandraVectorStoreAutoConfiguration.java
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/cassandra/CassandraVectorStoreAutoConfiguration.java
@@ -54,14 +54,14 @@ public class CassandraVectorStoreAutoConfiguration {
.withIndexName(properties.getIndexName())
.withFixedThreadPoolExecutorSize(properties.getFixedThreadPoolExecutorSize());
- if (properties.getDisallowSchemaCreation()) {
+ if (!properties.isInitializeSchema()) {
builder = builder.disallowSchemaChanges();
}
if (properties.getReturnEmbeddings()) {
builder = builder.returnEmbeddings();
}
- return new CassandraVectorStore(builder.build(), embeddingModel);
+ return CassandraVectorStore.create(builder.build(), embeddingModel);
}
@Bean
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/cassandra/CassandraVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/cassandra/CassandraVectorStoreProperties.java
index 9725e7559..f84a2f0e4 100644
--- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/cassandra/CassandraVectorStoreProperties.java
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/cassandra/CassandraVectorStoreProperties.java
@@ -16,7 +16,10 @@
package org.springframework.ai.autoconfigure.vectorstore.cassandra;
import com.google.api.client.util.Preconditions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.ai.autoconfigure.vectorstore.CommonVectorStoreProperties;
import org.springframework.ai.vectorstore.CassandraVectorStoreConfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -25,10 +28,12 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @since 1.0.0
*/
@ConfigurationProperties(CassandraVectorStoreProperties.CONFIG_PREFIX)
-public class CassandraVectorStoreProperties {
+public class CassandraVectorStoreProperties extends CommonVectorStoreProperties {
public static final String CONFIG_PREFIX = "spring.ai.vectorstore.cassandra";
+ private static final Logger logger = LoggerFactory.getLogger(CassandraVectorStoreProperties.class);
+
private String keyspace = CassandraVectorStoreConfig.DEFAULT_KEYSPACE_NAME;
private String table = CassandraVectorStoreConfig.DEFAULT_TABLE_NAME;
@@ -39,8 +44,6 @@ public class CassandraVectorStoreProperties {
private String embeddingColumnName = CassandraVectorStoreConfig.DEFAULT_EMBEDDING_COLUMN_NAME;
- private boolean disallowSchemaChanges = false;
-
private boolean returnEmbeddings = false;
private int fixedThreadPoolExecutorSize = CassandraVectorStoreConfig.DEFAULT_ADD_CONCURRENCY;
@@ -85,12 +88,16 @@ public class CassandraVectorStoreProperties {
this.embeddingColumnName = embeddingColumnName;
}
+ @Deprecated
public boolean getDisallowSchemaCreation() {
- return this.disallowSchemaChanges;
+ logger.warn("getDisallowSchemaCreation() is deprecated, use isInitializeSchema()");
+ return !super.isInitializeSchema();
}
+ @Deprecated
public void setDisallowSchemaCreation(boolean disallowSchemaCreation) {
- this.disallowSchemaChanges = disallowSchemaCreation;
+ logger.warn("setDisallowSchemaCreation(boolean) is deprecated, use setInitializeSchema(boolean)");
+ super.setInitializeSchema(!disallowSchemaCreation);
}
public boolean getReturnEmbeddings() {
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/chroma/ChromaVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/chroma/ChromaVectorStoreProperties.java
index 8283cb6ac..1768ed81d 100644
--- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/chroma/ChromaVectorStoreProperties.java
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/chroma/ChromaVectorStoreProperties.java
@@ -15,7 +15,7 @@
*/
package org.springframework.ai.autoconfigure.vectorstore.chroma;
-import org.springframework.ai.autoconfigure.CommonVectorStoreProperties;
+import org.springframework.ai.autoconfigure.vectorstore.CommonVectorStoreProperties;
import org.springframework.ai.vectorstore.ChromaVectorStore;
import org.springframework.boot.context.properties.ConfigurationProperties;
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/elasticsearch/ElasticsearchVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/elasticsearch/ElasticsearchVectorStoreProperties.java
index cf456b137..ba0100bb3 100644
--- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/elasticsearch/ElasticsearchVectorStoreProperties.java
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/elasticsearch/ElasticsearchVectorStoreProperties.java
@@ -15,7 +15,7 @@
*/
package org.springframework.ai.autoconfigure.vectorstore.elasticsearch;
-import org.springframework.ai.autoconfigure.CommonVectorStoreProperties;
+import org.springframework.ai.autoconfigure.vectorstore.CommonVectorStoreProperties;
import org.springframework.ai.vectorstore.SimilarityFunction;
import org.springframework.boot.context.properties.ConfigurationProperties;
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/milvus/MilvusVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/milvus/MilvusVectorStoreProperties.java
index 15a0b0cce..2a4b82864 100644
--- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/milvus/MilvusVectorStoreProperties.java
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/milvus/MilvusVectorStoreProperties.java
@@ -15,7 +15,7 @@
*/
package org.springframework.ai.autoconfigure.vectorstore.milvus;
-import org.springframework.ai.autoconfigure.CommonVectorStoreProperties;
+import org.springframework.ai.autoconfigure.vectorstore.CommonVectorStoreProperties;
import org.springframework.ai.vectorstore.MilvusVectorStore;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.Assert;
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/mongo/MongoDBAtlasVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/mongo/MongoDBAtlasVectorStoreProperties.java
index 12c16fdb8..799b58261 100644
--- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/mongo/MongoDBAtlasVectorStoreProperties.java
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/mongo/MongoDBAtlasVectorStoreProperties.java
@@ -15,7 +15,7 @@
*/
package org.springframework.ai.autoconfigure.vectorstore.mongo;
-import org.springframework.ai.autoconfigure.CommonVectorStoreProperties;
+import org.springframework.ai.autoconfigure.vectorstore.CommonVectorStoreProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jVectorStoreProperties.java
index 851cd100a..5d782b8a9 100644
--- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jVectorStoreProperties.java
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jVectorStoreProperties.java
@@ -15,7 +15,7 @@
*/
package org.springframework.ai.autoconfigure.vectorstore.neo4j;
-import org.springframework.ai.autoconfigure.CommonVectorStoreProperties;
+import org.springframework.ai.autoconfigure.vectorstore.CommonVectorStoreProperties;
import org.springframework.ai.vectorstore.Neo4jVectorStore;
import org.springframework.boot.context.properties.ConfigurationProperties;
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStoreProperties.java
index 2d70e23f7..2d5eb2f7f 100644
--- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStoreProperties.java
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStoreProperties.java
@@ -15,7 +15,7 @@
*/
package org.springframework.ai.autoconfigure.vectorstore.oracle;
-import org.springframework.ai.autoconfigure.CommonVectorStoreProperties;
+import org.springframework.ai.autoconfigure.vectorstore.CommonVectorStoreProperties;
import org.springframework.ai.vectorstore.OracleVectorStore;
import org.springframework.boot.context.properties.ConfigurationProperties;
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/pgvector/PgVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/pgvector/PgVectorStoreProperties.java
index 783041c76..b45541746 100644
--- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/pgvector/PgVectorStoreProperties.java
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/pgvector/PgVectorStoreProperties.java
@@ -15,7 +15,7 @@
*/
package org.springframework.ai.autoconfigure.vectorstore.pgvector;
-import org.springframework.ai.autoconfigure.CommonVectorStoreProperties;
+import org.springframework.ai.autoconfigure.vectorstore.CommonVectorStoreProperties;
import org.springframework.ai.vectorstore.PgVectorStore;
import org.springframework.ai.vectorstore.PgVectorStore.PgDistanceType;
import org.springframework.ai.vectorstore.PgVectorStore.PgIndexType;
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreProperties.java
index 795be25b8..880f6925d 100644
--- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreProperties.java
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreProperties.java
@@ -15,7 +15,7 @@
*/
package org.springframework.ai.autoconfigure.vectorstore.qdrant;
-import org.springframework.ai.autoconfigure.CommonVectorStoreProperties;
+import org.springframework.ai.autoconfigure.vectorstore.CommonVectorStoreProperties;
import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore;
import org.springframework.boot.context.properties.ConfigurationProperties;
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreProperties.java
index 8edff4ba0..eb79a1a40 100644
--- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreProperties.java
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreProperties.java
@@ -15,7 +15,7 @@
*/
package org.springframework.ai.autoconfigure.vectorstore.redis;
-import org.springframework.ai.autoconfigure.CommonVectorStoreProperties;
+import org.springframework.ai.autoconfigure.vectorstore.CommonVectorStoreProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreProperties.java
index 654a22409..7ff377963 100644
--- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreProperties.java
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreProperties.java
@@ -17,7 +17,7 @@ package org.springframework.ai.autoconfigure.vectorstore.weaviate;
import java.util.Map;
-import org.springframework.ai.autoconfigure.CommonVectorStoreProperties;
+import org.springframework.ai.autoconfigure.vectorstore.CommonVectorStoreProperties;
import org.springframework.ai.vectorstore.WeaviateVectorStore.WeaviateVectorStoreConfig;
import org.springframework.ai.vectorstore.WeaviateVectorStore.WeaviateVectorStoreConfig.ConsistentLevel;
import org.springframework.ai.vectorstore.WeaviateVectorStore.WeaviateVectorStoreConfig.MetadataField;
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index c6c8d7a28..bcc472eb6 100644
--- a/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -41,4 +41,5 @@ org.springframework.ai.autoconfigure.vectorstore.opensearch.OpenSearchVectorStor
org.springframework.ai.autoconfigure.moonshot.MoonshotAutoConfiguration
org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration
org.springframework.ai.autoconfigure.minimax.MiniMaxAutoConfiguration
-org.springframework.ai.autoconfigure.vertexai.embedding.VertexAiEmbeddingAutoConfiguration
\ No newline at end of file
+org.springframework.ai.autoconfigure.vertexai.embedding.VertexAiEmbeddingAutoConfiguration
+org.springframework.ai.autoconfigure.chat.memory.cassandra.CassandraAutoConfiguration
\ No newline at end of file
diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/chat/memory/cassandra/CassandraChatMemoryAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/chat/memory/cassandra/CassandraChatMemoryAutoConfigurationIT.java
new file mode 100644
index 000000000..86df7f40c
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/chat/memory/cassandra/CassandraChatMemoryAutoConfigurationIT.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2024 - 2024 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.autoconfigure.chat.memory.cassandra;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.ai.chat.memory.CassandraChatMemory;
+import org.springframework.ai.chat.messages.AssistantMessage;
+import org.springframework.ai.chat.messages.MessageType;
+import org.springframework.ai.chat.messages.UserMessage;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.testcontainers.containers.CassandraContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+import com.datastax.driver.core.utils.UUIDs;
+
+/**
+ * @author Mick Semb Wever
+ * @since 1.0.0
+ */
+@Testcontainers
+class CassandraChatMemoryAutoConfigurationIT {
+
+ static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("cassandra");
+
+ @Container
+ static CassandraContainer cassandraContainer = new CassandraContainer(DEFAULT_IMAGE_NAME.withTag("5.0"));
+
+ private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withConfiguration(
+ AutoConfigurations.of(CassandraChatMemoryAutoConfiguration.class, CassandraAutoConfiguration.class))
+ .withPropertyValues("spring.ai.chat.memory.cassandra.keyspace=test_autoconfigure");
+
+ @Test
+ void addAndGet() {
+ contextRunner.withPropertyValues("spring.cassandra.contactPoints=" + getContactPointHost())
+ .withPropertyValues("spring.cassandra.port=" + getContactPointPort())
+ .withPropertyValues("spring.cassandra.localDatacenter=" + cassandraContainer.getLocalDatacenter())
+
+ .run(context -> {
+ CassandraChatMemory memory = context.getBean(CassandraChatMemory.class);
+
+ String sessionId = UUIDs.timeBased().toString();
+ assertThat(memory.get(sessionId, Integer.MAX_VALUE)).isEmpty();
+
+ memory.add(sessionId, new UserMessage("test question"));
+
+ assertThat(memory.get(sessionId, Integer.MAX_VALUE)).hasSize(1);
+ assertThat(memory.get(sessionId, Integer.MAX_VALUE).get(0).getMessageType())
+ .isEqualTo(MessageType.USER);
+ assertThat(memory.get(sessionId, Integer.MAX_VALUE).get(0).getContent()).isEqualTo("test question");
+
+ memory.clear(sessionId);
+ assertThat(memory.get(sessionId, Integer.MAX_VALUE)).isEmpty();
+
+ memory.add(sessionId, List.of(new UserMessage("test question"), new AssistantMessage("test answer")));
+
+ assertThat(memory.get(sessionId, Integer.MAX_VALUE)).hasSize(2);
+ assertThat(memory.get(sessionId, Integer.MAX_VALUE).get(1).getMessageType())
+ .isEqualTo(MessageType.USER);
+ assertThat(memory.get(sessionId, Integer.MAX_VALUE).get(1).getContent()).isEqualTo("test question");
+ assertThat(memory.get(sessionId, Integer.MAX_VALUE).get(0).getMessageType())
+ .isEqualTo(MessageType.ASSISTANT);
+ assertThat(memory.get(sessionId, Integer.MAX_VALUE).get(0).getContent()).isEqualTo("test answer");
+
+ });
+ }
+
+ private String getContactPointHost() {
+ return cassandraContainer.getContactPoint().getHostString();
+ }
+
+ private String getContactPointPort() {
+ return String.valueOf(cassandraContainer.getContactPoint().getPort());
+ }
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/chat/memory/cassandra/CassandraChatMemoryPropertiesTest.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/chat/memory/cassandra/CassandraChatMemoryPropertiesTest.java
new file mode 100644
index 000000000..e3df66722
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/chat/memory/cassandra/CassandraChatMemoryPropertiesTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2024 - 2024 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.autoconfigure.chat.memory.cassandra;
+
+import java.time.Duration;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.ai.chat.memory.CassandraChatMemoryConfig;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Mick Semb Wever
+ * @since 1.0.0
+ */
+class CassandraChatMemoryPropertiesTest {
+
+ @Test
+ void defaultValues() {
+ var props = new CassandraChatMemoryProperties();
+ assertThat(props.getKeyspace()).isEqualTo(CassandraChatMemoryConfig.DEFAULT_KEYSPACE_NAME);
+ assertThat(props.getTable()).isEqualTo(CassandraChatMemoryConfig.DEFAULT_TABLE_NAME);
+ assertThat(props.getAssistantColumn()).isEqualTo(CassandraChatMemoryConfig.DEFAULT_ASSISTANT_COLUMN_NAME);
+ assertThat(props.getUserColumn()).isEqualTo(CassandraChatMemoryConfig.DEFAULT_USER_COLUMN_NAME);
+ assertThat(props.getTimeToLiveSeconds()).isNull();
+ assertThat(props.isInitializeSchema()).isTrue();
+ }
+
+ @Test
+ void customValues() {
+ var props = new CassandraChatMemoryProperties();
+ props.setKeyspace("my_keyspace");
+ props.setTable("my_table");
+ props.setAssistantColumn("my_assistant_column");
+ props.setUserColumn("my_user_column");
+ props.setTimeToLiveSeconds(Duration.ofDays(1));
+ props.setInitializeSchema(false);
+
+ assertThat(props.getKeyspace()).isEqualTo("my_keyspace");
+ assertThat(props.getTable()).isEqualTo("my_table");
+ assertThat(props.getAssistantColumn()).isEqualTo("my_assistant_column");
+ assertThat(props.getUserColumn()).isEqualTo("my_user_column");
+ assertThat(props.getTimeToLiveSeconds()).isEqualTo(Duration.ofDays(1));
+ assertThat(props.isInitializeSchema()).isFalse();
+ }
+
+}
diff --git a/vector-stores/spring-ai-cassandra-store/src/main/java/org/springframework/ai/cassandra/SchemaUtil.java b/vector-stores/spring-ai-cassandra-store/src/main/java/org/springframework/ai/cassandra/SchemaUtil.java
new file mode 100644
index 000000000..f945b9db8
--- /dev/null
+++ b/vector-stores/spring-ai-cassandra-store/src/main/java/org/springframework/ai/cassandra/SchemaUtil.java
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ *
+ * See the NOTICE file distributed with this work for additional information
+ * regarding copyright ownership.
+ */
+package org.springframework.ai.cassandra;
+
+import com.datastax.oss.driver.api.core.CqlSession;
+import com.datastax.oss.driver.api.core.cql.SimpleStatement;
+import com.datastax.oss.driver.api.querybuilder.SchemaBuilder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Duration;
+
+/**
+ * @author Mick Semb Wever
+ * @since 1.0.0
+ */
+public final class SchemaUtil {
+
+ private static final Logger logger = LoggerFactory.getLogger(SchemaUtil.class);
+
+ private SchemaUtil() {
+
+ }
+
+ public static void checkSchemaAgreement(CqlSession session) throws IllegalStateException {
+ if (!session.checkSchemaAgreement()) {
+ logger.warn("Waiting for cluster schema agreement, sleeping 10s…");
+ try {
+ Thread.sleep(Duration.ofSeconds(10).toMillis());
+ }
+ catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException(ex);
+ }
+ if (!session.checkSchemaAgreement()) {
+ logger.error("no cluster schema agreement still, continuing, let's hope this works…");
+ }
+ }
+ }
+
+ public static void ensureKeyspaceExists(CqlSession session, String keyspaceName) {
+ if (session.getMetadata().getKeyspace(keyspaceName).isEmpty()) {
+ SimpleStatement keyspaceStmt = SchemaBuilder.createKeyspace(keyspaceName)
+ .ifNotExists()
+ .withSimpleStrategy(1)
+ .build();
+
+ logger.debug("Executing {}", keyspaceStmt.getQuery());
+ session.execute(keyspaceStmt);
+ }
+ }
+
+}
diff --git a/vector-stores/spring-ai-cassandra-store/src/main/java/org/springframework/ai/chat/memory/CassandraChatMemory.java b/vector-stores/spring-ai-cassandra-store/src/main/java/org/springframework/ai/chat/memory/CassandraChatMemory.java
new file mode 100644
index 000000000..7dc82f370
--- /dev/null
+++ b/vector-stores/spring-ai-cassandra-store/src/main/java/org/springframework/ai/chat/memory/CassandraChatMemory.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2024 - 2024 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.chat.memory;
+
+import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder;
+import com.datastax.oss.driver.api.core.cql.PreparedStatement;
+import com.datastax.oss.driver.api.core.cql.Row;
+import com.datastax.oss.driver.api.querybuilder.QueryBuilder;
+import com.datastax.oss.driver.api.querybuilder.delete.Delete;
+import com.datastax.oss.driver.api.querybuilder.delete.DeleteSelection;
+import com.datastax.oss.driver.api.querybuilder.insert.InsertInto;
+import com.datastax.oss.driver.api.querybuilder.insert.RegularInsert;
+import com.datastax.oss.driver.api.querybuilder.select.Select;
+import com.datastax.oss.driver.shaded.guava.common.base.Preconditions;
+
+import org.springframework.ai.chat.memory.CassandraChatMemoryConfig.SchemaColumn;
+import org.springframework.ai.chat.messages.AssistantMessage;
+import org.springframework.ai.chat.messages.Message;
+import org.springframework.ai.chat.messages.UserMessage;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Create a CassandraChatMemory like
+CassandraChatMemory.create(CassandraChatMemoryConfig.builder().withTimeToLive(Duration.ofDays(1)).build());
+
+ *
+ * For example @see org.springframework.ai.chat.memory.CassandraChatMemory
+ *
+ * @author Mick Semb Wever
+ * @since 1.0.0
+ */
+public final class CassandraChatMemory implements ChatMemory {
+
+ public static final String CONVERSATION_TS = CassandraChatMemory.class.getSimpleName() + "_message_timestamp";
+
+ final CassandraChatMemoryConfig conf;
+
+ private final PreparedStatement addUserStmt, addAssistantStmt, getStmt, deleteStmt;
+
+ public static CassandraChatMemory create(CassandraChatMemoryConfig conf) {
+ return new CassandraChatMemory(conf);
+ }
+
+ public CassandraChatMemory(CassandraChatMemoryConfig config) {
+ this.conf = config;
+ this.conf.ensureSchemaExists();
+ this.addUserStmt = prepareAddStmt(this.conf.userColumn);
+ this.addAssistantStmt = prepareAddStmt(this.conf.assistantColumn);
+ this.getStmt = prepareGetStatement();
+ this.deleteStmt = prepareDeleteStmt();
+ }
+
+ @Override
+ public void add(String conversationId, List messages) {
+ final AtomicLong instantSeq = new AtomicLong(Instant.now().toEpochMilli());
+ messages.forEach((msg) -> {
+ if (msg.getMetadata().containsKey(CONVERSATION_TS)) {
+ msg.getMetadata().put(CONVERSATION_TS, Instant.ofEpochMilli(instantSeq.getAndIncrement()));
+ }
+ add(conversationId, msg);
+ });
+ }
+
+ @Override
+ public void add(String sessionId, Message msg) {
+
+ Preconditions.checkArgument(
+ !msg.getMetadata().containsKey(CONVERSATION_TS)
+ || msg.getMetadata().get(CONVERSATION_TS) instanceof Instant,
+ "messages only accept metadata '%s' entries of type Instant", CONVERSATION_TS);
+
+ msg.getMetadata().putIfAbsent(CONVERSATION_TS, Instant.now());
+
+ PreparedStatement stmt;
+ switch (msg.getMessageType()) {
+ case USER -> stmt = addUserStmt;
+ case ASSISTANT -> stmt = addAssistantStmt;
+ default -> throw new IllegalArgumentException("Cant add type " + msg);
+ }
+
+ List