1use serde::Deserialize;
2
3use crate::errors::ResponseError;
4
5#[derive(Debug, Deserialize)]
6pub struct Completion {
7 pub id: String,
8 pub choices: Vec<CompletionChoice>,
9 pub created: u64,
10 pub model: String,
11 pub object: String,
12 pub usage: Option<CompletionUsage>,
13}
14
15#[derive(Debug, Deserialize)]
16pub struct CompletionChoice {
17 pub delta: CompletionDelta,
18 pub index: u32,
19 pub logprobs: Option<ChoiceLogprobs>,
20 pub finish_reason: Option<FinishReason>,
21}
22
23#[derive(Debug, Deserialize)]
24#[serde(rename_all = "snake_case")]
25pub enum FinishReason {
26 Length,
27 Stop,
28 ContentFilter,
29 ToolCalls,
30 InsufficientSystemResource,
31}
32
33#[derive(Debug, Deserialize)]
34pub struct CompletionDelta {
35 #[serde(flatten)]
36 pub content: CompletionContent,
37 pub role: Option<CompletionRole>,
38}
39
40#[derive(Debug, Deserialize)]
41#[serde(rename_all = "snake_case")]
42pub enum CompletionRole {
43 User,
44 Assistant,
45 System,
46 Tool,
47}
48
49#[derive(Debug, Deserialize)]
50#[serde(rename_all = "snake_case")]
51pub enum CompletionContent {
52 Content(String),
53 ReasoningContent(String),
54}
55
56#[derive(Debug, Deserialize)]
57#[serde(rename_all = "snake_case")]
58pub enum ChoiceLogprobs {
59 Content(Vec<LogprobeContent>),
60 ReasoningContent(Vec<LogprobeContent>),
61}
62
63#[derive(Debug, Deserialize)]
64pub struct LogprobeContent {
65 pub token: String,
66 pub logprob: f32,
67 pub bytes: Option<Vec<u8>>,
68 pub top_logprobs: Vec<TopLogprob>,
69}
70
71#[derive(Debug, Deserialize)]
72pub struct TopLogprob {
73 pub token: String,
74 pub logprob: f32,
75 pub bytes: Option<Vec<u8>>,
76}
77
78#[derive(Debug, Deserialize)]
79pub struct CompletionUsage {
80 pub completion_tokens: usize,
81 pub prompt_tokens: usize,
82 pub total_tokens: usize,
83}
84
85impl Completion {
86 pub fn parse_string(content: &str) -> Result<Self, crate::errors::ResponseError> {
87 let parse_result: Result<Completion, _> = serde_json::from_str(content)
88 .map_err(|e| ResponseError::DeserializationError(e.to_string()));
89 parse_result
90 }
91}
92
93#[cfg(test)]
94mod test {
95 use super::*;
96
97 #[test]
98 fn streaming_example_deepseek() {
99 let streams = vec![
100 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}"#,
101 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"}"#,
102 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"}"#,
103 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"}"#,
104 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"}"#,
105 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"}"#,
106 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"}"#,
107 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"}"#,
108 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"}"#,
109 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"}"#,
110 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}}"#,
111 ];
112
113 for stream in streams {
114 let parsed = Completion::parse_string(stream);
115 match parsed {
116 Ok(completion) => {
117 println!("Deserialized: {:#?}", completion);
118 }
119 Err(e) => {
120 panic!("Failed to deserialize {}: {}", stream, e);
121 }
122 }
123 }
124 }
125
126 #[test]
127 fn streaming_example_qwen() {
128 let streams = vec![
129 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}"#,
130 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}"#,
131 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}"#,
132 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}"#,
133 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}"#,
134 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}"#,
135 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}"#,
136 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}"#,
137 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}"#,
138 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}}}"#,
139 ];
140
141 for stream in streams {
142 let parsed = Completion::parse_string(stream);
143 match parsed {
144 Ok(completion) => {
145 println!("Deserialized: {:#?}", completion);
146 }
147 Err(e) => {
148 panic!("Failed to deserialize {}: {}", stream, e);
149 }
150 }
151 }
152 }
153}