Skip to content

Commit f329d71

Browse files
committed
Improve OpenSearch AWS/non-AWS auto-configuration and document fallback logic
- Add OpenSearchNonAwsCondition to robustly activate non-AWS OpenSearch auto-configuration if either: - AWS SDK classes are missing, OR - spring.ai.vectorstore.opensearch.aws.enabled=false - Update OpenSearchVectorStoreAutoConfiguration to use the new condition for non-AWS configuration. - Ensure AWS-specific auto-configuration is only active when AWS SDKs are present and the property is true (default). - Add Javadoc to OpenSearchNonAwsCondition and clarify Javadoc on AWS config. - Update integration test (OpenSearchVectorStoreNonAwsFallbackIT) to verify fallback logic works with AWS SDKs present. - Add and refine documentation in opensearch.adoc: - Explain the purpose and usage of the spring.ai.vectorstore.opensearch.aws.enabled property. - Document the fallback logic and how to explicitly select AWS or non-AWS OpenSearch support. Fixes #1901 Signed-off-by: Mark Pollack <[email protected]>
1 parent 43aa893 commit f329d71

File tree

4 files changed

+196
-2
lines changed

4 files changed

+196
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2023-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.vectorstore.opensearch.autoconfigure;
18+
19+
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
20+
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
21+
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
22+
import org.springframework.context.annotation.ConditionContext;
23+
import org.springframework.core.type.AnnotatedTypeMetadata;
24+
25+
/**
26+
* Condition that matches if either:
27+
* <ul>
28+
* <li>The property <code>spring.ai.vectorstore.opensearch.aws.enabled</code> is
29+
* explicitly set to <code>false</code>.</li>
30+
* <li>Required AWS SDK classes are missing from the classpath.</li>
31+
* </ul>
32+
* <p>
33+
* This enables the non-AWS OpenSearch auto-configuration to be activated when the user
34+
* disables AWS support via property or when AWS SDKs are not present, ensuring correct
35+
* fallback behavior for non-AWS OpenSearch usage.
36+
*/
37+
public class OpenSearchNonAwsCondition extends SpringBootCondition {
38+
39+
private static final String AWS_ENABLED_PROPERTY = "spring.ai.vectorstore.opensearch.aws.enabled";
40+
41+
@Override
42+
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
43+
// 1. If AWS property is set to false, match
44+
String awsEnabled = context.getEnvironment().getProperty(AWS_ENABLED_PROPERTY);
45+
if ("false".equalsIgnoreCase(awsEnabled)) {
46+
return ConditionOutcome.match(ConditionMessage.forCondition("OpenSearchNonAwsCondition")
47+
.because("Property 'spring.ai.vectorstore.opensearch.aws.enabled' is false"));
48+
}
49+
// 2. If AWS SDK classes are missing, match
50+
boolean awsClassesPresent = isPresent("software.amazon.awssdk.auth.credentials.AwsCredentialsProvider")
51+
&& isPresent("software.amazon.awssdk.regions.Region")
52+
&& isPresent("software.amazon.awssdk.http.apache.ApacheHttpClient");
53+
if (!awsClassesPresent) {
54+
return ConditionOutcome.match(
55+
ConditionMessage.forCondition("OpenSearchNonAwsCondition").because("AWS SDK classes are missing"));
56+
}
57+
// 3. Otherwise, do not match
58+
return ConditionOutcome.noMatch(ConditionMessage.forCondition("OpenSearchNonAwsCondition")
59+
.because("AWS SDK classes are present and property is not false"));
60+
}
61+
62+
private boolean isPresent(String className) {
63+
try {
64+
Class.forName(className, false, getClass().getClassLoader());
65+
return true;
66+
}
67+
catch (ClassNotFoundException ex) {
68+
return false;
69+
}
70+
}
71+
72+
}

auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/main/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreAutoConfiguration.java

+14-2
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,7 @@ OpenSearchVectorStore vectorStore(OpenSearchVectorStoreProperties properties, Op
9494
}
9595

9696
@Configuration(proxyBeanMethods = false)
97-
@ConditionalOnMissingClass({ "software.amazon.awssdk.regions.Region",
98-
"software.amazon.awssdk.http.apache.ApacheHttpClient" })
97+
@org.springframework.context.annotation.Conditional(OpenSearchNonAwsCondition.class)
9998
static class OpenSearchConfiguration {
10099

101100
@Bean
@@ -134,8 +133,21 @@ private HttpHost createHttpHost(String s) {
134133

135134
}
136135

136+
/**
137+
* AWS OpenSearch configuration.
138+
* <p>
139+
* This configuration is only enabled if AWS SDK classes are present on the classpath
140+
* <b>and</b> the property {@code spring.ai.vectorstore.opensearch.aws.enabled} is set
141+
* to {@code true} (default: true).
142+
* <p>
143+
* Set {@code spring.ai.vectorstore.opensearch.aws.enabled=false} to disable
144+
* AWS-specific OpenSearch configuration when AWS SDK is present for other services
145+
* (e.g., S3).
146+
*/
137147
@Configuration(proxyBeanMethods = false)
138148
@ConditionalOnClass({ AwsCredentialsProvider.class, Region.class, ApacheHttpClient.class })
149+
@ConditionalOnProperty(name = "spring.ai.vectorstore.opensearch.aws.enabled", havingValue = "true",
150+
matchIfMissing = true)
139151
static class AwsOpenSearchConfiguration {
140152

141153
@Bean
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2023-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.vectorstore.opensearch.autoconfigure;
18+
19+
import java.io.IOException;
20+
import java.nio.charset.StandardCharsets;
21+
import java.util.List;
22+
import java.util.Map;
23+
24+
import org.junit.jupiter.api.Test;
25+
import org.opensearch.testcontainers.OpensearchContainer;
26+
import org.testcontainers.junit.jupiter.Container;
27+
import org.testcontainers.junit.jupiter.Testcontainers;
28+
import org.testcontainers.utility.DockerImageName;
29+
30+
import org.springframework.ai.document.Document;
31+
import org.springframework.ai.embedding.EmbeddingModel;
32+
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
33+
import org.springframework.ai.transformers.TransformersEmbeddingModel;
34+
import org.springframework.ai.vectorstore.opensearch.OpenSearchVectorStore;
35+
import org.springframework.boot.autoconfigure.AutoConfigurations;
36+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
37+
import org.springframework.context.annotation.Bean;
38+
import org.springframework.context.annotation.Configuration;
39+
import org.springframework.core.io.DefaultResourceLoader;
40+
41+
import static org.assertj.core.api.Assertions.assertThat;
42+
43+
@Testcontainers
44+
class OpenSearchVectorStoreNonAwsFallbackIT {
45+
46+
@Container
47+
private static final OpensearchContainer<?> opensearchContainer = new OpensearchContainer<>(
48+
DockerImageName.parse("opensearchproject/opensearch:2.13.0"));
49+
50+
private static final String DOCUMENT_INDEX = "nonaws-spring-ai-document-index";
51+
52+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
53+
.withConfiguration(AutoConfigurations.of(OpenSearchVectorStoreAutoConfiguration.class,
54+
SpringAiRetryAutoConfiguration.class))
55+
.withUserConfiguration(Config.class)
56+
.withPropertyValues("spring.ai.vectorstore.opensearch.aws.enabled=false",
57+
"spring.ai.vectorstore.opensearch.uris=" + opensearchContainer.getHttpHostAddress(),
58+
"spring.ai.vectorstore.opensearch.indexName=" + DOCUMENT_INDEX,
59+
"spring.ai.vectorstore.opensearch.mappingJson={\"properties\":{\"embedding\":{\"type\":\"knn_vector\",\"dimension\":384}}}");
60+
61+
private List<Document> documents = List.of(
62+
new Document("1", getText("classpath:/test/data/spring.ai.txt"), Map.of("meta1", "meta1")),
63+
new Document("2", getText("classpath:/test/data/time.shelter.txt"), Map.of()),
64+
new Document("3", getText("classpath:/test/data/great.depression.txt"), Map.of("meta2", "meta2")));
65+
66+
@Test
67+
void nonAwsFallbackConfigurationWorks() {
68+
this.contextRunner.run(context -> {
69+
// AWS-specific bean should NOT be present
70+
assertThat(context.containsBeanDefinition("awsOpenSearchConnectionDetails")).isFalse();
71+
// Standard OpenSearch bean should be present
72+
assertThat(context.getBeansOfType(OpenSearchConnectionDetails.class)).isNotEmpty();
73+
// OpenSearchVectorStore should still be present
74+
assertThat(context.getBeansOfType(OpenSearchVectorStore.class)).isNotEmpty();
75+
});
76+
}
77+
78+
private String getText(String uri) {
79+
var resource = new DefaultResourceLoader().getResource(uri);
80+
try {
81+
return resource.getContentAsString(StandardCharsets.UTF_8);
82+
}
83+
catch (IOException e) {
84+
throw new RuntimeException(e);
85+
}
86+
}
87+
88+
@Configuration(proxyBeanMethods = false)
89+
static class Config {
90+
91+
@Bean
92+
public EmbeddingModel embeddingModel() {
93+
return new TransformersEmbeddingModel();
94+
}
95+
96+
}
97+
98+
}

spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/opensearch.adoc

+12
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,18 @@ Properties starting with `spring.ai.vectorstore.opensearch.*` are used to config
130130
|`spring.ai.vectorstore.opensearch.aws.region`| AWS region | -
131131
|===
132132

133+
[NOTE]
134+
====
135+
You can control whether the AWS-specific OpenSearch auto-configuration is enabled using the `spring.ai.vectorstore.opensearch.aws.enabled` property.
136+
137+
- If this property is set to `false`, the non-AWS OpenSearch configuration is activated, even if AWS SDK classes are present on the classpath. This allows you to use self-managed or third-party OpenSearch clusters in environments where AWS SDKs are present for other services.
138+
- If AWS SDK classes are not present, the non-AWS configuration is always used.
139+
- If AWS SDK classes are present and the property is not set or set to `true`, the AWS-specific configuration is used by default.
140+
141+
This fallback logic ensures that users have explicit control over the type of OpenSearch integration, preventing accidental activation of AWS-specific logic when not desired.
142+
====
143+
144+
133145
The following similarity functions are available:
134146

135147
* `cosinesimil` - Default, suitable for most use cases. Measures cosine similarity between vectors.

0 commit comments

Comments
 (0)