1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::{Map, Value};
5use validator::Validate;
6
7use super::{
8 common::{
9 default_true, deserialize_null_as_false, validate_stop, ChatLogProbs, ContentPart,
10 Function, 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#[serde_with::skip_serializing_none]
26#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
27#[serde(tag = "role")]
28pub enum ChatMessage {
29 #[serde(rename = "system")]
30 System {
31 content: MessageContent,
32 name: Option<String>,
33 },
34 #[serde(rename = "user")]
35 User {
36 content: MessageContent,
37 name: Option<String>,
38 },
39 #[serde(rename = "assistant")]
40 Assistant {
41 content: Option<MessageContent>,
42 name: Option<String>,
43 tool_calls: Option<Vec<ToolCall>>,
44 reasoning_content: Option<String>,
46 },
47 #[serde(rename = "tool")]
48 Tool {
49 content: MessageContent,
50 tool_call_id: String,
51 },
52 #[serde(rename = "function")]
53 Function { content: String, name: String },
54 #[serde(rename = "developer")]
55 Developer {
56 content: MessageContent,
57 tools: Option<Vec<Tool>>,
58 name: Option<String>,
59 },
60}
61
62#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, schemars::JsonSchema)]
63#[serde(untagged)]
64pub enum MessageContent {
65 Text(String),
66 Parts(Vec<ContentPart>),
67}
68
69impl MessageContent {
70 pub fn to_simple_string(&self) -> String {
74 match self {
75 MessageContent::Text(text) => text.clone(),
76 MessageContent::Parts(parts) => {
77 let mut result = String::new();
78 let mut first = true;
79 for part in parts {
80 if let ContentPart::Text { text } = part {
81 if !first {
82 result.push(' ');
83 }
84 result.push_str(text);
85 first = false;
86 }
87 }
88 result
89 }
90 }
91 }
92
93 #[inline]
96 pub fn append_text_to(&self, buffer: &mut String) -> bool {
97 match self {
98 MessageContent::Text(text) => {
99 if text.is_empty() {
100 false
101 } else {
102 buffer.push_str(text);
103 true
104 }
105 }
106 MessageContent::Parts(parts) => {
107 let mut appended = false;
108 for part in parts {
109 if let ContentPart::Text { text } = part {
110 if !text.is_empty() {
111 if appended {
112 buffer.push(' ');
113 }
114 buffer.push_str(text);
115 appended = true;
116 }
117 }
118 }
119 appended
120 }
121 }
122 }
123
124 #[inline]
126 pub fn has_text(&self) -> bool {
127 match self {
128 MessageContent::Text(text) => !text.is_empty(),
129 MessageContent::Parts(parts) => parts
130 .iter()
131 .any(|part| matches!(part, ContentPart::Text { text } if !text.is_empty())),
132 }
133 }
134}
135
136#[serde_with::skip_serializing_none]
141#[derive(Debug, Clone, Deserialize, Serialize, Default, Validate, schemars::JsonSchema)]
142#[validate(schema(function = "validate_chat_cross_parameters"))]
143pub struct ChatCompletionRequest {
144 #[validate(custom(function = "validate_messages"))]
146 pub messages: Vec<ChatMessage>,
147
148 pub model: String,
150
151 #[validate(range(min = -2.0, max = 2.0))]
153 pub frequency_penalty: Option<f32>,
154
155 #[deprecated(note = "Use tool_choice instead")]
157 pub function_call: Option<FunctionCall>,
158
159 #[deprecated(note = "Use tools instead")]
161 pub functions: Option<Vec<Function>>,
162
163 pub logit_bias: Option<HashMap<String, f32>>,
165
166 #[serde(default, deserialize_with = "deserialize_null_as_false")]
168 pub logprobs: bool,
169
170 #[deprecated(note = "Use max_completion_tokens instead")]
172 #[validate(range(min = 1))]
173 pub max_tokens: Option<u32>,
174
175 #[validate(range(min = 1))]
177 pub max_completion_tokens: Option<u32>,
178
179 pub metadata: Option<HashMap<String, String>>,
181
182 pub modalities: Option<Vec<String>>,
184
185 #[validate(range(min = 1, max = 10))]
187 pub n: Option<u32>,
188
189 pub parallel_tool_calls: Option<bool>,
191
192 #[validate(range(min = -2.0, max = 2.0))]
194 pub presence_penalty: Option<f32>,
195
196 pub prompt_cache_key: Option<String>,
198
199 pub reasoning_effort: Option<String>,
201
202 pub response_format: Option<ResponseFormat>,
204
205 pub safety_identifier: Option<String>,
207
208 #[deprecated(note = "This feature is in Legacy mode")]
210 pub seed: Option<i64>,
211
212 pub service_tier: Option<String>,
214
215 #[validate(custom(function = "validate_stop"))]
217 pub stop: Option<StringOrArray>,
218
219 #[serde(default, deserialize_with = "deserialize_null_as_false")]
221 pub stream: bool,
222
223 pub stream_options: Option<StreamOptions>,
225
226 #[validate(range(min = 0.0, max = 2.0))]
228 pub temperature: Option<f32>,
229
230 pub tool_choice: Option<ToolChoice>,
232
233 pub tools: Option<Vec<Tool>>,
235
236 #[validate(range(min = 0, max = 20))]
238 pub top_logprobs: Option<u32>,
239
240 #[validate(custom(function = "validate_top_p_value"))]
242 pub top_p: Option<f32>,
243
244 pub verbosity: Option<i32>,
246
247 #[validate(custom(function = "validate_top_k_value"))]
255 pub top_k: Option<i32>,
256
257 #[validate(range(min = 0.0, max = 1.0))]
259 pub min_p: Option<f32>,
260
261 #[validate(range(min = 0))]
263 pub min_tokens: Option<u32>,
264
265 #[validate(range(min = 0.0, max = 2.0))]
267 pub repetition_penalty: Option<f32>,
268
269 pub regex: Option<String>,
271
272 pub ebnf: Option<String>,
274
275 pub stop_token_ids: Option<Vec<u32>>,
277
278 #[serde(default)]
280 pub no_stop_trim: bool,
281
282 #[serde(default)]
284 pub ignore_eos: bool,
285
286 #[serde(default)]
288 pub continue_final_message: bool,
289
290 #[serde(default = "default_true")]
292 pub skip_special_tokens: bool,
293
294 pub lora_path: Option<String>,
296
297 pub session_params: Option<HashMap<String, Value>>,
299
300 #[serde(default = "default_true")]
302 pub separate_reasoning: bool,
303
304 #[serde(default = "default_true")]
306 pub stream_reasoning: bool,
307
308 pub chat_template_kwargs: Option<HashMap<String, Value>>,
310
311 #[serde(default)]
313 pub return_hidden_states: bool,
314
315 pub sampling_seed: Option<u64>,
317
318 #[serde(flatten)]
320 pub other: Map<String, Value>,
321}
322
323fn validate_messages(messages: &[ChatMessage]) -> Result<(), validator::ValidationError> {
329 if messages.is_empty() {
330 return Err(validator::ValidationError::new("messages cannot be empty"));
331 }
332
333 for msg in messages {
334 if let ChatMessage::User { content, .. } = msg {
335 match content {
336 MessageContent::Text(text) if text.is_empty() => {
337 return Err(validator::ValidationError::new(
338 "message content cannot be empty",
339 ));
340 }
341 MessageContent::Parts(parts) if parts.is_empty() => {
342 return Err(validator::ValidationError::new(
343 "message content parts cannot be empty",
344 ));
345 }
346 _ => {}
347 }
348 }
349 }
350 Ok(())
351}
352
353fn validate_chat_cross_parameters(
355 req: &ChatCompletionRequest,
356) -> Result<(), validator::ValidationError> {
357 if req.top_logprobs.is_some() && !req.logprobs {
359 let mut e = validator::ValidationError::new("top_logprobs_requires_logprobs");
360 e.message = Some("top_logprobs is only allowed when logprobs is enabled".into());
361 return Err(e);
362 }
363
364 if req.stream_options.is_some() && !req.stream {
366 let mut e = validator::ValidationError::new("stream_options_requires_stream");
367 e.message =
368 Some("The 'stream_options' parameter is only allowed when 'stream' is enabled".into());
369 return Err(e);
370 }
371
372 if let (Some(min), Some(max)) = (req.min_tokens, req.max_completion_tokens) {
374 if min > max {
375 let mut e = validator::ValidationError::new("min_tokens_exceeds_max");
376 e.message = Some("min_tokens cannot exceed max_tokens/max_completion_tokens".into());
377 return Err(e);
378 }
379 }
380
381 let has_json_format = matches!(
383 req.response_format,
384 Some(ResponseFormat::JsonObject | ResponseFormat::JsonSchema { .. })
385 );
386
387 if has_json_format && req.regex.is_some() {
388 let mut e = validator::ValidationError::new("regex_conflicts_with_json");
389 e.message = Some("cannot use regex constraint with JSON response format".into());
390 return Err(e);
391 }
392
393 if has_json_format && req.ebnf.is_some() {
394 let mut e = validator::ValidationError::new("ebnf_conflicts_with_json");
395 e.message = Some("cannot use EBNF constraint with JSON response format".into());
396 return Err(e);
397 }
398
399 let constraint_count = [
401 req.regex.is_some(),
402 req.ebnf.is_some(),
403 matches!(req.response_format, Some(ResponseFormat::JsonSchema { .. })),
404 ]
405 .iter()
406 .filter(|&&x| x)
407 .count();
408
409 if constraint_count > 1 {
410 let mut e = validator::ValidationError::new("multiple_constraints");
411 e.message = Some("only one structured output constraint (regex, ebnf, or json_schema) can be active at a time".into());
412 return Err(e);
413 }
414
415 if let Some(ResponseFormat::JsonSchema { json_schema }) = &req.response_format {
417 if json_schema.name.is_empty() {
418 let mut e = validator::ValidationError::new("json_schema_name_empty");
419 e.message = Some("JSON schema name cannot be empty".into());
420 return Err(e);
421 }
422 }
423
424 if let Some(ref tool_choice) = req.tool_choice {
426 let has_tools = req.tools.as_ref().is_some_and(|t| !t.is_empty());
427
428 let is_some_choice = !matches!(tool_choice, ToolChoice::Value(ToolChoiceValue::None));
430
431 if is_some_choice && !has_tools {
432 let mut e = validator::ValidationError::new("tool_choice_requires_tools");
433 e.message = Some("Invalid value for 'tool_choice': 'tool_choice' is only allowed when 'tools' are specified.".into());
434 return Err(e);
435 }
436
437 if let Some(tools) = req.tools.as_ref().filter(|t| !t.is_empty()) {
439 match tool_choice {
440 ToolChoice::Function { function, .. } => {
441 let function_exists = tools.iter().any(|tool| {
443 tool.tool_type == "function" && tool.function.name == function.name
444 });
445
446 if !function_exists {
447 let mut e =
448 validator::ValidationError::new("tool_choice_function_not_found");
449 e.message = Some(
450 format!(
451 "Invalid value for 'tool_choice': function '{}' not found in 'tools'.",
452 function.name
453 )
454 .into(),
455 );
456 return Err(e);
457 }
458 }
459 ToolChoice::AllowedTools {
460 mode,
461 tools: allowed_tools,
462 ..
463 } => {
464 if mode != "auto" && mode != "required" {
466 let mut e = validator::ValidationError::new("tool_choice_invalid_mode");
467 e.message = Some(format!(
468 "Invalid value for 'tool_choice.mode': must be 'auto' or 'required', got '{mode}'."
469 ).into());
470 return Err(e);
471 }
472
473 for tool_ref in allowed_tools {
475 match tool_ref {
476 ToolReference::Function { name } => {
477 let tool_exists = tools.iter().any(|tool| {
479 tool.tool_type == "function" && tool.function.name == *name
480 });
481
482 if !tool_exists {
483 let mut e = validator::ValidationError::new(
484 "tool_choice_tool_not_found",
485 );
486 e.message = Some(
487 format!(
488 "Invalid value for 'tool_choice.tools': tool '{name}' not found in 'tools'."
489 )
490 .into(),
491 );
492 return Err(e);
493 }
494 }
495 _ => {
496 let mut e = validator::ValidationError::new(
498 "tool_choice_invalid_tool_type",
499 );
500 e.message = Some(
501 format!(
502 "Invalid value for 'tool_choice.tools': Chat Completion API only supports function tools, got '{}'.",
503 tool_ref.identifier()
504 )
505 .into(),
506 );
507 return Err(e);
508 }
509 }
510 }
511 }
512 ToolChoice::Value(_) => {}
513 }
514 }
515 }
516
517 Ok(())
518}
519
520impl Normalizable for ChatCompletionRequest {
525 fn normalize(&mut self) {
530 #[expect(deprecated)]
532 if self.max_completion_tokens.is_none() && self.max_tokens.is_some() {
533 self.max_completion_tokens = self.max_tokens;
534 self.max_tokens = None; }
536
537 #[expect(deprecated)]
539 if self.tools.is_none() && self.functions.is_some() {
540 tracing::warn!("functions is deprecated, use tools instead");
541 self.tools = self.functions.as_ref().map(|functions| {
542 functions
543 .iter()
544 .map(|func| Tool {
545 tool_type: "function".to_string(),
546 function: func.clone(),
547 })
548 .collect()
549 });
550 self.functions = None; }
552
553 #[expect(deprecated)]
555 if self.tool_choice.is_none() && self.function_call.is_some() {
556 tracing::warn!("function_call is deprecated, use tool_choice instead");
557 self.tool_choice = self.function_call.as_ref().map(|fc| match fc {
558 FunctionCall::None => ToolChoice::Value(ToolChoiceValue::None),
559 FunctionCall::Auto => ToolChoice::Value(ToolChoiceValue::Auto),
560 FunctionCall::Function { name } => ToolChoice::Function {
561 tool_type: "function".to_string(),
562 function: FunctionChoice { name: name.clone() },
563 },
564 });
565 self.function_call = None; }
567
568 if self.tool_choice.is_none() {
570 if let Some(tools) = &self.tools {
571 let choice_value = if tools.is_empty() {
572 ToolChoiceValue::None
573 } else {
574 ToolChoiceValue::Auto
575 };
576 self.tool_choice = Some(ToolChoice::Value(choice_value));
577 }
578 }
580 }
581}
582
583impl GenerationRequest for ChatCompletionRequest {
588 fn is_stream(&self) -> bool {
589 self.stream
590 }
591
592 fn get_model(&self) -> Option<&str> {
593 Some(&self.model)
594 }
595
596 fn extract_text_for_routing(&self) -> String {
597 let mut buffer = String::new();
600 let mut has_content = false;
601
602 for msg in &self.messages {
603 match msg {
604 ChatMessage::System { content, .. }
605 | ChatMessage::User { content, .. }
606 | ChatMessage::Tool { content, .. }
607 | ChatMessage::Developer { content, .. } => {
608 if has_content && content.has_text() {
609 buffer.push(' ');
610 }
611 if content.append_text_to(&mut buffer) {
612 has_content = true;
613 }
614 }
615 ChatMessage::Assistant {
616 content,
617 reasoning_content,
618 ..
619 } => {
620 if let Some(c) = content {
622 if has_content && c.has_text() {
623 buffer.push(' ');
624 }
625 if c.append_text_to(&mut buffer) {
626 has_content = true;
627 }
628 }
629 if let Some(reasoning) = reasoning_content {
631 if !reasoning.is_empty() {
632 if has_content {
633 buffer.push(' ');
634 }
635 buffer.push_str(reasoning);
636 has_content = true;
637 }
638 }
639 }
640 ChatMessage::Function { content, .. } => {
641 if !content.is_empty() {
642 if has_content {
643 buffer.push(' ');
644 }
645 buffer.push_str(content);
646 has_content = true;
647 }
648 }
649 }
650 }
651
652 buffer
653 }
654}
655
656#[serde_with::skip_serializing_none]
661#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
662pub struct ChatCompletionResponse {
663 pub id: String,
664 pub object: String, pub created: u64,
666 pub model: String,
667 pub choices: Vec<ChatChoice>,
668 pub usage: Option<Usage>,
669 pub system_fingerprint: Option<String>,
670}
671
672impl ChatCompletionResponse {
673 pub fn builder(
675 id: impl Into<String>,
676 model: impl Into<String>,
677 ) -> ChatCompletionResponseBuilder {
678 ChatCompletionResponseBuilder::new(id, model)
679 }
680}
681
682#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
684pub struct ChatCompletionMessage {
685 pub role: String, #[serde(skip_serializing_if = "Option::is_none")]
687 pub content: Option<String>,
688 #[serde(skip_serializing_if = "Option::is_none")]
689 pub tool_calls: Option<Vec<ToolCall>>,
690 pub reasoning_content: Option<String>,
691 }
694
695#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
696pub struct ChatChoice {
697 pub index: u32,
698 pub message: ChatCompletionMessage,
699 #[serde(skip_serializing_if = "Option::is_none")]
700 pub logprobs: Option<ChatLogProbs>,
701 pub finish_reason: Option<String>, #[serde(skip_serializing_if = "Option::is_none")]
704 pub matched_stop: Option<Value>, #[serde(skip_serializing_if = "Option::is_none")]
707 pub hidden_states: Option<Vec<f32>>,
708}
709
710#[serde_with::skip_serializing_none]
711#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
712pub struct ChatCompletionStreamResponse {
713 pub id: String,
714 pub object: String, pub created: u64,
716 pub model: String,
717 pub system_fingerprint: Option<String>,
718 pub choices: Vec<ChatStreamChoice>,
719 pub usage: Option<Usage>,
720}
721
722impl ChatCompletionStreamResponse {
723 pub fn builder(
725 id: impl Into<String>,
726 model: impl Into<String>,
727 ) -> ChatCompletionStreamResponseBuilder {
728 ChatCompletionStreamResponseBuilder::new(id, model)
729 }
730}
731
732#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
734pub struct ChatMessageDelta {
735 #[serde(skip_serializing_if = "Option::is_none")]
736 pub role: Option<String>,
737 #[serde(skip_serializing_if = "Option::is_none")]
738 pub content: Option<String>,
739 #[serde(skip_serializing_if = "Option::is_none")]
740 pub tool_calls: Option<Vec<ToolCallDelta>>,
741 pub reasoning_content: Option<String>,
742}
743
744#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
745pub struct ChatStreamChoice {
746 pub index: u32,
747 pub delta: ChatMessageDelta,
748 pub logprobs: Option<ChatLogProbs>,
749 pub finish_reason: Option<String>,
750 #[serde(skip_serializing_if = "Option::is_none")]
751 pub matched_stop: Option<Value>,
752}