use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatStreamResponse {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "super::serde_helpers::optional_string_from_number_or_string"
)]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
pub choices: Vec<StreamChoice>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<crate::model::chat_base_response::Usage>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamChoice {
#[serde(skip_serializing_if = "Option::is_none")]
pub index: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delta: Option<Delta>,
#[serde(skip_serializing_if = "Option::is_none")]
pub finish_reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Delta {
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<StreamToolCall>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamToolCall {
#[serde(skip_serializing_if = "Option::is_none")]
pub index: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub type_: Option<StreamToolCallType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub function: Option<StreamToolFunction>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum StreamToolCallType {
Function,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamToolFunction {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chunk_accepts_an_omitted_id() {
let chunk: ChatStreamResponse = serde_json::from_str(r#"{"choices":[]}"#).unwrap();
assert!(chunk.id.is_none());
}
#[test]
fn incremental_tool_function_fields_may_arrive_separately() {
let chunk: ChatStreamResponse = serde_json::from_value(serde_json::json!({
"choices": [{
"delta": {
"tool_calls": [
{"index": 0, "type": "function", "function": {"name": "lookup"}},
{"index": 0, "function": {"arguments": "{\"city\":"}}
]
}
}]
}))
.unwrap();
let calls = chunk.choices[0]
.delta
.as_ref()
.unwrap()
.tool_calls
.as_ref()
.unwrap();
assert_eq!(
calls[0].function.as_ref().unwrap().name.as_deref(),
Some("lookup")
);
assert!(calls[1].function.as_ref().unwrap().name.is_none());
}
}