From d8961cb8927118ebe340681eb292da5303bef751 Mon Sep 17 00:00:00 2001 From: Ilayaperumal Gopinathan Date: Mon, 7 Jul 2025 15:09:24 +0100 Subject: [PATCH] Fix NPE in AnthropicApi StreamHelper - When the error event occurs, the ChatCompletionResponseBuilder is empty and hence the contentBlockReference.get() throws NPE. - Add null check to unhandled event in addition to the logging the event type - Add test to verify the usecase Resolves #3740 Signed-off-by: Ilayaperumal Gopinathan --- .../ai/anthropic/api/StreamHelper.java | 9 +++++++ .../ai/anthropic/api/StreamHelperTests.java | 27 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/StreamHelperTests.java diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/StreamHelper.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/StreamHelper.java index 980c3342afe..f636f29a158 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/StreamHelper.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/StreamHelper.java @@ -20,6 +20,9 @@ import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionResponse; import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlock; import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlock.Type; @@ -56,6 +59,8 @@ */ public class StreamHelper { + private static final Logger logger = LoggerFactory.getLogger(StreamHelper.class); + public boolean isToolUseStart(StreamEvent event) { if (event == null || event.type() == null || event.type() != EventType.CONTENT_BLOCK_START) { return false; @@ -216,7 +221,11 @@ else if (event.type().equals(EventType.MESSAGE_STOP)) { } else { // Any other event types that should propagate upwards without content + if (contentBlockReference.get() == null) { + contentBlockReference.set(new ChatCompletionResponseBuilder()); + } contentBlockReference.get().withType(event.type().name()).withContent(List.of()); + logger.warn("Unhandled event type: {}", event.type().name()); } return contentBlockReference.get().build(); diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/StreamHelperTests.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/StreamHelperTests.java new file mode 100644 index 00000000000..ae112ad7daa --- /dev/null +++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/StreamHelperTests.java @@ -0,0 +1,27 @@ +package org.springframework.ai.anthropic.api; + +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.jupiter.api.Test; + +import org.springframework.ai.anthropic.api.StreamHelper.ChatCompletionResponseBuilder; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Ilayaperumal Gopinathan + */ +class StreamHelperTest { + + @Test + void testErrorEventTypeWithEmptyContentBlock() { + AnthropicApi.ErrorEvent errorEvent = new AnthropicApi.ErrorEvent(AnthropicApi.EventType.ERROR, + new AnthropicApi.ErrorEvent.Error("error", "error message")); + AtomicReference contentBlockReference = new AtomicReference<>(); + StreamHelper streamHelper = new StreamHelper(); + AnthropicApi.ChatCompletionResponse response = streamHelper.eventToChatCompletionResponse(errorEvent, + contentBlockReference); + assertThat(response).isNotNull(); + } + +}