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}