use std::collections::HashMap;
use std::time::Duration;
use crate::error::LlmError;
#[cfg(feature = "openai")]
pub async fn quick_openai() -> Result<crate::providers::openai::OpenAiClient, LlmError> {
quick_openai_with_model("gpt-4o-mini").await
}
#[cfg(feature = "openai")]
pub async fn quick_openai_with_model(
model: &str,
) -> Result<crate::providers::openai::OpenAiClient, LlmError> {
LlmBuilder::new().openai().model(model).build().await
}
#[cfg(feature = "anthropic")]
pub async fn quick_anthropic() -> Result<crate::providers::anthropic::AnthropicClient, LlmError> {
quick_anthropic_with_model("claude-3-5-sonnet-20241022").await
}
#[cfg(feature = "anthropic")]
pub async fn quick_anthropic_with_model(
model: &str,
) -> Result<crate::providers::anthropic::AnthropicClient, LlmError> {
LlmBuilder::new().anthropic().model(model).build().await
}
#[cfg(feature = "google")]
pub async fn quick_gemini() -> Result<crate::providers::gemini::GeminiClient, LlmError> {
quick_gemini_with_model("gemini-1.5-flash").await
}
#[cfg(feature = "google")]
pub async fn quick_gemini_with_model(
model: &str,
) -> Result<crate::providers::gemini::GeminiClient, LlmError> {
LlmBuilder::new().gemini().model(model).build().await
}
#[cfg(feature = "ollama")]
pub async fn quick_ollama() -> Result<crate::providers::ollama::OllamaClient, LlmError> {
quick_ollama_with_model("llama3.2").await
}
#[cfg(feature = "ollama")]
pub async fn quick_ollama_with_model(
model: &str,
) -> Result<crate::providers::ollama::OllamaClient, LlmError> {
LlmBuilder::new().ollama().model(model).build().await
}
#[cfg(feature = "groq")]
pub async fn quick_groq() -> Result<crate::providers::groq::GroqClient, LlmError> {
quick_groq_with_model(crate::providers::groq::models::popular::FLAGSHIP).await
}
#[cfg(feature = "groq")]
pub async fn quick_groq_with_model(
model: &str,
) -> Result<crate::providers::groq::GroqClient, LlmError> {
LlmBuilder::new().groq().model(model).build().await
}
#[cfg(feature = "xai")]
pub async fn quick_xai() -> Result<crate::providers::xai::XaiClient, LlmError> {
quick_xai_with_model(crate::providers::xai::models::popular::LATEST).await
}
#[cfg(feature = "xai")]
pub async fn quick_xai_with_model(
model: &str,
) -> Result<crate::providers::xai::XaiClient, LlmError> {
LlmBuilder::new().xai().model(model).build().await
}
#[derive(Debug, Clone)]
pub struct LlmBuilder {
pub(crate) http_client: Option<reqwest::Client>,
pub(crate) timeout: Option<Duration>,
pub(crate) connect_timeout: Option<Duration>,
pub(crate) user_agent: Option<String>,
pub(crate) default_headers: HashMap<String, String>,
pub(crate) http2_prior_knowledge: Option<bool>,
pub(crate) gzip: Option<bool>,
pub(crate) brotli: Option<bool>,
pub(crate) proxy: Option<String>,
pub(crate) cookie_store: Option<bool>,
}
impl LlmBuilder {
pub fn new() -> Self {
Self {
http_client: None,
timeout: None,
connect_timeout: None,
user_agent: None,
default_headers: HashMap::new(),
http2_prior_knowledge: None,
gzip: None,
brotli: None,
proxy: None,
cookie_store: None,
}
}
pub fn with_defaults() -> Self {
Self::new()
.with_timeout(crate::defaults::timeouts::STANDARD)
.with_connect_timeout(crate::defaults::http::CONNECT_TIMEOUT)
.with_user_agent(crate::defaults::http::USER_AGENT)
.with_gzip(true)
.with_brotli(true)
}
pub fn fast() -> Self {
Self::new()
.with_timeout(crate::defaults::timeouts::FAST)
.with_connect_timeout(Duration::from_secs(5))
.with_user_agent(crate::defaults::http::USER_AGENT)
}
pub fn long_running() -> Self {
Self::new()
.with_timeout(crate::defaults::timeouts::LONG_RUNNING)
.with_connect_timeout(Duration::from_secs(30))
.with_user_agent(crate::defaults::http::USER_AGENT)
}
pub fn with_http_client(mut self, client: reqwest::Client) -> Self {
self.http_client = Some(client);
self
}
pub const fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub const fn with_connect_timeout(mut self, timeout: Duration) -> Self {
self.connect_timeout = Some(timeout);
self
}
pub fn with_user_agent<S: Into<String>>(mut self, user_agent: S) -> Self {
self.user_agent = Some(user_agent.into());
self
}
pub fn with_header<K: Into<String>, V: Into<String>>(mut self, name: K, value: V) -> Self {
self.default_headers.insert(name.into(), value.into());
self
}
pub const fn with_http2_prior_knowledge(mut self, enabled: bool) -> Self {
self.http2_prior_knowledge = Some(enabled);
self
}
pub const fn with_gzip(mut self, enabled: bool) -> Self {
self.gzip = Some(enabled);
self
}
pub const fn with_brotli(mut self, enabled: bool) -> Self {
self.brotli = Some(enabled);
self
}
pub fn with_proxy<S: Into<String>>(mut self, proxy_url: S) -> Self {
self.proxy = Some(proxy_url.into());
self
}
pub const fn with_cookie_store(mut self, enabled: bool) -> Self {
self.cookie_store = Some(enabled);
self
}
pub(crate) fn build_http_client(&self) -> Result<reqwest::Client, LlmError> {
if let Some(client) = &self.http_client {
return Ok(client.clone());
}
let mut builder = reqwest::Client::builder();
if let Some(timeout) = self.timeout {
builder = builder.timeout(timeout);
}
if let Some(connect_timeout) = self.connect_timeout {
builder = builder.connect_timeout(connect_timeout);
}
if let Some(user_agent) = &self.user_agent {
builder = builder.user_agent(user_agent);
}
if let Some(_http2) = self.http2_prior_knowledge {
builder = builder.http2_prior_knowledge();
}
if let Some(proxy_url) = &self.proxy {
let proxy = reqwest::Proxy::all(proxy_url)
.map_err(|e| LlmError::ConfigurationError(format!("Invalid proxy URL: {e}")))?;
builder = builder.proxy(proxy);
}
let mut headers = reqwest::header::HeaderMap::new();
for (name, value) in &self.default_headers {
let header_name =
reqwest::header::HeaderName::from_bytes(name.as_bytes()).map_err(|e| {
LlmError::ConfigurationError(format!("Invalid header name '{name}': {e}"))
})?;
let header_value = reqwest::header::HeaderValue::from_str(value).map_err(|e| {
LlmError::ConfigurationError(format!("Invalid header value '{value}': {e}"))
})?;
headers.insert(header_name, header_value);
}
if !headers.is_empty() {
builder = builder.default_headers(headers);
}
builder
.build()
.map_err(|e| LlmError::ConfigurationError(format!("Failed to build HTTP client: {e}")))
}
}
impl Default for LlmBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_creation() {
let builder = LlmBuilder::new();
let _openai_builder = builder.openai();
}
#[test]
fn test_http_config_inheritance() {
use std::time::Duration;
let base_builder = LlmBuilder::new()
.with_timeout(Duration::from_secs(60))
.with_proxy("http://proxy.example.com:8080")
.with_user_agent("test-agent/1.0")
.with_header("X-Test-Header", "test-value");
let openai_builder = base_builder.clone().openai();
assert_eq!(openai_builder.base.timeout, Some(Duration::from_secs(60)));
assert_eq!(
openai_builder.base.proxy,
Some("http://proxy.example.com:8080".to_string())
);
assert_eq!(
openai_builder.base.user_agent,
Some("test-agent/1.0".to_string())
);
assert!(
openai_builder
.base
.default_headers
.contains_key("X-Test-Header")
);
let anthropic_builder = base_builder.clone().anthropic();
assert_eq!(
anthropic_builder.base.timeout,
Some(Duration::from_secs(60))
);
assert_eq!(
anthropic_builder.base.proxy,
Some("http://proxy.example.com:8080".to_string())
);
let gemini_builder = base_builder.clone().gemini();
assert_eq!(gemini_builder.base.timeout, Some(Duration::from_secs(60)));
assert_eq!(
gemini_builder.base.proxy,
Some("http://proxy.example.com:8080".to_string())
);
let ollama_builder = base_builder.clone().ollama();
assert_eq!(ollama_builder.base.timeout, Some(Duration::from_secs(60)));
assert_eq!(
ollama_builder.base.proxy,
Some("http://proxy.example.com:8080".to_string())
);
let xai_wrapper = base_builder.clone().xai();
assert_eq!(xai_wrapper.base.timeout, Some(Duration::from_secs(60)));
assert_eq!(
xai_wrapper.base.proxy,
Some("http://proxy.example.com:8080".to_string())
);
let groq_wrapper = base_builder.groq();
assert_eq!(groq_wrapper.base.timeout, Some(Duration::from_secs(60)));
assert_eq!(
groq_wrapper.base.proxy,
Some("http://proxy.example.com:8080".to_string())
);
}
}