Skip to content

Commit c920cbf

Browse files
committed
Merge branch 'main' into fix-issue-4251
# Conflicts: # spring-ai-template-st/src/test/java/org/springframework/ai/template/st/StTemplateRendererTests.java
2 parents 9d60992 + 74fa19a commit c920cbf

File tree

473 files changed

+31280
-2171
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

473 files changed

+31280
-2171
lines changed

.github/workflows/deploy-docs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ jobs:
2828
if: github.ref_type == 'branch'
2929
env:
3030
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31+
GIT_TRACE: 1
32+
GIT_CURL_VERBOSE: 1
3133
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) -f build-refname=${{ github.ref_name }}
3234
- name: Dispatch (full build)
3335
if: github.ref_type == 'tag'

.github/workflows/documentation-upload.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333

3434
- name: Generate assembly
3535
working-directory: spring-ai-docs
36-
run: ./mvnw --batch-mode assembly:single
36+
run: mvn --batch-mode assembly:single
3737

3838
- name: Setup SSH key
3939
env:

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
.checkstyle
12
target
23
.classpath
34
.project
@@ -50,3 +51,8 @@ CLAUDE.md
5051
qodana.yaml
5152
__pycache__/
5253
*.pyc
54+
tmp
55+
56+
57+
plans
58+

CONTRIBUTING.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ When issuing pull requests, please ensure that your commit history is linear.
110110
From the command line you can check this using:
111111

112112
----
113-
log --graph --pretty=oneline
113+
git log --graph --pretty=oneline
114114
----
115115

116116
As this may cause lots of typing, we recommend creating a global alias, e.g. `git logg` for this:

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ To clone it you have to either:
6565

6666
## Building
6767

68+
Build using Java 17.
69+
6870
To build with running unit tests
6971

7072
```shell
@@ -101,9 +103,10 @@ A full integration test is done twice a day in the [Spring AI Integration Test R
101103
One way to run integration tests on part of the code is to first do a quick compile and install of the project
102104

103105
```shell
104-
./mvnw clean install -DskipTests -Dmaven.javadoc.skip=true
106+
./mvnw spring-javaformat:apply clean install -DskipTests -Dmaven.javadoc.skip=true
105107
```
106108
Then run the integration test for a specific module using the `-pl` option
109+
107110
```shell
108111
./mvnw verify -Pintegration-tests -pl spring-ai-spring-boot-testcontainers
109112
```

advisors/spring-ai-advisors-vector-store/src/main/java/org/springframework/ai/chat/client/advisor/vectorstore/VectorStoreChatMemoryAdvisor.java

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.HashMap;
2121
import java.util.List;
2222
import java.util.Map;
23+
import java.util.stream.Collectors;
2324

2425
import reactor.core.publisher.Flux;
2526
import reactor.core.publisher.Mono;
@@ -37,9 +38,11 @@
3738
import org.springframework.ai.chat.messages.AssistantMessage;
3839
import org.springframework.ai.chat.messages.Message;
3940
import org.springframework.ai.chat.messages.MessageType;
41+
import org.springframework.ai.chat.messages.SystemMessage;
4042
import org.springframework.ai.chat.messages.UserMessage;
4143
import org.springframework.ai.chat.prompt.PromptTemplate;
4244
import org.springframework.ai.document.Document;
45+
import org.springframework.ai.vectorstore.SearchRequest;
4346
import org.springframework.ai.vectorstore.VectorStore;
4447
import org.springframework.util.Assert;
4548

@@ -122,31 +125,23 @@ public ChatClientRequest before(ChatClientRequest request, AdvisorChain advisorC
122125
String query = request.prompt().getUserMessage() != null ? request.prompt().getUserMessage().getText() : "";
123126
int topK = getChatMemoryTopK(request.context());
124127
String filter = DOCUMENT_METADATA_CONVERSATION_ID + "=='" + conversationId + "'";
125-
var searchRequest = org.springframework.ai.vectorstore.SearchRequest.builder()
126-
.query(query)
127-
.topK(topK)
128-
.filterExpression(filter)
129-
.build();
130-
java.util.List<org.springframework.ai.document.Document> documents = this.vectorStore
131-
.similaritySearch(searchRequest);
128+
SearchRequest searchRequest = SearchRequest.builder().query(query).topK(topK).filterExpression(filter).build();
129+
List<Document> documents = this.vectorStore.similaritySearch(searchRequest);
132130

133131
String longTermMemory = documents == null ? ""
134-
: documents.stream()
135-
.map(org.springframework.ai.document.Document::getText)
136-
.collect(java.util.stream.Collectors.joining(System.lineSeparator()));
132+
: documents.stream().map(Document::getText).collect(Collectors.joining(System.lineSeparator()));
137133

138-
org.springframework.ai.chat.messages.SystemMessage systemMessage = request.prompt().getSystemMessage();
134+
SystemMessage systemMessage = request.prompt().getSystemMessage();
139135
String augmentedSystemText = this.systemPromptTemplate
140-
.render(java.util.Map.of("instructions", systemMessage.getText(), "long_term_memory", longTermMemory));
136+
.render(Map.of("instructions", systemMessage.getText(), "long_term_memory", longTermMemory));
141137

142138
ChatClientRequest processedChatClientRequest = request.mutate()
143139
.prompt(request.prompt().augmentSystemMessage(augmentedSystemText))
144140
.build();
145141

146-
org.springframework.ai.chat.messages.UserMessage userMessage = processedChatClientRequest.prompt()
147-
.getUserMessage();
142+
UserMessage userMessage = processedChatClientRequest.prompt().getUserMessage();
148143
if (userMessage != null) {
149-
this.vectorStore.write(toDocuments(java.util.List.of(userMessage), conversationId));
144+
this.vectorStore.write(toDocuments(List.of(userMessage), conversationId));
150145
}
151146

152147
return processedChatClientRequest;
@@ -186,10 +181,11 @@ public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest
186181
}
187182

188183
private List<Document> toDocuments(List<Message> messages, String conversationId) {
189-
List<Document> docs = messages.stream()
184+
return messages.stream()
190185
.filter(m -> m.getMessageType() == MessageType.USER || m.getMessageType() == MessageType.ASSISTANT)
191186
.map(message -> {
192-
var metadata = new HashMap<>(message.getMetadata() != null ? message.getMetadata() : new HashMap<>());
187+
Map<String, Object> metadata = new HashMap<>(
188+
message.getMetadata() != null ? message.getMetadata() : new HashMap<>());
193189
metadata.put(DOCUMENT_METADATA_CONVERSATION_ID, conversationId);
194190
metadata.put(DOCUMENT_METADATA_MESSAGE_TYPE, message.getMessageType().name());
195191
if (message instanceof UserMessage userMessage) {
@@ -208,8 +204,6 @@ else if (message instanceof AssistantMessage assistantMessage) {
208204
throw new RuntimeException("Unknown message type: " + message.getMessageType());
209205
})
210206
.toList();
211-
212-
return docs;
213207
}
214208

215209
/**

auto-configurations/common/spring-ai-autoconfigure-retry/src/main/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryProperties.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public class SpringAiRetryProperties {
4242
* Exponential Backoff properties.
4343
*/
4444
@NestedConfigurationProperty
45-
private Backoff backoff = new Backoff();
45+
private final Backoff backoff = new Backoff();
4646

4747
/**
4848
* If false, throw a NonTransientAiException, and do not attempt retry for 4xx client

auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/pom.xml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,12 @@
3535
<optional>true</optional>
3636
</dependency>
3737

38-
<!-- <dependency>
39-
<groupId>io.modelcontextprotocol.sdk</groupId>
40-
<artifactId>mcp-spring-webflux</artifactId>
38+
<dependency>
39+
<groupId>org.springframework.ai</groupId>
40+
<artifactId>spring-ai-mcp-annotations</artifactId>
41+
<version>${project.parent.version}</version>
4142
<optional>true</optional>
42-
</dependency> -->
43+
</dependency>
4344

4445
<dependency>
4546
<groupId>org.springframework.boot</groupId>

auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/McpClientAutoConfiguration.java

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,23 @@
2323
import io.modelcontextprotocol.client.McpClient;
2424
import io.modelcontextprotocol.client.McpSyncClient;
2525
import io.modelcontextprotocol.spec.McpSchema;
26-
26+
import org.springaicommunity.mcp.method.changed.prompt.AsyncPromptListChangedSpecification;
27+
import org.springaicommunity.mcp.method.changed.prompt.SyncPromptListChangedSpecification;
28+
import org.springaicommunity.mcp.method.changed.resource.AsyncResourceListChangedSpecification;
29+
import org.springaicommunity.mcp.method.changed.resource.SyncResourceListChangedSpecification;
30+
import org.springaicommunity.mcp.method.changed.tool.AsyncToolListChangedSpecification;
31+
import org.springaicommunity.mcp.method.changed.tool.SyncToolListChangedSpecification;
32+
import org.springaicommunity.mcp.method.elicitation.AsyncElicitationSpecification;
33+
import org.springaicommunity.mcp.method.elicitation.SyncElicitationSpecification;
34+
import org.springaicommunity.mcp.method.logging.AsyncLoggingSpecification;
35+
import org.springaicommunity.mcp.method.logging.SyncLoggingSpecification;
36+
import org.springaicommunity.mcp.method.progress.AsyncProgressSpecification;
37+
import org.springaicommunity.mcp.method.progress.SyncProgressSpecification;
38+
import org.springaicommunity.mcp.method.sampling.AsyncSamplingSpecification;
39+
import org.springaicommunity.mcp.method.sampling.SyncSamplingSpecification;
40+
41+
import org.springframework.ai.mcp.client.common.autoconfigure.annotations.McpAsyncAnnotationCustomizer;
42+
import org.springframework.ai.mcp.client.common.autoconfigure.annotations.McpSyncAnnotationCustomizer;
2743
import org.springframework.ai.mcp.client.common.autoconfigure.configurer.McpAsyncClientConfigurer;
2844
import org.springframework.ai.mcp.client.common.autoconfigure.configurer.McpSyncClientConfigurer;
2945
import org.springframework.ai.mcp.client.common.autoconfigure.properties.McpClientCommonProperties;
@@ -95,8 +111,6 @@
95111
* @see McpSyncClientCustomizer
96112
* @see McpAsyncClientCustomizer
97113
* @see StdioTransportAutoConfiguration
98-
* @see SseHttpClientTransportAutoConfiguration
99-
* @see SseWebFluxTransportAutoConfiguration
100114
*/
101115
@AutoConfiguration(afterName = {
102116
"org.springframework.ai.mcp.client.common.autoconfigure.StdioTransportAutoConfiguration",
@@ -158,7 +172,7 @@ public List<McpSyncClient> mcpSyncClients(McpSyncClientConfigurer mcpSyncClientC
158172

159173
McpSchema.Implementation clientInfo = new McpSchema.Implementation(
160174
this.connectedClientName(commonProperties.getName(), namedTransport.name()),
161-
commonProperties.getVersion());
175+
namedTransport.name(), commonProperties.getVersion());
162176

163177
McpClient.SyncSpec spec = McpClient.sync(namedTransport.transport())
164178
.clientInfo(clientInfo)
@@ -208,6 +222,20 @@ McpSyncClientConfigurer mcpSyncClientConfigurer(ObjectProvider<McpSyncClientCust
208222
return new McpSyncClientConfigurer(customizerProvider.orderedStream().toList());
209223
}
210224

225+
@Bean
226+
@ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC",
227+
matchIfMissing = true)
228+
public McpSyncClientCustomizer mcpAnnotationMcpSyncClientCustomizer(List<SyncLoggingSpecification> loggingSpecs,
229+
List<SyncSamplingSpecification> samplingSpecs, List<SyncElicitationSpecification> elicitationSpecs,
230+
List<SyncProgressSpecification> progressSpecs,
231+
List<SyncToolListChangedSpecification> syncToolListChangedSpecifications,
232+
List<SyncResourceListChangedSpecification> syncResourceListChangedSpecifications,
233+
List<SyncPromptListChangedSpecification> syncPromptListChangedSpecifications) {
234+
return new McpSyncAnnotationCustomizer(samplingSpecs, loggingSpecs, elicitationSpecs, progressSpecs,
235+
syncToolListChangedSpecifications, syncResourceListChangedSpecifications,
236+
syncPromptListChangedSpecifications);
237+
}
238+
211239
// Async client configuration
212240

213241
@Bean
@@ -259,6 +287,18 @@ McpAsyncClientConfigurer mcpAsyncClientConfigurer(ObjectProvider<McpAsyncClientC
259287
return new McpAsyncClientConfigurer(customizerProvider.orderedStream().toList());
260288
}
261289

290+
@Bean
291+
@ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC")
292+
public McpAsyncClientCustomizer mcpAnnotationMcpAsyncClientCustomizer(List<AsyncLoggingSpecification> loggingSpecs,
293+
List<AsyncSamplingSpecification> samplingSpecs, List<AsyncElicitationSpecification> elicitationSpecs,
294+
List<AsyncProgressSpecification> progressSpecs,
295+
List<AsyncToolListChangedSpecification> toolListChangedSpecs,
296+
List<AsyncResourceListChangedSpecification> resourceListChangedSpecs,
297+
List<AsyncPromptListChangedSpecification> promptListChangedSpecs) {
298+
return new McpAsyncAnnotationCustomizer(samplingSpecs, loggingSpecs, elicitationSpecs, progressSpecs,
299+
toolListChangedSpecs, resourceListChangedSpecs, promptListChangedSpecs);
300+
}
301+
262302
/**
263303
* Record class that implements {@link AutoCloseable} to ensure proper cleanup of MCP
264304
* clients.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2025-2025 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.mcp.client.common.autoconfigure;
18+
19+
import java.util.Map;
20+
21+
import org.springframework.ai.mcp.client.common.autoconfigure.properties.McpSseClientProperties;
22+
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
23+
24+
/**
25+
* Connection details for an MCP client.
26+
*
27+
* @author Eddú Meléndez
28+
*/
29+
public interface McpSseClientConnectionDetails extends ConnectionDetails {
30+
31+
Map<String, McpSseClientProperties.SseParameters> getConnections();
32+
33+
}

0 commit comments

Comments
 (0)