Files
sol/src/config.rs

220 lines
7.2 KiB
Rust
Raw Normal View History

use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct Config {
pub matrix: MatrixConfig,
pub opensearch: OpenSearchConfig,
pub mistral: MistralConfig,
pub behavior: BehaviorConfig,
}
#[derive(Debug, Clone, Deserialize)]
pub struct MatrixConfig {
pub homeserver_url: String,
pub user_id: String,
pub state_store_path: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OpenSearchConfig {
pub url: String,
pub index: String,
#[serde(default = "default_batch_size")]
pub batch_size: usize,
#[serde(default = "default_flush_interval_ms")]
pub flush_interval_ms: u64,
#[serde(default = "default_embedding_pipeline")]
pub embedding_pipeline: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct MistralConfig {
#[serde(default = "default_model")]
pub default_model: String,
#[serde(default = "default_evaluation_model")]
pub evaluation_model: String,
#[serde(default = "default_research_model")]
pub research_model: String,
#[serde(default = "default_max_tool_iterations")]
pub max_tool_iterations: usize,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BehaviorConfig {
#[serde(default = "default_response_delay_min_ms")]
pub response_delay_min_ms: u64,
#[serde(default = "default_response_delay_max_ms")]
pub response_delay_max_ms: u64,
#[serde(default = "default_spontaneous_delay_min_ms")]
pub spontaneous_delay_min_ms: u64,
#[serde(default = "default_spontaneous_delay_max_ms")]
pub spontaneous_delay_max_ms: u64,
#[serde(default = "default_spontaneous_threshold")]
pub spontaneous_threshold: f32,
#[serde(default = "default_room_context_window")]
pub room_context_window: usize,
#[serde(default = "default_dm_context_window")]
pub dm_context_window: usize,
#[serde(default = "default_backfill_on_join")]
pub backfill_on_join: bool,
#[serde(default = "default_backfill_limit")]
pub backfill_limit: usize,
}
fn default_batch_size() -> usize { 50 }
fn default_flush_interval_ms() -> u64 { 2000 }
fn default_embedding_pipeline() -> String { "tuwunel_embedding_pipeline".into() }
fn default_model() -> String { "mistral-medium-latest".into() }
fn default_evaluation_model() -> String { "ministral-3b-latest".into() }
fn default_research_model() -> String { "mistral-large-latest".into() }
fn default_max_tool_iterations() -> usize { 5 }
fn default_response_delay_min_ms() -> u64 { 2000 }
fn default_response_delay_max_ms() -> u64 { 8000 }
fn default_spontaneous_delay_min_ms() -> u64 { 15000 }
fn default_spontaneous_delay_max_ms() -> u64 { 60000 }
fn default_spontaneous_threshold() -> f32 { 0.7 }
fn default_room_context_window() -> usize { 30 }
fn default_dm_context_window() -> usize { 100 }
fn default_backfill_on_join() -> bool { true }
fn default_backfill_limit() -> usize { 10000 }
impl Config {
pub fn load(path: &str) -> anyhow::Result<Self> {
let content = std::fs::read_to_string(path)?;
let config: Config = toml::from_str(&content)?;
Ok(config)
}
pub fn from_str(content: &str) -> anyhow::Result<Self> {
let config: Config = toml::from_str(content)?;
Ok(config)
}
}
#[cfg(test)]
mod tests {
use super::*;
const MINIMAL_CONFIG: &str = r#"
[matrix]
homeserver_url = "https://chat.sunbeam.pt"
user_id = "@sol:sunbeam.pt"
state_store_path = "/data/sol/state"
[opensearch]
url = "http://opensearch:9200"
index = "sol-archive"
[mistral]
[behavior]
"#;
const FULL_CONFIG: &str = r#"
[matrix]
homeserver_url = "https://chat.sunbeam.pt"
user_id = "@sol:sunbeam.pt"
state_store_path = "/data/sol/state"
[opensearch]
url = "http://opensearch:9200"
index = "sol-archive"
batch_size = 100
flush_interval_ms = 5000
embedding_pipeline = "my_pipeline"
[mistral]
default_model = "mistral-large-latest"
evaluation_model = "ministral-8b-latest"
research_model = "mistral-large-latest"
max_tool_iterations = 10
[behavior]
response_delay_min_ms = 1000
response_delay_max_ms = 5000
spontaneous_delay_min_ms = 10000
spontaneous_delay_max_ms = 30000
spontaneous_threshold = 0.8
room_context_window = 50
dm_context_window = 200
backfill_on_join = false
backfill_limit = 5000
"#;
#[test]
fn test_minimal_config_with_defaults() {
let config = Config::from_str(MINIMAL_CONFIG).unwrap();
assert_eq!(config.matrix.homeserver_url, "https://chat.sunbeam.pt");
assert_eq!(config.matrix.user_id, "@sol:sunbeam.pt");
assert_eq!(config.matrix.state_store_path, "/data/sol/state");
assert_eq!(config.opensearch.url, "http://opensearch:9200");
assert_eq!(config.opensearch.index, "sol-archive");
// Check defaults
assert_eq!(config.opensearch.batch_size, 50);
assert_eq!(config.opensearch.flush_interval_ms, 2000);
assert_eq!(config.opensearch.embedding_pipeline, "tuwunel_embedding_pipeline");
assert_eq!(config.mistral.default_model, "mistral-medium-latest");
assert_eq!(config.mistral.evaluation_model, "ministral-3b-latest");
assert_eq!(config.mistral.research_model, "mistral-large-latest");
assert_eq!(config.mistral.max_tool_iterations, 5);
assert_eq!(config.behavior.response_delay_min_ms, 2000);
assert_eq!(config.behavior.response_delay_max_ms, 8000);
assert_eq!(config.behavior.spontaneous_delay_min_ms, 15000);
assert_eq!(config.behavior.spontaneous_delay_max_ms, 60000);
assert!((config.behavior.spontaneous_threshold - 0.7).abs() < f32::EPSILON);
assert_eq!(config.behavior.room_context_window, 30);
assert_eq!(config.behavior.dm_context_window, 100);
assert!(config.behavior.backfill_on_join);
assert_eq!(config.behavior.backfill_limit, 10000);
}
#[test]
fn test_full_config_overrides() {
let config = Config::from_str(FULL_CONFIG).unwrap();
assert_eq!(config.opensearch.batch_size, 100);
assert_eq!(config.opensearch.flush_interval_ms, 5000);
assert_eq!(config.opensearch.embedding_pipeline, "my_pipeline");
assert_eq!(config.mistral.default_model, "mistral-large-latest");
assert_eq!(config.mistral.evaluation_model, "ministral-8b-latest");
assert_eq!(config.mistral.max_tool_iterations, 10);
assert_eq!(config.behavior.response_delay_min_ms, 1000);
assert_eq!(config.behavior.response_delay_max_ms, 5000);
assert!((config.behavior.spontaneous_threshold - 0.8).abs() < f32::EPSILON);
assert_eq!(config.behavior.room_context_window, 50);
assert_eq!(config.behavior.dm_context_window, 200);
assert!(!config.behavior.backfill_on_join);
assert_eq!(config.behavior.backfill_limit, 5000);
}
#[test]
fn test_missing_required_section_fails() {
let bad = r#"
[matrix]
homeserver_url = "https://chat.sunbeam.pt"
user_id = "@sol:sunbeam.pt"
state_store_path = "/data/sol/state"
"#;
assert!(Config::from_str(bad).is_err());
}
#[test]
fn test_missing_required_field_fails() {
let bad = r#"
[matrix]
homeserver_url = "https://chat.sunbeam.pt"
state_store_path = "/data/sol/state"
[opensearch]
url = "http://opensearch:9200"
index = "sol-archive"
[mistral]
[behavior]
"#;
assert!(Config::from_str(bad).is_err());
}
}