Skip to main content

open_agent/
config.rs

1//! # Configuration Helpers for the Open Agent SDK
2//!
3//! This module provides convenience utilities for configuring connections to different
4//! local LLM server providers. It simplifies the process of setting base URLs and model names
5//! by providing well-known defaults and environment variable support.
6//!
7//! ## Supported Providers
8//!
9//! - **LM Studio**: Popular local model server with GUI
10//! - **Ollama**: Command-line focused local model server
11//! - **llama.cpp**: C++ inference engine with server mode
12//! - **vLLM**: High-performance inference server
13//!
14//! ## Environment Variables
15//!
16//! - `OPEN_AGENT_BASE_URL`: Override base URL for any provider
17//! - `OPEN_AGENT_MODEL`: Override model name (when prefer_env is true)
18//!
19//! ## Examples
20//!
21//! ```rust
22//! use open_agent::{Provider, get_base_url, get_model, AgentOptions};
23//!
24//! // Quick setup with provider defaults
25//! let url = get_base_url(Some(Provider::Ollama), None);
26//! let model = get_model(Some("llama3:8b"), false);
27//!
28//! // Build options
29//! let options = AgentOptions::builder()
30//!     .system_prompt("You are a helpful assistant")
31//!     .model(model.unwrap())
32//!     .base_url(&url)
33//!     .build()
34//!     .unwrap();
35//! ```
36
37use std::env;
38use std::str::FromStr;
39
40/// Local inference providers with conventional OpenAI-compatible endpoint URLs.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum Provider {
43    /// LM Studio - Popular GUI-based local model server (default port 1234)
44    LMStudio,
45
46    /// Ollama - Command-line focused local model server (default port 11434)
47    Ollama,
48
49    /// llama.cpp - C++ inference engine with server mode (default port 8080)
50    LlamaCpp,
51
52    /// vLLM - High-performance inference server (default port 8000)
53    VLLM,
54}
55
56impl Provider {
57    /// Returns the conventional local URL, without consulting environment variables.
58    ///
59    /// LM Studio uses port 1234, Ollama 11434, llama.cpp 8080, and vLLM 8000;
60    /// each URL ends in `/v1`.
61    pub fn default_url(&self) -> &'static str {
62        match self {
63            Provider::LMStudio => "http://localhost:1234/v1",
64
65            Provider::Ollama => "http://localhost:11434/v1",
66
67            Provider::LlamaCpp => "http://localhost:8080/v1",
68
69            Provider::VLLM => "http://localhost:8000/v1",
70        }
71    }
72}
73
74impl FromStr for Provider {
75    type Err = String;
76
77    /// Parses a case-insensitive provider name.
78    ///
79    /// Accepts `lmstudio`, `lm-studio`, `lm_studio`, `ollama`, `llamacpp`, `llama-cpp`,
80    /// `llama_cpp`, `llama.cpp`, and `vllm`. Unknown names return an error.
81    fn from_str(s: &str) -> Result<Self, Self::Err> {
82        match s.to_lowercase().as_str() {
83            "lmstudio" | "lm-studio" | "lm_studio" => Ok(Provider::LMStudio),
84
85            "ollama" => Ok(Provider::Ollama),
86
87            "llamacpp" | "llama-cpp" | "llama_cpp" | "llama.cpp" => Ok(Provider::LlamaCpp),
88
89            "vllm" => Ok(Provider::VLLM),
90
91            _ => Err(format!("Unknown provider: {}", s)),
92        }
93    }
94}
95
96/// Resolves a base URL in this order:
97///
98/// 1. `OPEN_AGENT_BASE_URL`, when readable as Unicode.
99/// 2. The selected provider's default URL.
100/// 3. The explicit fallback.
101/// 4. LM Studio's default URL.
102///
103/// The returned value is not validated.
104pub fn get_base_url(provider: Option<Provider>, fallback: Option<&str>) -> String {
105    // Priority 1: Check environment variable first (allows runtime override)
106    if let Ok(url) = env::var("OPEN_AGENT_BASE_URL") {
107        return url;
108    }
109
110    // Priority 2: Use provider's default URL if specified
111    if let Some(p) = provider {
112        return p.default_url().to_string();
113    }
114
115    // Priority 3: Use explicit fallback, or default to LM Studio
116    fallback
117        .unwrap_or(Provider::LMStudio.default_url())
118        .to_string()
119}
120
121/// Resolves a model name with optional environment lookup.
122///
123/// When `prefer_env` is true, a readable `OPEN_AGENT_MODEL` takes precedence over
124/// `fallback`. Otherwise only `fallback` is considered. Neither value is validated;
125/// no value yields `None`.
126///
127/// ```rust
128/// use open_agent::get_model;
129///
130/// assert_eq!(get_model(Some("my-model"), false).as_deref(), Some("my-model"));
131/// let from_environment = get_model(None, true);
132/// ```
133pub fn get_model(fallback: Option<&str>, prefer_env: bool) -> Option<String> {
134    // If environment override is preferred, check it first
135    if prefer_env {
136        if let Ok(model) = env::var("OPEN_AGENT_MODEL") {
137            return Some(model);
138        }
139    }
140
141    // Fall back to the explicit parameter (if provided)
142    fallback.map(|s| s.to_string())
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn test_provider_default_urls() {
151        assert_eq!(Provider::LMStudio.default_url(), "http://localhost:1234/v1");
152        assert_eq!(Provider::Ollama.default_url(), "http://localhost:11434/v1");
153        assert_eq!(Provider::LlamaCpp.default_url(), "http://localhost:8080/v1");
154        assert_eq!(Provider::VLLM.default_url(), "http://localhost:8000/v1");
155    }
156
157    #[test]
158    fn test_provider_from_str() {
159        assert_eq!("lmstudio".parse::<Provider>(), Ok(Provider::LMStudio));
160        assert_eq!("LM-Studio".parse::<Provider>(), Ok(Provider::LMStudio));
161        assert_eq!("ollama".parse::<Provider>(), Ok(Provider::Ollama));
162        assert_eq!("llamacpp".parse::<Provider>(), Ok(Provider::LlamaCpp));
163        assert_eq!("llama.cpp".parse::<Provider>(), Ok(Provider::LlamaCpp));
164        assert_eq!("vllm".parse::<Provider>(), Ok(Provider::VLLM));
165        assert!("unknown".parse::<Provider>().is_err());
166    }
167}