Skip to content

Commit ee79d7b

Browse files
committed
merge main to fix conflict
2 parents 0b364b3 + 28f86bf commit ee79d7b

File tree

36 files changed

+605
-81
lines changed

36 files changed

+605
-81
lines changed

.github/workflows/backport-issue.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ on:
77

88
jobs:
99
backport-issue:
10-
uses: spring-io/spring-github-workflows/.github/workflows/spring-backport-issue.yml@v5
10+
uses: spring-io/spring-github-workflows/.github/workflows/spring-backport-issue.yml@main
1111
secrets:
1212
GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}

auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryHsqldbAutoConfigurationIT.java

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

1919
import java.util.List;
2020

21-
import org.junit.Before;
22-
import org.junit.Test;
23-
import org.junit.runner.RunWith;
21+
import org.junit.jupiter.api.BeforeEach;
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.extension.ExtendWith;
2424

2525
import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository;
2626
import org.springframework.ai.chat.messages.AssistantMessage;
@@ -34,12 +34,12 @@
3434
import org.springframework.boot.test.context.SpringBootTest;
3535
import org.springframework.context.ApplicationContext;
3636
import org.springframework.jdbc.core.JdbcTemplate;
37-
import org.springframework.test.context.junit4.SpringRunner;
37+
import org.springframework.test.context.junit.jupiter.SpringExtension;
3838

3939
import static org.assertj.core.api.Assertions.assertThat;
4040
import static org.assertj.core.api.Assertions.fail;
4141

42-
@RunWith(SpringRunner.class)
42+
@ExtendWith(SpringExtension.class)
4343
@SpringBootTest(classes = JdbcChatMemoryRepositoryHsqldbAutoConfigurationIT.TestConfig.class,
4444
properties = { "spring.datasource.url=jdbc:hsqldb:mem:chat_memory_auto_configuration_test;DB_CLOSE_DELAY=-1",
4545
"spring.datasource.username=sa", "spring.datasource.password=",
@@ -66,7 +66,7 @@ public class JdbcChatMemoryRepositoryHsqldbAutoConfigurationIT {
6666
/**
6767
* can't get the automatic loading of the schema with boot to work.
6868
*/
69-
@Before
69+
@BeforeEach
7070
public void setUp() {
7171
// Explicitly initialize the schema
7272
try {

auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatProperties.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,7 @@ public class BedrockConverseProxyChatProperties {
3333
public static final String CONFIG_PREFIX = "spring.ai.bedrock.converse.chat";
3434

3535
@NestedConfigurationProperty
36-
private ToolCallingChatOptions options = ToolCallingChatOptions.builder()
37-
.temperature(0.7)
38-
.maxTokens(300)
39-
.topK(10)
40-
.build();
36+
private ToolCallingChatOptions options = ToolCallingChatOptions.builder().temperature(0.7).maxTokens(300).build();
4137

4238
public ToolCallingChatOptions getOptions() {
4339
return this.options;

auto-configurations/models/spring-ai-autoconfigure-model-elevenlabs/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515
#
16-
org.springframework.ai.model.elevenlabs.autoconfigure.elevenlabsChatAutoConfiguration
16+
org.springframework.ai.model.elevenlabs.autoconfigure.ElevenLabsAutoConfiguration

auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@
1616

1717
package org.springframework.ai.model.tool.autoconfigure;
1818

19+
import io.micrometer.observation.ObservationRegistry;
1920
import java.util.ArrayList;
2021
import java.util.List;
21-
22-
import io.micrometer.observation.ObservationRegistry;
2322
import org.slf4j.Logger;
2423
import org.slf4j.LoggerFactory;
2524

@@ -43,12 +42,14 @@
4342
import org.springframework.boot.context.properties.EnableConfigurationProperties;
4443
import org.springframework.context.annotation.Bean;
4544
import org.springframework.context.support.GenericApplicationContext;
45+
import org.springframework.util.ClassUtils;
4646

4747
/**
4848
* Auto-configuration for common tool calling features of {@link ChatModel}.
4949
*
5050
* @author Thomas Vitale
5151
* @author Christian Tzolov
52+
* @author Daniel Garnier-Moiroux
5253
* @since 1.0.0
5354
*/
5455
@AutoConfiguration
@@ -78,7 +79,21 @@ ToolCallbackResolver toolCallbackResolver(GenericApplicationContext applicationC
7879
@Bean
7980
@ConditionalOnMissingBean
8081
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor(ToolCallingProperties properties) {
81-
return new DefaultToolExecutionExceptionProcessor(properties.isThrowExceptionOnError());
82+
ArrayList<Class<? extends RuntimeException>> rethrownExceptions = new ArrayList<>();
83+
84+
// ClientAuthorizationException is used by Spring Security in oauth2 flows,
85+
// for example with ServletOAuth2AuthorizedClientExchangeFilterFunction and
86+
// OAuth2ClientHttpRequestInterceptor.
87+
Class<? extends RuntimeException> oauth2Exception = getClassOrNull(
88+
"org.springframework.security.oauth2.client.ClientAuthorizationException");
89+
if (oauth2Exception != null) {
90+
rethrownExceptions.add(oauth2Exception);
91+
}
92+
93+
return DefaultToolExecutionExceptionProcessor.builder()
94+
.alwaysThrow(properties.isThrowExceptionOnError())
95+
.rethrowExceptions(rethrownExceptions)
96+
.build();
8297
}
8398

8499
@Bean
@@ -108,4 +123,14 @@ ToolCallingContentObservationFilter toolCallingContentObservationFilter() {
108123
return new ToolCallingContentObservationFilter();
109124
}
110125

126+
private static Class<? extends RuntimeException> getClassOrNull(String className) {
127+
try {
128+
return (Class<? extends RuntimeException>) ClassUtils.forName(className, null);
129+
}
130+
catch (ClassNotFoundException e) {
131+
logger.debug("Cannot load class", e);
132+
}
133+
return null;
134+
}
135+
111136
}

document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/layout/ForkPDFLayoutTextStripper.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import org.apache.pdfbox.text.PDFTextStripper;
2828
import org.apache.pdfbox.text.TextPosition;
2929
import org.apache.pdfbox.text.TextPositionComparator;
30+
import org.slf4j.Logger;
31+
import org.slf4j.LoggerFactory;
3032

3133
/**
3234
* This class extends PDFTextStripper to provide custom text extraction and formatting
@@ -38,6 +40,8 @@
3840
*/
3941
public class ForkPDFLayoutTextStripper extends PDFTextStripper {
4042

43+
private final static Logger logger = LoggerFactory.getLogger(ForkPDFLayoutTextStripper.class);
44+
4145
public static final boolean DEBUG = false;
4246

4347
public static final int OUTPUT_SPACE_CHARACTER_WIDTH_IN_PT = 4;
@@ -80,7 +84,7 @@ protected void writePage() throws IOException {
8084
this.sortTextPositionList(textList);
8185
}
8286
catch (java.lang.IllegalArgumentException e) {
83-
System.err.println(e);
87+
logger.error("Error sorting text positions", e);
8488
}
8589
this.iterateThroughTextList(textList.iterator());
8690
}

mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallback.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@
1717
package org.springframework.ai.mcp;
1818

1919
import io.modelcontextprotocol.client.McpAsyncClient;
20-
import io.modelcontextprotocol.spec.McpSchema;
2120
import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
2221
import io.modelcontextprotocol.spec.McpSchema.Tool;
2322
import java.util.Map;
24-
import reactor.core.publisher.Mono;
2523

2624
import org.springframework.ai.chat.model.ToolContext;
2725
import org.springframework.ai.model.ModelOptionsUtils;

models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,8 +1060,7 @@ public List<ContentBlockStartEvent.ContentBlockToolUse> getToolContentBlocks() {
10601060
* @return True if the event is empty, false otherwise.
10611061
*/
10621062
public boolean isEmpty() {
1063-
return (this.index == null || this.id == null || this.name == null
1064-
|| !StringUtils.hasText(this.partialJson));
1063+
return (this.index == null || this.id == null || this.name == null);
10651064
}
10661065

10671066
ToolUseAggregationEvent withIndex(Integer index) {

models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientMethodInvokingFunctionCallbackIT.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,23 @@
1919
import java.util.List;
2020
import java.util.Map;
2121
import java.util.concurrent.ConcurrentHashMap;
22+
import java.util.stream.Collectors;
2223

2324
import org.junit.jupiter.api.BeforeEach;
2425
import org.junit.jupiter.api.Test;
2526
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
27+
import org.junit.jupiter.params.ParameterizedTest;
28+
import org.junit.jupiter.params.provider.ValueSource;
2629
import org.slf4j.Logger;
2730
import org.slf4j.LoggerFactory;
2831

2932
import org.springframework.ai.anthropic.AnthropicTestConfiguration;
3033
import org.springframework.ai.chat.client.ChatClient;
3134
import org.springframework.ai.chat.messages.Message;
3235
import org.springframework.ai.chat.model.ChatModel;
36+
import org.springframework.ai.chat.model.ChatResponse;
3337
import org.springframework.ai.chat.model.ToolContext;
38+
import org.springframework.ai.model.tool.ToolCallingChatOptions;
3439
import org.springframework.ai.tool.annotation.Tool;
3540
import org.springframework.ai.tool.method.MethodToolCallback;
3641
import org.springframework.ai.tool.support.ToolDefinitions;
@@ -39,6 +44,8 @@
3944
import org.springframework.test.context.ActiveProfiles;
4045
import org.springframework.util.ReflectionUtils;
4146

47+
import reactor.core.publisher.Flux;
48+
4249
import static org.assertj.core.api.Assertions.assertThat;
4350
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
4451

@@ -262,6 +269,39 @@ void toolAnnotation() {
262269
.containsEntry("color", TestFunctionClass.LightColor.RED);
263270
}
264271

272+
// https://github.com/spring-projects/spring-ai/issues/1878
273+
@ParameterizedTest
274+
@ValueSource(strings = { "claude-opus-4-20250514", "claude-sonnet-4-20250514", "claude-3-7-sonnet-latest" })
275+
void streamingParameterLessTool(String modelName) {
276+
277+
ChatClient chatClient = ChatClient.builder(this.chatModel).build();
278+
279+
Flux<ChatResponse> responses = chatClient.prompt()
280+
.options(ToolCallingChatOptions.builder().model(modelName).build())
281+
.tools(new ParameterLessTools())
282+
.user("Get current weather in Amsterdam")
283+
.stream()
284+
.chatResponse();
285+
286+
String content = responses.collectList()
287+
.block()
288+
.stream()
289+
.filter(cr -> cr.getResult() != null)
290+
.map(cr -> cr.getResult().getOutput().getText())
291+
.collect(Collectors.joining());
292+
293+
assertThat(content).contains("20 degrees");
294+
}
295+
296+
public static class ParameterLessTools {
297+
298+
@Tool(description = "Get the current weather forecast in Amsterdam")
299+
String getCurrentDateTime() {
300+
return "Weather is hot and sunny with a temperature of 20 degrees";
301+
}
302+
303+
}
304+
265305
@Autowired
266306
ChatModel chatModel;
267307

models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/api/ConverseApiUtils.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -421,8 +421,7 @@ public List<ToolUseEntry> toolUseEntries() {
421421
}
422422

423423
public boolean isEmpty() {
424-
return (this.index == null || this.id == null || this.name == null
425-
|| !StringUtils.hasText(this.partialJson));
424+
return (this.index == null || this.id == null || this.name == null || this.partialJson == null);
426425
}
427426

428427
ToolUseAggregationEvent withIndex(Integer index) {
@@ -451,7 +450,9 @@ ToolUseAggregationEvent appendPartialJson(String partialJson) {
451450
}
452451

453452
void squashIntoContentBlock() {
454-
this.toolUseEntries.add(new ToolUseEntry(this.index, this.id, this.name, this.partialJson, this.usage));
453+
// Workaround to handle streaming tool calling with no input arguments.
454+
String json = StringUtils.hasText(this.partialJson) ? this.partialJson : "{}";
455+
this.toolUseEntries.add(new ToolUseEntry(this.index, this.id, this.name, json, this.usage));
455456
this.index = null;
456457
this.id = null;
457458
this.name = null;

0 commit comments

Comments
 (0)