pub mod streaming {
use serde::Deserialize;
use crate::chat::ServiceTier;
#[derive(Debug, Deserialize, Clone)]
pub struct ChatCompletionChunk {
pub id: String,
pub choices: Vec<CompletionChunkChoice>,
pub created: u64,
pub model: String,
pub object: ChatCompletionChunkObject,
pub service_tier: Option<ServiceTier>,
pub system_fingerprint: Option<String>,
pub usage: Option<CompletionUsage>,
pub moderation: Option<crate::chat::ChatModeration>,
}
#[derive(Debug, Deserialize, Clone)]
pub enum ChatCompletionChunkObject {
#[serde(rename = "chat.completion.chunk")]
ChatCompletionChunk,
}
#[derive(Debug, Deserialize, Clone)]
pub struct CompletionChunkChoice {
pub delta: ChoiceDelta,
pub index: u32,
pub logprobs: Option<ChoiceLogprobs>,
pub finish_reason: Option<FinishReason>,
}
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "snake_case")]
pub enum FinishReason {
Length,
Stop,
ContentFilter,
FunctionCall,
ToolCalls,
#[cfg(feature = "deepseek")]
InsufficientSystemResource,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ChoiceDelta {
pub content: Option<String>,
#[cfg(feature = "deepseek")]
pub reasoning_content: Option<String>,
pub function_call: Option<ChoiceDeltaFunctionCall>,
pub refusal: Option<String>,
pub role: Option<CompletionRole>,
pub tool_calls: Option<Vec<ChoiceDeltaToolCall>>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ChoiceDeltaToolCallFunction {
pub arguments: Option<String>,
pub name: Option<String>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ChoiceDeltaFunctionCall {
pub arguments: Option<String>,
pub name: Option<String>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ChoiceDeltaToolCall {
pub index: usize,
pub id: Option<String>,
pub function: Option<ChoiceDeltaToolCallFunction>,
#[serde(rename = "type")]
pub type_: Option<ChoiceDeltaToolCallType>,
}
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "snake_case")]
pub enum ChoiceDeltaToolCallType {
Function,
}
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "snake_case")]
pub enum CompletionRole {
Assistant,
Developer,
System,
Tool,
User,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ChoiceLogprobs {
pub content: Option<Vec<LogprobeContent>>,
#[cfg(feature = "deepseek")]
pub reasoning_content: Option<Vec<LogprobeContent>>,
pub refusal: Option<Vec<LogprobeContent>>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct LogprobeContent {
pub token: String,
pub logprob: f32,
pub bytes: Option<Vec<u8>>,
pub top_logprobs: Vec<TopLogprob>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct TopLogprob {
pub token: String,
pub logprob: f32,
pub bytes: Option<Vec<u8>>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct CompletionUsage {
pub completion_tokens: usize,
pub prompt_tokens: usize,
#[cfg(feature = "deepseek")]
pub prompt_cache_hit_tokens: Option<usize>,
#[cfg(feature = "deepseek")]
pub prompt_cache_miss_tokens: Option<usize>,
pub total_tokens: usize,
pub completion_tokens_details: Option<CompletionTokensDetails>,
pub prompt_tokens_details: Option<PromptTokensDetails>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct CompletionTokensDetails {
pub accepted_prediction_tokens: Option<usize>,
pub audio_tokens: Option<usize>,
pub reasoning_tokens: Option<usize>,
pub rejected_prediction_tokens: Option<usize>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct PromptTokensDetails {
pub audio_tokens: Option<usize>,
pub cached_tokens: Option<usize>,
}
crate::impl_from_str!(ChatCompletionChunk);
#[cfg(test)]
mod test {
use std::str::FromStr;
use super::*;
#[test]
fn streaming_example_deepseek() {
let streams = vec![
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}"#,
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"}"#,
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"}"#,
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"}"#,
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"}"#,
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"}"#,
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"}"#,
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"}"#,
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"}"#,
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"}"#,
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}}"#,
];
for stream in streams {
let parsed = ChatCompletionChunk::from_str(stream);
match parsed {
Ok(completion) => {
println!("Deserialized: {:#?}", completion);
}
Err(e) => {
panic!("Failed to deserialize {}: {}", stream, e);
}
}
}
}
#[test]
fn streaming_example_qwen() {
let streams = vec![
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}"#,
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}"#,
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}"#,
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}"#,
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}"#,
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}"#,
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}"#,
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}"#,
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}"#,
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}"#,
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}}}"#,
];
for stream in streams {
let parsed = ChatCompletionChunk::from_str(stream);
match parsed {
Ok(completion) => {
println!("Deserialized: {:#?}", completion);
}
Err(e) => {
panic!("Failed to deserialize {}: {}", stream, e);
}
}
}
}
}
}
pub mod no_streaming {
pub type ChatCompletion = crate::chat::ChatCompletion;
}