Skip to content

Commit 54a884d

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 54a884d

File tree

2 files changed

+44
-2
lines changed

2 files changed

+44
-2
lines changed

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -521,10 +521,21 @@ private ChatClientResponse doGetObservableChatClientResponse(ChatClientRequest c
521521

522522
@Nullable
523523
private static String getContentFromChatResponse(@Nullable ChatResponse chatResponse) {
524-
return Optional.ofNullable(chatResponse)
525-
.map(ChatResponse::getResult)
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
529+
// Converse API
530+
// with openai.gpt-oss models may return reasoning output first with null
531+
// content,
532+
// followed by the actual response)
533+
return chatResponse.getResults()
534+
.stream()
526535
.map(Generation::getOutput)
527536
.map(AbstractMessage::getText)
537+
.filter(text -> text != null)
538+
.findFirst()
528539
.orElse(null);
529540
}
530541

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,37 @@ 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
976+
// (reasoning output)
977+
// and the second one contains the actual response
978+
ChatModel chatModel = mock(ChatModel.class);
979+
ArgumentCaptor<Prompt> promptCaptor = ArgumentCaptor.forClass(Prompt.class);
980+
given(chatModel.call(promptCaptor.capture()))
981+
.willReturn(new ChatResponse(List.of(new Generation(new AssistantMessage(null)), // First
982+
// generation
983+
// with
984+
// null
985+
// content
986+
new Generation(new AssistantMessage("Hello! How can I help you today?")) // Second
987+
// generation
988+
// with
989+
// actual
990+
// content
991+
)));
992+
993+
ChatClient chatClient = new DefaultChatClientBuilder(chatModel).build();
994+
DefaultChatClient.DefaultChatClientRequestSpec chatClientRequestSpec = (DefaultChatClient.DefaultChatClientRequestSpec) chatClient
995+
.prompt("Hello");
996+
DefaultChatClient.DefaultCallResponseSpec spec = (DefaultChatClient.DefaultCallResponseSpec) chatClientRequestSpec
997+
.call();
998+
999+
String content = spec.content();
1000+
assertThat(content).isEqualTo("Hello! How can I help you today?");
1001+
}
1002+
9721003
@Test
9731004
void whenResponseEntityWithParameterizedTypeIsNull() {
9741005
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();

0 commit comments

Comments
 (0)