artificial-openai 0.7.0

OpenAI backend adapter for the Artificial prompt-engineering SDK
Documentation
use std::{env, sync::Arc};

use artificial_core::error::{ArtificialError, Result};

use crate::client::{HttpTimeoutConfig, OpenAiClient, RetryPolicy};

/// Thin wrapper that wires the HTTP client [`OpenAiClient`] into a value that
/// implements [`artificial_core::backend::Backend`].
///
/// Think of it as the **service locator** for the OpenAI back-end:
///
/// * stores the API key (and optionally a custom base URL in the future),
/// * owns a shareable, connection-pooled `reqwest::Client`,
/// * provides a fluent [`OpenAiAdapterBuilder`] so callers don’t have to juggle
///   `Option<String>` manually.
///
/// The type itself purposefully exposes **no additional methods**—all user-
/// facing functionality sits on the generic [`artificial_core::ArtificialClient`]
/// once the adapter is plugged in.
pub struct OpenAiAdapter {
    pub(crate) client: Arc<OpenAiClient>,
}

impl OpenAiAdapter {}

/// Builder-style configuration for constructing [`OpenAiAdapter`].
///
/// # Typical usage
///
/// ```rust,no_run
/// use artificial_openai::OpenAiAdapterOptions;
///
/// let backend = OpenAiAdapterOptions::new_from_env()
///     .build()
///     .expect("OPENAI_API_KEY must be set");
/// ```
///
/// The builder pattern keeps future options (proxy URL, organisation ID, …)
/// backwards compatible without breaking existing `build()` calls.
#[derive(Default)]
pub struct OpenAiAdapterOptions {
    pub(crate) api_key: Option<String>,
    pub(crate) retry: Option<RetryPolicy>,
    pub(crate) timeouts: Option<HttpTimeoutConfig>,
}

impl OpenAiAdapterOptions {
    /// Create an *empty* builder. Remember to supply an API key manually.
    pub fn new() -> Self {
        Self::default()
    }

    /// Convenience constructor that tries to load the `OPENAI_API_KEY`
    /// environment variable.
    ///
    /// # Panics
    ///
    /// Never panics. Missing keys only surface during [`Self::build`].
    pub fn new_from_env() -> Self {
        Self {
            api_key: env::var("OPENAI_API_KEY").ok(),
            retry: None,
            timeouts: None,
        }
    }

    /// Set API key explicitly.
    pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
        self.api_key = Some(api_key.into());
        self
    }

    /// Set a retry policy for OpenAI HTTP calls.
    pub fn with_retry_policy(mut self, retry: RetryPolicy) -> Self {
        self.retry = Some(retry);
        self
    }

    /// Set HTTP timeout configuration for upstream requests.
    pub fn with_http_timeouts(mut self, timeouts: HttpTimeoutConfig) -> Self {
        self.timeouts = Some(timeouts);
        self
    }

    /// Finalise the builder and return a ready-to-use adapter.
    ///
    /// # Errors
    ///
    /// * [`ArtificialError::Invalid`] – if the API key is missing.
    pub fn build(self) -> Result<OpenAiAdapter> {
        let api_key = self.api_key.ok_or(ArtificialError::Invalid(
            "missing env variable: `OPENAI_API_KEY`".into(),
        ))?;

        let mut client = if let Some(timeouts) = self.timeouts {
            OpenAiClient::new_with_timeouts(api_key, timeouts)
        } else {
            OpenAiClient::new(api_key)
        };
        if let Some(retry) = self.retry {
            client = client.with_retry_policy(retry);
        }

        Ok(OpenAiAdapter {
            client: Arc::new(client),
        })
    }
}

/// Backwards-compatible alias kept for existing code.
pub type OpenAiAdapterBuilder = OpenAiAdapterOptions;