Skip to content

Commit 34a291a

Browse files
committed
fix: enhance content extraction from chat response to handle multiple generations with null content
Signed-off-by: liugddx <[email protected]>
1 parent 0fdb911 commit 34a291a

File tree

2 files changed

+39
-8
lines changed

2 files changed

+39
-8
lines changed

spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -519,14 +519,23 @@ private ChatClientResponse doGetObservableChatClientResponse(ChatClientRequest c
519519
return chatClientResponse != null ? chatClientResponse : ChatClientResponse.builder().build();
520520
}
521521

522-
@Nullable
523-
private static String getContentFromChatResponse(@Nullable ChatResponse chatResponse) {
524-
return Optional.ofNullable(chatResponse)
525-
.map(ChatResponse::getResult)
526-
.map(Generation::getOutput)
527-
.map(AbstractMessage::getText)
528-
.orElse(null);
529-
}
522+
@Nullable
523+
private static String getContentFromChatResponse(@Nullable ChatResponse chatResponse) {
524+
if (chatResponse == null || CollectionUtils.isEmpty(chatResponse.getResults())) {
525+
return null;
526+
}
527+
// Iterate through all generations to find the first one with non-null content
528+
// This handles cases where models return multiple generations (e.g., Bedrock Converse API
529+
// with openai.gpt-oss models may return reasoning output first with null content,
530+
// followed by the actual response)
531+
return chatResponse.getResults()
532+
.stream()
533+
.map(Generation::getOutput)
534+
.map(AbstractMessage::getText)
535+
.filter(text -> text != null)
536+
.findFirst()
537+
.orElse(null);
538+
}
530539

531540
}
532541

spring-ai-client-chat/src/test/java/org/springframework/ai/chat/client/DefaultChatClientTests.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,28 @@ void whenChatResponseContentIsNull() {
969969
assertThat(content).isNull();
970970
}
971971

972+
@Test
973+
void whenMultipleGenerationsWithFirstContentNull() {
974+
// Test case for Bedrock Converse API with openai.gpt-oss models
975+
// which return multiple generations where the first one has null content (reasoning output)
976+
// and the second one contains the actual response
977+
ChatModel chatModel = mock(ChatModel.class);
978+
ArgumentCaptor<Prompt> promptCaptor = ArgumentCaptor.forClass(Prompt.class);
979+
given(chatModel.call(promptCaptor.capture())).willReturn(new ChatResponse(List.of(
980+
new Generation(new AssistantMessage(null)), // First generation with null content
981+
new Generation(new AssistantMessage("Hello! How can I help you today?")) // Second generation with actual content
982+
)));
983+
984+
ChatClient chatClient = new DefaultChatClientBuilder(chatModel).build();
985+
DefaultChatClient.DefaultChatClientRequestSpec chatClientRequestSpec = (DefaultChatClient.DefaultChatClientRequestSpec) chatClient
986+
.prompt("Hello");
987+
DefaultChatClient.DefaultCallResponseSpec spec = (DefaultChatClient.DefaultCallResponseSpec) chatClientRequestSpec
988+
.call();
989+
990+
String content = spec.content();
991+
assertThat(content).isEqualTo("Hello! How can I help you today?");
992+
}
993+
972994
@Test
973995
void whenResponseEntityWithParameterizedTypeIsNull() {
974996
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();

0 commit comments

Comments
 (0)