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 {
75 match self {
76 MessageContent::Text(text) => text.clone(),
77 MessageContent::Parts(parts) => {
78 let mut result = String::new();
80 let mut first = true;
81 for part in parts {
82 if let ContentPart::Text { text } = part {
83 if !first {
84 result.push(' ');
85 }
86 result.push_str(text);
87 first = false;
88 }
89 }
90 result
91 }
92 }
93 }
94
95 #[inline]
98 pub fn append_text_to(&self, buffer: &mut String) -> bool {
99 match self {
100 MessageContent::Text(text) => {
101 if text.is_empty() {
102 false
103 } else {
104 buffer.push_str(text);
105 true
106 }
107 }
108 MessageContent::Parts(parts) => {
109 let mut appended = false;
110 for part in parts {
111 if let ContentPart::Text { text } = part {
112 if !text.is_empty() {
113 if appended {
114 buffer.push(' ');
115 }
116 buffer.push_str(text);
117 appended = true;
118 }
119 }
120 }
121 appended
122 }
123 }
124 }
125
126 #[inline]
128 pub fn has_text(&self) -> bool {
129 match self {
130 MessageContent::Text(text) => !text.is_empty(),
131 MessageContent::Parts(parts) => parts
132 .iter()
133 .any(|part| matches!(part, ContentPart::Text { text } if !text.is_empty())),
134 }
135 }
136}
137
138#[serde_with::skip_serializing_none]
143#[derive(Debug, Clone, Deserialize, Serialize, Default, Validate, schemars::JsonSchema)]
144#[validate(schema(function = "validate_chat_cross_parameters"))]
145pub struct ChatCompletionRequest {
146 #[validate(custom(function = "validate_messages"))]
148 pub messages: Vec<ChatMessage>,
149
150 pub model: String,
152
153 #[validate(range(min = -2.0, max = 2.0))]
155 pub frequency_penalty: Option<f32>,
156
157 #[deprecated(note = "Use tool_choice instead")]
159 pub function_call: Option<FunctionCall>,
160
161 #[deprecated(note = "Use tools instead")]
163 pub functions: Option<Vec<Function>>,
164
165 pub logit_bias: Option<HashMap<String, f32>>,
167
168 #[serde(default, deserialize_with = "deserialize_null_as_false")]
170 pub logprobs: bool,
171
172 #[deprecated(note = "Use max_completion_tokens instead")]
174 #[validate(range(min = 1))]
175 pub max_tokens: Option<u32>,
176
177 #[validate(range(min = 1))]
179 pub max_completion_tokens: Option<u32>,
180
181 pub metadata: Option<HashMap<String, String>>,
183
184 pub modalities: Option<Vec<String>>,
186
187 #[validate(range(min = 1, max = 10))]
189 pub n: Option<u32>,
190
191 pub parallel_tool_calls: Option<bool>,
193
194 #[validate(range(min = -2.0, max = 2.0))]
196 pub presence_penalty: Option<f32>,
197
198 pub prompt_cache_key: Option<String>,
200
201 pub reasoning_effort: Option<String>,
203
204 pub response_format: Option<ResponseFormat>,
206
207 pub safety_identifier: Option<String>,
209
210 #[deprecated(note = "This feature is in Legacy mode")]
212 pub seed: Option<i64>,
213
214 pub service_tier: Option<String>,
216
217 #[validate(custom(function = "validate_stop"))]
219 pub stop: Option<StringOrArray>,
220
221 #[serde(default, deserialize_with = "deserialize_null_as_false")]
223 pub stream: bool,
224
225 pub stream_options: Option<StreamOptions>,
227
228 #[validate(range(min = 0.0, max = 2.0))]
230 pub temperature: Option<f32>,
231
232 pub tool_choice: Option<ToolChoice>,
234
235 pub tools: Option<Vec<Tool>>,
237
238 #[validate(range(min = 0, max = 20))]
240 pub top_logprobs: Option<u32>,
241
242 #[validate(custom(function = "validate_top_p_value"))]
244 pub top_p: Option<f32>,
245
246 pub verbosity: Option<i32>,
248
249 #[validate(custom(function = "validate_top_k_value"))]
257 pub top_k: Option<i32>,
258
259 #[validate(range(min = 0.0, max = 1.0))]
261 pub min_p: Option<f32>,
262
263 #[validate(range(min = 1))]
265 pub min_tokens: Option<u32>,
266
267 #[validate(range(min = 0.0, max = 2.0))]
269 pub repetition_penalty: Option<f32>,
270
271 pub regex: Option<String>,
273
274 pub ebnf: Option<String>,
276
277 pub stop_token_ids: Option<Vec<u32>>,
279
280 #[serde(default)]
282 pub no_stop_trim: bool,
283
284 #[serde(default)]
286 pub ignore_eos: bool,
287
288 #[serde(default)]
290 pub continue_final_message: bool,
291
292 #[serde(default = "default_true")]
294 pub skip_special_tokens: bool,
295
296 pub lora_path: Option<String>,
298
299 pub session_params: Option<HashMap<String, Value>>,
301
302 #[serde(default = "default_true")]
304 pub separate_reasoning: bool,
305
306 #[serde(default = "default_true")]
308 pub stream_reasoning: bool,
309
310 pub chat_template_kwargs: Option<HashMap<String, Value>>,
312
313 #[serde(default)]
315 pub return_hidden_states: bool,
316
317 pub sampling_seed: Option<u64>,
319
320 #[serde(flatten)]
322 pub other: Map<String, Value>,
323}
324
325fn validate_messages(messages: &[ChatMessage]) -> Result<(), validator::ValidationError> {
331 if messages.is_empty() {
332 return Err(validator::ValidationError::new("messages cannot be empty"));
333 }
334
335 for msg in messages {
336 if let ChatMessage::User { content, .. } = msg {
337 match content {
338 MessageContent::Text(text) if text.is_empty() => {
339 return Err(validator::ValidationError::new(
340 "message content cannot be empty",
341 ));
342 }
343 MessageContent::Parts(parts) if parts.is_empty() => {
344 return Err(validator::ValidationError::new(
345 "message content parts cannot be empty",
346 ));
347 }
348 _ => {}
349 }
350 }
351 }
352 Ok(())
353}
354
355fn validate_chat_cross_parameters(
357 req: &ChatCompletionRequest,
358) -> Result<(), validator::ValidationError> {
359 if req.top_logprobs.is_some() && !req.logprobs {
361 let mut e = validator::ValidationError::new("top_logprobs_requires_logprobs");
362 e.message = Some("top_logprobs is only allowed when logprobs is enabled".into());
363 return Err(e);
364 }
365
366 if req.stream_options.is_some() && !req.stream {
368 let mut e = validator::ValidationError::new("stream_options_requires_stream");
369 e.message =
370 Some("The 'stream_options' parameter is only allowed when 'stream' is enabled".into());
371 return Err(e);
372 }
373
374 if let (Some(min), Some(max)) = (req.min_tokens, req.max_completion_tokens) {
376 if min > max {
377 let mut e = validator::ValidationError::new("min_tokens_exceeds_max");
378 e.message = Some("min_tokens cannot exceed max_tokens/max_completion_tokens".into());
379 return Err(e);
380 }
381 }
382
383 let has_json_format = matches!(
385 req.response_format,
386 Some(ResponseFormat::JsonObject | ResponseFormat::JsonSchema { .. })
387 );
388
389 if has_json_format && req.regex.is_some() {
390 let mut e = validator::ValidationError::new("regex_conflicts_with_json");
391 e.message = Some("cannot use regex constraint with JSON response format".into());
392 return Err(e);
393 }
394
395 if has_json_format && req.ebnf.is_some() {
396 let mut e = validator::ValidationError::new("ebnf_conflicts_with_json");
397 e.message = Some("cannot use EBNF constraint with JSON response format".into());
398 return Err(e);
399 }
400
401 let constraint_count = [
403 req.regex.is_some(),
404 req.ebnf.is_some(),
405 matches!(req.response_format, Some(ResponseFormat::JsonSchema { .. })),
406 ]
407 .iter()
408 .filter(|&&x| x)
409 .count();
410
411 if constraint_count > 1 {
412 let mut e = validator::ValidationError::new("multiple_constraints");
413 e.message = Some("only one structured output constraint (regex, ebnf, or json_schema) can be active at a time".into());
414 return Err(e);
415 }
416
417 if let Some(ResponseFormat::JsonSchema { json_schema }) = &req.response_format {
419 if json_schema.name.is_empty() {
420 let mut e = validator::ValidationError::new("json_schema_name_empty");
421 e.message = Some("JSON schema name cannot be empty".into());
422 return Err(e);
423 }
424 }
425
426 if let Some(ref tool_choice) = req.tool_choice {
428 let has_tools = req.tools.as_ref().is_some_and(|t| !t.is_empty());
429
430 let is_some_choice = !matches!(tool_choice, ToolChoice::Value(ToolChoiceValue::None));
432
433 if is_some_choice && !has_tools {
434 let mut e = validator::ValidationError::new("tool_choice_requires_tools");
435 e.message = Some("Invalid value for 'tool_choice': 'tool_choice' is only allowed when 'tools' are specified.".into());
436 return Err(e);
437 }
438
439 if let Some(tools) = req.tools.as_ref().filter(|t| !t.is_empty()) {
441 match tool_choice {
442 ToolChoice::Function { function, .. } => {
443 let function_exists = tools.iter().any(|tool| {
445 tool.tool_type == "function" && tool.function.name == function.name
446 });
447
448 if !function_exists {
449 let mut e =
450 validator::ValidationError::new("tool_choice_function_not_found");
451 e.message = Some(
452 format!(
453 "Invalid value for 'tool_choice': function '{}' not found in 'tools'.",
454 function.name
455 )
456 .into(),
457 );
458 return Err(e);
459 }
460 }
461 ToolChoice::AllowedTools {
462 mode,
463 tools: allowed_tools,
464 ..
465 } => {
466 if mode != "auto" && mode != "required" {
468 let mut e = validator::ValidationError::new("tool_choice_invalid_mode");
469 e.message = Some(format!(
470 "Invalid value for 'tool_choice.mode': must be 'auto' or 'required', got '{mode}'."
471 ).into());
472 return Err(e);
473 }
474
475 for tool_ref in allowed_tools {
477 match tool_ref {
478 ToolReference::Function { name } => {
479 let tool_exists = tools.iter().any(|tool| {
481 tool.tool_type == "function" && tool.function.name == *name
482 });
483
484 if !tool_exists {
485 let mut e = validator::ValidationError::new(
486 "tool_choice_tool_not_found",
487 );
488 e.message = Some(
489 format!(
490 "Invalid value for 'tool_choice.tools': tool '{name}' not found in 'tools'."
491 )
492 .into(),
493 );
494 return Err(e);
495 }
496 }
497 _ => {
498 let mut e = validator::ValidationError::new(
500 "tool_choice_invalid_tool_type",
501 );
502 e.message = Some(
503 format!(
504 "Invalid value for 'tool_choice.tools': Chat Completion API only supports function tools, got '{}'.",
505 tool_ref.identifier()
506 )
507 .into(),
508 );
509 return Err(e);
510 }
511 }
512 }
513 }
514 ToolChoice::Value(_) => {}
515 }
516 }
517 }
518
519 Ok(())
520}
521
522impl Normalizable for ChatCompletionRequest {
527 fn normalize(&mut self) {
532 #[expect(deprecated)]
534 if self.max_completion_tokens.is_none() && self.max_tokens.is_some() {
535 self.max_completion_tokens = self.max_tokens;
536 self.max_tokens = None; }
538
539 #[expect(deprecated)]
541 if self.tools.is_none() && self.functions.is_some() {
542 tracing::warn!("functions is deprecated, use tools instead");
543 self.tools = self.functions.as_ref().map(|functions| {
544 functions
545 .iter()
546 .map(|func| Tool {
547 tool_type: "function".to_string(),
548 function: func.clone(),
549 })
550 .collect()
551 });
552 self.functions = None; }
554
555 #[expect(deprecated)]
557 if self.tool_choice.is_none() && self.function_call.is_some() {
558 tracing::warn!("function_call is deprecated, use tool_choice instead");
559 self.tool_choice = self.function_call.as_ref().map(|fc| match fc {
560 FunctionCall::None => ToolChoice::Value(ToolChoiceValue::None),
561 FunctionCall::Auto => ToolChoice::Value(ToolChoiceValue::Auto),
562 FunctionCall::Function { name } => ToolChoice::Function {
563 tool_type: "function".to_string(),
564 function: FunctionChoice { name: name.clone() },
565 },
566 });
567 self.function_call = None; }
569
570 if self.tool_choice.is_none() {
572 if let Some(tools) = &self.tools {
573 let choice_value = if tools.is_empty() {
574 ToolChoiceValue::None
575 } else {
576 ToolChoiceValue::Auto
577 };
578 self.tool_choice = Some(ToolChoice::Value(choice_value));
579 }
580 }
582 }
583}
584
585impl GenerationRequest for ChatCompletionRequest {
590 fn is_stream(&self) -> bool {
591 self.stream
592 }
593
594 fn get_model(&self) -> Option<&str> {
595 Some(&self.model)
596 }
597
598 fn extract_text_for_routing(&self) -> String {
599 let mut buffer = String::new();
602 let mut has_content = false;
603
604 for msg in &self.messages {
605 match msg {
606 ChatMessage::System { content, .. }
607 | ChatMessage::User { content, .. }
608 | ChatMessage::Tool { content, .. }
609 | ChatMessage::Developer { content, .. } => {
610 if has_content && content.has_text() {
611 buffer.push(' ');
612 }
613 if content.append_text_to(&mut buffer) {
614 has_content = true;
615 }
616 }
617 ChatMessage::Assistant {
618 content,
619 reasoning_content,
620 ..
621 } => {
622 if let Some(c) = content {
624 if has_content && c.has_text() {
625 buffer.push(' ');
626 }
627 if c.append_text_to(&mut buffer) {
628 has_content = true;
629 }
630 }
631 if let Some(reasoning) = reasoning_content {
633 if !reasoning.is_empty() {
634 if has_content {
635 buffer.push(' ');
636 }
637 buffer.push_str(reasoning);
638 has_content = true;
639 }
640 }
641 }
642 ChatMessage::Function { content, .. } => {
643 if !content.is_empty() {
644 if has_content {
645 buffer.push(' ');
646 }
647 buffer.push_str(content);
648 has_content = true;
649 }
650 }
651 }
652 }
653
654 buffer
655 }
656}
657
658#[serde_with::skip_serializing_none]
663#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
664pub struct ChatCompletionResponse {
665 pub id: String,
666 pub object: String, pub created: u64,
668 pub model: String,
669 pub choices: Vec<ChatChoice>,
670 pub usage: Option<Usage>,
671 pub system_fingerprint: Option<String>,
672}
673
674impl ChatCompletionResponse {
675 pub fn builder(
677 id: impl Into<String>,
678 model: impl Into<String>,
679 ) -> ChatCompletionResponseBuilder {
680 ChatCompletionResponseBuilder::new(id, model)
681 }
682}
683
684#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
686pub struct ChatCompletionMessage {
687 pub role: String, #[serde(skip_serializing_if = "Option::is_none")]
689 pub content: Option<String>,
690 #[serde(skip_serializing_if = "Option::is_none")]
691 pub tool_calls: Option<Vec<ToolCall>>,
692 pub reasoning_content: Option<String>,
693 }
696
697#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
698pub struct ChatChoice {
699 pub index: u32,
700 pub message: ChatCompletionMessage,
701 #[serde(skip_serializing_if = "Option::is_none")]
702 pub logprobs: Option<ChatLogProbs>,
703 pub finish_reason: Option<String>, #[serde(skip_serializing_if = "Option::is_none")]
706 pub matched_stop: Option<Value>, #[serde(skip_serializing_if = "Option::is_none")]
709 pub hidden_states: Option<Vec<f32>>,
710}
711
712#[serde_with::skip_serializing_none]
713#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
714pub struct ChatCompletionStreamResponse {
715 pub id: String,
716 pub object: String, pub created: u64,
718 pub model: String,
719 pub system_fingerprint: Option<String>,
720 pub choices: Vec<ChatStreamChoice>,
721 pub usage: Option<Usage>,
722}
723
724impl ChatCompletionStreamResponse {
725 pub fn builder(
727 id: impl Into<String>,
728 model: impl Into<String>,
729 ) -> ChatCompletionStreamResponseBuilder {
730 ChatCompletionStreamResponseBuilder::new(id, model)
731 }
732}
733
734#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
736pub struct ChatMessageDelta {
737 #[serde(skip_serializing_if = "Option::is_none")]
738 pub role: Option<String>,
739 #[serde(skip_serializing_if = "Option::is_none")]
740 pub content: Option<String>,
741 #[serde(skip_serializing_if = "Option::is_none")]
742 pub tool_calls: Option<Vec<ToolCallDelta>>,
743 pub reasoning_content: Option<String>,
744}
745
746#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
747pub struct ChatStreamChoice {
748 pub index: u32,
749 pub delta: ChatMessageDelta,
750 pub logprobs: Option<ChatLogProbs>,
751 pub finish_reason: Option<String>,
752 #[serde(skip_serializing_if = "Option::is_none")]
753 pub matched_stop: Option<Value>,
754}