Skip to content

Commit 7d0aff0

Browse files
sunyuhan1998ericbottard
authored andcommitted
feat: GH-4452 Added Builder pattern support for DeepSeekAssistantMessage and included corresponding unit tests.
Signed-off-by: Sun Yuhan <[email protected]> Signed-off-by: Eric Bottard <[email protected]>
1 parent 37b1fd0 commit 7d0aff0

File tree

3 files changed

+288
-7
lines changed

3 files changed

+288
-7
lines changed

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

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,46 +23,81 @@
2323
import org.springframework.ai.chat.messages.AssistantMessage;
2424
import org.springframework.ai.content.Media;
2525

26+
/**
27+
* @author Mark Pollack
28+
* @author Soby Chacko
29+
* @author Sun Yuhan
30+
*/
2631
public class DeepSeekAssistantMessage extends AssistantMessage {
2732

2833
private Boolean prefix;
2934

3035
private String reasoningContent;
3136

37+
/**
38+
* @deprecated in favor of {@link DeepSeekAssistantMessage.Builder}
39+
*/
40+
@Deprecated
3241
public DeepSeekAssistantMessage(String content) {
3342
super(content);
3443
}
3544

45+
/**
46+
* @deprecated in favor of {@link DeepSeekAssistantMessage.Builder}
47+
*/
48+
@Deprecated
3649
public DeepSeekAssistantMessage(String content, String reasoningContent) {
3750
super(content);
3851
this.reasoningContent = reasoningContent;
3952
}
4053

54+
/**
55+
* @deprecated in favor of {@link DeepSeekAssistantMessage.Builder}
56+
*/
57+
@Deprecated
4158
public DeepSeekAssistantMessage(String content, Map<String, Object> properties) {
4259
super(content, properties);
4360
}
4461

62+
/**
63+
* @deprecated in favor of {@link DeepSeekAssistantMessage.Builder}
64+
*/
65+
@Deprecated
4566
public DeepSeekAssistantMessage(String content, Map<String, Object> properties, List<ToolCall> toolCalls) {
4667
super(content, properties, toolCalls);
4768
}
4869

70+
/**
71+
* @deprecated in favor of {@link DeepSeekAssistantMessage.Builder}
72+
*/
73+
@Deprecated
4974
public DeepSeekAssistantMessage(String content, String reasoningContent, Map<String, Object> properties,
5075
List<ToolCall> toolCalls) {
5176
this(content, reasoningContent, properties, toolCalls, List.of());
5277
}
5378

79+
/**
80+
* @deprecated in favor of {@link DeepSeekAssistantMessage.Builder}
81+
*/
82+
@Deprecated
5483
public DeepSeekAssistantMessage(String content, String reasoningContent, Map<String, Object> properties,
5584
List<ToolCall> toolCalls, List<Media> media) {
85+
this(content, reasoningContent, null, properties, toolCalls, media);
86+
}
87+
88+
protected DeepSeekAssistantMessage(String content, String reasoningContent, Boolean prefix,
89+
Map<String, Object> properties, List<ToolCall> toolCalls, List<Media> media) {
5690
super(content, properties, toolCalls, media);
5791
this.reasoningContent = reasoningContent;
92+
this.prefix = prefix;
5893
}
5994

60-
public static DeepSeekAssistantMessage prefixAssistantMessage(String context) {
61-
return prefixAssistantMessage(context, null);
95+
public static DeepSeekAssistantMessage prefixAssistantMessage(String content) {
96+
return prefixAssistantMessage(content, null);
6297
}
6398

64-
public static DeepSeekAssistantMessage prefixAssistantMessage(String context, String reasoningContent) {
65-
return new DeepSeekAssistantMessage(context, reasoningContent);
99+
public static DeepSeekAssistantMessage prefixAssistantMessage(String content, String reasoningContent) {
100+
return new DeepSeekAssistantMessage.Builder().content(content).reasoningContent(reasoningContent).build();
66101
}
67102

68103
public Boolean getPrefix() {
@@ -102,9 +137,60 @@ public int hashCode() {
102137

103138
@Override
104139
public String toString() {
105-
return "AssistantMessage [messageType=" + this.messageType + ", toolCalls=" + super.getToolCalls()
140+
return "DeepSeekAssistantMessage [messageType=" + this.messageType + ", toolCalls=" + super.getToolCalls()
106141
+ ", textContent=" + this.textContent + ", reasoningContent=" + this.reasoningContent + ", prefix="
107142
+ this.prefix + ", metadata=" + this.metadata + "]";
108143
}
109144

145+
public static final class Builder {
146+
147+
private String content;
148+
149+
private Map<String, Object> properties = Map.of();
150+
151+
private List<ToolCall> toolCalls = List.of();
152+
153+
private List<Media> media = List.of();
154+
155+
private Boolean prefix;
156+
157+
private String reasoningContent;
158+
159+
public Builder content(String content) {
160+
this.content = content;
161+
return this;
162+
}
163+
164+
public Builder properties(Map<String, Object> properties) {
165+
this.properties = properties;
166+
return this;
167+
}
168+
169+
public Builder toolCalls(List<ToolCall> toolCalls) {
170+
this.toolCalls = toolCalls;
171+
return this;
172+
}
173+
174+
public Builder media(List<Media> media) {
175+
this.media = media;
176+
return this;
177+
}
178+
179+
public Builder prefix(Boolean prefix) {
180+
this.prefix = prefix;
181+
return this;
182+
}
183+
184+
public Builder reasoningContent(String reasoningContent) {
185+
this.reasoningContent = reasoningContent;
186+
return this;
187+
}
188+
189+
public DeepSeekAssistantMessage build() {
190+
return new DeepSeekAssistantMessage(this.content, this.reasoningContent, this.prefix, this.properties,
191+
this.toolCalls, this.media);
192+
}
193+
194+
}
195+
110196
}

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,13 @@ private Generation buildGeneration(Choice choice, Map<String, Object> metadata)
340340
String textContent = choice.message().content();
341341
String reasoningContent = choice.message().reasoningContent();
342342

343-
DeepSeekAssistantMessage assistantMessage = new DeepSeekAssistantMessage(textContent, reasoningContent,
344-
metadata, toolCalls);
343+
DeepSeekAssistantMessage.Builder builder = new DeepSeekAssistantMessage.Builder();
344+
DeepSeekAssistantMessage assistantMessage = builder.content(textContent)
345+
.reasoningContent(reasoningContent)
346+
.properties(metadata)
347+
.toolCalls(toolCalls)
348+
.build();
349+
345350
return new Generation(assistantMessage, generationMetadataBuilder.build());
346351
}
347352

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/*
2+
* Copyright 2025-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.deepseek;
18+
19+
import java.util.HashMap;
20+
import java.util.List;
21+
import java.util.Map;
22+
23+
import org.junit.jupiter.api.Test;
24+
25+
import org.springframework.ai.chat.messages.AssistantMessage.ToolCall;
26+
import org.springframework.ai.content.Media;
27+
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
import static org.assertj.core.api.Assertions.assertThatNoException;
30+
31+
/**
32+
* Unit tests for {@link DeepSeekAssistantMessage}.
33+
*
34+
* @author Sun Yuhan
35+
*/
36+
class DeepSeekAssistantMessageTests {
37+
38+
@Test
39+
public void testConstructorWithContentOnly() {
40+
String content = "Hello, world!";
41+
DeepSeekAssistantMessage message = new DeepSeekAssistantMessage(content);
42+
43+
assertThat(message.getText()).isEqualTo(content);
44+
assertThat(message.getReasoningContent()).isNull();
45+
assertThat(message.getPrefix()).isNull();
46+
}
47+
48+
@Test
49+
public void testConstructorWithContentAndReasoningContent() {
50+
String content = "Hello, world!";
51+
String reasoningContent = "This is my reasoning";
52+
DeepSeekAssistantMessage message = new DeepSeekAssistantMessage(content, reasoningContent);
53+
54+
assertThat(message.getText()).isEqualTo(content);
55+
assertThat(message.getReasoningContent()).isEqualTo(reasoningContent);
56+
assertThat(message.getPrefix()).isNull();
57+
}
58+
59+
@Test
60+
public void testConstructorWithContentAndProperties() {
61+
String content = "Hello, world!";
62+
Map<String, Object> properties = new HashMap<>();
63+
properties.put("key1", "value1");
64+
properties.put("key2", 123);
65+
66+
DeepSeekAssistantMessage message = new DeepSeekAssistantMessage(content, properties);
67+
68+
assertThat(message.getText()).isEqualTo(content);
69+
assertThat(message.getMetadata()).containsAllEntriesOf(properties);
70+
assertThat(message.getReasoningContent()).isNull();
71+
assertThat(message.getPrefix()).isNull();
72+
}
73+
74+
@Test
75+
public void testConstructorWithContentPropertiesAndToolCalls() {
76+
String content = "Hello, world!";
77+
Map<String, Object> properties = new HashMap<>();
78+
properties.put("key1", "value1");
79+
80+
List<ToolCall> toolCalls = List.of(new ToolCall("1", "function", "myFunction", "{}"));
81+
82+
DeepSeekAssistantMessage message = new DeepSeekAssistantMessage(content, properties, toolCalls);
83+
84+
assertThat(message.getText()).isEqualTo(content);
85+
assertThat(message.getMetadata()).containsAllEntriesOf(properties);
86+
assertThat(message.getToolCalls()).isEqualTo(toolCalls);
87+
assertThat(message.getReasoningContent()).isNull();
88+
assertThat(message.getPrefix()).isNull();
89+
}
90+
91+
@Test
92+
public void testConstructorWithAllParameters() {
93+
String content = "Hello, world!";
94+
String reasoningContent = "This is my reasoning";
95+
Boolean prefix = true;
96+
Map<String, Object> properties = new HashMap<>();
97+
properties.put("key1", "value1");
98+
List<ToolCall> toolCalls = List.of(new ToolCall("1", "function", "myFunction", "{}"));
99+
100+
DeepSeekAssistantMessage message = new DeepSeekAssistantMessage(content, reasoningContent, prefix, properties,
101+
toolCalls, List.of());
102+
103+
assertThat(message.getText()).isEqualTo(content);
104+
assertThat(message.getReasoningContent()).isEqualTo(reasoningContent);
105+
assertThat(message.getPrefix()).isEqualTo(prefix);
106+
assertThat(message.getMetadata()).containsAllEntriesOf(properties);
107+
assertThat(message.getToolCalls()).isEqualTo(toolCalls);
108+
}
109+
110+
@Test
111+
public void testPrefixAssistantMessageFactoryMethod() {
112+
String content = "Hello, world!";
113+
DeepSeekAssistantMessage message = DeepSeekAssistantMessage.prefixAssistantMessage(content);
114+
115+
assertThat(message.getText()).isEqualTo(content);
116+
assertThat(message.getReasoningContent()).isNull();
117+
}
118+
119+
@Test
120+
public void testPrefixAssistantMessageFactoryMethodWithReasoning() {
121+
String content = "Hello, world!";
122+
String reasoningContent = "This is my reasoning";
123+
DeepSeekAssistantMessage message = DeepSeekAssistantMessage.prefixAssistantMessage(content, reasoningContent);
124+
125+
assertThat(message.getText()).isEqualTo(content);
126+
assertThat(message.getReasoningContent()).isEqualTo(reasoningContent);
127+
}
128+
129+
@Test
130+
public void testSettersAndGetters() {
131+
DeepSeekAssistantMessage message = new DeepSeekAssistantMessage("test");
132+
133+
String reasoningContent = "New reasoning content";
134+
Boolean prefix = false;
135+
136+
message.setReasoningContent(reasoningContent);
137+
message.setPrefix(prefix);
138+
139+
assertThat(message.getReasoningContent()).isEqualTo(reasoningContent);
140+
assertThat(message.getPrefix()).isEqualTo(prefix);
141+
}
142+
143+
@Test
144+
public void testEqualsAndHashCode() {
145+
DeepSeekAssistantMessage message1 = new DeepSeekAssistantMessage("content", "reasoning", true, Map.of(),
146+
List.of(), List.of());
147+
DeepSeekAssistantMessage message2 = new DeepSeekAssistantMessage("content", "reasoning", true, Map.of(),
148+
List.of(), List.of());
149+
150+
assertThat(message1).isEqualTo(message2);
151+
assertThat(message1.hashCode()).isEqualTo(message2.hashCode());
152+
153+
DeepSeekAssistantMessage message3 = new DeepSeekAssistantMessage("content", "different reasoning", true,
154+
Map.of(), List.of(), List.of());
155+
assertThat(message1).isNotEqualTo(message3);
156+
}
157+
158+
@Test
159+
public void testToString() {
160+
DeepSeekAssistantMessage message = new DeepSeekAssistantMessage("content", "reasoning");
161+
message.setPrefix(true);
162+
163+
assertThatNoException().isThrownBy(message::toString);
164+
assertThat(message.toString()).contains("content", "reasoning", "true");
165+
}
166+
167+
@Test
168+
public void testBuilderComplete() {
169+
Map<String, Object> properties = Map.of("key", "value");
170+
List<ToolCall> toolCalls = List.of(new ToolCall("1", "function", "testFunction", "{}"));
171+
List<Media> media = List.of();
172+
173+
DeepSeekAssistantMessage.Builder builder = new DeepSeekAssistantMessage.Builder();
174+
DeepSeekAssistantMessage message = builder.content("content")
175+
.reasoningContent("reasoning")
176+
.prefix(true)
177+
.properties(properties)
178+
.toolCalls(toolCalls)
179+
.media(media)
180+
.build();
181+
182+
assertThat(message.getText()).isEqualTo("content");
183+
assertThat(message.getReasoningContent()).isEqualTo("reasoning");
184+
assertThat(message.getPrefix()).isEqualTo(true);
185+
assertThat(message.getMetadata()).containsAllEntriesOf(properties);
186+
assertThat(message.getToolCalls()).isEqualTo(toolCalls);
187+
assertThat(message.getMedia()).isEqualTo(media);
188+
}
189+
190+
}

0 commit comments

Comments
 (0)