llmoxide 0.1.0

Provider-agnostic Rust SDK for OpenAI, Anthropic, Gemini, and Ollama (streaming + tools)
Documentation
//! `llmoxide` is a small, provider-agnostic Rust SDK surface for LLM APIs.
//!
//! This crate will provide a normalized interface across providers (starting with
//! OpenAI and Anthropic), with robust streaming, retries, and tracing hooks.

pub mod chat;
pub mod client;
pub mod error;
pub mod providers;
pub mod sse;
pub mod types;

pub use crate::chat::{Chat, ChatSession};
pub use crate::client::{Client, ClientConfig};
pub use crate::error::{Error, Result};
pub use crate::providers::ListModels;
pub use crate::types::{
    ContentPart, DEFAULT_ANTHROPIC_MODEL, DEFAULT_GEMINI_MODEL, DEFAULT_OLLAMA_MODEL,
    DEFAULT_OPENAI_MODEL, Event, Message, Model, ModelInfo, Provider, Response, ResponseRequest,
    Role, ToolCall, ToolSpec,
};

/// Send a single user string per request, or call [`Prompt::list_models`].
///
/// For chat history or custom [`ResponseRequest`](crate::ResponseRequest) bodies, use
/// [`Prompt::client`] or [`Prompt::into_client`].
#[derive(Clone)]
pub struct Prompt {
    client: Client,
    model: Option<String>,
    max_output_tokens: Option<u32>,
}

impl Prompt {
    fn new(cfg: ClientConfig) -> Self {
        Self {
            client: Client::new(cfg),
            model: None,
            max_output_tokens: None,
        }
    }

    pub fn model(mut self, model: impl Into<String>) -> Self {
        self.model = Some(model.into());
        self
    }

    pub fn max_output_tokens(mut self, max: u32) -> Self {
        self.max_output_tokens = Some(max);
        self
    }

    fn request(&self, user_prompt: impl Into<String>) -> ResponseRequest {
        let mut req = match &self.model {
            Some(m) => ResponseRequest::new(m.as_str()),
            None => ResponseRequest::new_auto(),
        };
        if let Some(max) = self.max_output_tokens {
            req = req.max_output_tokens(max);
        }
        req.push_message(Message::text(Role::User, user_prompt.into()))
    }

    pub async fn send(&self, prompt: impl Into<String>) -> Result<Response> {
        self.client.send(self.request(prompt)).await
    }

    pub async fn stream<F>(&self, prompt: impl Into<String>, on_event: F) -> Result<Response>
    where
        F: FnMut(Event),
    {
        self.client.stream(self.request(prompt), on_event).await
    }

    pub async fn list_models(&self) -> Result<Vec<ModelInfo>> {
        self.client.list_models().await
    }

    pub fn client(&self) -> &Client {
        &self.client
    }

    pub fn into_client(self) -> Client {
        self.client
    }
}

pub fn openai(api_key: impl Into<String>) -> Prompt {
    Prompt::new(ClientConfig::openai(api_key))
}

pub fn anthropic(api_key: impl Into<String>) -> Prompt {
    Prompt::new(ClientConfig::anthropic(api_key))
}

pub fn gemini(api_key: impl Into<String>) -> Prompt {
    Prompt::new(ClientConfig::gemini(api_key))
}

pub fn ollama() -> Prompt {
    Prompt::new(ClientConfig::ollama_default())
}

pub fn ollama_at(base_url: impl Into<String>) -> Prompt {
    Prompt::new(ClientConfig::ollama(base_url))
}