use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ChatOptions {
pub temperature: Option<f64>,
pub max_tokens: Option<u32>,
pub top_p: Option<f64>,
pub stop_sequences: Vec<String>,
pub tool_choice: Option<ToolChoice>,
pub reasoning_effort: Option<ReasoningEffort>,
}
impl ChatOptions {
pub fn with_temperature(mut self, temperature: f64) -> Self {
self.temperature = Some(temperature);
self
}
pub fn with_max_tokens(mut self, max_tokens: u32) -> Self {
self.max_tokens = Some(max_tokens);
self
}
pub fn with_top_p(mut self, top_p: f64) -> Self {
self.top_p = Some(top_p);
self
}
pub fn with_stop_sequences(mut self, stop_sequences: Vec<String>) -> Self {
self.stop_sequences = stop_sequences;
self
}
pub fn with_tool_choice(mut self, tool_choice: ToolChoice) -> Self {
self.tool_choice = Some(tool_choice);
self
}
pub fn with_reasoning_effort(mut self, reasoning_effort: ReasoningEffort) -> Self {
self.reasoning_effort = Some(reasoning_effort);
self
}
pub(crate) fn to_genai(&self) -> genai::chat::ChatOptions {
genai::chat::ChatOptions {
temperature: self.temperature,
max_tokens: self.max_tokens,
top_p: self.top_p,
stop_sequences: self.stop_sequences.clone(),
tool_choice: self.tool_choice.clone().map(ToolChoice::into_genai),
reasoning_effort: self.reasoning_effort.map(ReasoningEffort::into_genai),
..Default::default()
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ToolChoice {
Auto,
None,
Required,
Tool(String),
}
impl ToolChoice {
pub(crate) fn into_genai(self) -> genai::chat::ToolChoice {
let choice = self;
match choice {
ToolChoice::Auto => genai::chat::ToolChoice::Auto,
ToolChoice::None => genai::chat::ToolChoice::None,
ToolChoice::Required => genai::chat::ToolChoice::Required,
ToolChoice::Tool(name) => genai::chat::ToolChoice::Tool { name },
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ReasoningEffort {
Low,
Medium,
High,
Budget(u32),
}
impl ReasoningEffort {
pub(crate) fn into_genai(self) -> genai::chat::ReasoningEffort {
let effort = self;
match effort {
ReasoningEffort::Low => genai::chat::ReasoningEffort::Low,
ReasoningEffort::Medium => genai::chat::ReasoningEffort::Medium,
ReasoningEffort::High => genai::chat::ReasoningEffort::High,
ReasoningEffort::Budget(tokens) => genai::chat::ReasoningEffort::Budget(tokens),
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Usage {
pub input_tokens: u64,
pub cached_input_tokens: u64,
pub cache_write_tokens: u64,
pub output_tokens: u64,
pub reasoning_tokens: u64,
pub total_tokens: u64,
}
impl std::ops::AddAssign for Usage {
fn add_assign(&mut self, other: Self) {
self.input_tokens += other.input_tokens;
self.cached_input_tokens += other.cached_input_tokens;
self.cache_write_tokens += other.cache_write_tokens;
self.output_tokens += other.output_tokens;
self.reasoning_tokens += other.reasoning_tokens;
self.total_tokens += other.total_tokens;
}
}
impl Usage {
pub(crate) fn from_genai(usage: &genai::chat::Usage) -> Self {
fn count(value: Option<i32>) -> u64 {
value.map_or(0, |v| v.max(0) as u64)
}
let prompt = usage.prompt_tokens_details.as_ref();
let completion = usage.completion_tokens_details.as_ref();
Self {
input_tokens: count(usage.prompt_tokens),
cached_input_tokens: count(prompt.and_then(|d| d.cached_tokens)),
cache_write_tokens: count(prompt.and_then(|d| d.cache_creation_tokens)),
output_tokens: count(usage.completion_tokens),
reasoning_tokens: count(completion.and_then(|d| d.reasoning_tokens)),
total_tokens: count(usage.total_tokens),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum StopReason {
Completed,
MaxTokens,
ToolCall,
ContentFilter,
StopSequence,
Other(String),
}
impl StopReason {
pub fn is_max_tokens(&self) -> bool {
matches!(self, Self::MaxTokens)
}
}
impl StopReason {
pub(crate) fn from_genai(reason: genai::chat::StopReason) -> Self {
use genai::chat::StopReason as G;
match reason {
G::Completed(_) => Self::Completed,
G::MaxTokens(_) => Self::MaxTokens,
G::ToolCall(_) => Self::ToolCall,
G::ContentFilter(_) => Self::ContentFilter,
G::StopSequence(_) => Self::StopSequence,
G::Other(raw) => Self::Other(raw),
}
}
}