1pub mod streaming {
2 use std::str::FromStr;
3
4 use serde::Deserialize;
5
6 use crate::{chat::ServiceTier, errors::OapiError};
7
8 #[derive(Debug, Deserialize, Clone)]
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, Clone)]
42 pub enum ChatCompletionChunkObject {
43 #[serde(rename = "chat.completion.chunk")]
44 ChatCompletionChunk,
45 }
46
47 #[derive(Debug, Deserialize, Clone)]
48 pub struct CompletionChunkChoice {
49 pub delta: ChoiceDelta,
51 pub index: u32,
53 pub logprobs: Option<ChoiceLogprobs>,
55 pub finish_reason: Option<FinishReason>,
63 }
64
65 #[derive(Debug, Deserialize, Clone)]
66 #[serde(rename_all = "snake_case")]
67 pub enum FinishReason {
68 Length,
70 Stop,
72 ContentFilter,
74 FunctionCall,
76 ToolCalls,
78 InsufficientSystemResource,
80 }
81
82 #[derive(Debug, Deserialize, Clone)]
83 pub struct ChoiceDelta {
84 #[serde(flatten)]
86 pub content: Option<CompletionContent>,
87 pub function_call: Option<ChoiceDeltaFunctionCall>,
92 pub refusal: Option<String>,
94 pub role: Option<CompletionRole>,
96 pub tool_calls: Option<Vec<ChoiceDeltaToolCall>>,
98 }
99
100 #[derive(Debug, Deserialize, Clone)]
101 pub struct ChoiceDeltaToolCallFunction {
102 pub arguments: Option<String>,
107 pub name: Option<String>,
109 }
110
111 #[derive(Debug, Deserialize, Clone)]
112 pub struct ChoiceDeltaFunctionCall {
113 pub arguments: Option<String>,
118 pub name: Option<String>,
120 }
121
122 #[derive(Debug, Deserialize, Clone)]
123 pub struct ChoiceDeltaToolCall {
124 pub index: usize,
126 pub id: Option<String>,
128 pub function: Option<ChoiceDeltaToolCallFunction>,
130 #[serde(rename = "type")]
132 pub type_: Option<ChoiceDeltaToolCallType>,
133 }
134
135 #[derive(Debug, Deserialize, Clone)]
136 #[serde(rename_all = "snake_case")]
137 pub enum ChoiceDeltaToolCallType {
138 Function,
139 }
140
141 #[derive(Debug, Deserialize, Clone)]
142 #[serde(rename_all = "snake_case")]
143 pub enum CompletionRole {
144 Assistant,
145 Developer,
146 System,
147 Tool,
148 User,
149 }
150
151 #[derive(Debug, Deserialize, Clone)]
152 #[serde(rename_all = "snake_case")]
153 pub enum CompletionContent {
154 Content(String),
155 ReasoningContent(String),
157 }
158
159 #[derive(Debug, Deserialize, Clone)]
160 #[serde(rename_all = "snake_case")]
161 pub enum ChoiceLogprobs {
162 Content(Vec<LogprobeContent>),
163 ReasoningContent(Vec<LogprobeContent>),
165 }
166
167 #[derive(Debug, Deserialize, Clone)]
169 pub struct LogprobeContent {
170 pub token: String,
171 pub logprob: f32,
172 pub bytes: Option<Vec<u8>>,
173 pub top_logprobs: Vec<TopLogprob>,
174 }
175
176 #[derive(Debug, Deserialize, Clone)]
179 pub struct TopLogprob {
180 pub token: String,
181 pub logprob: f32,
182 pub bytes: Option<Vec<u8>>,
183 }
184
185 #[derive(Debug, Deserialize, Clone)]
186 pub struct CompletionUsage {
187 pub completion_tokens: usize,
189 pub prompt_tokens: usize,
191
192 pub prompt_cache_hit_tokens: Option<usize>,
195 pub prompt_cache_miss_tokens: Option<usize>,
197
198 pub total_tokens: usize,
200 pub completion_tokens_details: Option<CompletionTokensDetails>,
202 pub prompt_tokens_details: Option<PromptTokensDetails>,
204 }
205
206 #[derive(Debug, Deserialize, Clone)]
207 pub struct CompletionTokensDetails {
208 pub accepted_prediction_tokens: Option<usize>,
211 pub audio_tokens: Option<usize>,
213 pub reasoning_tokens: Option<usize>,
215 pub rejected_prediction_tokens: Option<usize>,
220 }
221
222 #[derive(Debug, Deserialize, Clone)]
223 pub struct PromptTokensDetails {
224 pub audio_tokens: Option<usize>,
226 pub cached_tokens: Option<usize>,
228 }
229
230 impl FromStr for ChatCompletionChunk {
231 type Err = crate::errors::OapiError;
232
233 fn from_str(content: &str) -> Result<Self, Self::Err> {
234 let parse_result: Result<ChatCompletionChunk, _> = serde_json::from_str(content)
235 .map_err(|e| OapiError::DeserializationError(e.to_string()));
236 parse_result
237 }
238 }
239
240 #[cfg(test)]
241 mod test {
242 use super::*;
243
244 #[test]
245 fn streaming_example_deepseek() {
246 let streams = vec![
247 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}"#,
248 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"}"#,
249 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"}"#,
250 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"}"#,
251 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"}"#,
252 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"}"#,
253 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"}"#,
254 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"}"#,
255 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"}"#,
256 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"}"#,
257 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}}"#,
258 ];
259
260 for stream in streams {
261 let parsed = ChatCompletionChunk::from_str(stream);
262 match parsed {
263 Ok(completion) => {
264 println!("Deserialized: {:#?}", completion);
265 }
266 Err(e) => {
267 panic!("Failed to deserialize {}: {}", stream, e);
268 }
269 }
270 }
271 }
272
273 #[test]
274 fn streaming_example_qwen() {
275 let streams = vec![
276 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}"#,
277 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}"#,
278 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}"#,
279 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}"#,
280 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}"#,
281 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}"#,
282 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}"#,
283 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}"#,
284 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}"#,
285 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}}}"#,
286 ];
287
288 for stream in streams {
289 let parsed = ChatCompletionChunk::from_str(stream);
290 match parsed {
291 Ok(completion) => {
292 println!("Deserialized: {:#?}", completion);
293 }
294 Err(e) => {
295 panic!("Failed to deserialize {}: {}", stream, e);
296 }
297 }
298 }
299 }
300 }
301}
302
303pub mod no_streaming {
304 use std::str::FromStr;
305
306 use serde::Deserialize;
307
308 use crate::{chat::ServiceTier, errors::OapiError};
309
310 #[derive(Debug, Deserialize)]
311 pub struct ChatCompletion {
312 pub id: String,
314 pub choices: Vec<Choice>,
317 pub created: u64,
319 pub model: String,
321 pub service_tier: Option<ServiceTier>,
338 pub system_fingerprint: Option<String>,
342 pub object: ChatCompletionObject,
344 pub usage: Option<CompletionUsage>,
346 }
347
348 #[derive(Debug, Deserialize)]
350 pub enum ChatCompletionObject {
351 #[serde(rename = "chat.completion")]
353 ChatCompletion,
354 }
355
356 #[derive(Debug, Deserialize)]
357 pub struct Choice {
358 pub finish_reason: FinishReason,
366 pub index: usize,
368 pub logprobs: Option<ChoiceLogprobs>,
370 pub message: ChatCompletionMessage,
372 }
373
374 #[derive(Debug, Deserialize, PartialEq)]
375 #[serde(rename_all = "snake_case")]
376 pub enum FinishReason {
377 Length,
378 Stop,
379 ToolCalls,
380 FunctionCall,
381 ContentFilter,
382 InsufficientSystemResource,
384 }
385
386 #[derive(Debug, Deserialize)]
391 pub struct ChatCompletionMessage {
392 pub role: ResponseRole,
395 pub content: Option<String>,
397 pub reasoning_content: Option<String>,
398 pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>,
401 }
402
403 #[derive(Debug, Deserialize)]
404 #[serde(tag = "type", rename_all = "snake_case")]
405 pub enum ChatCompletionMessageToolCall {
406 Function {
409 id: String,
411 function: String, },
414 Custom {
417 id: String,
419 custom: MessageToolCallCustom,
421 },
422 }
423
424 #[derive(Debug, Deserialize)]
425 pub struct MessageToolCallCustom {
426 pub input: String,
428 pub name: String,
430 }
431
432 #[derive(Debug, Deserialize)]
433 pub struct MessageToolCallFunction {
434 pub arguments: String,
439 pub name: String,
441 }
442
443 #[derive(Debug, Deserialize)]
444 #[serde(rename_all = "snake_case")]
445 pub enum ResponseRole {
446 Assistant,
448 }
449
450 #[derive(Debug, Deserialize)]
451 pub struct ChoiceLogprobs {
452 pub content: Option<Vec<TokenLogProb>>,
454 pub reasoning_content: Option<Vec<TokenLogProb>>,
456 pub refusal: Option<Vec<TokenLogProb>>,
458 }
459
460 #[derive(Debug, Deserialize)]
461 pub struct TokenLogProb {
462 pub token: String,
464 pub logprob: f32,
468 pub bytes: Option<Vec<u8>>,
474 pub top_logprobs: Vec<TopLogprob>,
478 }
479
480 #[derive(Debug, Deserialize)]
481 pub struct TopLogprob {
482 pub token: String,
484 pub logprob: f32,
490 pub bytes: Option<Vec<u8>>,
494 }
495
496 #[derive(Debug, Deserialize)]
497 pub struct CompletionUsage {
498 pub completion_tokens: usize,
500 pub prompt_tokens: usize,
502
503 pub prompt_cache_hit_tokens: Option<usize>,
506 pub prompt_cache_miss_tokens: Option<usize>,
508
509 pub total_tokens: usize,
511 pub completion_tokens_details: Option<CompletionTokensDetails>,
513 pub prompt_tokens_details: Option<PromptTokensDetails>,
515 }
516
517 #[derive(Debug, Deserialize)]
518 pub struct CompletionTokensDetails {
519 pub accepted_prediction_tokens: Option<usize>,
522 pub audio_tokens: Option<usize>,
524 pub reasoning_tokens: Option<usize>,
526 pub rejected_prediction_tokens: Option<usize>,
531 }
532
533 #[derive(Debug, Deserialize)]
534 pub struct PromptTokensDetails {
535 pub audio_tokens: Option<usize>,
537 pub cached_tokens: Option<usize>,
539 }
540
541 impl FromStr for ChatCompletion {
542 type Err = crate::errors::OapiError;
543
544 fn from_str(content: &str) -> Result<Self, Self::Err> {
545 let parse_result: Result<ChatCompletion, _> = serde_json::from_str(content)
546 .map_err(|e| OapiError::DeserializationError(e.to_string()));
547 parse_result
548 }
549 }
550
551 #[cfg(test)]
552 mod test {
553 use super::*;
554
555 #[test]
556 fn no_streaming_example_deepseek() {
557 let json = r#"{
558 "id": "30f6413a-a827-4cf3-9898-f13a8634b798",
559 "object": "chat.completion",
560 "created": 1757944111,
561 "model": "deepseek-chat",
562 "choices": [
563 {
564 "index": 0,
565 "message": {
566 "role": "assistant",
567 "content": "Hello! How can I help you today? 😊"
568 },
569 "logprobs": null,
570 "finish_reason": "stop"
571 }
572 ],
573 "usage": {
574 "prompt_tokens": 10,
575 "completion_tokens": 11,
576 "total_tokens": 21,
577 "prompt_tokens_details": {
578 "cached_tokens": 0
579 },
580 "prompt_cache_hit_tokens": 0,
581 "prompt_cache_miss_tokens": 10
582 },
583 "system_fingerprint": "fp_08f168e49b_prod0820_fp8_kvcache"
584 }"#;
585
586 let parsed = ChatCompletion::from_str(json);
587 match parsed {
588 Ok(_) => {}
589 Err(e) => {
590 panic!("Failed to deserialize: {}", e);
591 }
592 }
593 }
594
595 #[test]
596 fn no_streaming_example_qwen() {
597 let json = r#"{
598 "choices": [
599 {
600 "message": {
601 "role": "assistant",
602 "content": "我是阿里云开发的一款超大规模语言模型,我叫通义千问。"
603 },
604 "finish_reason": "stop",
605 "index": 0,
606 "logprobs": null
607 }
608 ],
609 "object": "chat.completion",
610 "usage": {
611 "prompt_tokens": 3019,
612 "completion_tokens": 104,
613 "total_tokens": 3123,
614 "prompt_tokens_details": {
615 "cached_tokens": 2048
616 }
617 },
618 "created": 1735120033,
619 "system_fingerprint": null,
620 "model": "qwen-plus",
621 "id": "chatcmpl-6ada9ed2-7f33-9de2-8bb0-78bd4035025a"
622 }"#;
623
624 let parsed = ChatCompletion::from_str(json);
625 match parsed {
626 Ok(_) => {}
627 Err(e) => {
628 panic!("Failed to deserialize: {}", e);
629 }
630 }
631 }
632 }
633}