1use crate::DeepSeekClient;
6use serde::{Deserialize, Serialize};
7
8pub mod request;
9pub mod response;
10pub mod stream;
11
12pub use stream::{ChatStreamBlocking, ChatStreamItem};
13
14pub(crate) fn is_none_or_empty_vec<T>(opt: &Option<Vec<T>>) -> bool {
16 opt.as_ref().map(|v| v.is_empty()).unwrap_or(true)
17}
18
19pub type Chat = response::ChatGeneric<response::ChatChoice>;
21
22pub type ChatStream = response::ChatGeneric<response::ChatChoiceStream>;
24
25#[cfg(test)]
26mod tests {
27 use crate::{DEFAULT_BASE_URL, DeepSeekClient};
28
29 use super::request::*;
30 use super::response::*;
31 use serde_json::{Value, json};
32
33 #[test]
34 fn response_format_serializes_to_json_object() {
35 let format = ResponseFormat::json_object();
36 let value = serde_json::to_value(format).unwrap();
37 assert_eq!(value, json!({"type": "json_object"}));
38 }
39
40 #[test]
41 fn stop_supports_string_and_array() {
42 let single = Stop::from("END");
43 let many = Stop::from(vec!["END", "STOP"]);
44
45 let single_value = serde_json::to_value(single).unwrap();
46 let many_value = serde_json::to_value(many).unwrap();
47
48 assert_eq!(single_value, json!("END"));
49 assert_eq!(many_value, json!(["END", "STOP"]));
50
51 let single_back: Stop = serde_json::from_value(json!("END")).unwrap();
52 let many_back: Stop = serde_json::from_value(json!(["A", "B"])).unwrap();
53 assert!(matches!(single_back, Stop::One(_)));
54 assert!(matches!(many_back, Stop::Many(_)));
55
56 let none_back: Option<Stop> = serde_json::from_value(Value::Null).unwrap();
57 assert!(none_back.is_none());
58 }
59
60 #[test]
61 fn tool_choice_serializes_simple_and_named() {
62 let simple = ToolChoice::Simple(ChatToolChoice::Auto);
63 let simple_value = serde_json::to_value(simple).unwrap();
64 assert_eq!(simple_value, json!("auto"));
65
66 let named = ToolChoice::named(json!({"name": "get_weather"}));
67 let named_value = serde_json::to_value(named).unwrap();
68 assert_eq!(
69 named_value,
70 json!({"type": "function", "function": {"name": "get_weather"}})
71 );
72 }
73
74 #[test]
75 fn chat_message_serializes_role_and_omits_prefix_by_default() {
76 let message = ChatMessage::Assistant {
77 content: Some("Hello".to_string()),
78 name: None,
79 tool_calls: None,
80 };
81 let value = serde_json::to_value(message).unwrap();
82 assert_eq!(value.get("role"), Some(&json!("assistant")));
83 assert_eq!(value.get("content"), Some(&json!("Hello")));
84 assert!(value.get("reasoning_content").is_none());
85 }
86
87 #[test]
88 fn response_tool_call_type_serializes_as_function() {
89 let call = ToolCall::new("call_i", "get_weather", "{}");
90 let value = serde_json::to_value(call).unwrap();
91 assert_eq!(value.get("type"), Some(&json!("function")));
92 }
93
94 #[test]
95 fn builder_validation_rejects_out_of_range_values() {
96 fn base_builder() -> ChatRequestBuilder {
97 ChatRequestBuilder::default()
98 .model("deepseek-v4-pro")
99 .message(ChatMessage::User {
100 content: "Hi".into(),
101 name: None,
102 })
103 }
104
105 let too_hot = base_builder().temperature(2.5).build();
106 assert!(too_hot.is_err());
107
108 let bad_top_p = base_builder().top_p(1.1).build();
109 assert!(bad_top_p.is_err());
110
111 let bad_top_logprobs = base_builder().top_logprobs(21_u32).logprobs(true).build();
112 assert!(bad_top_logprobs.is_err());
113
114 let missing_logprobs = base_builder().top_logprobs(2_u32).build();
115 assert!(missing_logprobs.is_err());
116 }
117
118 #[test]
119 fn thinking_struct_serializes_type() {
120 let thinking = Thinking::disabled();
121 let value = serde_json::to_value(&thinking).unwrap();
122 assert_eq!(value.get("type"), Some(&json!("disabled")));
123
124 let req = ChatRequestBuilder::default()
125 .client(DeepSeekClient::new("sk-...", DEFAULT_BASE_URL.clone()))
126 .model("deepseek-v4-flash")
127 .message(ChatMessage::User {
128 content: "Hi".into(),
129 name: None,
130 })
131 .thinking(thinking)
132 .reasoning_effort(ReasoningEffort::Max)
133 .build();
134 assert!(req.is_ok());
136 }
137
138 #[test]
139 fn user_content_text_serializes_as_string() {
140 let content = UserContent::Text("Hello".to_string());
141 let value = serde_json::to_value(&content).unwrap();
142 assert_eq!(value, json!("Hello"));
143 }
144
145 #[test]
146 fn user_content_parts_serializes_as_array() {
147 let content = UserContent::Parts(vec![
148 UserContentPart::text("What is in this image?"),
149 UserContentPart::file_id("file-api-abc123"),
150 ]);
151 let value = serde_json::to_value(&content).unwrap();
152 assert_eq!(
153 value,
154 json!([
155 {"type": "text", "text": "What is in this image?"},
156 {"type": "file", "file_id": "file-api-abc123"}
157 ])
158 );
159 }
160
161 #[test]
162 fn user_content_image_url_serializes() {
163 let content = UserContent::image_url("https://example.com/image.jpg");
164 let value = serde_json::to_value(&content).unwrap();
165 assert_eq!(
166 value,
167 json!([
168 {
169 "type": "image_url",
170 "image_url": {"url": "https://example.com/image.jpg"}
171 }
172 ])
173 );
174 }
175
176 #[test]
177 fn user_content_image_url_with_detail_serializes() {
178 let content =
179 UserContent::image_url_with_detail("https://example.com/img.png", ImageDetail::Low);
180 let value = serde_json::to_value(&content).unwrap();
181 assert_eq!(
182 value,
183 json!([
184 {
185 "type": "image_url",
186 "image_url": {"url": "https://example.com/img.png", "detail": "low"}
187 }
188 ])
189 );
190 }
191
192 #[test]
193 fn user_content_file_data_serializes() {
194 let content = UserContent::file_data("data:image/jpeg;base64,abc123", "photo.jpg");
195 let value = serde_json::to_value(&content).unwrap();
196 assert_eq!(
197 value,
198 json!([
199 {
200 "type": "file",
201 "file_data": "data:image/jpeg;base64,abc123",
202 "filename": "photo.jpg"
203 }
204 ])
205 );
206 }
207
208 #[test]
209 fn user_content_from_str_into() {
210 let content: UserContent = "Hello".into();
211 let value = serde_json::to_value(&content).unwrap();
212 assert_eq!(value, json!("Hello"));
213 }
214
215 #[test]
216 fn user_content_from_string_into() {
217 let content: UserContent = "Hello".to_string().into();
218 let value = serde_json::to_value(&content).unwrap();
219 assert_eq!(value, json!("Hello"));
220 }
221
222 #[test]
223 fn user_content_from_vec_parts_into() {
224 let parts = vec![
225 UserContentPart::text("Hi"),
226 UserContentPart::file_id("file-xxx"),
227 ];
228 let content: UserContent = parts.into();
229 let value = serde_json::to_value(&content).unwrap();
230 assert!(value.is_array());
231 }
232
233 #[test]
234 fn chat_message_user_with_text_content() {
235 let msg = ChatMessage::User {
236 content: "Hello".into(),
237 name: None,
238 };
239 let value = serde_json::to_value(&msg).unwrap();
240 assert_eq!(value.get("role"), Some(&json!("user")));
241 assert_eq!(value.get("content"), Some(&json!("Hello")));
242 }
243
244 #[test]
245 fn chat_message_user_with_multimodal_content() {
246 let msg = ChatMessage::User {
247 content: UserContent::Parts(vec![
248 UserContentPart::text("Describe this image"),
249 UserContentPart::image_url("https://example.com/img.jpg"),
250 ]),
251 name: None,
252 };
253 let value = serde_json::to_value(&msg).unwrap();
254 assert_eq!(value.get("role"), Some(&json!("user")));
255 assert!(value.get("content").unwrap().is_array());
256 let content = value.get("content").unwrap().as_array().unwrap();
257 assert_eq!(content.len(), 2);
258 assert_eq!(content[0]["type"], json!("text"));
259 assert_eq!(content[1]["type"], json!("image_url"));
260 }
261
262 #[test]
263 fn user_content_deserializes_from_string() {
264 let content: UserContent = serde_json::from_value(json!("Hello")).unwrap();
265 assert_eq!(content, UserContent::Text("Hello".to_string()));
266 }
267
268 #[test]
269 fn user_content_part_file_with_id_deserializes() {
270 let part: UserContentPart =
271 serde_json::from_value(json!({"type": "file", "file_id": "file-api-abc123"})).unwrap();
272 assert_eq!(part, UserContentPart::file_id("file-api-abc123"));
273 }
274
275 #[test]
276 fn user_content_part_file_with_data_and_filename_deserializes() {
277 let part: UserContentPart = serde_json::from_value(json!({
278 "type": "file",
279 "file_data": "data:image/jpeg;base64,abc123",
280 "filename": "photo.jpg"
281 }))
282 .unwrap();
283 assert_eq!(
284 part,
285 UserContentPart::file_data("data:image/jpeg;base64,abc123", "photo.jpg")
286 );
287 }
288
289 #[test]
290 fn user_content_part_image_url_without_detail_deserializes() {
291 let part: UserContentPart = serde_json::from_value(json!({
292 "type": "image_url",
293 "image_url": {"url": "https://example.com/img.png"}
294 }))
295 .unwrap();
296 assert_eq!(
297 part,
298 UserContentPart::image_url("https://example.com/img.png")
299 );
300 }
301
302 #[test]
303 fn chat_message_user_multimodal_round_trips() {
304 let msg = ChatMessage::User {
305 content: UserContent::Parts(vec![
306 UserContentPart::text("What is in this image?"),
307 UserContentPart::file_id("file-api-abc123"),
308 ]),
309 name: None,
310 };
311 let value = serde_json::to_value(&msg).unwrap();
312 let back: ChatMessage = serde_json::from_value(value).unwrap();
313 assert_eq!(back, msg);
314 }
315
316 #[test]
317 fn chat_message_user_with_file_id_content() {
318 let msg = ChatMessage::User {
319 content: UserContent::file_id("file-api-abc123"),
320 name: None,
321 };
322 let value = serde_json::to_value(&msg).unwrap();
323 assert_eq!(value.get("role"), Some(&json!("user")));
324 let content = value.get("content").unwrap().as_array().unwrap();
325 assert_eq!(content[0]["type"], json!("file"));
326 assert_eq!(content[0]["file_id"], json!("file-api-abc123"));
327 }
328}