use std::collections::HashMap;
use std::sync::{Arc, OnceLock};
use std::time::Duration;
use async_openai::config::OpenAIConfig;
use async_openai::Client as OpenAIClient;
use crate::constants::{DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT};
use crate::error::Result;
use crate::http::{HttpConfig, HttpTransport};
use crate::openai_delegate::new_openai_delegate;
use crate::options::FloopyOptions;
use crate::resources::{
Batches, Constraints, Decisions, Evaluations, Experiments, Export, Feedback, Files, Routing,
Sessions,
};
#[derive(Clone)]
pub struct Floopy {
transport: Arc<HttpTransport>,
openai: Arc<OnceLock<OpenAIClient<OpenAIConfig>>>,
}
impl Floopy {
pub fn new(api_key: impl Into<String>) -> Result<Self> {
Self::builder(api_key).build()
}
#[must_use]
pub fn builder(api_key: impl Into<String>) -> FloopyBuilder {
FloopyBuilder::new(api_key)
}
#[must_use]
pub fn openai(&self) -> &OpenAIClient<OpenAIConfig> {
self.openai.get_or_init(|| {
new_openai_delegate(&self.transport)
.expect("delegate construction with SDK-produced headers is infallible")
})
}
#[must_use]
pub fn base_url(&self) -> &str {
self.transport.base_url()
}
#[must_use]
pub fn feedback(&self) -> Feedback {
Feedback::new(self.transport.clone())
}
#[must_use]
pub fn decisions(&self) -> Decisions {
Decisions::new(self.transport.clone())
}
#[must_use]
pub fn experiments(&self) -> Experiments {
Experiments::new(self.transport.clone())
}
#[must_use]
pub fn constraints(&self) -> Constraints {
Constraints::new(self.transport.clone())
}
#[must_use]
pub fn export(&self) -> Export {
Export::new(self.transport.clone())
}
#[must_use]
pub fn files(&self) -> Files {
Files::new(self.transport.clone())
}
#[must_use]
pub fn batches(&self) -> Batches {
Batches::new(self.transport.clone())
}
#[must_use]
pub fn evaluations(&self) -> Evaluations {
Evaluations::new(self.transport.clone())
}
#[must_use]
pub fn routing(&self) -> Routing {
Routing::new(self.transport.clone())
}
#[must_use]
pub fn sessions(&self) -> Sessions {
Sessions::new(self.transport.clone())
}
}
impl std::fmt::Debug for Floopy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Floopy")
.field("base_url", &self.transport.base_url())
.finish_non_exhaustive()
}
}
pub struct FloopyBuilder {
api_key: String,
base_url: String,
timeout: Duration,
max_retries: u32,
default_headers: HashMap<String, String>,
options: Option<FloopyOptions>,
http_client: Option<reqwest::Client>,
}
impl FloopyBuilder {
fn new(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
base_url: DEFAULT_BASE_URL.to_owned(),
timeout: DEFAULT_TIMEOUT,
max_retries: DEFAULT_MAX_RETRIES,
default_headers: HashMap::new(),
options: None,
http_client: None,
}
}
#[must_use]
pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
self.base_url = base_url.into();
self
}
#[must_use]
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
#[must_use]
pub fn max_retries(mut self, max_retries: u32) -> Self {
self.max_retries = max_retries;
self
}
#[must_use]
pub fn default_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.default_headers.insert(key.into(), value.into());
self
}
#[must_use]
pub fn options(mut self, options: FloopyOptions) -> Self {
self.options = Some(options);
self
}
#[must_use]
pub fn http_client(mut self, client: reqwest::Client) -> Self {
self.http_client = Some(client);
self
}
pub fn build(self) -> Result<Floopy> {
let transport = HttpTransport::new(HttpConfig {
api_key: self.api_key,
base_url: self.base_url,
timeout: self.timeout,
max_retries: self.max_retries,
default_headers: self.default_headers,
default_options: self.options,
http_client: self.http_client,
})?;
Ok(Floopy {
transport: Arc::new(transport),
openai: Arc::new(OnceLock::new()),
})
}
}