use crate::retry_api::RetryOptions;
use crate::{LlmBuilder, LlmError};
#[derive(Debug, Clone)]
pub struct GeminiBuilder {
pub(crate) base: LlmBuilder,
api_key: Option<String>,
base_url: Option<String>,
model: Option<String>,
temperature: Option<f32>,
max_tokens: Option<i32>,
top_p: Option<f32>,
top_k: Option<i32>,
stop_sequences: Option<Vec<String>>,
candidate_count: Option<i32>,
safety_settings: Option<Vec<crate::providers::gemini::SafetySetting>>,
json_schema: Option<serde_json::Value>,
thinking_config: Option<crate::providers::gemini::ThinkingConfig>,
tracing_config: Option<crate::tracing::TracingConfig>,
retry_options: Option<RetryOptions>,
}
impl GeminiBuilder {
pub const fn new(base: LlmBuilder) -> Self {
Self {
base,
api_key: None,
base_url: None,
model: None,
temperature: None,
max_tokens: None,
top_p: None,
top_k: None,
stop_sequences: None,
candidate_count: None,
safety_settings: None,
json_schema: None,
thinking_config: None,
tracing_config: None,
retry_options: None,
}
}
pub fn api_key<S: Into<String>>(mut self, api_key: S) -> Self {
self.api_key = Some(api_key.into());
self
}
pub fn base_url<S: Into<String>>(mut self, base_url: S) -> Self {
self.base_url = Some(base_url.into());
self
}
pub fn model<S: Into<String>>(mut self, model: S) -> Self {
self.model = Some(model.into());
self
}
pub const fn temperature(mut self, temperature: f32) -> Self {
self.temperature = Some(temperature);
self
}
pub const fn max_tokens(mut self, max_tokens: i32) -> Self {
self.max_tokens = Some(max_tokens);
self
}
pub const fn top_p(mut self, top_p: f32) -> Self {
self.top_p = Some(top_p);
self
}
pub const fn top_k(mut self, top_k: i32) -> Self {
self.top_k = Some(top_k);
self
}
pub fn stop_sequences(mut self, sequences: Vec<String>) -> Self {
self.stop_sequences = Some(sequences);
self
}
pub const fn candidate_count(mut self, count: i32) -> Self {
self.candidate_count = Some(count);
self
}
pub fn safety_settings(
mut self,
settings: Vec<crate::providers::gemini::SafetySetting>,
) -> Self {
self.safety_settings = Some(settings);
self
}
pub fn json_schema(mut self, schema: serde_json::Value) -> Self {
self.json_schema = Some(schema);
self
}
pub const fn thinking_budget(mut self, budget: i32) -> Self {
if self.thinking_config.is_none() {
self.thinking_config = Some(crate::providers::gemini::ThinkingConfig::new());
}
if let Some(ref mut config) = self.thinking_config {
config.thinking_budget = Some(budget);
if budget != 0 {
config.include_thoughts = Some(true);
} else {
config.include_thoughts = Some(false);
}
}
self
}
pub const fn thought_summaries(mut self, include: bool) -> Self {
if self.thinking_config.is_none() {
self.thinking_config = Some(crate::providers::gemini::ThinkingConfig::new());
}
if let Some(ref mut config) = self.thinking_config {
config.include_thoughts = Some(include);
}
self
}
pub const fn thinking(mut self) -> Self {
self.thinking_config = Some(crate::providers::gemini::ThinkingConfig::dynamic());
self
}
pub const fn reasoning(mut self, enable: bool) -> Self {
if enable {
self.thinking_config = Some(crate::providers::gemini::ThinkingConfig::dynamic());
} else {
self.thinking_config = Some(crate::providers::gemini::ThinkingConfig::disabled());
}
self
}
pub const fn reasoning_budget(self, budget: i32) -> Self {
self.thinking_budget(budget)
}
pub const fn disable_thinking(mut self) -> Self {
self.thinking_config = Some(crate::providers::gemini::ThinkingConfig::disabled());
self
}
pub fn tracing(mut self, config: crate::tracing::TracingConfig) -> Self {
self.tracing_config = Some(config);
self
}
pub fn debug_tracing(self) -> Self {
self.tracing(crate::tracing::TracingConfig::development())
}
pub fn minimal_tracing(self) -> Self {
self.tracing(crate::tracing::TracingConfig::minimal())
}
pub fn json_tracing(self) -> Self {
self.tracing(crate::tracing::TracingConfig::json_production())
}
pub fn pretty_json(mut self, pretty: bool) -> Self {
let config = self
.tracing_config
.take()
.unwrap_or_else(crate::tracing::TracingConfig::development)
.with_pretty_json(pretty);
self.tracing_config = Some(config);
self
}
pub fn mask_sensitive_values(mut self, mask: bool) -> Self {
let config = self
.tracing_config
.take()
.unwrap_or_else(crate::tracing::TracingConfig::development)
.with_mask_sensitive_values(mask);
self.tracing_config = Some(config);
self
}
pub fn with_retry(mut self, options: RetryOptions) -> Self {
self.retry_options = Some(options);
self
}
pub async fn build(self) -> Result<crate::providers::gemini::GeminiClient, LlmError> {
let api_key = self.api_key.ok_or_else(|| {
LlmError::ConfigurationError("API key is required for Gemini".to_string())
})?;
let _tracing_guard = if let Some(ref tracing_config) = self.tracing_config {
crate::tracing::init_tracing(tracing_config.clone())?
} else {
None
};
let mut config = crate::providers::gemini::GeminiConfig::new(api_key);
if let Some(base_url) = self.base_url {
config = config.with_base_url(base_url);
}
if let Some(thinking_config) = &self.thinking_config {
thinking_config.validate().map_err(|e| {
crate::error::LlmError::ConfigurationError(format!(
"Invalid thinking configuration: {e}"
))
})?;
}
if let Some(model) = self.model {
config = config.with_model(model);
}
let mut generation_config = crate::providers::gemini::GenerationConfig::new();
if let Some(temp) = self.temperature {
generation_config = generation_config.with_temperature(temp);
}
if let Some(max_tokens) = self.max_tokens {
generation_config = generation_config.with_max_output_tokens(max_tokens);
}
if let Some(top_p) = self.top_p {
generation_config = generation_config.with_top_p(top_p);
}
if let Some(top_k) = self.top_k {
generation_config = generation_config.with_top_k(top_k);
}
if let Some(stop_sequences) = self.stop_sequences {
generation_config = generation_config.with_stop_sequences(stop_sequences);
}
if let Some(count) = self.candidate_count {
generation_config = generation_config.with_candidate_count(count);
}
if let Some(schema) = self.json_schema {
generation_config = generation_config.with_response_schema(schema);
generation_config =
generation_config.with_response_mime_type("application/json".to_string());
}
if let Some(thinking_config) = &self.thinking_config {
generation_config = generation_config.with_thinking_config(thinking_config.clone());
}
config = config.with_generation_config(generation_config);
if let Some(safety_settings) = self.safety_settings {
config = config.with_safety_settings(safety_settings);
}
if let Some(timeout) = self.base.timeout {
config = config.with_timeout(timeout.as_secs());
}
let http_client = self.base.build_http_client()?;
let mut client =
crate::providers::gemini::GeminiClient::with_http_client(config, http_client)?;
client.set_tracing_guard(_tracing_guard);
client.set_tracing_config(self.tracing_config);
client.set_retry_options(self.retry_options.clone());
Ok(client)
}
}