Skip to content

Commit f6153c3

Browse files
committed
feat: GH-4452 Added Builder pattern support for DeepSeekAssistantMessage and included corresponding unit tests.
Signed-off-by: Sun Yuhan <[email protected]>
1 parent 84efb6a commit f6153c3

File tree

4 files changed

+261
-8
lines changed

4 files changed

+261
-8
lines changed

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ public DeepSeekAssistantMessage(String content, String reasoningContent, Map<Str
5757
this.reasoningContent = reasoningContent;
5858
}
5959

60+
public DeepSeekAssistantMessage(String content, String reasoningContent, Boolean prefix,
61+
Map<String, Object> properties, List<ToolCall> toolCalls, List<Media> media) {
62+
super(content, properties, toolCalls, media);
63+
this.reasoningContent = reasoningContent;
64+
this.prefix = prefix;
65+
}
66+
6067
public static DeepSeekAssistantMessage prefixAssistantMessage(String context) {
6168
return prefixAssistantMessage(context, null);
6269
}
@@ -107,4 +114,55 @@ public String toString() {
107114
+ this.prefix + ", metadata=" + this.metadata + "]";
108115
}
109116

117+
public static Builder builder() {
118+
return new Builder();
119+
}
120+
121+
public static final class Builder extends AssistantMessage.Builder {
122+
123+
private Boolean prefix;
124+
125+
private String reasoningContent;
126+
127+
@Override
128+
public Builder content(String content) {
129+
this.content = content;
130+
return this;
131+
}
132+
133+
@Override
134+
public Builder properties(Map<String, Object> properties) {
135+
this.properties = properties;
136+
return this;
137+
}
138+
139+
@Override
140+
public Builder toolCalls(List<ToolCall> toolCalls) {
141+
this.toolCalls = toolCalls;
142+
return this;
143+
}
144+
145+
@Override
146+
public Builder media(List<Media> media) {
147+
this.media = media;
148+
return this;
149+
}
150+
151+
public Builder prefix(Boolean prefix) {
152+
this.prefix = prefix;
153+
return this;
154+
}
155+
156+
public Builder reasoningContent(String reasoningContent) {
157+
this.reasoningContent = reasoningContent;
158+
return this;
159+
}
160+
161+
public DeepSeekAssistantMessage build() {
162+
return new DeepSeekAssistantMessage(this.content, this.reasoningContent, this.prefix, this.properties,
163+
this.toolCalls, this.media);
164+
}
165+
166+
}
167+
110168
}

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 assistantMessage = DeepSeekAssistantMessage.builder()
344+
.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 message = DeepSeekAssistantMessage.builder()
174+
.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+
}

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,17 +121,17 @@ public record ToolCall(String id, String type, String name, String arguments) {
121121

122122
}
123123

124-
public static final class Builder {
124+
public static class Builder {
125125

126-
private String content;
126+
protected String content;
127127

128-
private Map<String, Object> properties = Map.of();
128+
protected Map<String, Object> properties = Map.of();
129129

130-
private List<ToolCall> toolCalls = List.of();
130+
protected List<ToolCall> toolCalls = List.of();
131131

132-
private List<Media> media = List.of();
132+
protected List<Media> media = List.of();
133133

134-
private Builder() {
134+
protected Builder() {
135135
}
136136

137137
public Builder content(String content) {

0 commit comments

Comments
 (0)