zai_rs/model/
chat_stream_response.rs

1//! # Streaming Response Types for Chat API Models
2//!
3//! This module defines the data structures used for processing streaming responses
4//! from chat completion APIs. These types are specifically designed to handle
5//! Server-Sent Events (SSE) data chunks where responses arrive incrementally.
6//!
7//! ## Key Differences from Standard Responses
8//!
9//! Unlike regular chat completion responses, streaming responses:
10//! - Contain `delta` fields instead of complete `message` objects
11//! - Arrive as multiple chunks over time
12//! - Include partial content that gets assembled client-side
13//! - May contain reasoning content for models with thinking capabilities
14//!
15//! ## Streaming Protocol
16//!
17//! The streaming implementation expects SSE-formatted data with:
18//! - `data: ` prefixed lines containing JSON chunks
19//! - `[DONE]` marker to signal stream completion
20//! - Optional usage statistics on the final chunk
21//!
22//! ## Usage
23//!
24//! ```rust,ignore
25//! let mut client = ChatCompletion::new(model, messages, api_key).enable_stream();
26//! client.stream_for_each(|chunk| async move {
27//!     if let Some(delta) = &chunk.choices[0].delta {
28//!         if let Some(content) = &delta.content {
29//!             print!("{}", content);
30//!         }
31//!     }
32//!     Ok(())
33//! }).await?;
34//! ```
35
36use serde::{Deserialize, Deserializer, Serialize};
37
38/// Custom deserializer that accepts strings or numbers, converting to Option<String>.
39///
40/// This helper function handles the wire format flexibility where IDs may be
41/// transmitted as either strings or numbers, normalizing them to Option<String>.
42///
43/// ## Supported Formats
44///
45/// - `null` → `None`
46/// - `"string_id"` → `Some("string_id")`
47/// - `123` → `Some("123")`
48/// - Other types → deserialization error
49fn de_opt_string_from_number_or_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
50where
51    D: Deserializer<'de>,
52{
53    let v = serde_json::Value::deserialize(deserializer)?;
54    match v {
55        serde_json::Value::Null => Ok(None),
56        serde_json::Value::String(s) => Ok(Some(s)),
57        serde_json::Value::Number(n) => Ok(Some(n.to_string())),
58        other => Err(serde::de::Error::custom(format!(
59            "expected string or number, got {}",
60            other
61        ))),
62    }
63}
64
65/// Represents a single streaming chunk from the chat API.
66///
67/// This struct contains a portion of the complete response that arrives
68/// as part of an SSE stream. Multiple chunks are typically received
69/// and assembled to form the complete response.
70///
71/// ## Fields
72///
73/// - `id` - Unique identifier for the streaming session (optional)
74/// - `created` - Unix timestamp when the chunk was created (optional)
75/// - `model` - Name of the model generating the response (optional)
76/// - `choices` - Array of streaming choices, usually containing one item
77/// - `usage` - Token usage statistics, typically only on final chunk
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct ChatStreamResponse {
80    /// Unique identifier for the streaming session.
81    ///
82    /// May be a string or number in the wire format, converted to `Option<String>`.
83    #[serde(
84        skip_serializing_if = "Option::is_none",
85        deserialize_with = "de_opt_string_from_number_or_string"
86    )]
87    pub id: Option<String>,
88
89    /// Unix timestamp indicating when the chunk was created.
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub created: Option<u64>,
92
93    /// Name of the AI model generating the response.
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub model: Option<String>,
96
97    /// Array of streaming choices, typically containing one item per chunk.
98    ///
99    /// Each choice contains a delta with partial content updates.
100    pub choices: Vec<StreamChoice>,
101
102    /// Token usage statistics.
103    ///
104    /// This field typically appears only on the final chunk of the stream,
105    /// providing information about prompt and completion token counts.
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub usage: Option<crate::model::chat_base_response::Usage>,
108}
109
110/// Represents a single choice within a streaming response chunk.
111///
112/// Each choice contains a delta with incremental content updates and
113/// metadata about the generation process.
114///
115/// ## Fields
116///
117/// - `index` - Position of this choice in the results array
118/// - `delta` - Partial content update for this choice
119/// - `finish_reason` - Reason why generation stopped (on final chunk)
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct StreamChoice {
122    /// Index position of this choice in the results array.
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub index: Option<i32>,
125
126    /// Delta payload containing partial content updates.
127    ///
128    /// This field contains the incremental content that should be
129    /// appended to the accumulated response.
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub delta: Option<Delta>,
132
133    /// Reason why the generation process finished.
134    ///
135    /// This field typically appears only on the final chunk of a choice,
136    /// indicating why generation stopped (e.g., "stop", "length", etc.).
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub finish_reason: Option<String>,
139}
140
141/// Represents incremental content updates in streaming responses.
142///
143/// The delta contains partial content that should be appended to the
144/// accumulated response. Different fields may be present depending on
145/// the chunk type and model capabilities.
146///
147/// ## Fields
148///
149/// - `role` - Message role, typically "assistant" on first chunk
150/// - `content` - Partial text content to append
151/// - `reasoning_content` - Reasoning traces for thinking models
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct Delta {
154    /// Role of the message sender.
155    ///
156    /// Typically "assistant" on the first chunk of a response,
157    /// may be omitted on subsequent chunks.
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub role: Option<String>,
160
161    /// Partial text content that should be appended to the response.
162    ///
163    /// This field contains the incremental text content for the current chunk.
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub content: Option<String>,
166
167    /// Reasoning content for models with thinking capabilities.
168    ///
169    /// This field contains step-by-step reasoning traces when the model
170    /// is operating in thinking mode with reasoning enabled.
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub reasoning_content: Option<String>,
173
174    /// Streaming tool call payload for tool invocation.
175    ///
176    /// When `tool_stream` is enabled and the model emits tool calling information,
177    /// providers often stream this as an array of objects with partial fields.
178    /// Use a flexible Value here to accept strings/arrays/objects without failing
179    /// deserialization on type mismatch across increments.
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub tool_calls: Option<Vec<crate::model::chat_base_response::ToolCallMessage>>,
182}