use crate::v1::chat_completion::{
ChatCompletionChoice, Reasoning, ReasoningEffort, Tool, ToolChoiceType,
};
use crate::v1::common;
use crate::{
impl_builder_methods,
v1::chat_completion::{serialize_tool_choice, ChatCompletionMessage},
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ChatCompletionRequest {
pub model: String,
pub messages: Vec<ChatCompletionMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub n: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_format: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub presence_penalty: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub frequency_penalty: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub logit_bias: Option<HashMap<String, i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<Tool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parallel_tool_calls: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(serialize_with = "serialize_tool_choice")]
pub tool_choice: Option<ToolChoiceType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<Reasoning>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_effort: Option<ReasoningEffort>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transforms: Option<Vec<String>>,
}
impl ChatCompletionRequest {
pub fn new(model: String, messages: Vec<ChatCompletionMessage>) -> Self {
Self {
model,
messages,
temperature: None,
top_p: None,
n: None,
response_format: None,
stop: None,
max_tokens: None,
presence_penalty: None,
frequency_penalty: None,
logit_bias: None,
user: None,
seed: None,
tools: None,
parallel_tool_calls: None,
tool_choice: None,
reasoning: None,
reasoning_effort: None,
transforms: None,
}
}
}
impl_builder_methods!(
ChatCompletionRequest,
temperature: f64,
top_p: f64,
n: i64,
response_format: Value,
stop: Vec<String>,
max_tokens: i64,
presence_penalty: f64,
frequency_penalty: f64,
logit_bias: HashMap<String, i32>,
user: String,
seed: i64,
tools: Vec<Tool>,
parallel_tool_calls: bool,
tool_choice: ToolChoiceType,
reasoning: Reasoning,
reasoning_effort: ReasoningEffort,
transforms: Vec<String>
);
#[derive(Debug, Deserialize, Serialize)]
pub struct ChatCompletionResponse {
pub id: Option<String>,
pub object: Option<String>,
pub created: i64,
pub model: String,
pub choices: Vec<ChatCompletionChoice>,
pub usage: common::Usage,
pub system_fingerprint: Option<String>,
}
#[cfg(test)]
mod tests {
use crate::v1::chat_completion::{ReasoningEffort, ReasoningSummary};
use super::*;
use serde_json::json;
#[test]
fn test_reasoning_effort_serialization() {
let reasoning = Reasoning {
effort: Some(ReasoningEffort::High),
summary: Some(ReasoningSummary::Detailed),
};
let serialized = serde_json::to_value(&reasoning).unwrap();
let expected = json!({
"effort": "high",
"summary": "detailed"
});
assert_eq!(serialized, expected);
}
#[test]
fn test_reasoning_summary_only_serialization() {
let reasoning = Reasoning {
effort: None,
summary: Some(ReasoningSummary::Auto),
};
let serialized = serde_json::to_value(&reasoning).unwrap();
let expected = json!({
"summary": "auto"
});
assert_eq!(serialized, expected);
}
#[test]
fn test_reasoning_deserialization() {
let json_str = r#"{"effort": "medium", "summary": "concise"}"#;
let reasoning: Reasoning = serde_json::from_str(json_str).unwrap();
assert_eq!(reasoning.effort, Some(ReasoningEffort::Medium));
assert_eq!(reasoning.summary, Some(ReasoningSummary::Concise));
}
#[test]
fn test_chat_completion_request_with_reasoning() {
let mut req = ChatCompletionRequest::new("gpt-4".to_string(), vec![]);
req.reasoning = Some(Reasoning {
effort: Some(ReasoningEffort::Low),
summary: Some(ReasoningSummary::Auto),
});
let serialized = serde_json::to_value(&req).unwrap();
assert_eq!(serialized["reasoning"]["effort"], "low");
assert_eq!(serialized["reasoning"]["summary"], "auto");
}
#[test]
fn test_chat_completion_request_with_reasoning_effort() {
let mut req = ChatCompletionRequest::new("gpt-5.1".to_string(), vec![]);
req.reasoning_effort = Some(ReasoningEffort::None);
let serialized = serde_json::to_value(&req).unwrap();
assert_eq!(serialized["reasoning_effort"], "none");
}
#[test]
fn test_reasoning_effort_enum_deserialization() {
let effort: ReasoningEffort = serde_json::from_str(r#""xhigh""#).unwrap();
assert_eq!(effort, ReasoningEffort::Xhigh);
}
#[test]
fn test_transforms_none_serialization() {
let req = ChatCompletionRequest::new("gpt-4".to_string(), vec![]);
let serialised = serde_json::to_value(&req).unwrap();
assert!(!serialised.as_object().unwrap().contains_key("transforms"));
}
#[test]
fn test_transforms_some_serialization() {
let mut req = ChatCompletionRequest::new("gpt-4".to_string(), vec![]);
req.transforms = Some(vec!["transform1".to_string(), "transform2".to_string()]);
let serialised = serde_json::to_value(&req).unwrap();
assert_eq!(
serialised["transforms"],
serde_json::json!(["transform1", "transform2"])
);
}
#[test]
fn test_transforms_some_deserialization() {
let json_str =
r#"{"model": "gpt-4", "messages": [], "transforms": ["transform1", "transform2"]}"#;
let req: ChatCompletionRequest = serde_json::from_str(json_str).unwrap();
assert_eq!(
req.transforms,
Some(vec!["transform1".to_string(), "transform2".to_string()])
);
}
#[test]
fn test_transforms_none_deserialization() {
let json_str = r#"{"model": "gpt-4", "messages": []}"#;
let req: ChatCompletionRequest = serde_json::from_str(json_str).unwrap();
assert_eq!(req.transforms, None);
}
#[test]
fn test_transforms_builder_method() {
let transforms = vec!["transform1".to_string(), "transform2".to_string()];
let req =
ChatCompletionRequest::new("gpt-4".to_string(), vec![]).transforms(transforms.clone());
assert_eq!(req.transforms, Some(transforms));
}
#[test]
fn test_reasoning_effort_builder_method() {
let req = ChatCompletionRequest::new("gpt-5.1".to_string(), vec![])
.reasoning_effort(ReasoningEffort::Minimal);
assert_eq!(req.reasoning_effort, Some(ReasoningEffort::Minimal));
}
#[test]
fn test_openrouter_reasoning_response_deserialization() {
let json_str = r#"{
"id": "chatcmpl-123",
"object": "chat.completion",
"created": 1677652288,
"model": "openai/gpt-4",
"choices": [{
"index": 0,
"finish_reason": "stop",
"message": {
"role": "assistant",
"content": "Final answer",
"reasoning": "Intermediate reasoning"
}
}],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 5,
"total_tokens": 15
}
}"#;
let res: ChatCompletionResponse = serde_json::from_str(json_str).unwrap();
assert_eq!(
res.choices[0].message.reasoning_content.as_deref(),
Some("Intermediate reasoning")
);
}
}