Skip to content

Commit 6e4d95c

Browse files
committed
fix(bedrock): Handle streaming tool calling with no input arguments
- Fix isEmpty() check in ConverseApiUtils to properly handle null partialJson - Add workaround for streaming tool calls with empty arguments by defaulting to {} - Add parameterized tests for multiple Bedrock models (Nova Pro and Claude Sonnet) - Update AWS SDK and Bedrock runtime versions to 2.31.65 Fixes #1878 Signed-off-by: Christian Tzolov <[email protected]>
1 parent 3e02edc commit 6e4d95c

File tree

3 files changed

+35
-6
lines changed

3 files changed

+35
-6
lines changed

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;

models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/client/BedrockNovaChatClientIT.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.util.stream.Collectors;
2424

2525
import org.junit.jupiter.api.Test;
26+
import org.junit.jupiter.params.ParameterizedTest;
27+
import org.junit.jupiter.params.provider.ValueSource;
2628
import org.slf4j.Logger;
2729
import org.slf4j.LoggerFactory;
2830
import reactor.core.publisher.Flux;
@@ -186,13 +188,38 @@ void toolAnnotationWeatherForecast() {
186188
assertThat(response).contains("20 degrees");
187189
}
188190

191+
// https://github.com/spring-projects/spring-ai/issues/1878
192+
@ParameterizedTest
193+
@ValueSource(strings = { "amazon.nova-pro-v1:0", "us.anthropic.claude-3-7-sonnet-20250219-v1:0" })
194+
void toolAnnotationWeatherForecastStreaming(String modelName) {
195+
196+
ChatClient chatClient = ChatClient.builder(this.chatModel).build();
197+
198+
Flux<ChatResponse> responses = chatClient.prompt()
199+
.options(ToolCallingChatOptions.builder().model(modelName).build())
200+
.tools(new DummyWeatherForecastTools())
201+
.user("Get current weather in Amsterdam")
202+
.stream()
203+
.chatResponse();
204+
205+
String content = responses.collectList()
206+
.block()
207+
.stream()
208+
.filter(cr -> cr.getResult() != null)
209+
.map(cr -> cr.getResult().getOutput().getText())
210+
.collect(Collectors.joining());
211+
212+
assertThat(content).contains("20 degrees");
213+
}
214+
189215
@Test
190-
void toolAnnotationWeatherForecastStreaming() {
216+
void streamingToolCallingWithArgumentlessToolSonnet() {
191217

192218
ChatClient chatClient = ChatClient.builder(this.chatModel).build();
193219

194220
Flux<ChatResponse> responses = chatClient.prompt()
195221
.tools(new DummyWeatherForecastTools())
222+
.options(ToolCallingChatOptions.builder().model("us.anthropic.claude-3-7-sonnet-20250219-v1:0").build())
196223
.user("Get current weather in Amsterdam")
197224
.stream()
198225
.chatResponse();
@@ -257,6 +284,7 @@ public static class Config {
257284
public BedrockProxyChatModel bedrockConverseChatModel() {
258285

259286
String modelId = "amazon.nova-pro-v1:0";
287+
// String modelId = "us.anthropic.claude-3-7-sonnet-20250219-v1:0";
260288

261289
return BedrockProxyChatModel.builder()
262290
.credentialsProvider(EnvironmentVariableCredentialsProvider.create())

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,8 @@
264264
<kotlin.version>1.9.25</kotlin.version>
265265

266266
<!-- NOTE: keep bedrockruntime and awssdk versions aligned -->
267-
<bedrockruntime.version>2.31.26</bedrockruntime.version>
268-
<awssdk.version>2.29.29</awssdk.version>
267+
<bedrockruntime.version>2.31.65</bedrockruntime.version>
268+
<awssdk.version>2.31.65</awssdk.version>
269269

270270
<djl.version>0.32.0</djl.version>
271271
<onnxruntime.version>1.19.2</onnxruntime.version>

0 commit comments

Comments
 (0)