use figment::{
providers::{Format, Toml},
Figment,
};
use gradatum_curator::{CuratorLlmConfig, CuratorPipeline, CuratorPipelineConfig};
use serde::{Deserialize, Serialize};
use tracing::{error, info, warn};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkerCuratorConfig {
#[serde(default = "default_curator_backend")]
pub backend: String,
#[serde(default)]
pub llm: Option<WorkerLlmConfig>,
#[serde(default)]
pub heuristic_admit_threshold: Option<f32>,
#[serde(default)]
pub heuristic_default_status: Option<String>,
#[serde(default)]
pub llm_review_enabled: Option<bool>,
#[serde(default)]
pub confidence_threshold: Option<f32>,
#[serde(default)]
pub llm_review_endpoint: Option<String>,
#[serde(default)]
pub llm_review_model: Option<String>,
#[serde(default)]
pub llm_review_timeout_ms: Option<u32>,
#[serde(default)]
pub llm_review_max_tokens: Option<u32>,
#[serde(default)]
pub llm_review_fallback: Option<String>,
}
fn default_curator_backend() -> String {
"heuristic".to_string()
}
impl Default for WorkerCuratorConfig {
fn default() -> Self {
Self {
backend: default_curator_backend(),
llm: None,
heuristic_admit_threshold: None,
heuristic_default_status: None,
llm_review_enabled: None,
confidence_threshold: None,
llm_review_endpoint: None,
llm_review_model: None,
llm_review_timeout_ms: None,
llm_review_max_tokens: None,
llm_review_fallback: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkerLlmConfig {
pub backend: String,
pub base_url: String,
pub model: String,
#[serde(default)]
pub api_key_env: Option<String>,
#[serde(default = "default_timeout_ms")]
pub timeout_ms: u64,
#[serde(default)]
pub max_tokens: Option<u32>,
}
fn default_timeout_ms() -> u64 {
5000
}
impl From<&WorkerCuratorConfig> for CuratorPipelineConfig {
fn from(w: &WorkerCuratorConfig) -> Self {
CuratorPipelineConfig {
backend: w.backend.clone(),
llm: w.llm.as_ref().map(|l| CuratorLlmConfig {
backend: l.backend.clone(),
base_url: l.base_url.clone(),
model: l.model.clone(),
api_key_env: l.api_key_env.clone(),
timeout_ms: l.timeout_ms,
}),
heuristic_admit_threshold: w.heuristic_admit_threshold,
heuristic_default_status: w.heuristic_default_status.clone(),
llm_review_enabled: w.llm_review_enabled,
confidence_threshold: w.confidence_threshold,
llm_review_endpoint: w.llm_review_endpoint.clone(),
llm_review_model: w.llm_review_model.clone(),
llm_review_timeout_ms: w.llm_review_timeout_ms,
llm_review_max_tokens: w.llm_review_max_tokens,
llm_review_fallback: w.llm_review_fallback.clone(),
}
}
}
fn is_missing_field_error(e: &figment::Error) -> bool {
e.clone().into_iter().all(|inner| inner.missing())
}
pub fn build_curator_pipeline(config_path: &std::path::Path) -> CuratorPipeline {
if !config_path.exists() {
warn!(
config = %config_path.display(),
"Fichier config absent — Curator initialisé en mode heuristic offline"
);
return CuratorPipeline::new();
}
let curator_cfg: WorkerCuratorConfig = {
let fig = Figment::new().merge(Toml::file(config_path));
match fig.extract_inner::<WorkerCuratorConfig>("curator") {
Ok(cfg) => cfg,
Err(e) if is_missing_field_error(&e) => {
info!(
config = %config_path.display(),
"Section [curator] absente — mode heuristic offline par défaut"
);
WorkerCuratorConfig::default()
}
Err(e) => {
error!(
config = %config_path.display(),
error = %e,
"Échec parse config curator — fallback heuristic offline"
);
return CuratorPipeline::new();
}
}
};
let pipeline_cfg = CuratorPipelineConfig::from(&curator_cfg);
if curator_cfg.backend != "heuristic" && curator_cfg.llm.is_none() {
warn!(
backend = %curator_cfg.backend,
"backend curator LLM configuré mais [curator.llm] absent — fallback heuristic offline"
);
return CuratorPipeline::new();
}
let pipeline = CuratorPipeline::from_config(&pipeline_cfg);
info!(
backend = %curator_cfg.backend,
llm_review_enabled = curator_cfg.llm_review_enabled.unwrap_or(false),
confidence_threshold = curator_cfg.confidence_threshold.unwrap_or(0.7),
heuristic_admit_threshold = ?curator_cfg.heuristic_admit_threshold,
llm_review_fallback = curator_cfg.llm_review_fallback.as_deref().unwrap_or("pending-review-fallback"),
base_url = ?curator_cfg.llm.as_ref().map(|l| &l.base_url),
model = ?curator_cfg.llm.as_ref().map(|l| &l.model),
timeout_ms = ?curator_cfg.llm.as_ref().map(|l| l.timeout_ms),
"Curator config loaded"
);
pipeline
}