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}