1pub mod streaming {
2 use serde::Deserialize;
3
4 use crate::errors::ResponseError;
5
6 #[derive(Debug, Deserialize)]
7 pub struct Completion {
8 pub id: String,
9 pub choices: Vec<CompletionChoice>,
10 pub created: u64,
11 pub model: String,
12 pub object: String,
13 pub usage: Option<CompletionUsage>,
14 }
15
16 #[derive(Debug, Deserialize)]
17 pub struct CompletionChoice {
18 pub delta: CompletionDelta,
19 pub index: u32,
20 pub logprobs: Option<ChoiceLogprobs>,
21 pub finish_reason: Option<FinishReason>,
22 }
23
24 #[derive(Debug, Deserialize)]
25 #[serde(rename_all = "snake_case")]
26 pub enum FinishReason {
27 Length,
28 Stop,
29 ContentFilter,
30 ToolCalls,
31 InsufficientSystemResource,
32 }
33
34 #[derive(Debug, Deserialize)]
35 pub struct CompletionDelta {
36 #[serde(flatten)]
37 pub content: CompletionContent,
38 pub role: Option<CompletionRole>,
39 }
40
41 #[derive(Debug, Deserialize)]
42 #[serde(rename_all = "snake_case")]
43 pub enum CompletionRole {
44 User,
45 Assistant,
46 System,
47 Tool,
48 }
49
50 #[derive(Debug, Deserialize)]
51 #[serde(rename_all = "snake_case")]
52 pub enum CompletionContent {
53 Content(String),
54 ReasoningContent(String),
55 }
56
57 #[derive(Debug, Deserialize)]
58 #[serde(rename_all = "snake_case")]
59 pub enum ChoiceLogprobs {
60 Content(Vec<LogprobeContent>),
61 ReasoningContent(Vec<LogprobeContent>),
62 }
63
64 #[derive(Debug, Deserialize)]
65 pub struct LogprobeContent {
66 pub token: String,
67 pub logprob: f32,
68 pub bytes: Option<Vec<u8>>,
69 pub top_logprobs: Vec<TopLogprob>,
70 }
71
72 #[derive(Debug, Deserialize)]
73 pub struct TopLogprob {
74 pub token: String,
75 pub logprob: f32,
76 pub bytes: Option<Vec<u8>>,
77 }
78
79 #[derive(Debug, Deserialize)]
80 pub struct CompletionUsage {
81 pub completion_tokens: usize,
82 pub prompt_tokens: usize,
83 pub total_tokens: usize,
84 }
85
86 impl Completion {
87 pub fn parse_string(content: &str) -> Result<Self, crate::errors::ResponseError> {
88 let parse_result: Result<Completion, _> = serde_json::from_str(content)
89 .map_err(|e| ResponseError::DeserializationError(e.to_string()));
90 parse_result
91 }
92 }
93
94 #[cfg(test)]
95 mod test {
96 use super::*;
97
98 #[test]
99 fn streaming_example_deepseek() {
100 let streams = vec![
101 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}"#,
102 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"}"#,
103 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"}"#,
104 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"}"#,
105 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"}"#,
106 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"}"#,
107 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"}"#,
108 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"}"#,
109 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"}"#,
110 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"}"#,
111 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}}"#,
112 ];
113
114 for stream in streams {
115 let parsed = Completion::parse_string(stream);
116 match parsed {
117 Ok(completion) => {
118 println!("Deserialized: {:#?}", completion);
119 }
120 Err(e) => {
121 panic!("Failed to deserialize {}: {}", stream, e);
122 }
123 }
124 }
125 }
126
127 #[test]
128 fn streaming_example_qwen() {
129 let streams = vec![
130 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}"#,
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":null,"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":[{"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}"#,
139 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}}}"#,
140 ];
141
142 for stream in streams {
143 let parsed = Completion::parse_string(stream);
144 match parsed {
145 Ok(completion) => {
146 println!("Deserialized: {:#?}", completion);
147 }
148 Err(e) => {
149 panic!("Failed to deserialize {}: {}", stream, e);
150 }
151 }
152 }
153 }
154 }
155}
156
157pub mod no_streaming {
158 use serde::Deserialize;
159
160 use crate::errors::ResponseError;
161
162 #[derive(Debug, Deserialize)]
163 pub struct Completion {
164 pub id: String,
165 pub choices: Vec<ResponseChoice>,
166 pub created: u64,
167 pub model: String,
168 pub system_fingerprint: String,
169 pub object: String,
170 pub usage: CompletionUsage,
171 }
172
173 #[derive(Debug, Deserialize)]
174 pub struct ResponseChoice {
175 pub finish_reason: FinishReason,
176 pub index: usize,
177 pub message: ResponseMessage,
178 pub logprobs: Option<ResponseLogprobs>,
179 }
180
181 #[derive(Debug, Deserialize, PartialEq)]
182 #[serde(rename_all = "snake_case")]
183 pub enum FinishReason {
184 Length,
185 Stop,
186 ContentFilter,
187 InsufficientSystemResource,
188 }
189
190 #[derive(Debug, Deserialize)]
191 pub struct ResponseMessage {
192 pub role: ResponseRole,
194 pub content: Option<String>,
195 pub reasoning_content: Option<String>,
196 pub tool_calls: Option<String>,
198 }
199
200 #[derive(Debug, Deserialize)]
201 #[serde(rename_all = "snake_case")]
202 pub enum ResponseRole {
203 User,
204 Assistant,
205 System,
206 Tool,
207 }
208
209 #[derive(Debug, Deserialize)]
210 pub struct ResponseLogprobs {
211 pub content: Vec<LogProb>,
212 pub reasoning_content: Vec<LogProb>,
213 }
214
215 #[derive(Debug, Deserialize)]
216 pub struct LogProb {
217 pub token: String,
218 pub logprob: f32,
219 pub bytes: Option<Vec<u8>>,
220 pub top_logprobs: Vec<TopLogprob>,
221 }
222
223 #[derive(Debug, Deserialize)]
224 pub struct TopLogprob {
225 pub token: String,
226 pub logprob: f32,
227 pub bytes: Option<Vec<u8>>,
228 }
229
230 #[derive(Debug, Deserialize)]
231 pub struct CompletionUsage {
232 pub completion_tokens: usize,
233 pub prompt_tokens: usize,
234
235 pub prompt_cache_hit_tokens: Option<usize>,
237 pub prompt_cache_miss_tokens: Option<usize>,
238
239 pub total_tokens: usize,
240 pub completion_tokens_details: Option<CompletionTokensDetails>,
241 }
242
243 #[derive(Debug, Deserialize)]
244 pub struct CompletionTokensDetails {
245 pub reasoning_tokens: usize,
246 }
247
248 impl Completion {
249 pub fn parse_string(content: &str) -> Result<Self, crate::errors::ResponseError> {
250 let parse_result: Result<Completion, _> = serde_json::from_str(content)
251 .map_err(|e| ResponseError::DeserializationError(e.to_string()));
252 parse_result
253 }
254 }
255
256 #[cfg(test)]
257 mod test {
258
259 #[test]
260 fn no_streaming_example_deepseek() {
261 let json = r#"{
262 "id": "30f6413a-a827-4cf3-9898-f13a8634b798",
263 "object": "chat.completion",
264 "created": 1757944111,
265 "model": "deepseek-chat",
266 "choices": [
267 {
268 "index": 0,
269 "message": {
270 "role": "assistant",
271 "content": "Hello! How can I help you today? 😊"
272 },
273 "logprobs": null,
274 "finish_reason": "stop"
275 }
276 ],
277 "usage": {
278 "prompt_tokens": 10,
279 "completion_tokens": 11,
280 "total_tokens": 21,
281 "prompt_tokens_details": {
282 "cached_tokens": 0
283 },
284 "prompt_cache_hit_tokens": 0,
285 "prompt_cache_miss_tokens": 10
286 },
287 "system_fingerprint": "fp_08f168e49b_prod0820_fp8_kvcache"
288 }"#;
289
290 let parsed = super::Completion::parse_string(json);
291 match parsed {
292 Ok(_) => {}
293 Err(e) => {
294 panic!("Failed to deserialize: {}", e);
295 }
296 }
297 }
298 }
299}