1pub mod streaming {
5 use serde::Deserialize;
8
9 use crate::chat::ServiceTier;
10
11 #[derive(Debug, Deserialize, Clone)]
12 pub struct ChatCompletionChunk {
13 pub id: String,
15 pub choices: Vec<CompletionChunkChoice>,
19 pub created: u64,
22 pub model: String,
24 pub object: ChatCompletionChunkObject,
26 pub service_tier: Option<ServiceTier>,
33 pub system_fingerprint: Option<String>,
37 pub usage: Option<CompletionUsage>,
45 pub moderation: Option<crate::chat::ChatModeration>,
50 }
51
52 #[derive(Debug, Deserialize, Clone)]
53 pub enum ChatCompletionChunkObject {
54 #[serde(rename = "chat.completion.chunk")]
55 ChatCompletionChunk,
56 }
57
58 #[derive(Debug, Deserialize, Clone)]
59 pub struct CompletionChunkChoice {
60 pub delta: ChoiceDelta,
62 pub index: u32,
64 pub logprobs: Option<ChoiceLogprobs>,
66 pub finish_reason: Option<FinishReason>,
74 }
75
76 #[derive(Debug, Deserialize, Clone)]
77 #[serde(rename_all = "snake_case")]
78 pub enum FinishReason {
79 Length,
81 Stop,
83 ContentFilter,
85 FunctionCall,
87 ToolCalls,
89 #[cfg(feature = "deepseek")]
92 InsufficientSystemResource,
93 }
94
95 #[derive(Debug, Deserialize, Clone)]
96 pub struct ChoiceDelta {
97 pub content: Option<String>,
99 #[cfg(feature = "deepseek")]
102 pub reasoning_content: Option<String>,
103 pub function_call: Option<ChoiceDeltaFunctionCall>,
108 pub refusal: Option<String>,
110 pub role: Option<CompletionRole>,
112 pub tool_calls: Option<Vec<ChoiceDeltaToolCall>>,
114 }
115
116 #[derive(Debug, Deserialize, Clone)]
117 pub struct ChoiceDeltaToolCallFunction {
118 pub arguments: Option<String>,
123 pub name: Option<String>,
125 }
126
127 #[derive(Debug, Deserialize, Clone)]
128 pub struct ChoiceDeltaFunctionCall {
129 pub arguments: Option<String>,
134 pub name: Option<String>,
136 }
137
138 #[derive(Debug, Deserialize, Clone)]
139 pub struct ChoiceDeltaToolCall {
140 pub index: usize,
142 pub id: Option<String>,
144 pub function: Option<ChoiceDeltaToolCallFunction>,
146 #[serde(rename = "type")]
148 pub type_: Option<ChoiceDeltaToolCallType>,
149 }
150
151 #[derive(Debug, Deserialize, Clone)]
152 #[serde(rename_all = "snake_case")]
153 pub enum ChoiceDeltaToolCallType {
154 Function,
155 }
156
157 #[derive(Debug, Deserialize, Clone)]
158 #[serde(rename_all = "snake_case")]
159 pub enum CompletionRole {
160 Assistant,
161 Developer,
162 System,
163 Tool,
164 User,
165 }
166
167 #[derive(Debug, Deserialize, Clone)]
169 pub struct ChoiceLogprobs {
170 pub content: Option<Vec<LogprobeContent>>,
172 #[cfg(feature = "deepseek")]
175 pub reasoning_content: Option<Vec<LogprobeContent>>,
176 pub refusal: Option<Vec<LogprobeContent>>,
178 }
179
180 #[derive(Debug, Deserialize, Clone)]
182 pub struct LogprobeContent {
183 pub token: String,
184 pub logprob: f32,
185 pub bytes: Option<Vec<u8>>,
186 pub top_logprobs: Vec<TopLogprob>,
187 }
188
189 #[derive(Debug, Deserialize, Clone)]
192 pub struct TopLogprob {
193 pub token: String,
194 pub logprob: f32,
195 pub bytes: Option<Vec<u8>>,
196 }
197
198 #[derive(Debug, Deserialize, Clone)]
199 pub struct CompletionUsage {
200 pub completion_tokens: usize,
202 pub prompt_tokens: usize,
204
205 #[cfg(feature = "deepseek")]
207 pub prompt_cache_hit_tokens: Option<usize>,
208 #[cfg(feature = "deepseek")]
210 pub prompt_cache_miss_tokens: Option<usize>,
211
212 pub total_tokens: usize,
214 pub completion_tokens_details: Option<CompletionTokensDetails>,
216 pub prompt_tokens_details: Option<PromptTokensDetails>,
218 }
219
220 #[derive(Debug, Deserialize, Clone)]
221 pub struct CompletionTokensDetails {
222 pub accepted_prediction_tokens: Option<usize>,
225 pub audio_tokens: Option<usize>,
227 pub reasoning_tokens: Option<usize>,
229 pub rejected_prediction_tokens: Option<usize>,
234 }
235
236 #[derive(Debug, Deserialize, Clone)]
237 pub struct PromptTokensDetails {
238 pub audio_tokens: Option<usize>,
240 pub cached_tokens: Option<usize>,
242 }
243
244 crate::impl_from_str!(ChatCompletionChunk);
245
246 #[cfg(test)]
247 mod test {
248 use std::str::FromStr;
249
250 use super::*;
251
252 #[test]
253 fn streaming_example_deepseek() {
254 let streams = vec![
255 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}"#,
256 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"}"#,
257 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"}"#,
258 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"}"#,
259 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"}"#,
260 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"}"#,
261 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"}"#,
262 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"}"#,
263 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"}"#,
264 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"}"#,
265 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}}"#,
266 ];
267
268 for stream in streams {
269 let parsed = ChatCompletionChunk::from_str(stream);
270 match parsed {
271 Ok(completion) => {
272 println!("Deserialized: {:#?}", completion);
273 }
274 Err(e) => {
275 panic!("Failed to deserialize {}: {}", stream, e);
276 }
277 }
278 }
279 }
280
281 #[test]
282 fn streaming_example_qwen() {
283 let streams = vec![
284 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}"#,
285 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}"#,
286 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}"#,
287 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}"#,
288 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}"#,
289 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}"#,
290 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}"#,
291 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}"#,
292 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}"#,
293 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}"#,
294 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}}}"#,
295 ];
296
297 for stream in streams {
298 let parsed = ChatCompletionChunk::from_str(stream);
299 match parsed {
300 Ok(completion) => {
301 println!("Deserialized: {:#?}", completion);
302 }
303 Err(e) => {
304 panic!("Failed to deserialize {}: {}", stream, e);
305 }
306 }
307 }
308 }
309 }
310}
311
312pub mod no_streaming {
313 pub type ChatCompletion = crate::chat::ChatCompletion;
318}