diff --git a/spring-ai-spring-boot-autoconfigure/pom.xml b/spring-ai-spring-boot-autoconfigure/pom.xml
index 84bd54a188..894ae1e83a 100644
--- a/spring-ai-spring-boot-autoconfigure/pom.xml
+++ b/spring-ai-spring-boot-autoconfigure/pom.xml
@@ -281,6 +281,13 @@
true
+
+ org.springframework.ai
+ spring-ai-gemfire-store
+ ${project.parent.version}
+ true
+
+
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/gemfire/GemFireConnectionDetails.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/gemfire/GemFireConnectionDetails.java
new file mode 100644
index 0000000000..b74eb61e2f
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/gemfire/GemFireConnectionDetails.java
@@ -0,0 +1,29 @@
+/*
+ * 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.vectorstore.gemfire;
+
+import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
+
+/**
+ * @author Philipp Kessler
+ */
+public interface GemFireConnectionDetails extends ConnectionDetails {
+
+ String getHost();
+
+ int getPort();
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/gemfire/GemFireVectorStoreAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/gemfire/GemFireVectorStoreAutoConfiguration.java
new file mode 100644
index 0000000000..ddbd384c10
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/gemfire/GemFireVectorStoreAutoConfiguration.java
@@ -0,0 +1,84 @@
+/*
+ * 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.vectorstore.gemfire;
+
+import org.springframework.ai.embedding.EmbeddingModel;
+import org.springframework.ai.vectorstore.GemFireVectorStore;
+import org.springframework.ai.vectorstore.GemFireVectorStore.GemFireVectorStoreConfig;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+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 Philipp Kessler
+ */
+@AutoConfiguration
+@ConditionalOnClass({ GemFireVectorStore.class, EmbeddingModel.class })
+@EnableConfigurationProperties({ GemFireVectorStoreProperties.class })
+public class GemFireVectorStoreAutoConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean(GemFireConnectionDetails.class)
+ public PropertiesGemFireConnectionDetails gemFireConnectionDetails(GemFireVectorStoreProperties properties) {
+ return new PropertiesGemFireConnectionDetails(properties);
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public GemFireVectorStore vectorStore(EmbeddingModel embeddingModel, GemFireConnectionDetails connectionDetails,
+ GemFireVectorStoreProperties properties) {
+ var vectoreStoreConfig = GemFireVectorStoreConfig.builder()
+ .withHost(connectionDetails.getHost())
+ .withPort(connectionDetails.getPort())
+ .withIndex(properties.getIndex())
+ .withDocumentField(properties.getDocumentField())
+ .withTopK(properties.getTopK())
+ .withTopKPerBucket(properties.getTopKPerBucket())
+ .withSslEnabled(properties.isSslEnabled())
+ .withConnectionTimeout(properties.getConnectionTimeout())
+ .withRequestTimeout(properties.getRequestTimeout())
+ .build();
+
+ var vectorStore = new GemFireVectorStore(vectoreStoreConfig, embeddingModel);
+
+ vectorStore.setIndexName(properties.getIndex());
+
+ return vectorStore;
+ }
+
+ private static class PropertiesGemFireConnectionDetails implements GemFireConnectionDetails {
+
+ private final GemFireVectorStoreProperties properties;
+
+ public PropertiesGemFireConnectionDetails(GemFireVectorStoreProperties properties) {
+ this.properties = properties;
+ }
+
+ @Override
+ public String getHost() {
+ return properties.getHost();
+ }
+
+ @Override
+ public int getPort() {
+ return properties.getPort();
+ }
+
+ }
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/gemfire/GemFireVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/gemfire/GemFireVectorStoreProperties.java
new file mode 100644
index 0000000000..f0c9efe43c
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/gemfire/GemFireVectorStoreProperties.java
@@ -0,0 +1,119 @@
+/*
+ * 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.vectorstore.gemfire;
+
+import org.springframework.ai.vectorstore.GemFireVectorStore;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * @author Philipp Kessler
+ */
+@ConfigurationProperties(GemFireVectorStoreProperties.CONFIG_PREFIX)
+public class GemFireVectorStoreProperties {
+
+ public static final String CONFIG_PREFIX = "spring.ai.vectorstore.gemfire";
+
+ private String host;
+
+ private int port = GemFireVectorStore.DEFAULT_PORT;
+
+ private boolean sslEnabled;
+
+ private long connectionTimeout;
+
+ private long requestTimeout;
+
+ private String index;
+
+ private int topK = GemFireVectorStore.DEFAULT_TOP_K;
+
+ private int topKPerBucket = GemFireVectorStore.DEFAULT_TOP_K_PER_BUCKET;
+
+ private String documentField = GemFireVectorStore.DEFAULT_DOCUMENT_FIELD;
+
+ 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 boolean isSslEnabled() {
+ return sslEnabled;
+ }
+
+ public void setSslEnabled(boolean sslEnabled) {
+ this.sslEnabled = sslEnabled;
+ }
+
+ public long getConnectionTimeout() {
+ return connectionTimeout;
+ }
+
+ public void setConnectionTimeout(long connectionTimeout) {
+ this.connectionTimeout = connectionTimeout;
+ }
+
+ public long getRequestTimeout() {
+ return requestTimeout;
+ }
+
+ public void setRequestTimeout(long requestTimeout) {
+ this.requestTimeout = requestTimeout;
+ }
+
+ public String getIndex() {
+ return index;
+ }
+
+ public void setIndex(String index) {
+ this.index = index;
+ }
+
+ public int getTopKPerBucket() {
+ return topKPerBucket;
+ }
+
+ public void setTopKPerBucket(int topKPerBucket) {
+ this.topKPerBucket = topKPerBucket;
+ }
+
+ public int getTopK() {
+ return topK;
+ }
+
+ public void setTopK(int topK) {
+ this.topK = topK;
+ }
+
+ public String getDocumentField() {
+ return documentField;
+ }
+
+ public void setDocumentField(String documentField) {
+ this.documentField = documentField;
+ }
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/gemfire/GemFireVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/gemfire/GemFireVectorStoreAutoConfigurationIT.java
new file mode 100644
index 0000000000..7dce84ffb4
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/gemfire/GemFireVectorStoreAutoConfigurationIT.java
@@ -0,0 +1,113 @@
+/*
+ * 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.vectorstore.gemfire;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.File;
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.springframework.ai.ResourceUtils;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.embedding.EmbeddingModel;
+import org.springframework.ai.transformers.TransformersEmbeddingModel;
+import org.springframework.ai.vectorstore.SearchRequest;
+import org.springframework.ai.vectorstore.VectorStore;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.testcontainers.containers.DockerComposeContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+/**
+ * @author Philipp Kessler
+ */
+@Testcontainers
+class GemFireVectorStoreAutoConfigurationIT {
+
+ private static DockerComposeContainer gemFireContainer;
+
+ @BeforeAll
+ public static void beforeAll() {
+
+ gemFireContainer = new DockerComposeContainer(new File("src/test/resources/gemfire/docker-compose.yml"))
+ .withExposedService("gemfire", 7070,
+ Wait.forHttp("/gemfire-api/v1/ping")
+ .forStatusCode(200)
+ .withStartupTimeout(Duration.ofSeconds(100)));
+ gemFireContainer.start();
+ }
+
+ @AfterAll
+ public static void afterAll() {
+ gemFireContainer.stop();
+ }
+
+ List documents = List.of(
+ new Document(ResourceUtils.getText("classpath:/test/data/spring.ai.txt"), Map.of("spring", "great")),
+ new Document(ResourceUtils.getText("classpath:/test/data/time.shelter.txt")), new Document(
+ ResourceUtils.getText("classpath:/test/data/great.depression.txt"), Map.of("depression", "bad")));
+
+ private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withConfiguration(AutoConfigurations.of(GemFireVectorStoreAutoConfiguration.class))
+ .withUserConfiguration(Config.class)
+ .withPropertyValues("spring.ai.vectorstore.gemfire.index=test_index",
+ "spring.ai.vectorstore.gemfire.documentField=doc_chunk",
+ "spring.ai.vectorstore.gemfire.host=" + gemFireContainer.getServiceHost("gemfire", 7070),
+ "spring.ai.vectorstore.gemfire.port=" + gemFireContainer.getServicePort("gemfire", 7070));
+
+ @Test
+ void addAndSearch() {
+ contextRunner
+
+ .run(context -> {
+ VectorStore vectorStore = context.getBean(VectorStore.class);
+ vectorStore.add(documents);
+
+ List results = vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(1));
+
+ assertThat(results).hasSize(1);
+ Document resultDoc = results.get(0);
+ assertThat(resultDoc.getId()).isEqualTo(documents.get(0).getId());
+ assertThat(resultDoc.getContent()).contains(
+ "Spring AI provides abstractions that serve as the foundation for developing AI applications.");
+
+ // Remove all documents from the store
+ vectorStore.delete(documents.stream().map(doc -> doc.getId()).toList());
+
+ results = vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(1));
+ assertThat(results).isEmpty();
+ });
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ static class Config {
+
+ @Bean
+ public EmbeddingModel embeddingModel() {
+ return new TransformersEmbeddingModel();
+ }
+
+ }
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/gemfire/GemFireVectorStorePropertiesTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/gemfire/GemFireVectorStorePropertiesTests.java
new file mode 100644
index 0000000000..97fb8367c2
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/gemfire/GemFireVectorStorePropertiesTests.java
@@ -0,0 +1,66 @@
+/*
+ * 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.vectorstore.gemfire;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.ai.vectorstore.GemFireVectorStore;
+
+/**
+ * @author Philipp Kessler
+ */
+class GemFireVectorStorePropertiesTests {
+
+ @Test
+ void defaultValues() {
+ var props = new GemFireVectorStoreProperties();
+ assertThat(props.getHost()).isNull();
+ assertThat(props.getPort()).isEqualTo(GemFireVectorStore.DEFAULT_PORT);
+ assertThat(props.isSslEnabled()).isEqualTo(false);
+ assertThat(props.getConnectionTimeout()).isEqualTo(0);
+ assertThat(props.getRequestTimeout()).isEqualTo(0);
+ assertThat(props.getIndex()).isNull();
+ assertThat(props.getTopK()).isEqualTo(GemFireVectorStore.DEFAULT_TOP_K);
+ assertThat(props.getTopKPerBucket()).isEqualTo(GemFireVectorStore.DEFAULT_TOP_K_PER_BUCKET);
+ assertThat(props.getDocumentField()).isEqualTo(GemFireVectorStore.DEFAULT_DOCUMENT_FIELD);
+ }
+
+ @Test
+ void customValues() {
+ var props = new GemFireVectorStoreProperties();
+ props.setHost("127.0.0.1");
+ props.setPort(9043);
+ props.setSslEnabled(true);
+ props.setConnectionTimeout(100);
+ props.setRequestTimeout(200);
+ props.setIndex("index");
+ props.setTopK(10);
+ props.setTopKPerBucket(20);
+ props.setDocumentField("document");
+
+ assertThat(props.getHost()).isEqualTo("127.0.0.1");
+ assertThat(props.getPort()).isEqualTo(9043);
+ assertThat(props.isSslEnabled()).isTrue();
+ assertThat(props.getConnectionTimeout()).isEqualTo(100);
+ assertThat(props.getRequestTimeout()).isEqualTo(200);
+ assertThat(props.getIndex()).isEqualTo("index");
+ assertThat(props.getTopK()).isEqualTo(10);
+ assertThat(props.getTopKPerBucket()).isEqualTo(20);
+ assertThat(props.getDocumentField()).isEqualTo("document");
+ }
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/test/resources/gemfire/docker-compose.yml b/spring-ai-spring-boot-autoconfigure/src/test/resources/gemfire/docker-compose.yml
new file mode 100644
index 0000000000..c62e75a2ac
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/test/resources/gemfire/docker-compose.yml
@@ -0,0 +1,21 @@
+version: '3.5'
+
+services:
+ gemfire:
+ image: gemfire/gemfire:9.15.11
+ ports:
+ - "7070:7070"
+ environment:
+ - ACCEPT_TERMS=y
+ volumes:
+ - ./extensions:/gemfire/extensions
+ command: gfsh start server --start-rest-api
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:7070/gemfire-api/v1/ping"]
+ interval: 5s
+ timeout: 5s
+ retries: 5
+
+networks:
+ default:
+ name: gemfire
diff --git a/spring-ai-spring-boot-autoconfigure/src/test/resources/gemfire/extensions/vmware-gemfire-vectordb-1.1.0.gfm b/spring-ai-spring-boot-autoconfigure/src/test/resources/gemfire/extensions/vmware-gemfire-vectordb-1.1.0.gfm
new file mode 100644
index 0000000000..b61b188d47
Binary files /dev/null and b/spring-ai-spring-boot-autoconfigure/src/test/resources/gemfire/extensions/vmware-gemfire-vectordb-1.1.0.gfm differ
diff --git a/vector-stores/spring-ai-gemfire-store/src/main/java/org/springframework/ai/vectorstore/GemFireVectorStore.java b/vector-stores/spring-ai-gemfire-store/src/main/java/org/springframework/ai/vectorstore/GemFireVectorStore.java
index c91fff95df..959c9d8edb 100644
--- a/vector-stores/spring-ai-gemfire-store/src/main/java/org/springframework/ai/vectorstore/GemFireVectorStore.java
+++ b/vector-stores/spring-ai-gemfire-store/src/main/java/org/springframework/ai/vectorstore/GemFireVectorStore.java
@@ -176,15 +176,15 @@ public GemFireVectorStoreConfig build() {
}
- private static final int DEFAULT_PORT = 9090;
+ public static final int DEFAULT_PORT = 9090;
- public static final String DEFAULT_URI = "http{ssl}://{host}:{port}/gemfire-vectordb/v1/indexes";
+ public static final int DEFAULT_TOP_K_PER_BUCKET = 10;
- private static final int DEFAULT_TOP_K_PER_BUCKET = 10;
+ public static final int DEFAULT_TOP_K = 10;
- private static final int DEFAULT_TOP_K = 10;
+ public static final String DEFAULT_DOCUMENT_FIELD = "document";
- private static final String DEFAULT_DOCUMENT_FIELD = "document";
+ private static final String DEFAULT_URI = "http{ssl}://{host}:{port}/gemfire-vectordb/v1/indexes";
public String indexName;