llm_brain/
config.rs

1use std::path::PathBuf;
2use std::sync::OnceLock;
3
4use config::{Config as ConfigRs, Environment};
5use serde::Deserialize;
6
7use crate::error::{LLMBrainError, Result};
8
9/// Global configuration instance accessed via `Config::get()`
10static CONFIG: OnceLock<Config> = OnceLock::new();
11
12/// Database configuration settings
13#[derive(Deserialize, Debug, Clone)]
14pub struct DatabaseConfig {
15    /// Path to `SurrealDB` storage files
16    pub path: PathBuf,
17    /// `SurrealDB` namespace
18    pub namespace: String,
19    /// `SurrealDB` database name
20    pub database: String,
21}
22
23/// `ConceptNet` integration configuration
24#[derive(Deserialize, Debug, Clone)]
25pub struct ConceptNetConfig {
26    /// Optional custom API URL for `ConceptNet`
27    pub base_url: Option<String>,
28}
29
30/// LLM provider configuration
31#[derive(Deserialize, Debug, Clone)]
32pub struct LlmConfig {
33    /// `OpenAI` API key (optional if set in environment `OPENAI_API_KEY`)
34    pub openai_api_key: Option<String>,
35    /// Embedding model to use for vector generation (defaults to
36    /// `text-embedding-3-small`)
37    pub embedding_model: Option<String>,
38    /// Optional custom base URL for the `OpenAI` API.
39    pub openai_api_base: Option<String>,
40}
41
42/// Main configuration struct containing all settings
43#[derive(Deserialize, Debug, Clone)]
44pub struct Config {
45    /// Database settings
46    pub database: DatabaseConfig,
47    /// Optional `ConceptNet` integration settings
48    pub conceptnet: Option<ConceptNetConfig>,
49    /// Optional LLM provider settings
50    pub llm: Option<LlmConfig>,
51}
52
53impl Config {
54    /// Loads configuration from environment variables and optionally a `.env`
55    /// file.
56    ///
57    /// The configuration is loaded in the following order (later sources
58    /// override earlier ones):
59    /// 1. `.env` file (loaded into environment variables if present)
60    /// 2. Environment variables prefixed with `LLM_BRAIN_`
61    ///
62    /// Example: `LLM_BRAIN_DATABASE__PATH=/path/to/db` sets the
63    /// `database.path` setting.
64    pub fn load() -> Result<()> {
65        // Load .env file first. Ignore error if .env doesn't exist.
66        dotenvy::dotenv().ok();
67
68        // Removed loading from default.toml and local.toml
69        // let default_path = "config/default.toml";
70        // let local_path = "config/local.toml";
71
72        // Start with an empty builder
73        let builder = ConfigRs::builder();
74
75        // Removed adding File sources
76        // let builder =
77        // builder.add_source(File::with_name(default_path).required(true)); let
78        // builder = if PathBuf::from(local_path).exists() {     builder.
79        // add_source(File::with_name(local_path).required(false)) } else {
80        //     builder
81        // };
82
83        // Layer on environment variables (which now include those from .env)
84        let builder = builder.add_source(
85            Environment::with_prefix("LLM_BRAIN")
86                .prefix_separator("_")
87                .separator("__")
88                .try_parsing(true), // Attempt to parse types from env vars
89        );
90
91        // Build and parse the configuration
92        let settings = builder
93            .build()
94            .map_err(|e| LLMBrainError::ConfigError(format!("Failed to build config: {e}")))?;
95
96        let config: Config = settings.try_deserialize().map_err(|e| {
97            LLMBrainError::ConfigError(format!("Failed to deserialize config: {e}"))
98        })?;
99
100        // Store the loaded config in the OnceLock
101        if CONFIG.set(config).is_err() {
102            return Err(LLMBrainError::ConfigError(
103                "Configuration already loaded".to_owned(),
104            ));
105        }
106
107        Ok(())
108    }
109
110    /// Gets a reference to the loaded global configuration.
111    ///
112    /// # Panics
113    ///
114    /// Panics if `load()` has not been called successfully first.
115    pub fn get() -> &'static Config {
116        CONFIG
117            .get()
118            .expect("Configuration not loaded. Call Config::load() first.")
119    }
120}