1pub mod streaming {
2 use std::str::FromStr;
3
4 use serde::Deserialize;
5
6 use crate::errors::ResponseError;
7
8 #[derive(Debug, Deserialize)]
9 pub struct ChatCompletion {
10 pub id: String,
12 pub choices: Vec<CompletionChoice>,
15 pub created: u64,
17 pub model: String,
19 pub object: String,
21 pub usage: Option<CompletionUsage>,
22 }
23
24 #[derive(Debug, Deserialize)]
25 pub struct CompletionChoice {
26 pub delta: CompletionDelta,
27 pub index: u32,
28 pub logprobs: Option<ChoiceLogprobs>,
29 pub finish_reason: Option<FinishReason>,
30 }
31
32 #[derive(Debug, Deserialize)]
33 #[serde(rename_all = "snake_case")]
34 pub enum FinishReason {
35 Length,
36 Stop,
37 ContentFilter,
38 ToolCalls,
39 InsufficientSystemResource,
40 }
41
42 #[derive(Debug, Deserialize)]
43 pub struct CompletionDelta {
44 #[serde(flatten)]
45 pub content: CompletionContent,
46 pub role: Option<CompletionRole>,
47 }
48
49 #[derive(Debug, Deserialize)]
50 #[serde(rename_all = "snake_case")]
51 pub enum CompletionRole {
52 User,
53 Assistant,
54 System,
55 Tool,
56 }
57
58 #[derive(Debug, Deserialize)]
59 #[serde(rename_all = "snake_case")]
60 pub enum CompletionContent {
61 Content(String),
62 ReasoningContent(String),
63 }
64
65 #[derive(Debug, Deserialize)]
66 #[serde(rename_all = "snake_case")]
67 pub enum ChoiceLogprobs {
68 Content(Vec<LogprobeContent>),
69 ReasoningContent(Vec<LogprobeContent>),
70 }
71
72 #[derive(Debug, Deserialize)]
73 pub struct LogprobeContent {
74 pub token: String,
75 pub logprob: f32,
76 pub bytes: Option<Vec<u8>>,
77 pub top_logprobs: Vec<TopLogprob>,
78 }
79
80 #[derive(Debug, Deserialize)]
81 pub struct TopLogprob {
82 pub token: String,
83 pub logprob: f32,
84 pub bytes: Option<Vec<u8>>,
85 }
86
87 #[derive(Debug, Deserialize)]
88 pub struct CompletionUsage {
89 pub completion_tokens: usize,
90 pub prompt_tokens: usize,
91 pub total_tokens: usize,
92 }
93
94 impl FromStr for ChatCompletion {
95 type Err = crate::errors::ResponseError;
96
97 fn from_str(content: &str) -> Result<Self, Self::Err> {
98 let parse_result: Result<ChatCompletion, _> = serde_json::from_str(content)
99 .map_err(|e| ResponseError::DeserializationError(e.to_string()));
100 parse_result
101 }
102 }
103
104 #[cfg(test)]
105 mod test {
106 use super::*;
107
108 #[test]
109 fn streaming_example_deepseek() {
110 let streams = vec![
111 r#"{"id": "1f633d8bfc032625086f14113c411638", "choices": [{"index": 0, "delta": {"content": "", "role": "assistant"}, "finish_reason": null, "logprobs": null}], "created": 1718345013, "model": "deepseek-chat", "system_fingerprint": "fp_a49d71b8a1", "object": "chat.completion.chunk", "usage": null}"#,
112 r#"{"choices": [{"delta": {"content": "Hello", "role": "assistant"}, "finish_reason": null, "index": 0, "logprobs": null}], "created": 1718345013, "id": "1f633d8bfc032625086f14113c411638", "model": "deepseek-chat", "object": "chat.completion.chunk", "system_fingerprint": "fp_a49d71b8a1"}"#,
113 r#"{"choices": [{"delta": {"content": "!", "role": "assistant"}, "finish_reason": null, "index": 0, "logprobs": null}], "created": 1718345013, "id": "1f633d8bfc032625086f14113c411638", "model": "deepseek-chat", "object": "chat.completion.chunk", "system_fingerprint": "fp_a49d71b8a1"}"#,
114 r#"{"choices": [{"delta": {"content": " How", "role": "assistant"}, "finish_reason": null, "index": 0, "logprobs": null}], "created": 1718345013, "id": "1f633d8bfc032625086f14113c411638", "model": "deepseek-chat", "object": "chat.completion.chunk", "system_fingerprint": "fp_a49d71b8a1"}"#,
115 r#"{"choices": [{"delta": {"content": " can", "role": "assistant"}, "finish_reason": null, "index": 0, "logprobs": null}], "created": 1718345013, "id": "1f633d8bfc032625086f14113c411638", "model": "deepseek-chat", "object": "chat.completion.chunk", "system_fingerprint": "fp_a49d71b8a1"}"#,
116 r#"{"choices": [{"delta": {"content": " I", "role": "assistant"}, "finish_reason": null, "index": 0, "logprobs": null}], "created": 1718345013, "id": "1f633d8bfc032625086f14113c411638", "model": "deepseek-chat", "object": "chat.completion.chunk", "system_fingerprint": "fp_a49d71b8a1"}"#,
117 r#"{"choices": [{"delta": {"content": " assist", "role": "assistant"}, "finish_reason": null, "index": 0, "logprobs": null}], "created": 1718345013, "id": "1f633d8bfc032625086f14113c411638", "model": "deepseek-chat", "object": "chat.completion.chunk", "system_fingerprint": "fp_a49d71b8a1"}"#,
118 r#"{"choices": [{"delta": {"content": " you", "role": "assistant"}, "finish_reason": null, "index": 0, "logprobs": null}], "created": 1718345013, "id": "1f633d8bfc032625086f14113c411638", "model": "deepseek-chat", "object": "chat.completion.chunk", "system_fingerprint": "fp_a49d71b8a1"}"#,
119 r#"{"choices": [{"delta": {"content": " today", "role": "assistant"}, "finish_reason": null, "index": 0, "logprobs": null}], "created": 1718345013, "id": "1f633d8bfc032625086f14113c411638", "model": "deepseek-chat", "object": "chat.completion.chunk", "system_fingerprint": "fp_a49d71b8a1"}"#,
120 r#"{"choices": [{"delta": {"content": "?", "role": "assistant"}, "finish_reason": null, "index": 0, "logprobs": null}], "created": 1718345013, "id": "1f633d8bfc032625086f14113c411638", "model": "deepseek-chat", "object": "chat.completion.chunk", "system_fingerprint": "fp_a49d71b8a1"}"#,
121 r#"{"choices": [{"delta": {"content": "", "role": null}, "finish_reason": "stop", "index": 0, "logprobs": null}], "created": 1718345013, "id": "1f633d8bfc032625086f14113c411638", "model": "deepseek-chat", "object": "chat.completion.chunk", "system_fingerprint": "fp_a49d71b8a1", "usage": {"completion_tokens": 9, "prompt_tokens": 17, "total_tokens": 26}}"#,
122 ];
123
124 for stream in streams {
125 let parsed = ChatCompletion::from_str(stream);
126 match parsed {
127 Ok(completion) => {
128 println!("Deserialized: {:#?}", completion);
129 }
130 Err(e) => {
131 panic!("Failed to deserialize {}: {}", stream, e);
132 }
133 }
134 }
135 }
136
137 #[test]
138 fn streaming_example_qwen() {
139 let streams = vec![
140 r#"{"id":"chatcmpl-e30f5ae7-3063-93c4-90fe-beb5f900bd57","choices":[{"delta":{"content":"","function_call":null,"refusal":null,"role":"assistant","tool_calls":null},"finish_reason":null,"index":0,"logprobs":null}],"created":1735113344,"model":"qwen-plus","object":"chat.completion.chunk","service_tier":null,"system_fingerprint":null,"usage":null}"#,
141 r#"{"id":"chatcmpl-e30f5ae7-3063-93c4-90fe-beb5f900bd57","choices":[{"delta":{"content":"我是","function_call":null,"refusal":null,"role":null,"tool_calls":null},"finish_reason":null,"index":0,"logprobs":null}],"created":1735113344,"model":"qwen-plus","object":"chat.completion.chunk","service_tier":null,"system_fingerprint":null,"usage":null}"#,
142 r#"{"id":"chatcmpl-e30f5ae7-3063-93c4-90fe-beb5f900bd57","choices":[{"delta":{"content":"来自","function_call":null,"refusal":null,"role":null,"tool_calls":null},"finish_reason":null,"index":0,"logprobs":null}],"created":1735113344,"model":"qwen-plus","object":"chat.completion.chunk","service_tier":null,"system_fingerprint":null,"usage":null}"#,
143 r#"{"id":"chatcmpl-e30f5ae7-3063-93c4-90fe-beb5f900bd57","choices":[{"delta":{"content":"阿里","function_call":null,"refusal":null,"role":null,"tool_calls":null},"finish_reason":null,"index":0,"logprobs":null}],"created":1735113344,"model":"qwen-plus","object":"chat.completion.chunk","service_tier":null,"system_fingerprint":null,"usage":null}"#,
144 r#"{"id":"chatcmpl-e30f5ae7-3063-93c4-90fe-beb5f900bd57","choices":[{"delta":{"content":"云的超大规模","function_call":null,"refusal":null,"role":null,"tool_calls":null},"finish_reason":null,"index":0,"logprobs":null}],"created":1735113344,"model":"qwen-plus","object":"chat.completion.chunk","service_tier":null,"system_fingerprint":null,"usage":null}"#,
145 r#"{"id":"chatcmpl-e30f5ae7-3063-93c4-90fe-beb5f900bd57","choices":[{"delta":{"content":"语言模型,我","function_call":null,"refusal":null,"role":null,"tool_calls":null},"finish_reason":null,"index":0,"logprobs":null}],"created":1735113344,"model":"qwen-plus","object":"chat.completion.chunk","service_tier":null,"system_fingerprint":null,"usage":null}"#,
146 r#"{"id":"chatcmpl-e30f5ae7-3063-93c4-90fe-beb5f900bd57","choices":[{"delta":{"content":"叫通义千","function_call":null,"refusal":null,"role":null,"tool_calls":null},"finish_reason":null,"index":0,"logprobs":null}],"created":1735113344,"model":"qwen-plus","object":"chat.completion.chunk","service_tier":null,"system_fingerprint":null,"usage":null}"#,
147 r#"{"id":"chatcmpl-e30f5ae7-3063-93c4-90fe-beb5f900bd57","choices":[{"delta":{"content":"问。","function_call":null,"refusal":null,"role":null,"tool_calls":null},"finish_reason":null,"index":0,"logprobs":null}],"created":1735113344,"model":"qwen-plus","object":"chat.completion.chunk","service_tier":null,"system_fingerprint":null,"usage":null}"#,
148 r#"{"id":"chatcmpl-e30f5ae7-3063-93c4-90fe-beb5f900bd57","choices":[{"delta":{"content":"","function_call":null,"refusal":null,"role":null,"tool_calls":null},"finish_reason":"stop","index":0,"logprobs":null}],"created":1735113344,"model":"qwen-plus","object":"chat.completion.chunk","service_tier":null,"system_fingerprint":null,"usage":null}"#,
149 r#"{"id":"chatcmpl-e30f5ae7-3063-93c4-90fe-beb5f900bd57","choices":[],"created":1735113344,"model":"qwen-plus","object":"chat.completion.chunk","service_tier":null,"system_fingerprint":null,"usage":{"completion_tokens":17,"prompt_tokens":22,"total_tokens":39,"completion_tokens_details":null,"prompt_tokens_details":{"audio_tokens":null,"cached_tokens":0}}}"#,
150 ];
151
152 for stream in streams {
153 let parsed = ChatCompletion::from_str(stream);
154 match parsed {
155 Ok(completion) => {
156 println!("Deserialized: {:#?}", completion);
157 }
158 Err(e) => {
159 panic!("Failed to deserialize {}: {}", stream, e);
160 }
161 }
162 }
163 }
164 }
165}
166
167pub mod no_streaming {
168 use std::str::FromStr;
169
170 use serde::Deserialize;
171
172 use crate::errors::ResponseError;
173
174 #[derive(Debug, Deserialize)]
175 pub struct ChatCompletion {
176 pub id: String,
178 pub choices: Vec<Choice>,
181 pub created: u64,
183 pub model: String,
185 pub service_tier: Option<ServiceTier>,
202 pub system_fingerprint: Option<String>,
206 pub object: ChatCompletionObject,
208 pub usage: Option<CompletionUsage>,
210 }
211
212 #[derive(Debug, Deserialize)]
213 #[serde(rename_all = "lowercase")]
214 pub enum ServiceTier {
215 Auto,
216 Default,
217 Flex,
218 Scale,
219 Priority,
220 }
221
222 #[derive(Debug, Deserialize)]
224 pub enum ChatCompletionObject {
225 #[serde(rename = "chat.completion")]
227 ChatCompletion,
228 }
229
230 #[derive(Debug, Deserialize)]
231 pub struct Choice {
232 pub finish_reason: FinishReason,
240 pub index: usize,
242 pub logprobs: Option<ChoiceLogprobs>,
244 pub message: ChatCompletionMessage,
246 }
247
248 #[derive(Debug, Deserialize, PartialEq)]
249 #[serde(rename_all = "snake_case")]
250 pub enum FinishReason {
251 Length,
252 Stop,
253 ToolCalls,
254 FunctionCall,
255 ContentFilter,
256 InsufficientSystemResource,
258 }
259
260 #[derive(Debug, Deserialize)]
265 pub struct ChatCompletionMessage {
266 pub role: ResponseRole,
269 pub content: Option<String>,
271 pub reasoning_content: Option<String>,
272 pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>,
275 }
276
277 #[derive(Debug, Deserialize)]
278 #[serde(tag = "type", rename_all = "snake_case")]
279 pub enum ChatCompletionMessageToolCall {
280 Function {
283 id: String,
285 function: String, },
288 Custom {
291 id: String,
293 custom: MessageToolCallCustom,
295 },
296 }
297
298 #[derive(Debug, Deserialize)]
299 pub struct MessageToolCallCustom {
300 pub input: String,
302 pub name: String,
304 }
305
306 #[derive(Debug, Deserialize)]
307 pub struct MessageToolCallFunction {
308 pub arguments: String,
313 pub name: String,
315 }
316
317 #[derive(Debug, Deserialize)]
318 #[serde(rename_all = "snake_case")]
319 pub enum ResponseRole {
320 Assistant,
322 }
323
324 #[derive(Debug, Deserialize)]
325 pub struct ChoiceLogprobs {
326 pub content: Option<Vec<TokenLogProb>>,
328 pub reasoning_content: Option<Vec<TokenLogProb>>,
330 pub refusal: Option<Vec<TokenLogProb>>,
332 }
333
334 #[derive(Debug, Deserialize)]
335 pub struct TokenLogProb {
336 pub token: String,
338 pub logprob: f32,
342 pub bytes: Option<Vec<u8>>,
348 pub top_logprobs: Vec<TopLogprob>,
352 }
353
354 #[derive(Debug, Deserialize)]
355 pub struct TopLogprob {
356 pub token: String,
358 pub logprob: f32,
364 pub bytes: Option<Vec<u8>>,
368 }
369
370 #[derive(Debug, Deserialize)]
371 pub struct CompletionUsage {
372 pub completion_tokens: usize,
374 pub prompt_tokens: usize,
376
377 pub prompt_cache_hit_tokens: Option<usize>,
380 pub prompt_cache_miss_tokens: Option<usize>,
382
383 pub total_tokens: usize,
385 pub completion_tokens_details: Option<CompletionTokensDetails>,
387 pub prompt_tokens_details: Option<PromptTokensDetails>,
389 }
390
391 #[derive(Debug, Deserialize)]
392 pub struct CompletionTokensDetails {
393 pub accepted_prediction_tokens: Option<usize>,
396 pub audio_tokens: Option<usize>,
398 pub reasoning_tokens: Option<usize>,
400 pub rejected_prediction_tokens: Option<usize>,
405 }
406
407 #[derive(Debug, Deserialize)]
408 pub struct PromptTokensDetails {
409 pub audio_tokens: Option<usize>,
411 pub cached_tokens: Option<usize>,
413 }
414
415 impl FromStr for ChatCompletion {
416 type Err = crate::errors::ResponseError;
417
418 fn from_str(content: &str) -> Result<Self, Self::Err> {
419 let parse_result: Result<ChatCompletion, _> = serde_json::from_str(content)
420 .map_err(|e| ResponseError::DeserializationError(e.to_string()));
421 parse_result
422 }
423 }
424
425 #[cfg(test)]
426 mod test {
427 use super::*;
428
429 #[test]
430 fn no_streaming_example_deepseek() {
431 let json = r#"{
432 "id": "30f6413a-a827-4cf3-9898-f13a8634b798",
433 "object": "chat.completion",
434 "created": 1757944111,
435 "model": "deepseek-chat",
436 "choices": [
437 {
438 "index": 0,
439 "message": {
440 "role": "assistant",
441 "content": "Hello! How can I help you today? 😊"
442 },
443 "logprobs": null,
444 "finish_reason": "stop"
445 }
446 ],
447 "usage": {
448 "prompt_tokens": 10,
449 "completion_tokens": 11,
450 "total_tokens": 21,
451 "prompt_tokens_details": {
452 "cached_tokens": 0
453 },
454 "prompt_cache_hit_tokens": 0,
455 "prompt_cache_miss_tokens": 10
456 },
457 "system_fingerprint": "fp_08f168e49b_prod0820_fp8_kvcache"
458 }"#;
459
460 let parsed = ChatCompletion::from_str(json);
461 match parsed {
462 Ok(_) => {}
463 Err(e) => {
464 panic!("Failed to deserialize: {}", e);
465 }
466 }
467 }
468 }
469}