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 }
46
47 #[derive(Debug, Deserialize, Clone)]
48 pub enum ChatCompletionChunkObject {
49 #[serde(rename = "chat.completion.chunk")]
50 ChatCompletionChunk,
51 }
52
53 #[derive(Debug, Deserialize, Clone)]
54 pub struct CompletionChunkChoice {
55 pub delta: ChoiceDelta,
57 pub index: u32,
59 pub logprobs: Option<ChoiceLogprobs>,
61 pub finish_reason: Option<FinishReason>,
69 }
70
71 #[derive(Debug, Deserialize, Clone)]
72 #[serde(rename_all = "snake_case")]
73 pub enum FinishReason {
74 Length,
76 Stop,
78 ContentFilter,
80 FunctionCall,
82 ToolCalls,
84 #[cfg(feature = "deepseek")]
87 InsufficientSystemResource,
88 }
89
90 #[derive(Debug, Deserialize, Clone)]
91 pub struct ChoiceDelta {
92 pub content: Option<String>,
94 #[cfg(feature = "deepseek")]
97 pub reasoning_content: Option<String>,
98 pub function_call: Option<ChoiceDeltaFunctionCall>,
103 pub refusal: Option<String>,
105 pub role: Option<CompletionRole>,
107 pub tool_calls: Option<Vec<ChoiceDeltaToolCall>>,
109 }
110
111 #[derive(Debug, Deserialize, Clone)]
112 pub struct ChoiceDeltaToolCallFunction {
113 pub arguments: Option<String>,
118 pub name: Option<String>,
120 }
121
122 #[derive(Debug, Deserialize, Clone)]
123 pub struct ChoiceDeltaFunctionCall {
124 pub arguments: Option<String>,
129 pub name: Option<String>,
131 }
132
133 #[derive(Debug, Deserialize, Clone)]
134 pub struct ChoiceDeltaToolCall {
135 pub index: usize,
137 pub id: Option<String>,
139 pub function: Option<ChoiceDeltaToolCallFunction>,
141 #[serde(rename = "type")]
143 pub type_: Option<ChoiceDeltaToolCallType>,
144 }
145
146 #[derive(Debug, Deserialize, Clone)]
147 #[serde(rename_all = "snake_case")]
148 pub enum ChoiceDeltaToolCallType {
149 Function,
150 }
151
152 #[derive(Debug, Deserialize, Clone)]
153 #[serde(rename_all = "snake_case")]
154 pub enum CompletionRole {
155 Assistant,
156 Developer,
157 System,
158 Tool,
159 User,
160 }
161
162 #[derive(Debug, Deserialize, Clone)]
164 pub struct ChoiceLogprobs {
165 pub content: Option<Vec<LogprobeContent>>,
167 #[cfg(feature = "deepseek")]
170 pub reasoning_content: Option<Vec<LogprobeContent>>,
171 pub refusal: Option<Vec<LogprobeContent>>,
173 }
174
175 #[derive(Debug, Deserialize, Clone)]
177 pub struct LogprobeContent {
178 pub token: String,
179 pub logprob: f32,
180 pub bytes: Option<Vec<u8>>,
181 pub top_logprobs: Vec<TopLogprob>,
182 }
183
184 #[derive(Debug, Deserialize, Clone)]
187 pub struct TopLogprob {
188 pub token: String,
189 pub logprob: f32,
190 pub bytes: Option<Vec<u8>>,
191 }
192
193 #[derive(Debug, Deserialize, Clone)]
194 pub struct CompletionUsage {
195 pub completion_tokens: usize,
197 pub prompt_tokens: usize,
199
200 #[cfg(feature = "deepseek")]
202 pub prompt_cache_hit_tokens: Option<usize>,
203 #[cfg(feature = "deepseek")]
205 pub prompt_cache_miss_tokens: Option<usize>,
206
207 pub total_tokens: usize,
209 pub completion_tokens_details: Option<CompletionTokensDetails>,
211 pub prompt_tokens_details: Option<PromptTokensDetails>,
213 }
214
215 #[derive(Debug, Deserialize, Clone)]
216 pub struct CompletionTokensDetails {
217 pub accepted_prediction_tokens: Option<usize>,
220 pub audio_tokens: Option<usize>,
222 pub reasoning_tokens: Option<usize>,
224 pub rejected_prediction_tokens: Option<usize>,
229 }
230
231 #[derive(Debug, Deserialize, Clone)]
232 pub struct PromptTokensDetails {
233 pub audio_tokens: Option<usize>,
235 pub cached_tokens: Option<usize>,
237 }
238
239 crate::impl_from_str!(ChatCompletionChunk);
240
241 #[cfg(test)]
242 mod test {
243 use std::str::FromStr;
244
245 use super::*;
246
247 #[test]
248 fn streaming_example_deepseek() {
249 let streams = vec![
250 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}"#,
251 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"}"#,
252 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"}"#,
253 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"}"#,
254 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"}"#,
255 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"}"#,
256 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"}"#,
257 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"}"#,
258 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"}"#,
259 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"}"#,
260 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}}"#,
261 ];
262
263 for stream in streams {
264 let parsed = ChatCompletionChunk::from_str(stream);
265 match parsed {
266 Ok(completion) => {
267 println!("Deserialized: {:#?}", completion);
268 }
269 Err(e) => {
270 panic!("Failed to deserialize {}: {}", stream, e);
271 }
272 }
273 }
274 }
275
276 #[test]
277 fn streaming_example_qwen() {
278 let streams = vec![
279 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}"#,
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":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":"stop","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":[],"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}}}"#,
290 ];
291
292 for stream in streams {
293 let parsed = ChatCompletionChunk::from_str(stream);
294 match parsed {
295 Ok(completion) => {
296 println!("Deserialized: {:#?}", completion);
297 }
298 Err(e) => {
299 panic!("Failed to deserialize {}: {}", stream, e);
300 }
301 }
302 }
303 }
304 }
305}
306
307pub mod no_streaming {
308 pub type ChatCompletion = crate::chat::ChatCompletion;
313}