use crate::enums::{ReasoningEffort, VerbosityLevel};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Gpt5Request {
pub model: String,
pub input: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<RequestReasoning>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<Tool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_output_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<RequestText>,
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
#[serde(skip_serializing)]
#[serde(skip_deserializing)]
pub web_search_config: Option<WebSearchConfig>,
#[serde(flatten)]
pub parameters: HashMap<String, Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RequestReasoning {
pub effort: ReasoningEffort,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RequestText {
#[serde(skip_serializing_if = "Option::is_none")]
pub verbosity: Option<VerbosityLevel>,
}
#[derive(Debug, Clone, Default)]
pub struct WebSearchConfig {
pub enabled: bool,
pub name: Option<String>,
pub description: Option<String>,
pub query: Option<String>,
pub max_results: Option<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tool {
#[serde(rename = "type")]
pub tool_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parameters: Option<Value>,
}
pub struct Gpt5RequestBuilder {
model: crate::models::Gpt5Model,
input: String,
reasoning: Option<RequestReasoning>,
tools: Option<Vec<Tool>>,
tool_choice: Option<String>,
max_output_tokens: Option<u32>,
top_p: Option<f64>,
text: Option<RequestText>,
instructions: Option<String>,
web_search: Option<WebSearchConfig>,
parameters: HashMap<String, Value>,
}
impl Gpt5RequestBuilder {
pub fn new(model: crate::models::Gpt5Model) -> Self {
Self {
model,
input: String::new(),
reasoning: None,
tools: None,
tool_choice: None,
max_output_tokens: None,
top_p: None,
text: None,
instructions: None,
web_search: None,
parameters: HashMap::new(),
}
}
pub fn input(mut self, text: &str) -> Self {
self.input = text.to_string();
self
}
pub fn web_search_enabled(mut self, enabled: bool) -> Self {
let mut config = self.web_search.unwrap_or_default();
config.enabled = enabled;
self.web_search = Some(config);
self
}
pub fn web_search_query(mut self, query: &str) -> Self {
let mut config = self.web_search.unwrap_or_else(|| WebSearchConfig {
enabled: true,
..Default::default()
});
config.query = Some(query.to_string());
if !config.enabled {
config.enabled = true;
}
self.web_search = Some(config);
self
}
pub fn web_search_max_results(mut self, max_results: u8) -> Self {
let mut config = self.web_search.unwrap_or_else(|| WebSearchConfig {
enabled: true,
..Default::default()
});
config.max_results = Some(max_results);
if !config.enabled {
config.enabled = true;
}
self.web_search = Some(config);
self
}
pub fn user_text(self, text: &str) -> Self {
self.input(text)
}
pub fn instructions(mut self, instructions: &str) -> Self {
self.instructions = Some(instructions.to_string());
self
}
pub fn tools(mut self, tools: Vec<Tool>) -> Self {
self.tools = Some(tools);
self
}
pub fn tool_choice(mut self, choice: &str) -> Self {
self.tool_choice = Some(choice.to_string());
self
}
pub fn reasoning_effort(mut self, effort: ReasoningEffort) -> Self {
self.reasoning = Some(RequestReasoning { effort });
self
}
pub fn verbosity(mut self, level: VerbosityLevel) -> Self {
self.text = Some(RequestText {
verbosity: Some(level),
});
self
}
pub fn max_output_tokens(mut self, tokens: u32) -> Self {
self.max_output_tokens = Some(tokens);
self
}
pub fn top_p(mut self, p: f64) -> Self {
self.top_p = Some(p);
self
}
pub fn param<T: Into<Value>>(mut self, key: &str, value: T) -> Self {
self.parameters.insert(key.to_string(), value.into());
self
}
pub fn build(self) -> Gpt5Request {
self.validate();
let Gpt5RequestBuilder {
model,
input,
reasoning,
tools,
tool_choice,
max_output_tokens,
top_p,
text,
instructions,
web_search,
parameters,
} = self;
let mut tools = tools.unwrap_or_default();
let mut web_search_config = None;
if let Some(config) = web_search {
if config.enabled {
let already_configured = tools.iter().any(|tool| tool.tool_type == "web_search");
if !already_configured {
tools.push(config.to_tool());
}
web_search_config = Some(config);
}
}
let tools = if tools.is_empty() { None } else { Some(tools) };
Gpt5Request {
model: model.as_str().to_string(),
input,
reasoning,
tools,
tool_choice,
max_output_tokens,
top_p,
text,
instructions,
web_search_config,
parameters,
}
}
fn validate(&self) {
if self.input.trim().is_empty() {
tracing::warn!("Gpt5RequestBuilder: Input is empty, this may result in no response");
}
if let Some(tokens) = self.max_output_tokens {
if tokens < 10 {
tracing::warn!("Gpt5RequestBuilder: max_output_tokens ({}) is very low, response may be truncated", tokens);
} else if tokens > 100000 {
tracing::warn!("Gpt5RequestBuilder: max_output_tokens ({}) is very high, this may be expensive", tokens);
}
}
if let Some(top_p) = self.top_p {
if !(0.0..=1.0).contains(&top_p) {
tracing::warn!(
"Gpt5RequestBuilder: top_p ({}) should be between 0.0 and 1.0",
top_p
);
}
}
if let Some(ref reasoning) = self.reasoning {
if let Some(ref text) = self.text {
if let Some(ref verbosity) = text.verbosity {
match (&reasoning.effort, verbosity) {
(ReasoningEffort::High, VerbosityLevel::Low) => {
tracing::warn!("Gpt5RequestBuilder: High reasoning effort with low verbosity may not produce detailed output");
}
(ReasoningEffort::Low, VerbosityLevel::High) => {
tracing::warn!("Gpt5RequestBuilder: Low reasoning effort with high verbosity may not produce the expected detailed output");
}
_ => {} }
}
}
}
if let Some(ref web_search) = self.web_search {
if let Some(max_results) = web_search.max_results {
if max_results == 0 {
tracing::warn!(
"Gpt5RequestBuilder: web_search_max_results is zero; search results will be ignored"
);
}
}
}
if let Some(ref tools) = self.tools {
if tools.is_empty() {
tracing::warn!("Gpt5RequestBuilder: Empty tools array provided");
}
}
tracing::info!("Gpt5RequestBuilder: Request validation completed");
}
}
impl WebSearchConfig {
fn to_tool(&self) -> Tool {
Tool {
tool_type: "web_search".to_string(),
name: None,
description: None,
parameters: None,
}
}
}