use crate::client::env::{self, EnvError};
use crate::providers::openai::wire::{Chat, OLLAMA, OpenAIConfig};
use crate::wire::Secret;
use serde::{Deserialize, Serialize};
use super::OLLAMA_API_BASE_URL;
const BASE_URL_ENV: &str = "OLLAMA_API_BASE_URL";
const API_KEY_ENV: &str = "OLLAMA_API_KEY";
const OPENAI_COMPATIBLE_PATH: &str = "/v1";
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OllamaConfig {
pub base_url: String,
pub api_key: Secret,
}
impl Default for OllamaConfig {
fn default() -> Self {
Self::new()
}
}
impl OllamaConfig {
pub fn new() -> Self {
Self {
base_url: OLLAMA_API_BASE_URL.to_owned(),
api_key: Secret::default(),
}
}
pub fn from_env() -> Result<Self, EnvError> {
let mut provider = Self::new();
if let Some(base_url) = env::optional(BASE_URL_ENV)? {
provider = provider.with_base_url(base_url);
}
if let Some(api_key) = env::optional(API_KEY_ENV)? {
provider.api_key = api_key.into();
}
Ok(provider)
}
pub fn with_base_url(mut self, base_url: impl AsRef<str>) -> Self {
self.base_url = base_url.as_ref().trim_end_matches('/').to_owned();
self
}
pub fn with_api_key(mut self, api_key: impl Into<Secret>) -> Self {
self.api_key = api_key.into();
self
}
pub(crate) fn completion(&self, model: impl Into<String>) -> Chat {
OpenAIConfig::with_key(&OLLAMA, self.api_key.clone())
.with_base_url(format!("{}{OPENAI_COMPATIBLE_PATH}", self.base_url))
.chat(model)
}
pub(crate) fn native_completion(&self, model: impl Into<String>) -> super::Chat {
super::Chat::new(self.clone(), model)
}
pub(super) fn request(&self, method: http::Method, path: &str) -> http::request::Builder {
let builder = http::Request::builder()
.method(method)
.uri(format!("{}{path}", self.base_url))
.header(http::header::CONTENT_TYPE, "application/json");
if self.api_key.is_empty() {
builder
} else {
builder.header(
http::header::AUTHORIZATION,
format!("Bearer {}", self.api_key.expose()),
)
}
}
}
#[cfg(test)]
mod tests;