Skip to main content

llm_stack_openai/
config.rs

1//! `OpenAI` provider configuration.
2
3use std::time::Duration;
4
5/// Configuration for the `OpenAI` provider.
6///
7/// Use struct update syntax with [`Default`] for ergonomic construction:
8///
9/// ```rust
10/// use llm_stack_openai::OpenAiConfig;
11///
12/// let config = OpenAiConfig {
13///     api_key: "sk-...".into(),
14///     model: "gpt-4o".into(),
15///     ..Default::default()
16/// };
17/// ```
18#[derive(Clone)]
19pub struct OpenAiConfig {
20    /// `OpenAI` API key. Required.
21    pub api_key: String,
22    /// Model identifier (e.g. `"gpt-4o"`, `"gpt-4o-mini"`).
23    pub model: String,
24    /// Base URL for the API. Override for proxies, Azure, or local servers.
25    pub base_url: String,
26    /// Optional organization ID for API requests.
27    pub organization: Option<String>,
28    /// Request timeout. `None` uses reqwest's default.
29    pub timeout: Option<Duration>,
30    /// Pre-configured HTTP client for connection pooling across providers.
31    /// When `None`, a new client is created.
32    pub client: Option<reqwest::Client>,
33}
34
35impl std::fmt::Debug for OpenAiConfig {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        f.debug_struct("OpenAiConfig")
38            .field("api_key", &"[REDACTED]")
39            .field("model", &self.model)
40            .field("base_url", &self.base_url)
41            .field("organization", &self.organization)
42            .field("timeout", &self.timeout)
43            .field("client", &self.client.as_ref().map(|_| "..."))
44            .finish()
45    }
46}
47
48impl Default for OpenAiConfig {
49    fn default() -> Self {
50        Self {
51            api_key: String::new(),
52            model: "gpt-4o".into(),
53            base_url: "https://api.openai.com/v1".into(),
54            organization: None,
55            timeout: None,
56            client: None,
57        }
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn test_default_config() {
67        let config = OpenAiConfig::default();
68        assert_eq!(config.model, "gpt-4o");
69        assert_eq!(config.base_url, "https://api.openai.com/v1");
70        assert!(config.api_key.is_empty());
71        assert!(config.organization.is_none());
72        assert!(config.timeout.is_none());
73        assert!(config.client.is_none());
74    }
75
76    #[test]
77    fn test_debug_redacts_api_key() {
78        let config = OpenAiConfig {
79            api_key: "sk-super-secret".into(),
80            ..Default::default()
81        };
82        let debug_output = format!("{config:?}");
83        assert!(!debug_output.contains("sk-super-secret"));
84        assert!(debug_output.contains("[REDACTED]"));
85    }
86
87    #[test]
88    fn test_config_override() {
89        let config = OpenAiConfig {
90            api_key: "test-key".into(),
91            model: "gpt-4o-mini".into(),
92            organization: Some("org-123".into()),
93            ..Default::default()
94        };
95        assert_eq!(config.api_key, "test-key");
96        assert_eq!(config.model, "gpt-4o-mini");
97        assert_eq!(config.organization.as_deref(), Some("org-123"));
98    }
99}