diff --git a/pom.xml b/pom.xml
index 077de7aed..a70d22acd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -77,6 +77,7 @@
models/spring-ai-zhipuai
models/spring-ai-moonshot
spring-ai-spring-boot-starters/spring-ai-starter-anthropic
+ spring-ai-spring-boot-starters/spring-ai-starter-aws-opensearch-store
spring-ai-spring-boot-starters/spring-ai-starter-azure-openai
spring-ai-spring-boot-starters/spring-ai-starter-bedrock-ai
spring-ai-spring-boot-starters/spring-ai-starter-huggingface
@@ -177,6 +178,7 @@
1.9.1
0.5.0
2.10.1
+ 2.20.161
5.3.1
diff --git a/spring-ai-bom/pom.xml b/spring-ai-bom/pom.xml
index eec22e618..1614278c3 100644
--- a/spring-ai-bom/pom.xml
+++ b/spring-ai-bom/pom.xml
@@ -307,6 +307,12 @@
${project.version}
+
+ org.springframework.ai
+ spring-ai-aws-opensearch-store-spring-boot-starter
+ ${project.version}
+
+
org.springframework.ai
spring-ai-opensearch-store-spring-boot-starter
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/opensearch.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/opensearch.adoc
index 83ef08983..c514188b3 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/opensearch.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/opensearch.adoc
@@ -114,7 +114,7 @@ List results = vectorStore.similaritySearch(SearchRequest.query("Sprin
=== Configuration properties
-You can use the following properties in your Spring Boot configuration to customize the PGVector vector store.
+You can use the following properties in your Spring Boot configuration to customize the OpenSearch vector store.
[cols="2,5,1"]
|===
@@ -134,6 +134,11 @@ fields are stored and indexed. |
}
}
}
+|`spring.opensearch.aws.host`| Hostname of the OpenSearch instance. | -
+|`spring.opensearch.aws.service-name`| AWS service name for the OpenSearch instance. | -
+|`spring.opensearch.aws.access-key`| AWS access key for the OpenSearch instance. | -
+|`spring.opensearch.aws.secret-key`| AWS secret key for the OpenSearch instance. | -
+|`spring.opensearch.aws.region`| AWS region for the OpenSearch instance. | -
|===
=== Customizing OpenSearch Client Configuration
@@ -148,9 +153,8 @@ To enable it, add the following dependency to your project's Maven `pom.xml` fil
[source,xml]
----
- software.amazon.awssdk
- apache-client
- 2.25.40
+ org.springframework.ai
+ spring-ai-aws-opensearch-store-spring-boot-starter
----
@@ -159,24 +163,7 @@ or to your Gradle `build.gradle` build file.
[source,groovy]
----
dependencies {
- implementation 'software.amazon.awssdk:apache-client:2.25.40'
-}
-----
-
-Here is an example of the needed bean:
-
-[source,java]
-----
-@Bean
-public OpenSearchClient openSearchClient() {
- return new OpenSearchClient(
- new AwsSdk2Transport(
- ApacheHttpClient.builder().build(),
- "search-...us-west-2.es.amazonaws.com", // OpenSearch endpoint, without https://
- "es",
- Region.US_WEST_2, // signing service region
- AwsSdk2TransportOptions.builder().build())
- );
+ implementation 'org.springframework.ai:spring-ai-aws-opensearch-store-spring-boot-starter'
}
----
diff --git a/spring-ai-spring-boot-autoconfigure/pom.xml b/spring-ai-spring-boot-autoconfigure/pom.xml
index 9a59492fe..9c7688b49 100644
--- a/spring-ai-spring-boot-autoconfigure/pom.xml
+++ b/spring-ai-spring-boot-autoconfigure/pom.xml
@@ -342,6 +342,13 @@
true
+
+ software.amazon.awssdk
+ apache-client
+ ${awssdk.version}
+ true
+
+
@@ -435,6 +442,12 @@
test
+
+ org.testcontainers
+ localstack
+ test
+
+
org.testcontainers
milvus
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/opensearch/OpenSearchVectorStoreAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/opensearch/OpenSearchVectorStoreAutoConfiguration.java
index 1763aabc0..97c8a2055 100644
--- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/opensearch/OpenSearchVectorStoreAutoConfiguration.java
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/opensearch/OpenSearchVectorStoreAutoConfiguration.java
@@ -20,14 +20,24 @@ import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
import org.apache.hc.core5.http.HttpHost;
import org.opensearch.client.opensearch.OpenSearchClient;
+import org.opensearch.client.transport.OpenSearchTransport;
+import org.opensearch.client.transport.aws.AwsSdk2Transport;
+import org.opensearch.client.transport.aws.AwsSdk2TransportOptions;
import org.opensearch.client.transport.httpclient5.ApacheHttpClient5TransportBuilder;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.OpenSearchVectorStore;
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.condition.ConditionalOnMissingClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.http.SdkHttpClient;
+import software.amazon.awssdk.http.apache.ApacheHttpClient;
+import software.amazon.awssdk.regions.Region;
import java.net.URISyntaxException;
import java.util.List;
@@ -48,45 +58,76 @@ public class OpenSearchVectorStoreAutoConfiguration {
@ConditionalOnMissingBean
OpenSearchVectorStore vectorStore(OpenSearchVectorStoreProperties properties, OpenSearchClient openSearchClient,
EmbeddingModel embeddingModel) {
- return new OpenSearchVectorStore(
- Optional.ofNullable(properties.getIndexName()).orElse(OpenSearchVectorStore.DEFAULT_INDEX_NAME),
- openSearchClient, embeddingModel, Optional.ofNullable(properties.getMappingJson())
- .orElse(OpenSearchVectorStore.DEFAULT_MAPPING_EMBEDDING_TYPE_KNN_VECTOR_DIMENSION_1536));
+ var indexName = Optional.ofNullable(properties.getIndexName()).orElse(OpenSearchVectorStore.DEFAULT_INDEX_NAME);
+ var mappingJson = Optional.ofNullable(properties.getMappingJson())
+ .orElse(OpenSearchVectorStore.DEFAULT_MAPPING_EMBEDDING_TYPE_KNN_VECTOR_DIMENSION_1536);
+ return new OpenSearchVectorStore(indexName, openSearchClient, embeddingModel, mappingJson);
}
- @Bean
- @ConditionalOnMissingBean
- OpenSearchClient openSearchClient(OpenSearchConnectionDetails connectionDetails) {
- HttpHost[] httpHosts = connectionDetails.getUris()
- .stream()
- .map(s -> createHttpHost(s))
- .toArray(HttpHost[]::new);
- ApacheHttpClient5TransportBuilder transportBuilder = ApacheHttpClient5TransportBuilder.builder(httpHosts);
+ @Configuration(proxyBeanMethods = false)
+ @ConditionalOnMissingClass({ "software.amazon.awssdk.regions.Region",
+ "software.amazon.awssdk.http.apache.ApacheHttpClient" })
+ static class OpenSearchConfiguration {
- Optional.ofNullable(connectionDetails.getUsername())
- .map(username -> createBasicCredentialsProvider(httpHosts[0], username, connectionDetails.getPassword()))
- .ifPresent(basicCredentialsProvider -> transportBuilder
- .setHttpClientConfigCallback(httpAsyncClientBuilder -> httpAsyncClientBuilder
- .setDefaultCredentialsProvider(basicCredentialsProvider)));
+ @Bean
+ @ConditionalOnMissingBean
+ OpenSearchClient openSearchClient(OpenSearchVectorStoreProperties properties) {
+ HttpHost[] httpHosts = properties.getUris().stream().map(s -> createHttpHost(s)).toArray(HttpHost[]::new);
+ ApacheHttpClient5TransportBuilder transportBuilder = ApacheHttpClient5TransportBuilder.builder(httpHosts);
- return new OpenSearchClient(transportBuilder.build());
- }
-
- private BasicCredentialsProvider createBasicCredentialsProvider(HttpHost httpHost, String username,
- String password) {
- BasicCredentialsProvider basicCredentialsProvider = new BasicCredentialsProvider();
- basicCredentialsProvider.setCredentials(new AuthScope(httpHost),
- new UsernamePasswordCredentials(username, password.toCharArray()));
- return basicCredentialsProvider;
- }
-
- private HttpHost createHttpHost(String s) {
- try {
- return HttpHost.create(s);
+ Optional.ofNullable(properties.getUsername())
+ .map(username -> createBasicCredentialsProvider(httpHosts[0], username, properties.getPassword()))
+ .ifPresent(basicCredentialsProvider -> transportBuilder
+ .setHttpClientConfigCallback(httpAsyncClientBuilder -> httpAsyncClientBuilder
+ .setDefaultCredentialsProvider(basicCredentialsProvider)));
+ return new OpenSearchClient(transportBuilder.build());
}
- catch (URISyntaxException e) {
- throw new RuntimeException(e);
+
+ private BasicCredentialsProvider createBasicCredentialsProvider(HttpHost httpHost, String username,
+ String password) {
+ BasicCredentialsProvider basicCredentialsProvider = new BasicCredentialsProvider();
+ basicCredentialsProvider.setCredentials(new AuthScope(httpHost),
+ new UsernamePasswordCredentials(username, password.toCharArray()));
+ return basicCredentialsProvider;
}
+
+ private HttpHost createHttpHost(String s) {
+ try {
+ return HttpHost.create(s);
+ }
+ catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @ConditionalOnClass({ Region.class, ApacheHttpClient.class })
+ static class AwsOpenSearchConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ OpenSearchClient openSearchClient(OpenSearchVectorStoreProperties properties, AwsSdk2TransportOptions options) {
+ OpenSearchVectorStoreProperties.Aws aws = properties.getAws();
+ Region region = Region.of(aws.getRegion());
+
+ SdkHttpClient httpClient = ApacheHttpClient.builder().build();
+ OpenSearchTransport transport = new AwsSdk2Transport(httpClient, aws.getHost(), aws.getServiceName(),
+ region, options);
+ return new OpenSearchClient(transport);
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ AwsSdk2TransportOptions options(OpenSearchVectorStoreProperties properties) {
+ OpenSearchVectorStoreProperties.Aws aws = properties.getAws();
+ return AwsSdk2TransportOptions.builder()
+ .setCredentials(StaticCredentialsProvider
+ .create(AwsBasicCredentials.create(aws.getAccessKey(), aws.getSecretKey())))
+ .build();
+ }
+
}
static class PropertiesOpenSearchConnectionDetails implements OpenSearchConnectionDetails {
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/opensearch/OpenSearchVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/opensearch/OpenSearchVectorStoreProperties.java
index 723e0b388..6650500e6 100644
--- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/opensearch/OpenSearchVectorStoreProperties.java
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/opensearch/OpenSearchVectorStoreProperties.java
@@ -37,6 +37,8 @@ public class OpenSearchVectorStoreProperties {
private String mappingJson;
+ private Aws aws = new Aws();
+
public List getUris() {
return uris;
}
@@ -77,4 +79,66 @@ public class OpenSearchVectorStoreProperties {
this.mappingJson = mappingJson;
}
+ public Aws getAws() {
+ return this.aws;
+ }
+
+ public void setAws(Aws aws) {
+ this.aws = aws;
+ }
+
+ static class Aws {
+
+ private String host;
+
+ private String serviceName;
+
+ private String accessKey;
+
+ private String secretKey;
+
+ private String region;
+
+ public String getHost() {
+ return this.host;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public String getServiceName() {
+ return this.serviceName;
+ }
+
+ public void setServiceName(String serviceName) {
+ this.serviceName = serviceName;
+ }
+
+ public String getAccessKey() {
+ return this.accessKey;
+ }
+
+ public void setAccessKey(String accessKey) {
+ this.accessKey = accessKey;
+ }
+
+ public String getSecretKey() {
+ return this.secretKey;
+ }
+
+ public void setSecretKey(String secretKey) {
+ this.secretKey = secretKey;
+ }
+
+ public String getRegion() {
+ return this.region;
+ }
+
+ public void setRegion(String region) {
+ this.region = region;
+ }
+
+ }
+
}
diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/opensearch/AwsOpenSearchVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/opensearch/AwsOpenSearchVectorStoreAutoConfigurationIT.java
new file mode 100644
index 000000000..87a0ee2bf
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/opensearch/AwsOpenSearchVectorStoreAutoConfigurationIT.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2023 - 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.opensearch;
+
+import com.jayway.jsonpath.JsonPath;
+import net.minidev.json.JSONArray;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.embedding.EmbeddingModel;
+import org.springframework.ai.transformers.TransformersEmbeddingModel;
+import org.springframework.ai.vectorstore.OpenSearchVectorStore;
+import org.springframework.ai.vectorstore.SearchRequest;
+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.springframework.core.io.DefaultResourceLoader;
+import org.testcontainers.containers.localstack.LocalStackContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+import static org.hamcrest.Matchers.hasSize;
+
+@Testcontainers
+class AwsOpenSearchVectorStoreAutoConfigurationIT {
+
+ @Container
+ private static final LocalStackContainer localstack = new LocalStackContainer(
+ DockerImageName.parse("localstack/localstack:3.5.0"))
+ .withEnv("LOCALSTACK_HOST", "localhost.localstack.cloud");
+
+ private static final String DOCUMENT_INDEX = "auto-spring-ai-document-index";
+
+ private List documents = List.of(
+ new Document("1", getText("classpath:/test/data/spring.ai.txt"), Map.of("meta1", "meta1")),
+ new Document("2", getText("classpath:/test/data/time.shelter.txt"), Map.of()),
+ new Document("3", getText("classpath:/test/data/great.depression.txt"), Map.of("meta2", "meta2")));
+
+ private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withConfiguration(AutoConfigurations.of(OpenSearchVectorStoreAutoConfiguration.class,
+ SpringAiRetryAutoConfiguration.class))
+ .withUserConfiguration(Config.class)
+ .withPropertyValues(
+ OpenSearchVectorStoreProperties.CONFIG_PREFIX + ".aws.host="
+ + String.format("testcontainers-domain.%s.opensearch.localhost.localstack.cloud:%s",
+ localstack.getRegion(), localstack.getMappedPort(4566)),
+ OpenSearchVectorStoreProperties.CONFIG_PREFIX + ".aws.service-name=opensearch",
+ OpenSearchVectorStoreProperties.CONFIG_PREFIX + ".aws.region=" + localstack.getRegion(),
+ OpenSearchVectorStoreProperties.CONFIG_PREFIX + ".aws.access-key=" + localstack.getAccessKey(),
+ OpenSearchVectorStoreProperties.CONFIG_PREFIX + ".aws.secret-key=" + localstack.getSecretKey(),
+ OpenSearchVectorStoreProperties.CONFIG_PREFIX + ".indexName=" + DOCUMENT_INDEX,
+ OpenSearchVectorStoreProperties.CONFIG_PREFIX + ".mappingJson=" + """
+ {
+ "properties":{
+ "embedding":{
+ "type":"knn_vector",
+ "dimension":384
+ }
+ }
+ }
+ """);
+
+ @BeforeAll
+ static void beforeAll() throws IOException, InterruptedException {
+ String[] createDomainCmd = { "awslocal", "opensearch", "create-domain", "--domain-name",
+ "testcontainers-domain", "--region", localstack.getRegion() };
+ localstack.execInContainer(createDomainCmd);
+
+ String[] describeDomainCmd = { "awslocal", "opensearch", "describe-domain", "--domain-name",
+ "testcontainers-domain", "--region", localstack.getRegion() };
+ await().pollInterval(Duration.ofSeconds(30)).atMost(Duration.ofSeconds(300)).untilAsserted(() -> {
+ org.testcontainers.containers.Container.ExecResult execResult = localstack
+ .execInContainer(describeDomainCmd);
+ String response = execResult.getStdout();
+ JSONArray processed = JsonPath.read(response, "$.DomainStatus[?(@.Processing == false)]");
+ assertThat(processed).isNotEmpty();
+ });
+ }
+
+ @Test
+ public void addAndSearchTest() {
+
+ this.contextRunner.run(context -> {
+ OpenSearchVectorStore vectorStore = context.getBean(OpenSearchVectorStore.class);
+
+ vectorStore.add(documents);
+
+ Awaitility.await()
+ .until(() -> vectorStore
+ .similaritySearch(SearchRequest.query("Great Depression").withTopK(1).withSimilarityThreshold(0)),
+ hasSize(1));
+
+ List results = vectorStore
+ .similaritySearch(SearchRequest.query("Great Depression").withTopK(1).withSimilarityThreshold(0));
+
+ assertThat(results).hasSize(1);
+ Document resultDoc = results.get(0);
+ assertThat(resultDoc.getId()).isEqualTo(documents.get(2).getId());
+ assertThat(resultDoc.getContent()).contains("The Great Depression (1929–1939) was an economic shock");
+ assertThat(resultDoc.getMetadata()).hasSize(2);
+ assertThat(resultDoc.getMetadata()).containsKey("meta2");
+ assertThat(resultDoc.getMetadata()).containsKey("distance");
+
+ // Remove all documents from the store
+ vectorStore.delete(documents.stream().map(Document::getId).toList());
+
+ Awaitility.await()
+ .until(() -> vectorStore
+ .similaritySearch(SearchRequest.query("Great Depression").withTopK(1).withSimilarityThreshold(0)),
+ hasSize(0));
+ });
+ }
+
+ private String getText(String uri) {
+ var resource = new DefaultResourceLoader().getResource(uri);
+ try {
+ return resource.getContentAsString(StandardCharsets.UTF_8);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @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/opensearch/OpenSearchVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/opensearch/OpenSearchVectorStoreAutoConfigurationIT.java
index bfcf986fb..f9d5a3493 100644
--- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/opensearch/OpenSearchVectorStoreAutoConfigurationIT.java
+++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/opensearch/OpenSearchVectorStoreAutoConfigurationIT.java
@@ -25,6 +25,7 @@ import org.springframework.ai.transformers.TransformersEmbeddingModel;
import org.springframework.ai.vectorstore.OpenSearchVectorStore;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -32,6 +33,8 @@ import org.springframework.core.io.DefaultResourceLoader;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;
+import software.amazon.awssdk.http.apache.ApacheHttpClient;
+import software.amazon.awssdk.regions.Region;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -58,6 +61,7 @@ class OpenSearchVectorStoreAutoConfigurationIT {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(OpenSearchVectorStoreAutoConfiguration.class,
SpringAiRetryAutoConfiguration.class))
+ .withClassLoader(new FilteredClassLoader(Region.class, ApacheHttpClient.class))
.withUserConfiguration(Config.class)
.withPropertyValues(
OpenSearchVectorStoreProperties.CONFIG_PREFIX + ".uris=" + opensearchContainer.getHttpHostAddress(),
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-aws-opensearch-store/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-aws-opensearch-store/pom.xml
new file mode 100644
index 000000000..dc5d637b9
--- /dev/null
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-aws-opensearch-store/pom.xml
@@ -0,0 +1,48 @@
+
+
+ 4.0.0
+
+ org.springframework.ai
+ spring-ai
+ 1.0.0-SNAPSHOT
+ ../../pom.xml
+
+ spring-ai-aws-opensearch-store-spring-boot-starter
+ jar
+ Spring AI Starter - AWS OpenSearch Store
+ Spring AI AWS OpenSearch Store Auto Configuration
+ https://github.com/spring-projects/spring-ai
+
+
+ https://github.com/spring-projects/spring-ai
+ git://github.com/spring-projects/spring-ai.git
+ git@github.com:spring-projects/spring-ai.git
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.ai
+ spring-ai-spring-boot-autoconfigure
+ ${project.parent.version}
+
+
+
+ org.springframework.ai
+ spring-ai-opensearch-store
+ ${project.parent.version}
+
+
+
+ software.amazon.awssdk
+ apache-client
+ ${awssdk.version}
+
+
+
+