Skip to content

Commit f35502b

Browse files
authored
Merge branch 'spring-projects:main' into add-observability-to-bedrock-titan
2 parents b127661 + 09a6a6e commit f35502b

File tree

33 files changed

+1522
-874
lines changed

33 files changed

+1522
-874
lines changed

auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-neo4j/src/main/java/org/springframework/ai/model/chat/memory/neo4j/autoconfigure/Neo4jChatMemoryAutoConfiguration.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818

1919
import org.neo4j.driver.Driver;
2020

21-
import org.springframework.ai.chat.memory.neo4j.Neo4jChatMemory;
2221
import org.springframework.ai.chat.memory.neo4j.Neo4jChatMemoryConfig;
22+
import org.springframework.ai.chat.memory.neo4j.Neo4jChatMemoryRepository;
2323
import org.springframework.ai.model.chat.memory.autoconfigure.ChatMemoryAutoConfiguration;
2424
import org.springframework.boot.autoconfigure.AutoConfiguration;
2525
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -29,19 +29,19 @@
2929
import org.springframework.context.annotation.Bean;
3030

3131
/**
32-
* {@link AutoConfiguration Auto-configuration} for {@link Neo4jChatMemory}.
32+
* {@link AutoConfiguration Auto-configuration} for {@link Neo4jChatMemoryRepository}.
3333
*
3434
* @author Enrico Rampazzo
3535
* @since 1.0.0
3636
*/
3737
@AutoConfiguration(after = Neo4jAutoConfiguration.class, before = ChatMemoryAutoConfiguration.class)
38-
@ConditionalOnClass({ Neo4jChatMemory.class, Driver.class })
38+
@ConditionalOnClass({ Neo4jChatMemoryRepository.class, Driver.class })
3939
@EnableConfigurationProperties(Neo4jChatMemoryProperties.class)
4040
public class Neo4jChatMemoryAutoConfiguration {
4141

4242
@Bean
4343
@ConditionalOnMissingBean
44-
public Neo4jChatMemory chatMemory(Neo4jChatMemoryProperties properties, Driver driver) {
44+
public Neo4jChatMemoryRepository chatMemoryRepository(Neo4jChatMemoryProperties properties, Driver driver) {
4545

4646
var builder = Neo4jChatMemoryConfig.builder()
4747
.withMediaLabel(properties.getMediaLabel())
@@ -52,7 +52,7 @@ public Neo4jChatMemory chatMemory(Neo4jChatMemoryProperties properties, Driver d
5252
.withToolResponseLabel(properties.getToolResponseLabel())
5353
.withDriver(driver);
5454

55-
return Neo4jChatMemory.create(builder.build());
55+
return new Neo4jChatMemoryRepository(builder.build());
5656
}
5757

5858
}
Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@
2323
import java.util.UUID;
2424

2525
import org.junit.jupiter.api.Test;
26+
import org.springframework.ai.chat.memory.ChatMemory;
27+
import org.springframework.ai.chat.memory.ChatMemoryRepository;
28+
import org.springframework.ai.chat.memory.neo4j.Neo4jChatMemoryRepository;
2629
import org.testcontainers.containers.Neo4jContainer;
2730
import org.testcontainers.junit.jupiter.Container;
2831
import org.testcontainers.junit.jupiter.Testcontainers;
2932
import org.testcontainers.utility.DockerImageName;
3033

31-
import org.springframework.ai.chat.memory.neo4j.Neo4jChatMemory;
3234
import org.springframework.ai.chat.memory.neo4j.Neo4jChatMemoryConfig;
3335
import org.springframework.ai.chat.messages.AssistantMessage;
3436
import org.springframework.ai.chat.messages.Message;
@@ -51,7 +53,7 @@
5153
* @since 1.0.0
5254
*/
5355
@Testcontainers
54-
class Neo4jChatMemoryAutoConfigurationIT {
56+
class Neo4jChatMemoryRepositoryAutoConfigurationIT {
5557

5658
static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("neo4j");
5759

@@ -67,31 +69,31 @@ class Neo4jChatMemoryAutoConfigurationIT {
6769
@Test
6870
void addAndGet() {
6971
this.contextRunner.withPropertyValues("spring.neo4j.uri=" + neo4jContainer.getBoltUrl()).run(context -> {
70-
Neo4jChatMemory memory = context.getBean(Neo4jChatMemory.class);
72+
ChatMemoryRepository memory = context.getBean(ChatMemoryRepository.class);
7173

7274
String sessionId = UUID.randomUUID().toString();
73-
assertThat(memory.get(sessionId, Integer.MAX_VALUE)).isEmpty();
75+
assertThat(memory.findByConversationId(sessionId)).isEmpty();
7476

7577
UserMessage userMessage = new UserMessage("test question");
7678

77-
memory.add(sessionId, userMessage);
78-
List<Message> messages = memory.get(sessionId, Integer.MAX_VALUE);
79+
memory.saveAll(sessionId, List.of(userMessage));
80+
List<Message> messages = memory.findByConversationId(sessionId);
7981
assertThat(messages).hasSize(1);
8082
assertThat(messages.get(0)).usingRecursiveAssertion().isEqualTo(userMessage);
8183

82-
memory.clear(sessionId);
83-
assertThat(memory.get(sessionId, Integer.MAX_VALUE)).isEmpty();
84+
memory.deleteByConversationId(sessionId);
85+
assertThat(memory.findByConversationId(sessionId)).isEmpty();
8486

8587
AssistantMessage assistantMessage = new AssistantMessage("test answer", Map.of(),
8688
List.of(new AssistantMessage.ToolCall("id", "type", "name", "arguments")));
8789

88-
memory.add(sessionId, List.of(userMessage, assistantMessage));
89-
messages = memory.get(sessionId, Integer.MAX_VALUE);
90+
memory.saveAll(sessionId, List.of(userMessage, assistantMessage));
91+
messages = memory.findByConversationId(sessionId);
9092
assertThat(messages).hasSize(2);
91-
assertThat(messages.get(1)).isEqualTo(userMessage);
93+
assertThat(messages.get(0)).isEqualTo(userMessage);
9294

93-
assertThat(messages.get(0)).isEqualTo(assistantMessage);
94-
memory.clear(sessionId);
95+
assertThat(messages.get(1)).isEqualTo(assistantMessage);
96+
memory.deleteByConversationId(sessionId);
9597
MimeType textPlain = MimeType.valueOf("text/plain");
9698
List<Media> media = List.of(
9799
Media.builder()
@@ -102,28 +104,28 @@ void addAndGet() {
102104
.build(),
103105
Media.builder().data(URI.create("http://www.google.com")).mimeType(textPlain).build());
104106
UserMessage userMessageWithMedia = UserMessage.builder().text("Message with media").media(media).build();
105-
memory.add(sessionId, userMessageWithMedia);
107+
memory.saveAll(sessionId, List.of(userMessageWithMedia));
106108

107-
messages = memory.get(sessionId, Integer.MAX_VALUE);
109+
messages = memory.findByConversationId(sessionId);
108110
assertThat(messages.size()).isEqualTo(1);
109111
assertThat(messages.get(0)).isEqualTo(userMessageWithMedia);
110112
assertThat(((UserMessage) messages.get(0)).getMedia()).hasSize(2);
111113
assertThat(((UserMessage) messages.get(0)).getMedia()).usingRecursiveFieldByFieldElementComparator()
112114
.isEqualTo(media);
113-
memory.clear(sessionId);
115+
memory.deleteByConversationId(sessionId);
114116
ToolResponseMessage toolResponseMessage = new ToolResponseMessage(
115117
List.of(new ToolResponse("id", "name", "responseData"),
116118
new ToolResponse("id2", "name2", "responseData2")),
117119
Map.of("id", "id", "metadataKey", "metadata"));
118-
memory.add(sessionId, toolResponseMessage);
119-
messages = memory.get(sessionId, Integer.MAX_VALUE);
120+
memory.saveAll(sessionId, List.of(toolResponseMessage));
121+
messages = memory.findByConversationId(sessionId);
120122
assertThat(messages.size()).isEqualTo(1);
121123
assertThat(messages.get(0)).isEqualTo(toolResponseMessage);
122124

123-
memory.clear(sessionId);
125+
memory.deleteByConversationId(sessionId);
124126
SystemMessage sm = new SystemMessage("this is a System message");
125-
memory.add(sessionId, sm);
126-
messages = memory.get(sessionId, Integer.MAX_VALUE);
127+
memory.saveAll(sessionId, List.of(sm));
128+
messages = memory.findByConversationId(sessionId);
127129
assertThat(messages).hasSize(1);
128130
assertThat(messages.get(0)).usingRecursiveAssertion().isEqualTo(sm);
129131
});
@@ -148,7 +150,7 @@ void setCustomConfiguration() {
148150
propertyBase.formatted("toolresponselabel", toolResponseLabel),
149151
propertyBase.formatted("medialabel", mediaLabel))
150152
.run(context -> {
151-
Neo4jChatMemory chatMemory = context.getBean(Neo4jChatMemory.class);
153+
Neo4jChatMemoryRepository chatMemory = context.getBean(Neo4jChatMemoryRepository.class);
152154
Neo4jChatMemoryConfig config = chatMemory.getConfig();
153155
assertThat(config.getMessageLabel()).isEqualTo(messageLabel);
154156
assertThat(config.getMediaLabel()).isEqualTo(mediaLabel);

auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
<dependency>
9595
<groupId>org.testcontainers</groupId>
9696
<artifactId>chromadb</artifactId>
97+
<version>1.21.0</version>
9798
<scope>test</scope>
9899
</dependency>
99100
<dependency>

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 the original author or authors.
2+
* Copyright 2023-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -65,8 +65,11 @@ public ChromaApi chromaApi(ChromaApiProperties apiProperties,
6565

6666
String chromaUrl = String.format("%s:%s", connectionDetails.getHost(), connectionDetails.getPort());
6767

68-
var chromaApi = new ChromaApi(chromaUrl, restClientBuilderProvider.getIfAvailable(RestClient::builder),
69-
objectMapper);
68+
var chromaApi = ChromaApi.builder()
69+
.baseUrl(chromaUrl)
70+
.restClientBuilder(restClientBuilderProvider.getIfAvailable(RestClient::builder))
71+
.objectMapper(objectMapper)
72+
.build();
7073

7174
if (StringUtils.hasText(connectionDetails.getKeyToken())) {
7275
chromaApi.withKeyToken(connectionDetails.getKeyToken());

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

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 the original author or authors.
2+
* Copyright 2023-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
1616

1717
package org.springframework.ai.vectorstore.chroma.autoconfigure;
1818

19-
import org.springframework.ai.chroma.vectorstore.ChromaVectorStore;
19+
import org.springframework.ai.chroma.vectorstore.common.ChromaApiConstants;
2020
import org.springframework.ai.vectorstore.properties.CommonVectorStoreProperties;
2121
import org.springframework.boot.context.properties.ConfigurationProperties;
2222

@@ -25,13 +25,34 @@
2525
*
2626
* @author Christian Tzolov
2727
* @author Soby Chacko
28+
* @author Jonghoon Park
2829
*/
2930
@ConfigurationProperties(ChromaVectorStoreProperties.CONFIG_PREFIX)
3031
public class ChromaVectorStoreProperties extends CommonVectorStoreProperties {
3132

3233
public static final String CONFIG_PREFIX = "spring.ai.vectorstore.chroma";
3334

34-
private String collectionName = ChromaVectorStore.DEFAULT_COLLECTION_NAME;
35+
private String tenantName = ChromaApiConstants.DEFAULT_TENANT_NAME;
36+
37+
private String databaseName = ChromaApiConstants.DEFAULT_DATABASE_NAME;
38+
39+
private String collectionName = ChromaApiConstants.DEFAULT_COLLECTION_NAME;
40+
41+
public String getTenantName() {
42+
return tenantName;
43+
}
44+
45+
public void setTenantName(String tenantName) {
46+
this.tenantName = tenantName;
47+
}
48+
49+
public String getDatabaseName() {
50+
return databaseName;
51+
}
52+
53+
public void setDatabaseName(String databaseName) {
54+
this.databaseName = databaseName;
55+
}
3556

3657
public String getCollectionName() {
3758
return this.collectionName;

auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/src/test/java/org/springframework/ai/vectorstore/chroma/autoconfigure/ChromaVectorStoreAutoConfigurationIT.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
public class ChromaVectorStoreAutoConfigurationIT {
5959

6060
@Container
61-
static ChromaDBContainer chroma = new ChromaDBContainer("ghcr.io/chroma-core/chroma:0.5.20");
61+
static ChromaDBContainer chroma = new ChromaDBContainer("ghcr.io/chroma-core/chroma:1.0.0");
6262

6363
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
6464
.withConfiguration(AutoConfigurations
@@ -69,7 +69,6 @@ public class ChromaVectorStoreAutoConfigurationIT {
6969
"spring.ai.vectorstore.chroma.collectionName=TestCollection");
7070

7171
@Test
72-
@Disabled("This throws an Invalid HTTP request exception - will investigate")
7372
public void addAndSearchWithFilters() {
7473

7574
this.contextRunner.withPropertyValues("spring.ai.vectorstore.chroma.initializeSchema=true").run(context -> {
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

Lines changed: 14 additions & 2 deletions
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

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ class OpenSearchVectorStoreAutoConfigurationIT {
6565
SpringAiRetryAutoConfiguration.class))
6666
.withClassLoader(new FilteredClassLoader(Region.class, ApacheHttpClient.class))
6767
.withUserConfiguration(Config.class)
68-
.withPropertyValues("spring.ai.vectorstore.opensearch.initialize-schema=true",
68+
.withPropertyValues("spring.ai.vectorstore.opensearch.aws.enabled=false",
69+
"spring.ai.vectorstore.opensearch.initialize-schema=true",
6970
OpenSearchVectorStoreProperties.CONFIG_PREFIX + ".uris=" + opensearchContainer.getHttpHostAddress(),
7071
OpenSearchVectorStoreProperties.CONFIG_PREFIX + ".indexName=" + DOCUMENT_INDEX,
7172
OpenSearchVectorStoreProperties.CONFIG_PREFIX + ".mappingJson=" + """

0 commit comments

Comments
 (0)