#![allow(deprecated)]
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use log::{info, warn};
use reqwest::{header, Client};
use serde::{Deserialize, Serialize};
use serde_json::{json, to_value, Value};
use crate::{
apis::OpenAiApiEndpoints,
completions::ThinkingLevel,
constants::{OPENAI_API_URL, OPENAI_BASE_INSTRUCTIONS, OPENAI_FUNCTION_INSTRUCTIONS},
domain::{
OpenAPIChatResponse, OpenAPICompletionsResponse, OpenAPIResponsesContentType,
OpenAPIResponsesOutputType, OpenAPIResponsesResponse, OpenAPIResponsesRole, RateLimit,
},
llm_models::{
tools::{
OpenAICodeInterpreterConfig, OpenAIComputerUseConfig, OpenAIFileSearchConfig,
OpenAIWebSearchConfig,
},
LLMModel, LLMTools,
},
utils::{map_to_range, remove_json_wrapper, remove_schema_wrappers},
};
#[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq)]
pub enum OpenAIModels {
Gpt5_5,
Gpt5_5Pro,
Gpt5_4,
Gpt5_4Pro,
Gpt5_4Mini,
Gpt5_4Nano,
Gpt5_2,
Gpt5_2Pro,
Gpt5_1,
Gpt5,
Gpt5Mini,
Gpt5Nano,
Gpt4_5Preview,
Gpt4oMini,
Gpt4_1,
Gpt4_1Mini,
Gpt4_1Nano,
Gpt4o20240806,
Gpt4o,
Gpt4TurboPreview,
Gpt4Turbo,
Gpt4_32k,
Gpt4,
Gpt3_5Turbo16k,
Gpt3_5Turbo0613,
Gpt3_5Turbo,
O4Mini,
O3,
O3Mini,
O1Pro,
O1,
O1Preview, O1Mini, Custom { name: String },
TextDavinci003,
}
#[async_trait(?Send)]
impl LLMModel for OpenAIModels {
fn as_str(&self) -> &str {
match self {
OpenAIModels::Gpt5_5 => "gpt-5.5",
OpenAIModels::Gpt5_5Pro => "gpt-5.5-pro",
OpenAIModels::Gpt5_4 => "gpt-5.4",
OpenAIModels::Gpt5_4Pro => "gpt-5.4-pro",
OpenAIModels::Gpt5_4Mini => "gpt-5.4-mini",
OpenAIModels::Gpt5_4Nano => "gpt-5.4-nano",
OpenAIModels::Gpt5_2 => "gpt-5.2",
OpenAIModels::Gpt5_2Pro => "gpt-5.2-pro",
OpenAIModels::Gpt5_1 => "gpt-5.1",
OpenAIModels::Gpt5 => "gpt-5",
OpenAIModels::Gpt5Mini => "gpt-5-mini",
OpenAIModels::Gpt5Nano => "gpt-5-nano",
OpenAIModels::Gpt3_5Turbo => "gpt-3.5-turbo",
OpenAIModels::Gpt3_5Turbo0613 => "gpt-3.5-turbo-0613",
OpenAIModels::Gpt3_5Turbo16k => "gpt-3.5-turbo-16k",
OpenAIModels::Gpt4 => "gpt-4",
OpenAIModels::Gpt4_32k => "gpt-4-32k",
OpenAIModels::TextDavinci003 => "text-davinci-003",
OpenAIModels::Gpt4Turbo => "gpt-4-turbo",
OpenAIModels::Gpt4TurboPreview => "gpt-4-turbo-preview",
OpenAIModels::Gpt4o => "gpt-4o",
OpenAIModels::Gpt4o20240806 => "gpt-4o-2024-08-06",
OpenAIModels::Gpt4oMini => "gpt-4o-mini",
OpenAIModels::Gpt4_1 => "gpt-4.1",
OpenAIModels::Gpt4_1Mini => "gpt-4.1-mini",
OpenAIModels::Gpt4_1Nano => "gpt-4.1-nano",
OpenAIModels::Gpt4_5Preview => "gpt-4.5-preview",
OpenAIModels::O1Preview => "o1-preview",
OpenAIModels::O1Mini => "o1-mini",
OpenAIModels::O1 => "o1",
OpenAIModels::O1Pro => "o1-pro",
OpenAIModels::O3 => "o3",
OpenAIModels::O3Mini => "o3-mini",
OpenAIModels::O4Mini => "o4-mini",
OpenAIModels::Custom { name } => name.as_str(),
}
}
fn try_from_str(name: &str) -> Option<Self> {
match name.to_lowercase().as_str() {
"gpt-5.5" => Some(OpenAIModels::Gpt5_5),
"gpt-5.5-2026-04-23" => Some(OpenAIModels::Gpt5_5),
"gpt-5.5-pro" => Some(OpenAIModels::Gpt5_5Pro),
"gpt-5.5-pro-2026-04-23" => Some(OpenAIModels::Gpt5_5Pro),
"gpt-5.4" => Some(OpenAIModels::Gpt5_4),
"gpt-5.4-2026-03-05" => Some(OpenAIModels::Gpt5_4),
"gpt-5.4-pro" => Some(OpenAIModels::Gpt5_4Pro),
"gpt-5.4-pro-2026-03-05" => Some(OpenAIModels::Gpt5_4Pro),
"gpt-5.4-mini" => Some(OpenAIModels::Gpt5_4Mini),
"gpt-5.4-mini-2026-03-17" => Some(OpenAIModels::Gpt5_4Mini),
"gpt-5.4-nano" => Some(OpenAIModels::Gpt5_4Nano),
"gpt-5.4-nano-2026-03-17" => Some(OpenAIModels::Gpt5_4Nano),
"gpt-5.2" => Some(OpenAIModels::Gpt5_2),
"gpt-5.2-2025-12-11" => Some(OpenAIModels::Gpt5_2),
"gpt-5.2-pro" => Some(OpenAIModels::Gpt5_2Pro),
"gpt-5.2-pro-2025-12-11" => Some(OpenAIModels::Gpt5_2Pro),
"gpt-5.1" => Some(OpenAIModels::Gpt5_1),
"gpt-5.1-2025-11-13" => Some(OpenAIModels::Gpt5_1),
"gpt-5" => Some(OpenAIModels::Gpt5),
"gpt-5-2025-08-07" => Some(OpenAIModels::Gpt5),
"gpt-5-mini" => Some(OpenAIModels::Gpt5Mini),
"gpt-5-mini-2025-08-07" => Some(OpenAIModels::Gpt5Mini),
"gpt-5-nano" => Some(OpenAIModels::Gpt5Nano),
"gpt-5-nano-2025-08-07" => Some(OpenAIModels::Gpt5Nano),
"gpt-3.5-turbo" => Some(OpenAIModels::Gpt3_5Turbo),
"gpt-3.5-turbo-0613" => Some(OpenAIModels::Gpt3_5Turbo0613),
"gpt-3.5-turbo-16k" => Some(OpenAIModels::Gpt3_5Turbo16k),
"gpt-4" => Some(OpenAIModels::Gpt4),
"gpt-4-32k" => Some(OpenAIModels::Gpt4_32k),
"text-davinci-003" => Some(OpenAIModels::TextDavinci003),
"gpt-4-turbo" => Some(OpenAIModels::Gpt4Turbo),
"gpt-4-turbo-preview" => Some(OpenAIModels::Gpt4TurboPreview),
"gpt-4o" => Some(OpenAIModels::Gpt4o),
"gpt-4o-2024-08-06" => Some(OpenAIModels::Gpt4o20240806),
"gpt-4o-mini" => Some(OpenAIModels::Gpt4oMini),
"gpt-4.1" => Some(OpenAIModels::Gpt4_1),
"gpt-4.1-mini" => Some(OpenAIModels::Gpt4_1Mini),
"gpt-4.1-nano" => Some(OpenAIModels::Gpt4_1Nano),
"gpt-4.5-preview" => Some(OpenAIModels::Gpt4_5Preview),
"o1-preview" => Some(OpenAIModels::O1Preview),
"o1-mini" => Some(OpenAIModels::O1Mini),
"o1" => Some(OpenAIModels::O1),
"o1-pro" => Some(OpenAIModels::O1Pro),
"o3" => Some(OpenAIModels::O3),
"o3-mini" => Some(OpenAIModels::O3Mini),
"o4-mini" => Some(OpenAIModels::O4Mini),
_ => Some(OpenAIModels::Custom {
name: name.to_string(),
}),
}
}
fn default_max_tokens(&self) -> usize {
match self {
OpenAIModels::Gpt5_5 => 1_050_000,
OpenAIModels::Gpt5_5Pro => 1_050_000,
OpenAIModels::Gpt5_4 => 1_050_000,
OpenAIModels::Gpt5_4Pro => 1_050_000,
OpenAIModels::Gpt5_4Mini => 400_000,
OpenAIModels::Gpt5_4Nano => 400_000,
OpenAIModels::Gpt5_2 => 400_000,
OpenAIModels::Gpt5_2Pro => 400_000,
OpenAIModels::Gpt5_1 => 400_000,
OpenAIModels::Gpt5 => 400_000,
OpenAIModels::Gpt5Mini => 400_000,
OpenAIModels::Gpt5Nano => 400_000,
OpenAIModels::Gpt3_5Turbo => 4096,
OpenAIModels::Gpt3_5Turbo0613 => 4096,
OpenAIModels::Gpt3_5Turbo16k => 16384,
OpenAIModels::Gpt4 => 8192,
OpenAIModels::Gpt4_32k => 32768,
OpenAIModels::TextDavinci003 => 4097,
OpenAIModels::Gpt4Turbo => 128_000,
OpenAIModels::Gpt4TurboPreview => 128_000,
OpenAIModels::Gpt4o => 128_000,
OpenAIModels::Gpt4o20240806 => 128_000,
OpenAIModels::Gpt4oMini => 128_000,
OpenAIModels::Gpt4_1 => 1_047_576,
OpenAIModels::Gpt4_1Mini => 1_047_576,
OpenAIModels::Gpt4_1Nano => 1_047_576,
OpenAIModels::Gpt4_5Preview => 128_000,
OpenAIModels::O1Preview => 128_000,
OpenAIModels::O1Mini => 128_000,
OpenAIModels::O1 => 200_000,
OpenAIModels::O1Pro => 200_000,
OpenAIModels::O3 => 200_000,
OpenAIModels::O3Mini => 200_000,
OpenAIModels::O4Mini => 200_000,
OpenAIModels::Custom { .. } => 128_000,
}
}
fn get_version_endpoint(&self, version: Option<String>) -> String {
let version = version
.map(|version| OpenAiApiEndpoints::from_str(&version))
.unwrap_or_default();
match (version, self) {
#[allow(deprecated)]
(
OpenAiApiEndpoints::OpenAI | OpenAiApiEndpoints::OpenAICompletions,
OpenAIModels::Gpt3_5Turbo
| OpenAIModels::Gpt3_5Turbo0613
| OpenAIModels::Gpt3_5Turbo16k
| OpenAIModels::Gpt4
| OpenAIModels::Gpt4Turbo
| OpenAIModels::Gpt4TurboPreview
| OpenAIModels::Gpt4o
| OpenAIModels::Gpt4o20240806
| OpenAIModels::Gpt4oMini
| OpenAIModels::Gpt4_1
| OpenAIModels::Gpt4_1Mini
| OpenAIModels::Gpt4_1Nano
| OpenAIModels::Gpt4_5Preview
| OpenAIModels::Gpt4_32k
| OpenAIModels::O1Preview
| OpenAIModels::O1Mini
| OpenAIModels::O1
| OpenAIModels::O3
| OpenAIModels::O3Mini
| OpenAIModels::O4Mini
| OpenAIModels::Gpt5
| OpenAIModels::Gpt5Mini
| OpenAIModels::Gpt5Nano
| OpenAIModels::Gpt5_1
| OpenAIModels::Gpt5_2
| OpenAIModels::Gpt5_4
| OpenAIModels::Gpt5_4Mini
| OpenAIModels::Gpt5_4Nano
| OpenAIModels::Gpt5_5
| OpenAIModels::Custom { .. },
) => {
format!(
"{OPENAI_API_URL}/v1/chat/completions",
OPENAI_API_URL = *OPENAI_API_URL
)
}
(
OpenAiApiEndpoints::OpenAIResponses,
OpenAIModels::Gpt3_5Turbo
| OpenAIModels::Gpt3_5Turbo0613
| OpenAIModels::Gpt3_5Turbo16k
| OpenAIModels::Gpt4
| OpenAIModels::Gpt4Turbo
| OpenAIModels::Gpt4TurboPreview
| OpenAIModels::Gpt4o
| OpenAIModels::Gpt4o20240806
| OpenAIModels::Gpt4oMini
| OpenAIModels::Gpt4_1
| OpenAIModels::Gpt4_1Mini
| OpenAIModels::Gpt4_1Nano
| OpenAIModels::Gpt4_5Preview
| OpenAIModels::Gpt4_32k
| OpenAIModels::Gpt5
| OpenAIModels::Gpt5Mini
| OpenAIModels::Gpt5Nano
| OpenAIModels::Gpt5_1
| OpenAIModels::Gpt5_2
| OpenAIModels::Gpt5_2Pro
| OpenAIModels::Gpt5_4
| OpenAIModels::Gpt5_4Pro
| OpenAIModels::Gpt5_4Mini
| OpenAIModels::Gpt5_4Nano
| OpenAIModels::Gpt5_5
| OpenAIModels::Gpt5_5Pro
| OpenAIModels::O1Preview
| OpenAIModels::O1Mini
| OpenAIModels::O1
| OpenAIModels::O1Pro
| OpenAIModels::O3
| OpenAIModels::O3Mini
| OpenAIModels::O4Mini
| OpenAIModels::Custom { .. },
) => format!(
"{OPENAI_API_URL}/v1/responses",
OPENAI_API_URL = *OPENAI_API_URL
),
(
OpenAiApiEndpoints::OpenAI | OpenAiApiEndpoints::OpenAICompletions,
OpenAIModels::O1Pro
| OpenAIModels::Gpt5_2Pro
| OpenAIModels::Gpt5_4Pro
| OpenAIModels::Gpt5_5Pro,
) => {
warn!("[allms] The selected model is not supported in Completions API. Responses API will be used instead.");
format!(
"{OPENAI_API_URL}/v1/responses",
OPENAI_API_URL = *OPENAI_API_URL
)
}
#[allow(deprecated)]
(
OpenAiApiEndpoints::OpenAI
| OpenAiApiEndpoints::OpenAICompletions
| OpenAiApiEndpoints::OpenAIResponses,
OpenAIModels::TextDavinci003,
) => format!(
"{OPENAI_API_URL}/v1/completions",
OPENAI_API_URL = *OPENAI_API_URL
),
#[allow(deprecated)]
(
OpenAiApiEndpoints::Azure { version }
| OpenAiApiEndpoints::AzureCompletions { version },
OpenAIModels::TextDavinci003
| OpenAIModels::Gpt3_5Turbo
| OpenAIModels::Gpt3_5Turbo0613
| OpenAIModels::Gpt3_5Turbo16k
| OpenAIModels::Gpt4
| OpenAIModels::Gpt4Turbo
| OpenAIModels::Gpt4TurboPreview
| OpenAIModels::Gpt4o
| OpenAIModels::Gpt4_1
| OpenAIModels::Gpt4_1Mini
| OpenAIModels::Gpt4_1Nano
| OpenAIModels::Gpt4_5Preview
| OpenAIModels::Gpt4o20240806
| OpenAIModels::Gpt4oMini
| OpenAIModels::Gpt4_32k
| OpenAIModels::Gpt5
| OpenAIModels::Gpt5Mini
| OpenAIModels::Gpt5Nano
| OpenAIModels::Gpt5_1
| OpenAIModels::Gpt5_2
| OpenAIModels::Gpt5_4
| OpenAIModels::Gpt5_4Mini
| OpenAIModels::Gpt5_4Nano
| OpenAIModels::Gpt5_5
| OpenAIModels::O1Preview
| OpenAIModels::O1Mini
| OpenAIModels::O1
| OpenAIModels::O3
| OpenAIModels::O3Mini
| OpenAIModels::O4Mini
| OpenAIModels::Custom { .. },
) => {
format!(
"{}/openai/deployments/{}/chat/completions?api-version={}",
&*OPENAI_API_URL,
self.as_str(),
version
)
}
(
OpenAiApiEndpoints::AzureResponses { version },
OpenAIModels::TextDavinci003
| OpenAIModels::Gpt3_5Turbo
| OpenAIModels::Gpt3_5Turbo0613
| OpenAIModels::Gpt3_5Turbo16k
| OpenAIModels::Gpt4
| OpenAIModels::Gpt4Turbo
| OpenAIModels::Gpt4TurboPreview
| OpenAIModels::Gpt4o
| OpenAIModels::Gpt4_1
| OpenAIModels::Gpt4_1Mini
| OpenAIModels::Gpt4_1Nano
| OpenAIModels::Gpt4_5Preview
| OpenAIModels::Gpt4o20240806
| OpenAIModels::Gpt4oMini
| OpenAIModels::Gpt4_32k
| OpenAIModels::Gpt5
| OpenAIModels::Gpt5Mini
| OpenAIModels::Gpt5Nano
| OpenAIModels::Gpt5_1
| OpenAIModels::Gpt5_2
| OpenAIModels::Gpt5_2Pro
| OpenAIModels::Gpt5_4
| OpenAIModels::Gpt5_4Pro
| OpenAIModels::Gpt5_4Mini
| OpenAIModels::Gpt5_4Nano
| OpenAIModels::Gpt5_5
| OpenAIModels::Gpt5_5Pro
| OpenAIModels::O1Preview
| OpenAIModels::O1Mini
| OpenAIModels::O1
| OpenAIModels::O1Pro
| OpenAIModels::O3
| OpenAIModels::O3Mini
| OpenAIModels::O4Mini
| OpenAIModels::Custom { .. },
) => format!(
"{}/openai/deployments/{}/responses?api-version={}",
&*OPENAI_API_URL,
self.as_str(),
version
),
(
OpenAiApiEndpoints::Azure { version }
| OpenAiApiEndpoints::AzureCompletions { version },
OpenAIModels::O1Pro
| OpenAIModels::Gpt5_2Pro
| OpenAIModels::Gpt5_4Pro
| OpenAIModels::Gpt5_5Pro,
) => {
warn!("[allms] The selected model is not supported in Completions API. Responses API will be used instead.");
format!(
"{}/openai/deployments/{}/responses?api-version={}",
&*OPENAI_API_URL,
self.as_str(),
version
)
}
}
}
fn get_base_instructions(&self, function_call: Option<bool>) -> String {
let function_call = function_call.unwrap_or_else(|| self.function_call_default());
match function_call {
true => OPENAI_FUNCTION_INSTRUCTIONS.to_string(),
false => OPENAI_BASE_INSTRUCTIONS.to_string(),
}
}
fn function_call_default(&self) -> bool {
match self {
OpenAIModels::TextDavinci003
| OpenAIModels::Gpt3_5Turbo
| OpenAIModels::Gpt4_32k
| OpenAIModels::O1Preview
| OpenAIModels::O1
| OpenAIModels::O1Mini
| OpenAIModels::O1Pro
| OpenAIModels::O3
| OpenAIModels::O3Mini
| OpenAIModels::O4Mini => false,
OpenAIModels::Gpt3_5Turbo0613
| OpenAIModels::Gpt3_5Turbo16k
| OpenAIModels::Gpt4
| OpenAIModels::Gpt4Turbo
| OpenAIModels::Gpt4TurboPreview
| OpenAIModels::Gpt4o
| OpenAIModels::Gpt4o20240806
| OpenAIModels::Gpt4oMini
| OpenAIModels::Gpt4_1
| OpenAIModels::Gpt4_1Mini
| OpenAIModels::Gpt4_1Nano
| OpenAIModels::Gpt4_5Preview
| OpenAIModels::Gpt5
| OpenAIModels::Gpt5Mini
| OpenAIModels::Gpt5Nano
| OpenAIModels::Gpt5_1
| OpenAIModels::Gpt5_2
| OpenAIModels::Gpt5_2Pro
| OpenAIModels::Gpt5_4
| OpenAIModels::Gpt5_4Pro
| OpenAIModels::Gpt5_4Mini
| OpenAIModels::Gpt5_4Nano
| OpenAIModels::Gpt5_5
| OpenAIModels::Gpt5_5Pro
| OpenAIModels::Custom { .. } => true,
}
}
#[allow(clippy::too_many_arguments)]
fn get_version_body(
&self,
instructions: &str,
json_schema: &Value,
function_call: bool,
max_tokens: &usize,
temperature: &f32,
version: Option<String>,
tools: Option<&[LLMTools]>,
_thinking_level: Option<&ThinkingLevel>,
) -> serde_json::Value {
let version = version
.map(|version| OpenAiApiEndpoints::from_str(&version))
.unwrap_or_default();
let base_instructions = self.get_base_instructions(Some(function_call));
let user_message_str = format!(
"<instructions>
{instructions}
</instructions>
<output json schema>
{json_schema}
</output json schema>"
);
match (version, self) {
#[allow(deprecated)]
(
OpenAiApiEndpoints::OpenAI
| OpenAiApiEndpoints::OpenAICompletions
| OpenAiApiEndpoints::Azure { .. }
| OpenAiApiEndpoints::AzureCompletions { .. },
OpenAIModels::Gpt3_5Turbo
| OpenAIModels::Gpt3_5Turbo0613
| OpenAIModels::Gpt3_5Turbo16k
| OpenAIModels::Gpt4
| OpenAIModels::Gpt4Turbo
| OpenAIModels::Gpt4TurboPreview
| OpenAIModels::Gpt4o
| OpenAIModels::Gpt4o20240806
| OpenAIModels::Gpt4oMini
| OpenAIModels::Gpt4_1
| OpenAIModels::Gpt4_1Mini
| OpenAIModels::Gpt4_1Nano
| OpenAIModels::Gpt4_5Preview
| OpenAIModels::Gpt4_32k
| OpenAIModels::Gpt5
| OpenAIModels::Gpt5Mini
| OpenAIModels::Gpt5Nano
| OpenAIModels::Gpt5_1
| OpenAIModels::Gpt5_2
| OpenAIModels::Gpt5_4
| OpenAIModels::Gpt5_4Mini
| OpenAIModels::Gpt5_4Nano
| OpenAIModels::Gpt5_5
| OpenAIModels::Custom { .. },
) => {
let system_message = json!({
"role": "system",
"content": base_instructions,
});
let mut json_body = match function_call {
true => {
let user_message = json!({
"role": "user",
"content": instructions,
});
let function = json!({
"name": "analyze_data",
"description": "Use this function to compute the answer based on input data, instructions and your language model. Output should be a fully formed JSON object.",
"parameters": json_schema,
});
let function_call = json!({
"name": "analyze_data"
});
json!({
"model": self.as_str(),
"messages": vec![
system_message,
user_message,
],
"functions": vec![
function,
],
"function_call": function_call,
})
}
false => {
let user_message = json!({
"role": "user",
"content": user_message_str,
});
json!({
"model": self.as_str(),
"messages": vec![
system_message,
user_message,
],
})
}
};
if !self.is_gpt5_model() {
json_body["temperature"] = json!(temperature);
}
json_body
}
#[allow(deprecated)]
(
OpenAiApiEndpoints::OpenAI
| OpenAiApiEndpoints::OpenAICompletions
| OpenAiApiEndpoints::Azure { .. }
| OpenAiApiEndpoints::AzureCompletions { .. },
OpenAIModels::O1Preview
| OpenAIModels::O1Mini
| OpenAIModels::O1
| OpenAIModels::O3
| OpenAIModels::O3Mini
| OpenAIModels::O4Mini,
) => {
let system_message = json!({
"role": "user",
"content": base_instructions,
});
let user_message = json!({
"role": "user",
"content": user_message_str,
});
json!({
"model": self.as_str(),
"messages": vec![
system_message,
user_message,
],
})
}
(
OpenAiApiEndpoints::OpenAIResponses | OpenAiApiEndpoints::AzureResponses { .. },
OpenAIModels::Gpt3_5Turbo
| OpenAIModels::Gpt3_5Turbo0613
| OpenAIModels::Gpt3_5Turbo16k
| OpenAIModels::Gpt4
| OpenAIModels::Gpt4Turbo
| OpenAIModels::Gpt4TurboPreview
| OpenAIModels::Gpt4o
| OpenAIModels::Gpt4o20240806
| OpenAIModels::Gpt4oMini
| OpenAIModels::Gpt4_1
| OpenAIModels::Gpt4_1Mini
| OpenAIModels::Gpt4_1Nano
| OpenAIModels::Gpt4_5Preview
| OpenAIModels::Gpt4_32k
| OpenAIModels::Gpt5
| OpenAIModels::Gpt5Mini
| OpenAIModels::Gpt5Nano
| OpenAIModels::Gpt5_1
| OpenAIModels::Gpt5_2
| OpenAIModels::Gpt5_2Pro
| OpenAIModels::Gpt5_4
| OpenAIModels::Gpt5_4Pro
| OpenAIModels::Gpt5_4Mini
| OpenAIModels::Gpt5_4Nano
| OpenAIModels::Gpt5_5
| OpenAIModels::Gpt5_5Pro
| OpenAIModels::Custom { .. },
) => {
json!({
"model": self.as_str(),
"input": user_message_str,
"instructions": base_instructions,
"max_output_tokens": max_tokens,
"temperature": if self.is_gpt5_model() { json!(null) } else { json!(temperature) },
"tools": tools.map(|tools_inner| tools_inner
.iter()
.filter(|tool| !matches!(tool, LLMTools::OpenAIReasoning(_)))
.filter(|tool| self.get_supported_tools().iter().any(|supported| std::mem::discriminant(*tool) == std::mem::discriminant(supported)))
.filter_map(LLMTools::get_config_json)
.collect::<Vec<Value>>()
),
})
}
(
OpenAiApiEndpoints::OpenAIResponses | OpenAiApiEndpoints::AzureResponses { .. },
OpenAIModels::O1Preview
| OpenAIModels::O1Mini
| OpenAIModels::O1
| OpenAIModels::O1Pro
| OpenAIModels::O3
| OpenAIModels::O3Mini
| OpenAIModels::O4Mini,
)
| (
OpenAiApiEndpoints::OpenAI
| OpenAiApiEndpoints::OpenAICompletions
| OpenAiApiEndpoints::Azure { .. }
| OpenAiApiEndpoints::AzureCompletions { .. },
OpenAIModels::O1Pro | OpenAIModels::Gpt5_2Pro | OpenAIModels::Gpt5_4Pro | OpenAIModels::Gpt5_5Pro,
) => {
let reasoning_opt = tools.and_then(|tools_inner| {
tools_inner.iter().find_map(|tool| {
if let LLMTools::OpenAIReasoning(cfg) = tool {
to_value(cfg).ok()
} else {
None
}
})
});
json!({
"model": self.as_str(),
"input": user_message_str,
"instructions": base_instructions,
"max_output_tokens": max_tokens,
"reasoning": reasoning_opt,
"tools": tools.map(|tools_inner| tools_inner
.iter()
.filter(|tool| self.get_supported_tools().iter().any(|supported| std::mem::discriminant(*tool) == std::mem::discriminant(supported)))
.filter_map(LLMTools::get_config_json)
.collect::<Vec<Value>>()
),
})
}
(_, OpenAIModels::TextDavinci003) => {
json!({
"model": self.as_str(),
"max_tokens": max_tokens,
"temperature": temperature,
"prompt": format!("{base_instructions}{user_message_str}"),
})
}
}
}
async fn call_api(
&self,
api_key: &str,
version: Option<String>,
body: &serde_json::Value,
debug: bool,
_tools: Option<&[LLMTools]>,
) -> Result<String> {
let model_url = self.get_version_endpoint(version);
if debug {
info!("[debug] OpenAI API URL: {:#?}", model_url);
}
let client = Client::new();
let response = client
.post(model_url)
.header(header::CONTENT_TYPE, "application/json")
.bearer_auth(api_key)
.json(&body)
.send()
.await?;
let response_status = response.status();
let response_text = response.text().await?;
if debug {
info!(
"[debug] OpenAI API response: [{}] {:#?}",
&response_status, &response_text
);
}
Ok(response_text)
}
fn get_version_data(
&self,
response_text: &str,
function_call: bool,
version: Option<String>,
) -> Result<String> {
let version = version
.map(|version| OpenAiApiEndpoints::from_str(&version))
.unwrap_or_default();
match (version, self) {
#[allow(deprecated)]
(
OpenAiApiEndpoints::OpenAI
| OpenAiApiEndpoints::OpenAICompletions
| OpenAiApiEndpoints::Azure { .. }
| OpenAiApiEndpoints::AzureCompletions { .. },
OpenAIModels::Gpt3_5Turbo
| OpenAIModels::Gpt3_5Turbo0613
| OpenAIModels::Gpt3_5Turbo16k
| OpenAIModels::Gpt4
| OpenAIModels::Gpt4Turbo
| OpenAIModels::Gpt4TurboPreview
| OpenAIModels::Gpt4o
| OpenAIModels::Gpt4o20240806
| OpenAIModels::Gpt4oMini
| OpenAIModels::Gpt4_1
| OpenAIModels::Gpt4_1Mini
| OpenAIModels::Gpt4_1Nano
| OpenAIModels::Gpt4_5Preview
| OpenAIModels::Gpt4_32k
| OpenAIModels::Gpt5
| OpenAIModels::Gpt5Mini
| OpenAIModels::Gpt5Nano
| OpenAIModels::Gpt5_1
| OpenAIModels::Gpt5_2
| OpenAIModels::Gpt5_4
| OpenAIModels::Gpt5_4Mini
| OpenAIModels::Gpt5_4Nano
| OpenAIModels::Gpt5_5
| OpenAIModels::O1
| OpenAIModels::O1Mini
| OpenAIModels::O1Preview
| OpenAIModels::O3
| OpenAIModels::O3Mini
| OpenAIModels::O4Mini
| OpenAIModels::Custom { .. },
) => {
if let Some(data_str) = self.try_extract_data_field(response_text) {
return Ok(data_str);
}
let chat_response: OpenAPIChatResponse = serde_json::from_str(response_text)?;
match chat_response.choices {
Some(choices) => Ok(choices
.into_iter()
.filter_map(|item| {
match function_call {
true => item.message.function_call.map(|function_call| {
self.sanitize_json_response(&function_call.arguments)
}),
false => item
.message
.content
.map(|content| self.sanitize_json_response(&content)),
}
})
.collect()),
None => Err(anyhow!("Unable to retrieve response from OpenAI Chat API")),
}
}
(
OpenAiApiEndpoints::OpenAIResponses | OpenAiApiEndpoints::AzureResponses { .. },
OpenAIModels::Gpt3_5Turbo
| OpenAIModels::Gpt3_5Turbo0613
| OpenAIModels::Gpt3_5Turbo16k
| OpenAIModels::Gpt4
| OpenAIModels::Gpt4Turbo
| OpenAIModels::Gpt4TurboPreview
| OpenAIModels::Gpt4o
| OpenAIModels::Gpt4o20240806
| OpenAIModels::Gpt4oMini
| OpenAIModels::Gpt4_1
| OpenAIModels::Gpt4_1Mini
| OpenAIModels::Gpt4_1Nano
| OpenAIModels::Gpt4_5Preview
| OpenAIModels::Gpt4_32k
| OpenAIModels::Gpt5
| OpenAIModels::Gpt5Mini
| OpenAIModels::Gpt5Nano
| OpenAIModels::Gpt5_1
| OpenAIModels::Gpt5_2
| OpenAIModels::Gpt5_2Pro
| OpenAIModels::Gpt5_4
| OpenAIModels::Gpt5_4Pro
| OpenAIModels::Gpt5_4Mini
| OpenAIModels::Gpt5_4Nano
| OpenAIModels::Gpt5_5
| OpenAIModels::Gpt5_5Pro
| OpenAIModels::O1Preview
| OpenAIModels::O1Mini
| OpenAIModels::O1
| OpenAIModels::O1Pro
| OpenAIModels::O3
| OpenAIModels::O3Mini
| OpenAIModels::O4Mini
| OpenAIModels::Custom { .. },
)
| (
OpenAiApiEndpoints::OpenAI
| OpenAiApiEndpoints::OpenAICompletions
| OpenAiApiEndpoints::Azure { .. }
| OpenAiApiEndpoints::AzureCompletions { .. },
OpenAIModels::O1Pro | OpenAIModels::Gpt5_2Pro | OpenAIModels::Gpt5_4Pro | OpenAIModels::Gpt5_5Pro,
) => {
let responses_response: OpenAPIResponsesResponse =
serde_json::from_str(response_text)?;
Ok(responses_response
.output
.into_iter()
.filter(|output| {
matches!(output.role, Some(OpenAPIResponsesRole::Assistant))
&& matches!(output.r#type, Some(OpenAPIResponsesOutputType::Message))
})
.flat_map(|output| output.content.unwrap_or_default())
.filter(|content| {
matches!(content.r#type, OpenAPIResponsesContentType::OutputText)
})
.filter_map(|content| content.text)
.map(|text| self.sanitize_json_response(&text))
.collect())
}
(_, OpenAIModels::TextDavinci003) => {
if let Some(data_str) = self.try_extract_data_field(response_text) {
return Ok(data_str);
}
let completions_response: OpenAPICompletionsResponse =
serde_json::from_str(response_text)?;
match completions_response.choices {
Some(choices) => Ok(choices.into_iter().filter_map(|item| item.text).collect()),
None => Err(anyhow!(
"Unable to retrieve response from OpenAI Completions API"
)),
}
}
}
}
fn get_rate_limit(&self) -> RateLimit {
match self {
OpenAIModels::Gpt5_5 => RateLimit {
tpm: 40_000_000,
rpm: 15_000,
},
OpenAIModels::Gpt5_5Pro => RateLimit {
tpm: 30_000_000,
rpm: 10_000,
},
OpenAIModels::Gpt5_4 => RateLimit {
tpm: 40_000_000,
rpm: 15_000,
},
OpenAIModels::Gpt5_4Pro => RateLimit {
tpm: 30_000_000,
rpm: 10_000,
},
OpenAIModels::Gpt5_4Mini => RateLimit {
tpm: 180_000_000,
rpm: 30_000,
},
OpenAIModels::Gpt5_4Nano => RateLimit {
tpm: 180_000_000,
rpm: 30_000,
},
OpenAIModels::Gpt5_2 => RateLimit {
tpm: 40_000_000,
rpm: 15_000,
},
OpenAIModels::Gpt5_2Pro => RateLimit {
tpm: 30_000_000,
rpm: 10_000,
},
OpenAIModels::Gpt5_1 => RateLimit {
tpm: 40_000_000,
rpm: 15_000,
},
OpenAIModels::Gpt5 => RateLimit {
tpm: 40_000_000,
rpm: 15_000,
},
OpenAIModels::Gpt5Mini => RateLimit {
tpm: 180_000_000,
rpm: 30_000,
},
OpenAIModels::Gpt5Nano => RateLimit {
tpm: 180_000_000,
rpm: 30_000,
},
OpenAIModels::Gpt3_5Turbo => RateLimit {
tpm: 50_000_000,
rpm: 10_000,
},
OpenAIModels::Gpt3_5Turbo0613 => RateLimit {
tpm: 2_000_000,
rpm: 10_000,
},
OpenAIModels::Gpt3_5Turbo16k => RateLimit {
tpm: 2_000_000,
rpm: 10_000,
},
OpenAIModels::Gpt4 => RateLimit {
tpm: 1_000_000,
rpm: 10_000,
},
OpenAIModels::Gpt4Turbo => RateLimit {
tpm: 2_000_000,
rpm: 10_000,
},
OpenAIModels::Gpt4TurboPreview => RateLimit {
tpm: 2_000_000,
rpm: 10_000,
},
OpenAIModels::Gpt4_32k => RateLimit {
tpm: 300_000,
rpm: 10_000,
},
OpenAIModels::Gpt4o | OpenAIModels::Custom { .. } => RateLimit {
tpm: 150_000_000,
rpm: 50_000,
},
OpenAIModels::Gpt4o20240806 => RateLimit {
tpm: 150_000_000,
rpm: 50_000,
},
OpenAIModels::Gpt4oMini => RateLimit {
tpm: 150_000_000,
rpm: 30_000,
},
OpenAIModels::Gpt4_1 => RateLimit {
tpm: 30_000_000,
rpm: 10_000,
},
OpenAIModels::Gpt4_1Mini => RateLimit {
tpm: 150_000_000,
rpm: 30_000,
},
OpenAIModels::Gpt4_1Nano => RateLimit {
tpm: 150_000_000,
rpm: 30_000,
},
OpenAIModels::Gpt4_5Preview => RateLimit {
tpm: 2_000_000,
rpm: 10_000,
},
OpenAIModels::O1Preview => RateLimit {
tpm: 30_000_000,
rpm: 10_000,
},
OpenAIModels::O1Mini => RateLimit {
tpm: 150_000_000,
rpm: 30_000,
},
OpenAIModels::O1 => RateLimit {
tpm: 30_000_000,
rpm: 10_000,
},
OpenAIModels::O1Pro => RateLimit {
tpm: 30_000_000,
rpm: 10_000,
},
OpenAIModels::O3 => RateLimit {
tpm: 30_000_000,
rpm: 10_000,
},
OpenAIModels::O3Mini => RateLimit {
tpm: 150_000_000,
rpm: 30_000,
},
OpenAIModels::O4Mini => RateLimit {
tpm: 150_000_000,
rpm: 30_000,
},
OpenAIModels::TextDavinci003 => RateLimit {
tpm: 250_000,
rpm: 3_000,
},
}
}
fn get_normalized_temperature(&self, relative_temp: u32) -> f32 {
let min = 0u32;
let max = 2u32;
map_to_range(min, max, relative_temp)
}
fn sanitize_json_response(&self, json_response: &str) -> String {
let without_wrapper = remove_json_wrapper(json_response);
remove_schema_wrappers(&without_wrapper)
}
}
impl OpenAIModels {
fn try_extract_data_field(&self, response_text: &str) -> Option<String> {
let value: Value = serde_json::from_str(response_text).ok()?;
let data = value.get("data")?;
Some(match data {
Value::String(s) => s.clone(),
other => serde_json::to_string(other).unwrap_or_default(),
})
}
pub fn tools_support(&self) -> bool {
matches!(
self,
OpenAIModels::Gpt3_5Turbo
| OpenAIModels::Gpt4Turbo
| OpenAIModels::Gpt4TurboPreview
| OpenAIModels::Gpt4o
| OpenAIModels::Gpt4o20240806
| OpenAIModels::Gpt4oMini
| OpenAIModels::Gpt4_1
| OpenAIModels::Gpt4_1Mini
| OpenAIModels::Gpt4_1Nano
| OpenAIModels::Gpt4_5Preview
| OpenAIModels::Gpt5
| OpenAIModels::Gpt5Mini
| OpenAIModels::Gpt5Nano
| OpenAIModels::Gpt5_4
| OpenAIModels::Gpt5_4Pro
| OpenAIModels::Gpt5_4Mini
| OpenAIModels::Gpt5_4Nano
| OpenAIModels::Gpt5_5Pro
| OpenAIModels::Gpt5_5
| OpenAIModels::Custom { .. }
)
}
pub fn structured_output_support(&self) -> bool {
matches!(
self,
OpenAIModels::Gpt4o
| OpenAIModels::Gpt4o20240806
| OpenAIModels::Gpt4oMini
| OpenAIModels::Gpt4_1
| OpenAIModels::Gpt4_1Mini
| OpenAIModels::Gpt4_1Nano
| OpenAIModels::Gpt4_5Preview
| OpenAIModels::Gpt5
| OpenAIModels::Gpt5Mini
| OpenAIModels::Gpt5Nano
| OpenAIModels::Gpt5_1
| OpenAIModels::Gpt5_2
| OpenAIModels::Gpt5_4
| OpenAIModels::Gpt5_4Mini
| OpenAIModels::Gpt5_4Nano
| OpenAIModels::Gpt5_5Pro
| OpenAIModels::Gpt5_5
| OpenAIModels::Custom { .. }
)
}
pub fn assistants_support(&self) -> bool {
!matches!(
self,
OpenAIModels::O1Preview
| OpenAIModels::O1Mini
| OpenAIModels::O1
| OpenAIModels::O1Pro
| OpenAIModels::O3
| OpenAIModels::O3Mini
| OpenAIModels::O4Mini
| OpenAIModels::Gpt5_1
| OpenAIModels::Gpt5_2
| OpenAIModels::Gpt5_2Pro
| OpenAIModels::Gpt5_4
| OpenAIModels::Gpt5_4Pro
| OpenAIModels::Gpt5_4Mini
| OpenAIModels::Gpt5_4Nano
| OpenAIModels::Gpt5_5Pro
| OpenAIModels::Gpt5_5
)
}
pub fn is_gpt5_model(&self) -> bool {
matches!(
self,
OpenAIModels::Gpt5
| OpenAIModels::Gpt5Mini
| OpenAIModels::Gpt5Nano
| OpenAIModels::Gpt5_1
| OpenAIModels::Gpt5_2
| OpenAIModels::Gpt5_2Pro
| OpenAIModels::Gpt5_4
| OpenAIModels::Gpt5_4Pro
| OpenAIModels::Gpt5_4Mini
| OpenAIModels::Gpt5_4Nano
| OpenAIModels::Gpt5_5Pro
| OpenAIModels::Gpt5_5
)
}
pub fn get_supported_tools(&self) -> Vec<LLMTools> {
match self {
OpenAIModels::O1Preview
| OpenAIModels::O1Mini
| OpenAIModels::O1
| OpenAIModels::O1Pro
| OpenAIModels::O3
| OpenAIModels::O3Mini
| OpenAIModels::O4Mini => vec![
LLMTools::OpenAIFileSearch(OpenAIFileSearchConfig::new(vec![])),
LLMTools::OpenAICodeInterpreter(OpenAICodeInterpreterConfig::new()),
],
OpenAIModels::Gpt5_4Nano => vec![
LLMTools::OpenAIFileSearch(OpenAIFileSearchConfig::new(vec![])),
LLMTools::OpenAICodeInterpreter(OpenAICodeInterpreterConfig::new()),
LLMTools::OpenAIWebSearch(OpenAIWebSearchConfig::new()),
],
OpenAIModels::Gpt5_2 => vec![
LLMTools::OpenAIFileSearch(OpenAIFileSearchConfig::new(vec![])),
LLMTools::OpenAICodeInterpreter(OpenAICodeInterpreterConfig::new()),
LLMTools::OpenAIWebSearch(OpenAIWebSearchConfig::new()),
],
OpenAIModels::Gpt5_2Pro => vec![
LLMTools::OpenAIFileSearch(OpenAIFileSearchConfig::new(vec![])),
LLMTools::OpenAIWebSearch(OpenAIWebSearchConfig::new()),
],
OpenAIModels::Gpt5_4Pro | OpenAIModels::Gpt5_5Pro => vec![
LLMTools::OpenAIFileSearch(OpenAIFileSearchConfig::new(vec![])),
LLMTools::OpenAIWebSearch(OpenAIWebSearchConfig::new()),
LLMTools::OpenAIComputerUse(OpenAIComputerUseConfig::new(
1920,
1080,
"default".to_string(),
)),
],
_ => vec![
LLMTools::OpenAIFileSearch(OpenAIFileSearchConfig::new(vec![])),
LLMTools::OpenAICodeInterpreter(OpenAICodeInterpreterConfig::new()),
LLMTools::OpenAIWebSearch(OpenAIWebSearchConfig::new()),
LLMTools::OpenAIComputerUse(OpenAIComputerUseConfig::new(
1920,
1080,
"default".to_string(),
)),
],
}
}
}
#[cfg(test)]
mod tests {
use crate::llm_models::llm_model::LLMModel;
use crate::llm_models::OpenAIModels;
#[test]
fn test_gpt3_5turbo_max_requests() {
let model = OpenAIModels::Gpt3_5Turbo;
let max_requests = model.get_max_requests();
let expected_max = std::cmp::min(10_000, 50_000_000 / ((4096_f64 * 0.5).ceil() as usize));
assert_eq!(max_requests, expected_max);
}
#[test]
fn test_gpt3_5turbo0613_max_requests() {
let model = OpenAIModels::Gpt3_5Turbo0613;
let max_requests = model.get_max_requests();
let expected_max = std::cmp::min(10_000, 2_000_000 / ((4096_f64 * 0.5).ceil() as usize));
assert_eq!(max_requests, expected_max);
}
#[test]
fn test_gpt3_5turbo16k_max_requests() {
let model = OpenAIModels::Gpt3_5Turbo16k;
let max_requests = model.get_max_requests();
let expected_max = std::cmp::min(10_000, 2_000_000 / ((16384_f64 * 0.5).ceil() as usize));
assert_eq!(max_requests, expected_max);
}
#[test]
fn test_gpt4_max_requests() {
let model = OpenAIModels::Gpt4;
let max_requests = model.get_max_requests();
let expected_max = std::cmp::min(10_000, 1_000_000 / ((8192_f64 * 0.5).ceil() as usize));
assert_eq!(max_requests, expected_max);
}
#[test]
fn test_try_from_str_standard_models() {
assert_eq!(
OpenAIModels::try_from_str("gpt-3.5-turbo"),
Some(OpenAIModels::Gpt3_5Turbo)
);
assert_eq!(
OpenAIModels::try_from_str("gpt-3.5-turbo-0613"),
Some(OpenAIModels::Gpt3_5Turbo0613)
);
assert_eq!(
OpenAIModels::try_from_str("gpt-3.5-turbo-16k"),
Some(OpenAIModels::Gpt3_5Turbo16k)
);
assert_eq!(
OpenAIModels::try_from_str("gpt-4"),
Some(OpenAIModels::Gpt4)
);
assert_eq!(
OpenAIModels::try_from_str("gpt-4-32k"),
Some(OpenAIModels::Gpt4_32k)
);
assert_eq!(
OpenAIModels::try_from_str("text-davinci-003"),
Some(OpenAIModels::TextDavinci003)
);
assert_eq!(
OpenAIModels::try_from_str("gpt-4-turbo"),
Some(OpenAIModels::Gpt4Turbo)
);
assert_eq!(
OpenAIModels::try_from_str("gpt-4-turbo-preview"),
Some(OpenAIModels::Gpt4TurboPreview)
);
assert_eq!(
OpenAIModels::try_from_str("gpt-4o"),
Some(OpenAIModels::Gpt4o)
);
assert_eq!(
OpenAIModels::try_from_str("gpt-4o-2024-08-06"),
Some(OpenAIModels::Gpt4o20240806)
);
assert_eq!(
OpenAIModels::try_from_str("gpt-4o-mini"),
Some(OpenAIModels::Gpt4oMini)
);
assert_eq!(
OpenAIModels::try_from_str("gpt-4.5-preview"),
Some(OpenAIModels::Gpt4_5Preview)
);
assert_eq!(
OpenAIModels::try_from_str("gpt-5.4-mini"),
Some(OpenAIModels::Gpt5_4Mini)
);
assert_eq!(
OpenAIModels::try_from_str("gpt-5.4-nano"),
Some(OpenAIModels::Gpt5_4Nano)
);
}
#[test]
fn test_try_from_str_case_insensitivity() {
assert_eq!(
OpenAIModels::try_from_str("GPT-4"),
Some(OpenAIModels::Gpt4)
);
assert_eq!(
OpenAIModels::try_from_str("GPT-4o-MiNI"),
Some(OpenAIModels::Gpt4oMini)
);
assert_eq!(
OpenAIModels::try_from_str("GPT-4.5-pREVIEW"),
Some(OpenAIModels::Gpt4_5Preview)
);
}
#[test]
fn test_try_from_str_custom_model() {
assert_eq!(
OpenAIModels::try_from_str("my-custom-model"),
Some(OpenAIModels::Custom {
name: "my-custom-model".to_string()
})
);
assert_eq!(
OpenAIModels::try_from_str("AnotherModel"),
Some(OpenAIModels::Custom {
name: "AnotherModel".to_string()
})
);
}
}