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 ChatCompletionChunk {
10 pub id: String,
12 pub choices: Vec<CompletionChunkChoice>,
16 pub created: u64,
19 pub model: String,
21 pub object: ChatCompletionChunkObject,
23 pub service_tier: Option<ServiceTier>,
27 pub system_fingerprint: Option<String>,
31 pub usage: Option<CompletionUsage>,
39 }
40
41 #[derive(Debug, Deserialize)]
42 pub enum ChatCompletionChunkObject {
43 #[serde(rename = "chat.completion.chunk")]
44 ChatCompletionChunk,
45 }
46
47 #[derive(Debug, Deserialize)]
53 #[serde(rename_all = "lowercase")]
54 pub enum ServiceTier {
55 Auto,
57 Default,
59 Flex,
61 Scale,
63 Priority,
65 }
66
67 #[derive(Debug, Deserialize)]
68 pub struct CompletionChunkChoice {
69 pub delta: ChoiceDelta,
71 pub index: u32,
73 pub logprobs: Option<ChoiceLogprobs>,
75 pub finish_reason: Option<FinishReason>,
83 }
84
85 #[derive(Debug, Deserialize)]
86 #[serde(rename_all = "snake_case")]
87 pub enum FinishReason {
88 Length,
90 Stop,
92 ContentFilter,
94 FunctionCall,
96 ToolCalls,
98 InsufficientSystemResource,
100 }
101
102 #[derive(Debug, Deserialize)]
103 pub struct ChoiceDelta {
104 #[serde(flatten)]
106 pub content: Option<CompletionContent>,
107 pub function_call: Option<ChoiceDeltaFunctionCall>,
112 pub refusal: Option<String>,
114 pub role: Option<CompletionRole>,
116 pub tool_calls: Option<Vec<ChoiceDeltaToolCall>>,
118 }
119
120 #[derive(Debug, Deserialize)]
121 pub struct ChoiceDeltaToolCallFunction {
122 pub arguments: Option<String>,
127 pub name: Option<String>,
129 }
130
131 #[derive(Debug, Deserialize)]
132 pub struct ChoiceDeltaFunctionCall {
133 pub arguments: Option<String>,
138 pub name: Option<String>,
140 }
141
142 #[derive(Debug, Deserialize)]
143 pub struct ChoiceDeltaToolCall {
144 pub index: usize,
146 pub id: Option<String>,
148 pub function: Option<ChoiceDeltaToolCallFunction>,
150 #[serde(rename = "type")]
152 pub type_: Option<ChoiceDeltaToolCallType>,
153 }
154
155 #[derive(Debug, Deserialize)]
156 #[serde(rename_all = "snake_case")]
157 pub enum ChoiceDeltaToolCallType {
158 Function,
159 }
160
161 #[derive(Debug, Deserialize)]
162 #[serde(rename_all = "snake_case")]
163 pub enum CompletionRole {
164 Assistant,
165 Developer,
166 System,
167 Tool,
168 User,
169 }
170
171 #[derive(Debug, Deserialize)]
172 #[serde(rename_all = "snake_case")]
173 pub enum CompletionContent {
174 Content(String),
175 ReasoningContent(String),
177 }
178
179 #[derive(Debug, Deserialize)]
180 #[serde(rename_all = "snake_case")]
181 pub enum ChoiceLogprobs {
182 Content(Vec<LogprobeContent>),
183 ReasoningContent(Vec<LogprobeContent>),
185 }
186
187 #[derive(Debug, Deserialize)]
189 pub struct LogprobeContent {
190 pub token: String,
191 pub logprob: f32,
192 pub bytes: Option<Vec<u8>>,
193 pub top_logprobs: Vec<TopLogprob>,
194 }
195
196 #[derive(Debug, Deserialize)]
199 pub struct TopLogprob {
200 pub token: String,
201 pub logprob: f32,
202 pub bytes: Option<Vec<u8>>,
203 }
204
205 #[derive(Debug, Deserialize)]
206 pub struct CompletionUsage {
207 pub completion_tokens: usize,
209 pub prompt_tokens: usize,
211
212 pub prompt_cache_hit_tokens: Option<usize>,
215 pub prompt_cache_miss_tokens: Option<usize>,
217
218 pub total_tokens: usize,
220 pub completion_tokens_details: Option<CompletionTokensDetails>,
222 pub prompt_tokens_details: Option<PromptTokensDetails>,
224 }
225
226 #[derive(Debug, Deserialize)]
227 pub struct CompletionTokensDetails {
228 pub accepted_prediction_tokens: Option<usize>,
231 pub audio_tokens: Option<usize>,
233 pub reasoning_tokens: Option<usize>,
235 pub rejected_prediction_tokens: Option<usize>,
240 }
241
242 #[derive(Debug, Deserialize)]
243 pub struct PromptTokensDetails {
244 pub audio_tokens: Option<usize>,
246 pub cached_tokens: Option<usize>,
248 }
249
250 impl FromStr for ChatCompletionChunk {
251 type Err = crate::errors::ResponseError;
252
253 fn from_str(content: &str) -> Result<Self, Self::Err> {
254 let parse_result: Result<ChatCompletionChunk, _> = serde_json::from_str(content)
255 .map_err(|e| ResponseError::DeserializationError(e.to_string()));
256 parse_result
257 }
258 }
259
260 #[cfg(test)]
261 mod test {
262 use super::*;
263
264 #[test]
265 fn streaming_example_deepseek() {
266 let streams = vec![
267 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}"#,
268 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"}"#,
269 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"}"#,
270 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"}"#,
271 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"}"#,
272 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"}"#,
273 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"}"#,
274 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"}"#,
275 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"}"#,
276 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"}"#,
277 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}}"#,
278 ];
279
280 for stream in streams {
281 let parsed = ChatCompletionChunk::from_str(stream);
282 match parsed {
283 Ok(completion) => {
284 println!("Deserialized: {:#?}", completion);
285 }
286 Err(e) => {
287 panic!("Failed to deserialize {}: {}", stream, e);
288 }
289 }
290 }
291 }
292
293 #[test]
294 fn streaming_example_qwen() {
295 let streams = vec![
296 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}"#,
297 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}"#,
298 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}"#,
299 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}"#,
300 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}"#,
301 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}"#,
302 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}"#,
303 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}"#,
304 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}"#,
305 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}}}"#,
306 ];
307
308 for stream in streams {
309 let parsed = ChatCompletionChunk::from_str(stream);
310 match parsed {
311 Ok(completion) => {
312 println!("Deserialized: {:#?}", completion);
313 }
314 Err(e) => {
315 panic!("Failed to deserialize {}: {}", stream, e);
316 }
317 }
318 }
319 }
320 }
321}
322
323pub mod no_streaming {
324 use std::str::FromStr;
325
326 use serde::Deserialize;
327
328 use crate::errors::ResponseError;
329
330 #[derive(Debug, Deserialize)]
331 pub struct ChatCompletion {
332 pub id: String,
334 pub choices: Vec<Choice>,
337 pub created: u64,
339 pub model: String,
341 pub service_tier: Option<ServiceTier>,
358 pub system_fingerprint: Option<String>,
362 pub object: ChatCompletionObject,
364 pub usage: Option<CompletionUsage>,
366 }
367
368 #[derive(Debug, Deserialize)]
369 #[serde(rename_all = "lowercase")]
370 pub enum ServiceTier {
371 Auto,
372 Default,
373 Flex,
374 Scale,
375 Priority,
376 }
377
378 #[derive(Debug, Deserialize)]
380 pub enum ChatCompletionObject {
381 #[serde(rename = "chat.completion")]
383 ChatCompletion,
384 }
385
386 #[derive(Debug, Deserialize)]
387 pub struct Choice {
388 pub finish_reason: FinishReason,
396 pub index: usize,
398 pub logprobs: Option<ChoiceLogprobs>,
400 pub message: ChatCompletionMessage,
402 }
403
404 #[derive(Debug, Deserialize, PartialEq)]
405 #[serde(rename_all = "snake_case")]
406 pub enum FinishReason {
407 Length,
408 Stop,
409 ToolCalls,
410 FunctionCall,
411 ContentFilter,
412 InsufficientSystemResource,
414 }
415
416 #[derive(Debug, Deserialize)]
421 pub struct ChatCompletionMessage {
422 pub role: ResponseRole,
425 pub content: Option<String>,
427 pub reasoning_content: Option<String>,
428 pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>,
431 }
432
433 #[derive(Debug, Deserialize)]
434 #[serde(tag = "type", rename_all = "snake_case")]
435 pub enum ChatCompletionMessageToolCall {
436 Function {
439 id: String,
441 function: String, },
444 Custom {
447 id: String,
449 custom: MessageToolCallCustom,
451 },
452 }
453
454 #[derive(Debug, Deserialize)]
455 pub struct MessageToolCallCustom {
456 pub input: String,
458 pub name: String,
460 }
461
462 #[derive(Debug, Deserialize)]
463 pub struct MessageToolCallFunction {
464 pub arguments: String,
469 pub name: String,
471 }
472
473 #[derive(Debug, Deserialize)]
474 #[serde(rename_all = "snake_case")]
475 pub enum ResponseRole {
476 Assistant,
478 }
479
480 #[derive(Debug, Deserialize)]
481 pub struct ChoiceLogprobs {
482 pub content: Option<Vec<TokenLogProb>>,
484 pub reasoning_content: Option<Vec<TokenLogProb>>,
486 pub refusal: Option<Vec<TokenLogProb>>,
488 }
489
490 #[derive(Debug, Deserialize)]
491 pub struct TokenLogProb {
492 pub token: String,
494 pub logprob: f32,
498 pub bytes: Option<Vec<u8>>,
504 pub top_logprobs: Vec<TopLogprob>,
508 }
509
510 #[derive(Debug, Deserialize)]
511 pub struct TopLogprob {
512 pub token: String,
514 pub logprob: f32,
520 pub bytes: Option<Vec<u8>>,
524 }
525
526 #[derive(Debug, Deserialize)]
527 pub struct CompletionUsage {
528 pub completion_tokens: usize,
530 pub prompt_tokens: usize,
532
533 pub prompt_cache_hit_tokens: Option<usize>,
536 pub prompt_cache_miss_tokens: Option<usize>,
538
539 pub total_tokens: usize,
541 pub completion_tokens_details: Option<CompletionTokensDetails>,
543 pub prompt_tokens_details: Option<PromptTokensDetails>,
545 }
546
547 #[derive(Debug, Deserialize)]
548 pub struct CompletionTokensDetails {
549 pub accepted_prediction_tokens: Option<usize>,
552 pub audio_tokens: Option<usize>,
554 pub reasoning_tokens: Option<usize>,
556 pub rejected_prediction_tokens: Option<usize>,
561 }
562
563 #[derive(Debug, Deserialize)]
564 pub struct PromptTokensDetails {
565 pub audio_tokens: Option<usize>,
567 pub cached_tokens: Option<usize>,
569 }
570
571 impl FromStr for ChatCompletion {
572 type Err = crate::errors::ResponseError;
573
574 fn from_str(content: &str) -> Result<Self, Self::Err> {
575 let parse_result: Result<ChatCompletion, _> = serde_json::from_str(content)
576 .map_err(|e| ResponseError::DeserializationError(e.to_string()));
577 parse_result
578 }
579 }
580
581 #[cfg(test)]
582 mod test {
583 use super::*;
584
585 #[test]
586 fn no_streaming_example_deepseek() {
587 let json = r#"{
588 "id": "30f6413a-a827-4cf3-9898-f13a8634b798",
589 "object": "chat.completion",
590 "created": 1757944111,
591 "model": "deepseek-chat",
592 "choices": [
593 {
594 "index": 0,
595 "message": {
596 "role": "assistant",
597 "content": "Hello! How can I help you today? 😊"
598 },
599 "logprobs": null,
600 "finish_reason": "stop"
601 }
602 ],
603 "usage": {
604 "prompt_tokens": 10,
605 "completion_tokens": 11,
606 "total_tokens": 21,
607 "prompt_tokens_details": {
608 "cached_tokens": 0
609 },
610 "prompt_cache_hit_tokens": 0,
611 "prompt_cache_miss_tokens": 10
612 },
613 "system_fingerprint": "fp_08f168e49b_prod0820_fp8_kvcache"
614 }"#;
615
616 let parsed = ChatCompletion::from_str(json);
617 match parsed {
618 Ok(_) => {}
619 Err(e) => {
620 panic!("Failed to deserialize: {}", e);
621 }
622 }
623 }
624
625 #[test]
626 fn no_streaming_example_qwen() {
627 let json = r#"{
628 "choices": [
629 {
630 "message": {
631 "role": "assistant",
632 "content": "我是阿里云开发的一款超大规模语言模型,我叫通义千问。"
633 },
634 "finish_reason": "stop",
635 "index": 0,
636 "logprobs": null
637 }
638 ],
639 "object": "chat.completion",
640 "usage": {
641 "prompt_tokens": 3019,
642 "completion_tokens": 104,
643 "total_tokens": 3123,
644 "prompt_tokens_details": {
645 "cached_tokens": 2048
646 }
647 },
648 "created": 1735120033,
649 "system_fingerprint": null,
650 "model": "qwen-plus",
651 "id": "chatcmpl-6ada9ed2-7f33-9de2-8bb0-78bd4035025a"
652 }"#;
653
654 let parsed = ChatCompletion::from_str(json);
655 match parsed {
656 Ok(_) => {}
657 Err(e) => {
658 panic!("Failed to deserialize: {}", e);
659 }
660 }
661 }
662 }
663}