Skip to content

Commit ae2500b

Browse files
committed
GH-3657: Fix DeepSeek tool call content null issue
Closes #3657 * Add content fallback for DeepSeek when only tool calls are present * Ensures AssistantMessage has proper output for downstream processing * Add test case for AssistantMessage with tool calls only Signed-off-by: Dongha Koo <[email protected]>
1 parent aa590e8 commit ae2500b

File tree

3 files changed

+32
-1
lines changed

3 files changed

+32
-1
lines changed

models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
* backed by {@link DeepSeekApi}.
7777
*
7878
* @author Geng Rong
79+
* @author Dongha Koo
7980
*/
8081
public class DeepSeekChatModel implements ChatModel {
8182

@@ -338,7 +339,9 @@ private Generation buildGeneration(Choice choice, Map<String, Object> metadata)
338339

339340
String textContent = choice.message().content();
340341
String reasoningContent = choice.message().reasoningContent();
341-
342+
if (textContent == null && !toolCalls.isEmpty()) {
343+
textContent = "__tool_call__";
344+
}
342345
DeepSeekAssistantMessage assistantMessage = new DeepSeekAssistantMessage(textContent, reasoningContent,
343346
metadata, toolCalls);
344347
return new Generation(assistantMessage, generationMetadataBuilder.build());

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717
package org.springframework.ai.chat.client;
1818

1919
import java.util.HashMap;
20+
import java.util.List;
2021
import java.util.Map;
2122

2223
import org.junit.jupiter.api.Test;
24+
import org.springframework.ai.chat.messages.AssistantMessage;
25+
import org.springframework.ai.chat.model.Generation;
2326

2427
import static org.assertj.core.api.Assertions.assertThat;
2528
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -28,6 +31,7 @@
2831
* Unit tests for {@link ChatClientResponse}.
2932
*
3033
* @author Thomas Vitale
34+
* @author Dongha Koo
3135
*/
3236
class ChatClientResponseTests {
3337

@@ -82,4 +86,15 @@ void whenMutateThenImmutableContext() {
8286
assertThat(response.context()).containsEntry("key", "value");
8387
}
8488

89+
@Test
90+
void whenAssistantMessageHasOnlyToolCalls_thenContentIsToolCallMarker() {
91+
var toolCall = new AssistantMessage.ToolCall("tool-1", "function", "doSomething", "{\"foo\":\"bar\"}");
92+
var assistantMessage = new AssistantMessage(null, Map.of(), List.of(toolCall), List.of());
93+
94+
assertThat(assistantMessage.getDisplayText()).isEqualTo("__tool_call__");
95+
96+
var generation = new Generation(assistantMessage);
97+
assertThat(generation.getOutput().getDisplayText()).isEqualTo("__tool_call__");
98+
}
99+
85100
}

spring-ai-model/src/main/java/org/springframework/ai/chat/messages/AssistantMessage.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
*
3434
* @author Mark Pollack
3535
* @author Christian Tzolov
36+
* @author Dongha Koo
3637
* @since 1.0.0
3738
*/
3839
public class AssistantMessage extends AbstractMessage implements MediaContent {
@@ -104,4 +105,16 @@ public record ToolCall(String id, String type, String name, String arguments) {
104105

105106
}
106107

108+
/**
109+
* Returns a safe, non-null text representation. If text is null or empty and
110+
* toolCalls exist, returns "__tool_call__".
111+
*/
112+
public String getDisplayText() {
113+
String text = super.getText();
114+
if ((text == null || text.trim().isEmpty()) && this.hasToolCalls()) {
115+
return "__tool_call__";
116+
}
117+
return (text != null) ? text : "";
118+
}
119+
107120
}

0 commit comments

Comments
 (0)