openai_protocol/
chat.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use validator::Validate;
6
7use super::{
8    common::{
9        default_model, default_true, validate_stop, ChatLogProbs, ContentPart, Function,
10        FunctionCall, FunctionChoice, GenerationRequest, ResponseFormat, StreamOptions,
11        StringOrArray, Tool, ToolCall, ToolCallDelta, ToolChoice, ToolChoiceValue, ToolReference,
12        Usage,
13    },
14    sampling_params::{validate_top_k_value, validate_top_p_value},
15};
16use crate::{
17    builders::{ChatCompletionResponseBuilder, ChatCompletionStreamResponseBuilder},
18    validated::Normalizable,
19};
20
21// ============================================================================
22// Chat Messages
23// ============================================================================
24
25#[derive(Debug, Clone, Deserialize, Serialize)]
26#[serde(tag = "role")]
27pub enum ChatMessage {
28    #[serde(rename = "system")]
29    System {
30        content: MessageContent,
31        #[serde(skip_serializing_if = "Option::is_none")]
32        name: Option<String>,
33    },
34    #[serde(rename = "user")]
35    User {
36        content: MessageContent,
37        #[serde(skip_serializing_if = "Option::is_none")]
38        name: Option<String>,
39    },
40    #[serde(rename = "assistant")]
41    Assistant {
42        #[serde(skip_serializing_if = "Option::is_none")]
43        content: Option<MessageContent>,
44        #[serde(skip_serializing_if = "Option::is_none")]
45        name: Option<String>,
46        #[serde(skip_serializing_if = "Option::is_none")]
47        tool_calls: Option<Vec<ToolCall>>,
48        /// Reasoning content for O1-style models (SGLang extension)
49        #[serde(skip_serializing_if = "Option::is_none")]
50        reasoning_content: Option<String>,
51    },
52    #[serde(rename = "tool")]
53    Tool {
54        content: MessageContent,
55        tool_call_id: String,
56    },
57    #[serde(rename = "function")]
58    Function { content: String, name: String },
59    #[serde(rename = "developer")]
60    Developer {
61        content: MessageContent,
62        #[serde(skip_serializing_if = "Option::is_none")]
63        tools: Option<Vec<Tool>>,
64        #[serde(skip_serializing_if = "Option::is_none")]
65        name: Option<String>,
66    },
67}
68
69#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
70#[serde(untagged)]
71pub enum MessageContent {
72    Text(String),
73    Parts(Vec<ContentPart>),
74}
75
76impl MessageContent {
77    /// Returns the text content, cloning only when necessary.
78    /// For simple text, returns a clone of the string.
79    /// For parts, concatenates text parts with spaces.
80    /// Optimized to avoid intermediate Vec allocation.
81    pub fn to_simple_string(&self) -> String {
82        match self {
83            MessageContent::Text(text) => text.clone(),
84            MessageContent::Parts(parts) => {
85                // Use fold to build string directly without intermediate Vec allocation
86                let mut result = String::new();
87                let mut first = true;
88                for part in parts {
89                    if let ContentPart::Text { text } = part {
90                        if !first {
91                            result.push(' ');
92                        }
93                        result.push_str(text);
94                        first = false;
95                    }
96                }
97                result
98            }
99        }
100    }
101
102    /// Appends text content directly to a buffer, avoiding intermediate allocations.
103    /// Returns true if any content was appended.
104    #[inline]
105    pub fn append_text_to(&self, buffer: &mut String) -> bool {
106        match self {
107            MessageContent::Text(text) => {
108                if !text.is_empty() {
109                    buffer.push_str(text);
110                    true
111                } else {
112                    false
113                }
114            }
115            MessageContent::Parts(parts) => {
116                let mut appended = false;
117                for part in parts {
118                    if let ContentPart::Text { text } = part {
119                        if !text.is_empty() {
120                            if appended {
121                                buffer.push(' ');
122                            }
123                            buffer.push_str(text);
124                            appended = true;
125                        }
126                    }
127                }
128                appended
129            }
130        }
131    }
132
133    /// Returns true if this content contains any non-empty text.
134    #[inline]
135    pub fn has_text(&self) -> bool {
136        match self {
137            MessageContent::Text(text) => !text.is_empty(),
138            MessageContent::Parts(parts) => parts
139                .iter()
140                .any(|part| matches!(part, ContentPart::Text { text } if !text.is_empty())),
141        }
142    }
143}
144
145// ============================================================================
146// Chat Completion Request
147// ============================================================================
148
149#[derive(Debug, Clone, Deserialize, Serialize, Default, Validate)]
150#[validate(schema(function = "validate_chat_cross_parameters"))]
151pub struct ChatCompletionRequest {
152    /// A list of messages comprising the conversation so far
153    #[validate(custom(function = "validate_messages"))]
154    pub messages: Vec<ChatMessage>,
155
156    /// ID of the model to use
157    #[serde(default = "default_model")]
158    pub model: String,
159
160    /// Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far
161    #[serde(skip_serializing_if = "Option::is_none")]
162    #[validate(range(min = -2.0, max = 2.0))]
163    pub frequency_penalty: Option<f32>,
164
165    /// Deprecated: Replaced by tool_choice
166    #[serde(skip_serializing_if = "Option::is_none")]
167    #[deprecated(note = "Use tool_choice instead")]
168    pub function_call: Option<FunctionCall>,
169
170    /// Deprecated: Replaced by tools
171    #[serde(skip_serializing_if = "Option::is_none")]
172    #[deprecated(note = "Use tools instead")]
173    pub functions: Option<Vec<Function>>,
174
175    /// Modify the likelihood of specified tokens appearing in the completion
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub logit_bias: Option<HashMap<String, f32>>,
178
179    /// Whether to return log probabilities of the output tokens
180    #[serde(default)]
181    pub logprobs: bool,
182
183    /// Deprecated: Replaced by max_completion_tokens
184    #[serde(skip_serializing_if = "Option::is_none")]
185    #[deprecated(note = "Use max_completion_tokens instead")]
186    #[validate(range(min = 1))]
187    pub max_tokens: Option<u32>,
188
189    /// An upper bound for the number of tokens that can be generated for a completion
190    #[serde(skip_serializing_if = "Option::is_none")]
191    #[validate(range(min = 1))]
192    pub max_completion_tokens: Option<u32>,
193
194    /// Developer-defined tags and values used for filtering completions in the dashboard
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub metadata: Option<HashMap<String, String>>,
197
198    /// Output types that you would like the model to generate for this request
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub modalities: Option<Vec<String>>,
201
202    /// How many chat completion choices to generate for each input message
203    #[serde(skip_serializing_if = "Option::is_none")]
204    #[validate(range(min = 1, max = 10))]
205    pub n: Option<u32>,
206
207    /// Whether to enable parallel function calling during tool use
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub parallel_tool_calls: Option<bool>,
210
211    /// Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far
212    #[serde(skip_serializing_if = "Option::is_none")]
213    #[validate(range(min = -2.0, max = 2.0))]
214    pub presence_penalty: Option<f32>,
215
216    /// Cache key for prompts (beta feature)
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub prompt_cache_key: Option<String>,
219
220    /// Effort level for reasoning models (low, medium, high)
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub reasoning_effort: Option<String>,
223
224    /// An object specifying the format that the model must output
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub response_format: Option<ResponseFormat>,
227
228    /// Safety identifier for content moderation
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub safety_identifier: Option<String>,
231
232    /// Deprecated: This feature is in Legacy mode
233    #[serde(skip_serializing_if = "Option::is_none")]
234    #[deprecated(note = "This feature is in Legacy mode")]
235    pub seed: Option<i64>,
236
237    /// The service tier to use for this request
238    #[serde(skip_serializing_if = "Option::is_none")]
239    pub service_tier: Option<String>,
240
241    /// Up to 4 sequences where the API will stop generating further tokens
242    #[serde(skip_serializing_if = "Option::is_none")]
243    #[validate(custom(function = "validate_stop"))]
244    pub stop: Option<StringOrArray>,
245
246    /// If set, partial message deltas will be sent
247    #[serde(default)]
248    pub stream: bool,
249
250    /// Options for streaming response
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub stream_options: Option<StreamOptions>,
253
254    /// What sampling temperature to use, between 0 and 2
255    #[serde(skip_serializing_if = "Option::is_none")]
256    #[validate(range(min = 0.0, max = 2.0))]
257    pub temperature: Option<f32>,
258
259    /// Controls which (if any) tool is called by the model
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub tool_choice: Option<ToolChoice>,
262
263    /// A list of tools the model may call
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub tools: Option<Vec<Tool>>,
266
267    /// An integer between 0 and 20 specifying the number of most likely tokens to return
268    #[serde(skip_serializing_if = "Option::is_none")]
269    #[validate(range(min = 0, max = 20))]
270    pub top_logprobs: Option<u32>,
271
272    /// An alternative to sampling with temperature
273    #[serde(skip_serializing_if = "Option::is_none")]
274    #[validate(custom(function = "validate_top_p_value"))]
275    pub top_p: Option<f32>,
276
277    /// Verbosity level for debugging
278    #[serde(skip_serializing_if = "Option::is_none")]
279    pub verbosity: Option<i32>,
280
281    // =============================================================================
282    // Engine-Specific Sampling Parameters
283    // =============================================================================
284    // These parameters are extensions beyond the OpenAI API specification and
285    // control model generation behavior in engine-specific ways.
286    // =============================================================================
287    /// Top-k sampling parameter (-1 to disable)
288    #[serde(skip_serializing_if = "Option::is_none")]
289    #[validate(custom(function = "validate_top_k_value"))]
290    pub top_k: Option<i32>,
291
292    /// Min-p nucleus sampling parameter
293    #[serde(skip_serializing_if = "Option::is_none")]
294    #[validate(range(min = 0.0, max = 1.0))]
295    pub min_p: Option<f32>,
296
297    /// Minimum number of tokens to generate
298    #[serde(skip_serializing_if = "Option::is_none")]
299    #[validate(range(min = 1))]
300    pub min_tokens: Option<u32>,
301
302    /// Repetition penalty for reducing repetitive text
303    #[serde(skip_serializing_if = "Option::is_none")]
304    #[validate(range(min = 0.0, max = 2.0))]
305    pub repetition_penalty: Option<f32>,
306
307    /// Regex constraint for output generation
308    #[serde(skip_serializing_if = "Option::is_none")]
309    pub regex: Option<String>,
310
311    /// EBNF grammar constraint for structured output
312    #[serde(skip_serializing_if = "Option::is_none")]
313    pub ebnf: Option<String>,
314
315    /// Specific token IDs to use as stop conditions
316    #[serde(skip_serializing_if = "Option::is_none")]
317    pub stop_token_ids: Option<Vec<u32>>,
318
319    /// Skip trimming stop tokens from output
320    #[serde(default)]
321    pub no_stop_trim: bool,
322
323    /// Ignore end-of-sequence tokens during generation
324    #[serde(default)]
325    pub ignore_eos: bool,
326
327    /// Continue generating from final assistant message
328    #[serde(default)]
329    pub continue_final_message: bool,
330
331    /// Skip special tokens during detokenization
332    #[serde(default = "default_true")]
333    pub skip_special_tokens: bool,
334
335    /// Path to LoRA adapter(s) for model customization
336    #[serde(skip_serializing_if = "Option::is_none")]
337    pub lora_path: Option<String>,
338
339    /// Session parameters for continual prompting
340    #[serde(skip_serializing_if = "Option::is_none")]
341    pub session_params: Option<HashMap<String, Value>>,
342
343    /// Separate reasoning content from final answer (O1-style models)
344    #[serde(default = "default_true")]
345    pub separate_reasoning: bool,
346
347    /// Stream reasoning tokens during generation
348    #[serde(default = "default_true")]
349    pub stream_reasoning: bool,
350
351    /// Chat template kwargs
352    #[serde(skip_serializing_if = "Option::is_none")]
353    pub chat_template_kwargs: Option<HashMap<String, Value>>,
354
355    /// Return model hidden states
356    #[serde(default)]
357    pub return_hidden_states: bool,
358
359    /// Random seed for sampling for deterministic outputs
360    #[serde(skip_serializing_if = "Option::is_none")]
361    pub sampling_seed: Option<u64>,
362}
363
364// ============================================================================
365// Validation Functions
366// ============================================================================
367
368/// Validates messages array is not empty and has valid content
369fn validate_messages(messages: &[ChatMessage]) -> Result<(), validator::ValidationError> {
370    if messages.is_empty() {
371        return Err(validator::ValidationError::new("messages cannot be empty"));
372    }
373
374    for msg in messages.iter() {
375        if let ChatMessage::User { content, .. } = msg {
376            match content {
377                MessageContent::Text(text) if text.is_empty() => {
378                    return Err(validator::ValidationError::new(
379                        "message content cannot be empty",
380                    ));
381                }
382                MessageContent::Parts(parts) if parts.is_empty() => {
383                    return Err(validator::ValidationError::new(
384                        "message content parts cannot be empty",
385                    ));
386                }
387                _ => {}
388            }
389        }
390    }
391    Ok(())
392}
393
394/// Schema-level validation for cross-field dependencies
395fn validate_chat_cross_parameters(
396    req: &ChatCompletionRequest,
397) -> Result<(), validator::ValidationError> {
398    // 1. Validate logprobs dependency
399    if req.top_logprobs.is_some() && !req.logprobs {
400        let mut e = validator::ValidationError::new("top_logprobs_requires_logprobs");
401        e.message = Some("top_logprobs is only allowed when logprobs is enabled".into());
402        return Err(e);
403    }
404
405    // 2. Validate stream_options dependency
406    if req.stream_options.is_some() && !req.stream {
407        let mut e = validator::ValidationError::new("stream_options_requires_stream");
408        e.message =
409            Some("The 'stream_options' parameter is only allowed when 'stream' is enabled".into());
410        return Err(e);
411    }
412
413    // 3. Validate token limits - min <= max
414    if let (Some(min), Some(max)) = (req.min_tokens, req.max_completion_tokens) {
415        if min > max {
416            let mut e = validator::ValidationError::new("min_tokens_exceeds_max");
417            e.message = Some("min_tokens cannot exceed max_tokens/max_completion_tokens".into());
418            return Err(e);
419        }
420    }
421
422    // 4. Validate structured output conflicts
423    let has_json_format = matches!(
424        req.response_format,
425        Some(ResponseFormat::JsonObject | ResponseFormat::JsonSchema { .. })
426    );
427
428    if has_json_format && req.regex.is_some() {
429        let mut e = validator::ValidationError::new("regex_conflicts_with_json");
430        e.message = Some("cannot use regex constraint with JSON response format".into());
431        return Err(e);
432    }
433
434    if has_json_format && req.ebnf.is_some() {
435        let mut e = validator::ValidationError::new("ebnf_conflicts_with_json");
436        e.message = Some("cannot use EBNF constraint with JSON response format".into());
437        return Err(e);
438    }
439
440    // 5. Validate mutually exclusive structured output constraints
441    let constraint_count = [
442        req.regex.is_some(),
443        req.ebnf.is_some(),
444        matches!(req.response_format, Some(ResponseFormat::JsonSchema { .. })),
445    ]
446    .iter()
447    .filter(|&&x| x)
448    .count();
449
450    if constraint_count > 1 {
451        let mut e = validator::ValidationError::new("multiple_constraints");
452        e.message = Some("only one structured output constraint (regex, ebnf, or json_schema) can be active at a time".into());
453        return Err(e);
454    }
455
456    // 6. Validate response format JSON schema name
457    if let Some(ResponseFormat::JsonSchema { json_schema }) = &req.response_format {
458        if json_schema.name.is_empty() {
459            let mut e = validator::ValidationError::new("json_schema_name_empty");
460            e.message = Some("JSON schema name cannot be empty".into());
461            return Err(e);
462        }
463    }
464
465    // 7. Validate tool_choice requires tools (except for "none")
466    if let Some(ref tool_choice) = req.tool_choice {
467        let has_tools = req.tools.as_ref().is_some_and(|t| !t.is_empty());
468
469        // Check if tool_choice is anything other than "none"
470        let is_some_choice = !matches!(tool_choice, ToolChoice::Value(ToolChoiceValue::None));
471
472        if is_some_choice && !has_tools {
473            let mut e = validator::ValidationError::new("tool_choice_requires_tools");
474            e.message = Some("Invalid value for 'tool_choice': 'tool_choice' is only allowed when 'tools' are specified.".into());
475            return Err(e);
476        }
477
478        // Additional validation when tools are present
479        if has_tools {
480            let tools = req.tools.as_ref().unwrap();
481
482            match tool_choice {
483                ToolChoice::Function { function, .. } => {
484                    // Validate that the specified function name exists in tools
485                    let function_exists = tools.iter().any(|tool| {
486                        tool.tool_type == "function" && tool.function.name == function.name
487                    });
488
489                    if !function_exists {
490                        let mut e =
491                            validator::ValidationError::new("tool_choice_function_not_found");
492                        e.message = Some(
493                            format!(
494                            "Invalid value for 'tool_choice': function '{}' not found in 'tools'.",
495                            function.name
496                        )
497                            .into(),
498                        );
499                        return Err(e);
500                    }
501                }
502                ToolChoice::AllowedTools {
503                    mode,
504                    tools: allowed_tools,
505                    ..
506                } => {
507                    // Validate mode is "auto" or "required"
508                    if mode != "auto" && mode != "required" {
509                        let mut e = validator::ValidationError::new("tool_choice_invalid_mode");
510                        e.message = Some(format!(
511                            "Invalid value for 'tool_choice.mode': must be 'auto' or 'required', got '{}'.",
512                            mode
513                        ).into());
514                        return Err(e);
515                    }
516
517                    // Validate that all ToolReferences are Function type (Chat API only supports function tools)
518                    for tool_ref in allowed_tools {
519                        match tool_ref {
520                            ToolReference::Function { name } => {
521                                // Validate that the function exists in tools array
522                                let tool_exists = tools.iter().any(|tool| {
523                                    tool.tool_type == "function" && tool.function.name == *name
524                                });
525
526                                if !tool_exists {
527                                    let mut e = validator::ValidationError::new(
528                                        "tool_choice_tool_not_found",
529                                    );
530                                    e.message = Some(
531                                        format!(
532                                            "Invalid value for 'tool_choice.tools': tool '{}' not found in 'tools'.",
533                                            name
534                                        )
535                                        .into(),
536                                    );
537                                    return Err(e);
538                                }
539                            }
540                            _ => {
541                                // Chat Completion API only supports function tools in tool_choice
542                                let mut e = validator::ValidationError::new(
543                                    "tool_choice_invalid_tool_type",
544                                );
545                                e.message = Some(
546                                    format!(
547                                        "Invalid value for 'tool_choice.tools': Chat Completion API only supports function tools, got '{}'.",
548                                        tool_ref.identifier()
549                                    )
550                                    .into(),
551                                );
552                                return Err(e);
553                            }
554                        }
555                    }
556                }
557                _ => {}
558            }
559        }
560    }
561
562    Ok(())
563}
564
565// ============================================================================
566// Normalizable Implementation
567// ============================================================================
568
569impl Normalizable for ChatCompletionRequest {
570    /// Normalize the request by applying migrations and defaults:
571    /// 1. Migrate deprecated fields to their replacements
572    /// 2. Clear deprecated fields and log warnings
573    /// 3. Apply OpenAI defaults for tool_choice
574    fn normalize(&mut self) {
575        // Migrate deprecated max_tokens → max_completion_tokens
576        #[allow(deprecated)]
577        if self.max_completion_tokens.is_none() && self.max_tokens.is_some() {
578            self.max_completion_tokens = self.max_tokens;
579            self.max_tokens = None; // Clear deprecated field
580        }
581
582        // Migrate deprecated functions → tools
583        #[allow(deprecated)]
584        if self.tools.is_none() && self.functions.is_some() {
585            tracing::warn!("functions is deprecated, use tools instead");
586            self.tools = self.functions.as_ref().map(|functions| {
587                functions
588                    .iter()
589                    .map(|func| Tool {
590                        tool_type: "function".to_string(),
591                        function: func.clone(),
592                    })
593                    .collect()
594            });
595            self.functions = None; // Clear deprecated field
596        }
597
598        // Migrate deprecated function_call → tool_choice
599        #[allow(deprecated)]
600        if self.tool_choice.is_none() && self.function_call.is_some() {
601            tracing::warn!("function_call is deprecated, use tool_choice instead");
602            self.tool_choice = self.function_call.as_ref().map(|fc| match fc {
603                FunctionCall::None => ToolChoice::Value(ToolChoiceValue::None),
604                FunctionCall::Auto => ToolChoice::Value(ToolChoiceValue::Auto),
605                FunctionCall::Function { name } => ToolChoice::Function {
606                    tool_type: "function".to_string(),
607                    function: FunctionChoice { name: name.clone() },
608                },
609            });
610            self.function_call = None; // Clear deprecated field
611        }
612
613        // Apply tool_choice defaults
614        if self.tool_choice.is_none() {
615            if let Some(tools) = &self.tools {
616                let choice_value = if !tools.is_empty() {
617                    ToolChoiceValue::Auto
618                } else {
619                    ToolChoiceValue::None
620                };
621                self.tool_choice = Some(ToolChoice::Value(choice_value));
622            }
623            // If tools is None, leave tool_choice as None (don't set it)
624        }
625    }
626}
627
628// ============================================================================
629// GenerationRequest Trait Implementation
630// ============================================================================
631
632impl GenerationRequest for ChatCompletionRequest {
633    fn is_stream(&self) -> bool {
634        self.stream
635    }
636
637    fn get_model(&self) -> Option<&str> {
638        Some(&self.model)
639    }
640
641    fn extract_text_for_routing(&self) -> String {
642        // Extract text from messages for routing decisions
643        // Use a single buffer to avoid intermediate Vec<String> allocations
644        let mut buffer = String::new();
645        let mut has_content = false;
646
647        for msg in &self.messages {
648            match msg {
649                ChatMessage::System { content, .. }
650                | ChatMessage::User { content, .. }
651                | ChatMessage::Tool { content, .. }
652                | ChatMessage::Developer { content, .. } => {
653                    if has_content && content.has_text() {
654                        buffer.push(' ');
655                    }
656                    if content.append_text_to(&mut buffer) {
657                        has_content = true;
658                    }
659                }
660                ChatMessage::Assistant {
661                    content,
662                    reasoning_content,
663                    ..
664                } => {
665                    // Append main content
666                    if let Some(c) = content {
667                        if has_content && c.has_text() {
668                            buffer.push(' ');
669                        }
670                        if c.append_text_to(&mut buffer) {
671                            has_content = true;
672                        }
673                    }
674                    // Append reasoning content
675                    if let Some(reasoning) = reasoning_content {
676                        if !reasoning.is_empty() {
677                            if has_content {
678                                buffer.push(' ');
679                            }
680                            buffer.push_str(reasoning);
681                            has_content = true;
682                        }
683                    }
684                }
685                ChatMessage::Function { content, .. } => {
686                    if !content.is_empty() {
687                        if has_content {
688                            buffer.push(' ');
689                        }
690                        buffer.push_str(content);
691                        has_content = true;
692                    }
693                }
694            }
695        }
696
697        buffer
698    }
699}
700
701// ============================================================================
702// Response Types
703// ============================================================================
704
705#[derive(Debug, Clone, Deserialize, Serialize)]
706pub struct ChatCompletionResponse {
707    pub id: String,
708    pub object: String, // "chat.completion"
709    pub created: u64,
710    pub model: String,
711    pub choices: Vec<ChatChoice>,
712    #[serde(skip_serializing_if = "Option::is_none")]
713    pub usage: Option<Usage>,
714    #[serde(skip_serializing_if = "Option::is_none")]
715    pub system_fingerprint: Option<String>,
716}
717
718impl ChatCompletionResponse {
719    /// Create a new builder for ChatCompletionResponse
720    pub fn builder(
721        id: impl Into<String>,
722        model: impl Into<String>,
723    ) -> ChatCompletionResponseBuilder {
724        ChatCompletionResponseBuilder::new(id, model)
725    }
726}
727
728/// Response message structure for ChatCompletionResponse (different from request ChatMessage)
729#[derive(Debug, Clone, Deserialize, Serialize)]
730pub struct ChatCompletionMessage {
731    pub role: String, // Always "assistant" for responses
732    #[serde(skip_serializing_if = "Option::is_none")]
733    pub content: Option<String>,
734    #[serde(skip_serializing_if = "Option::is_none")]
735    pub tool_calls: Option<Vec<ToolCall>>,
736    pub reasoning_content: Option<String>,
737    // Note: function_call is deprecated and not included
738    // Note: refusal, annotations, audio are not added yet
739}
740
741#[derive(Debug, Clone, Deserialize, Serialize)]
742pub struct ChatChoice {
743    pub index: u32,
744    pub message: ChatCompletionMessage,
745    #[serde(skip_serializing_if = "Option::is_none")]
746    pub logprobs: Option<ChatLogProbs>,
747    pub finish_reason: Option<String>, // "stop", "length", "tool_calls", "content_filter", "function_call"
748    /// Information about which stop condition was matched
749    #[serde(skip_serializing_if = "Option::is_none")]
750    pub matched_stop: Option<Value>, // Can be string or integer
751    /// Hidden states from the model (SGLang extension)
752    #[serde(skip_serializing_if = "Option::is_none")]
753    pub hidden_states: Option<Vec<f32>>,
754}
755
756#[derive(Debug, Clone, Deserialize, Serialize)]
757pub struct ChatCompletionStreamResponse {
758    pub id: String,
759    pub object: String, // "chat.completion.chunk"
760    pub created: u64,
761    pub model: String,
762    #[serde(skip_serializing_if = "Option::is_none")]
763    pub system_fingerprint: Option<String>,
764    pub choices: Vec<ChatStreamChoice>,
765    #[serde(skip_serializing_if = "Option::is_none")]
766    pub usage: Option<Usage>,
767}
768
769impl ChatCompletionStreamResponse {
770    /// Create a new builder for ChatCompletionStreamResponse
771    pub fn builder(
772        id: impl Into<String>,
773        model: impl Into<String>,
774    ) -> ChatCompletionStreamResponseBuilder {
775        ChatCompletionStreamResponseBuilder::new(id, model)
776    }
777}
778
779/// Delta structure for streaming chat completion responses
780#[derive(Debug, Clone, Deserialize, Serialize)]
781pub struct ChatMessageDelta {
782    #[serde(skip_serializing_if = "Option::is_none")]
783    pub role: Option<String>,
784    #[serde(skip_serializing_if = "Option::is_none")]
785    pub content: Option<String>,
786    #[serde(skip_serializing_if = "Option::is_none")]
787    pub tool_calls: Option<Vec<ToolCallDelta>>,
788    pub reasoning_content: Option<String>,
789}
790
791#[derive(Debug, Clone, Deserialize, Serialize)]
792pub struct ChatStreamChoice {
793    pub index: u32,
794    pub delta: ChatMessageDelta,
795    pub logprobs: Option<ChatLogProbs>,
796    pub finish_reason: Option<String>,
797    #[serde(skip_serializing_if = "Option::is_none")]
798    pub matched_stop: Option<Value>,
799}