Skip to content

Commit 88e03cd

Browse files
ybezsonovilayaperumalg
authored andcommitted
Fix null response for OpenAI gpt-oss models on Amazon Bedrock
Add filter to skip ContentBlocks with null text content when processing responses. OpenAI gpt-oss models return multiple ContentBlocks including ReasoningContent (null) and Text blocks. The fix ensures only blocks with actual text content are processed. Fixes #4861 Signed-off-by: Yuriy Bezsonov <ybezsonov@gmail.com>
1 parent 0f0f33e commit 88e03cd

File tree

2 files changed

+66
-0
lines changed

2 files changed

+66
-0
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,7 @@ private ChatResponse toChatResponse(ConverseResponse response, ChatResponse perv
670670
List<Generation> generations = message.content()
671671
.stream()
672672
.filter(content -> content.type() != ContentBlock.Type.TOOL_USE)
673+
.filter(content -> content.text() != null)
673674
.map(content -> new Generation(
674675
AssistantMessage.builder().content(content.text()).properties(Map.of()).build(),
675676
ChatGenerationMetadata.builder().finishReason(response.stopReasonAsString()).build()))

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

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,71 @@ Each alert includes the official alert level (advisory, watch, warning), affecte
871871
""").inputType(MockWeatherService.Request.class).build());
872872
}
873873

874+
@Test
875+
void testOpenAIGptOssModelResponse() {
876+
// Test for OpenAI gpt-oss models on Bedrock which return ReasoningContent + Text
877+
// blocks
878+
// This test verifies the fix for null responses when gpt-oss models return
879+
// multiple
880+
// ContentBlocks
881+
String model = "openai.gpt-oss-120b-1:0";
882+
883+
UserMessage userMessage = new UserMessage("What is 2+2? Answer briefly.");
884+
Prompt prompt = new Prompt(List.of(userMessage), BedrockChatOptions.builder().model(model).build());
885+
886+
ChatResponse response = this.chatModel.call(prompt);
887+
888+
// Verify response is not null and contains expected content
889+
assertThat(response.getResults()).hasSize(1);
890+
Generation generation = response.getResults().get(0);
891+
892+
// The key assertion: response text should NOT be null
893+
assertThat(generation.getOutput().getText()).as("gpt-oss model should return non-null text content")
894+
.isNotNull()
895+
.isNotEmpty();
896+
897+
// Verify the response contains the expected answer
898+
assertThat(generation.getOutput().getText()).as("gpt-oss should correctly answer the math question")
899+
.containsAnyOf("4", "four");
900+
901+
// Verify metadata
902+
assertThat(generation.getMetadata().getFinishReason()).isEqualTo("end_turn");
903+
assertThat(response.getMetadata().getUsage().getPromptTokens()).isPositive();
904+
assertThat(response.getMetadata().getUsage().getCompletionTokens()).isPositive();
905+
assertThat(response.getMetadata().getUsage().getTotalTokens()).isPositive();
906+
907+
logger.info("gpt-oss Response: {}", generation.getOutput().getText());
908+
logger.info("Response metadata: {}", response.getMetadata());
909+
}
910+
911+
@Test
912+
void testOpenAIGptOssModelStreamingResponse() {
913+
// Test streaming with OpenAI gpt-oss models to ensure ReasoningContent blocks are
914+
// handled correctly
915+
String model = "openai.gpt-oss-120b-1:0";
916+
917+
UserMessage userMessage = new UserMessage("Who are you?");
918+
Prompt prompt = new Prompt(List.of(userMessage), BedrockChatOptions.builder().model(model).build());
919+
920+
Flux<ChatResponse> responseFlux = this.chatModel.stream(prompt);
921+
922+
String fullResponse = responseFlux.collectList()
923+
.block()
924+
.stream()
925+
.filter(cr -> cr.getResult() != null)
926+
.map(cr -> cr.getResult().getOutput().getText())
927+
.collect(Collectors.joining());
928+
929+
// Verify streaming response is not null or empty
930+
assertThat(fullResponse).as("gpt-oss streaming response should not be null or empty").isNotNull().isNotEmpty();
931+
932+
// Verify the response contains expected gpt-oss identification
933+
assertThat(fullResponse.toLowerCase()).as("gpt-oss model should identify itself")
934+
.containsAnyOf("chatgpt", "gpt", "openai", "language model", "ai");
935+
936+
logger.info("gpt-oss Streaming Response: {}", fullResponse);
937+
}
938+
874939
record ActorsFilmsRecord(String actor, List<String> movies) {
875940

876941
}

0 commit comments

Comments
 (0)