use crate::Headers;
use crate::chat::chat_req_response_format::ChatResponseFormat;
use crate::{Error, Result};
use serde::{Deserialize, Serialize};
use std::ops::Deref;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ChatOptions {
pub temperature: Option<f64>,
pub max_tokens: Option<u32>,
pub top_p: Option<f64>,
pub stop_sequences: Vec<String>,
pub capture_usage: Option<bool>,
pub capture_content: Option<bool>,
pub capture_reasoning_content: Option<bool>,
pub capture_tool_calls: Option<bool>,
pub capture_raw_body: Option<bool>,
pub response_format: Option<ChatResponseFormat>,
pub normalize_reasoning_content: Option<bool>,
pub reasoning_effort: Option<ReasoningEffort>,
pub verbosity: Option<Verbosity>,
pub seed: Option<u64>,
pub service_tier: Option<ServiceTier>,
pub extra_headers: Option<Headers>,
}
impl ChatOptions {
pub fn with_temperature(mut self, value: f64) -> Self {
self.temperature = Some(value);
self
}
pub fn with_max_tokens(mut self, value: u32) -> Self {
self.max_tokens = Some(value);
self
}
pub fn with_top_p(mut self, value: f64) -> Self {
self.top_p = Some(value);
self
}
pub fn with_capture_usage(mut self, value: bool) -> Self {
self.capture_usage = Some(value);
self
}
pub fn with_capture_content(mut self, value: bool) -> Self {
self.capture_content = Some(value);
self
}
pub fn with_capture_reasoning_content(mut self, value: bool) -> Self {
self.capture_reasoning_content = Some(value);
self
}
pub fn with_capture_tool_calls(mut self, value: bool) -> Self {
self.capture_tool_calls = Some(value);
self
}
pub fn with_capture_raw_body(mut self, value: bool) -> Self {
self.capture_raw_body = Some(value);
self
}
pub fn with_stop_sequences(mut self, values: Vec<String>) -> Self {
self.stop_sequences = values;
self
}
pub fn with_normalize_reasoning_content(mut self, value: bool) -> Self {
self.normalize_reasoning_content = Some(value);
self
}
pub fn with_response_format(mut self, res_format: impl Into<ChatResponseFormat>) -> Self {
self.response_format = Some(res_format.into());
self
}
pub fn with_reasoning_effort(mut self, value: ReasoningEffort) -> Self {
self.reasoning_effort = Some(value);
self
}
pub fn with_verbosity(mut self, value: Verbosity) -> Self {
self.verbosity = Some(value);
self
}
pub fn with_seed(mut self, value: u64) -> Self {
self.seed = Some(value);
self
}
pub fn with_service_tier(mut self, value: ServiceTier) -> Self {
self.service_tier = Some(value);
self
}
pub fn with_extra_headers(mut self, headers: impl Into<Headers>) -> Self {
self.extra_headers = Some(headers.into());
self
}
#[deprecated(note = "Use with_response_format(ChatResponseFormat::JsonMode)")]
pub fn with_json_mode(mut self, value: bool) -> Self {
if value {
self.response_format = Some(ChatResponseFormat::JsonMode);
}
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ReasoningEffort {
None,
Low,
Medium,
High,
Budget(u32),
Minimal,
}
impl ReasoningEffort {
pub fn variant_name(&self) -> &'static str {
match self {
ReasoningEffort::None => "none",
ReasoningEffort::Low => "low",
ReasoningEffort::Medium => "medium",
ReasoningEffort::High => "high",
ReasoningEffort::Budget(_) => "budget",
ReasoningEffort::Minimal => "minimal",
}
}
pub fn as_keyword(&self) -> Option<&'static str> {
match self {
ReasoningEffort::None => Some("none"),
ReasoningEffort::Low => Some("low"),
ReasoningEffort::Medium => Some("medium"),
ReasoningEffort::High => Some("high"),
ReasoningEffort::Budget(_) => None,
ReasoningEffort::Minimal => Some("minimal"),
}
}
pub fn from_keyword(name: &str) -> Option<Self> {
match name {
"none" => Some(ReasoningEffort::None),
"low" => Some(ReasoningEffort::Low),
"medium" => Some(ReasoningEffort::Medium),
"high" => Some(ReasoningEffort::High),
"minimal" => Some(ReasoningEffort::Minimal),
_ => None,
}
}
pub fn from_model_name(model_name: &str) -> (Option<Self>, &str) {
if let Some((prefix, last)) = model_name.rsplit_once('-')
&& let Some(effort) = ReasoningEffort::from_keyword(last)
{
return (Some(effort), prefix);
}
(None, model_name)
}
}
impl std::fmt::Display for ReasoningEffort {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ReasoningEffort::None => write!(f, "none"),
ReasoningEffort::Low => write!(f, "low"),
ReasoningEffort::Medium => write!(f, "medium"),
ReasoningEffort::High => write!(f, "high"),
ReasoningEffort::Budget(n) => write!(f, "{n}"),
ReasoningEffort::Minimal => write!(f, "minimal"),
}
}
}
impl std::str::FromStr for ReasoningEffort {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::from_keyword(s)
.or_else(|| s.parse::<u32>().ok().map(Self::Budget))
.ok_or(Error::ReasoningParsingError { actual: s.to_string() })
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Verbosity {
Low,
Medium,
High,
}
impl Verbosity {
pub fn variant_name(&self) -> &'static str {
match self {
Verbosity::Low => "low",
Verbosity::Medium => "medium",
Verbosity::High => "high",
}
}
pub fn as_keyword(&self) -> Option<&'static str> {
match self {
Verbosity::Low => Some("low"),
Verbosity::Medium => Some("medium"),
Verbosity::High => Some("high"),
}
}
pub fn from_keyword(name: &str) -> Option<Self> {
match name {
"low" => Some(Verbosity::Low),
"medium" => Some(Verbosity::Medium),
"high" => Some(Verbosity::High),
_ => None,
}
}
pub fn from_model_name(model_name: &str) -> (Option<Self>, &str) {
if let Some((prefix, last)) = model_name.rsplit_once('-')
&& let Some(effort) = Verbosity::from_keyword(last)
{
return (Some(effort), prefix);
}
(None, model_name)
}
}
impl std::fmt::Display for Verbosity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Verbosity::Low => write!(f, "low"),
Verbosity::Medium => write!(f, "medium"),
Verbosity::High => write!(f, "high"),
}
}
}
impl std::str::FromStr for Verbosity {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::from_keyword(s).ok_or(Error::VerbosityParsing { actual: s.to_string() })
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServiceTier {
Flex,
Auto,
Default,
}
impl ServiceTier {
pub fn variant_name(&self) -> &'static str {
match self {
ServiceTier::Flex => "flex",
ServiceTier::Auto => "auto",
ServiceTier::Default => "default",
}
}
pub fn as_keyword(&self) -> Option<&'static str> {
match self {
ServiceTier::Flex => Some("flex"),
ServiceTier::Auto => Some("auto"),
ServiceTier::Default => Some("default"),
}
}
pub fn from_keyword(name: &str) -> Option<Self> {
match name {
"flex" => Some(ServiceTier::Flex),
"auto" => Some(ServiceTier::Auto),
"default" => Some(ServiceTier::Default),
_ => None,
}
}
}
impl std::fmt::Display for ServiceTier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ServiceTier::Flex => write!(f, "flex"),
ServiceTier::Auto => write!(f, "auto"),
ServiceTier::Default => write!(f, "default"),
}
}
}
impl std::str::FromStr for ServiceTier {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::from_keyword(s).ok_or(Error::ServiceTierParsing { actual: s.to_string() })
}
}
#[derive(Default, Clone)]
pub(crate) struct ChatOptionsSet<'a, 'b> {
client: Option<&'a ChatOptions>,
chat: Option<&'b ChatOptions>,
}
impl<'a, 'b> ChatOptionsSet<'a, 'b> {
pub fn with_client_options(mut self, options: Option<&'a ChatOptions>) -> Self {
self.client = options;
self
}
pub fn with_chat_options(mut self, options: Option<&'b ChatOptions>) -> Self {
self.chat = options;
self
}
}
impl ChatOptionsSet<'_, '_> {
pub fn temperature(&self) -> Option<f64> {
self.chat
.and_then(|chat| chat.temperature)
.or_else(|| self.client.and_then(|client| client.temperature))
}
pub fn max_tokens(&self) -> Option<u32> {
self.chat
.and_then(|chat| chat.max_tokens)
.or_else(|| self.client.and_then(|client| client.max_tokens))
}
pub fn top_p(&self) -> Option<f64> {
self.chat
.and_then(|chat| chat.top_p)
.or_else(|| self.client.and_then(|client| client.top_p))
}
pub fn stop_sequences(&self) -> &[String] {
self.chat
.map(|chat| chat.stop_sequences.deref())
.or_else(|| self.client.map(|client| client.stop_sequences.deref()))
.unwrap_or(&[])
}
pub fn capture_usage(&self) -> Option<bool> {
self.chat
.and_then(|chat| chat.capture_usage)
.or_else(|| self.client.and_then(|client| client.capture_usage))
}
pub fn capture_content(&self) -> Option<bool> {
self.chat
.and_then(|chat| chat.capture_content)
.or_else(|| self.client.and_then(|client| client.capture_content))
}
pub fn capture_reasoning_content(&self) -> Option<bool> {
self.chat
.and_then(|chat| chat.capture_reasoning_content)
.or_else(|| self.client.and_then(|client| client.capture_reasoning_content))
}
pub fn capture_tool_calls(&self) -> Option<bool> {
self.chat
.and_then(|chat| chat.capture_tool_calls)
.or_else(|| self.client.and_then(|client| client.capture_tool_calls))
}
pub fn capture_raw_body(&self) -> Option<bool> {
self.chat
.and_then(|chat| chat.capture_raw_body)
.or_else(|| self.client.and_then(|client| client.capture_raw_body))
}
pub fn response_format(&self) -> Option<&ChatResponseFormat> {
self.chat
.and_then(|chat| chat.response_format.as_ref())
.or_else(|| self.client.and_then(|client| client.response_format.as_ref()))
}
pub fn normalize_reasoning_content(&self) -> Option<bool> {
self.chat
.and_then(|chat| chat.normalize_reasoning_content)
.or_else(|| self.client.and_then(|client| client.normalize_reasoning_content))
}
pub fn reasoning_effort(&self) -> Option<&ReasoningEffort> {
self.chat
.and_then(|chat| chat.reasoning_effort.as_ref())
.or_else(|| self.client.and_then(|client| client.reasoning_effort.as_ref()))
}
pub fn verbosity(&self) -> Option<&Verbosity> {
self.chat
.and_then(|chat| chat.verbosity.as_ref())
.or_else(|| self.client.and_then(|client| client.verbosity.as_ref()))
}
pub fn seed(&self) -> Option<u64> {
self.chat
.and_then(|chat| chat.seed)
.or_else(|| self.client.and_then(|client| client.seed))
}
pub fn service_tier(&self) -> Option<&ServiceTier> {
self.chat
.and_then(|chat| chat.service_tier.as_ref())
.or_else(|| self.client.and_then(|client| client.service_tier.as_ref()))
}
#[allow(unused)]
pub fn extra_headers(&self) -> Option<&Headers> {
self.chat
.and_then(|chat| chat.extra_headers.as_ref())
.or_else(|| self.client.and_then(|client| client.extra_headers.as_ref()))
}
#[deprecated(note = "Use .response_format()")]
#[allow(unused)]
pub fn json_mode(&self) -> Option<bool> {
match self.response_format() {
Some(ChatResponseFormat::JsonMode) => Some(true),
None => None,
_ => Some(false),
}
}
}