use super::OutputFormat;
use super::documents::{ApiKey, SummarizationStrategy};
use super::overrides::DocumentsCliOverrides;
use super::source::{ConfigSource, ProvenanceMap};
use super::v1::ConfigV1;
#[derive(Debug, Default, Clone)]
pub struct ConfigLayers {
pub toml_file: Option<ConfigV1>,
pub env: Option<DocumentsCliOverrides>,
pub cli: Option<DocumentsCliOverrides>,
}
#[derive(Debug, Clone)]
pub struct LoadedConfig {
pub config: ConfigV1,
pub provenance: ProvenanceMap,
}
pub fn merge_layers(defaults: ConfigV1, layers: ConfigLayers) -> LoadedConfig {
let mut config = defaults;
let mut provenance: ProvenanceMap = ProvenanceMap::new();
for path in DOCUMENT_LEAVES {
provenance.insert(path, ConfigSource::Default);
}
if let Some(file) = layers.toml_file {
config = file;
for path in DOCUMENT_LEAVES {
provenance.insert(path, ConfigSource::File);
}
}
if let Some(env) = layers.env.as_ref() {
apply_documents_overrides(&mut config, env, ConfigSource::Env, Some(&mut provenance));
}
if let Some(cli) = layers.cli.as_ref() {
apply_documents_overrides(&mut config, cli, ConfigSource::Cli, Some(&mut provenance));
}
LoadedConfig { config, provenance }
}
pub fn defaults_only() -> LoadedConfig {
merge_layers(ConfigV1::with_defaults(), ConfigLayers::default())
}
const DOCUMENT_LEAVES: &[&str] = &[
"documents.enabled",
"documents.max_characters",
"documents.overlap",
"documents.embedding_preset",
"documents.embed",
"documents.language.auto_detect",
"documents.language.min_confidence",
"documents.language.detect_multiple",
"documents.reranker.enabled",
"documents.reranker.preset",
"documents.reranker.top_k",
"documents.keywords.enabled",
"documents.keywords.max_keywords",
"documents.keywords.min_score",
"documents.ner.enabled",
"documents.summarization.enabled",
"documents.summarization.strategy",
"documents.summarization.max_tokens",
"documents.output.format",
"llm.model",
"llm.api_key",
"llm.base_url",
"llm.temperature",
"llm.timeout_secs",
"llm.max_retries",
"llm.max_tokens",
];
pub(crate) fn apply_documents_overrides(
config: &mut ConfigV1,
overrides: &DocumentsCliOverrides,
source: ConfigSource,
mut provenance: Option<&mut ProvenanceMap>,
) {
let d = &mut config.documents;
if let Some(v) = overrides.enabled {
d.enabled = v;
if let Some(p) = provenance.as_mut() {
p.insert("documents.enabled", source);
}
}
if let Some(v) = overrides.max_characters {
d.max_characters = v;
if let Some(p) = provenance.as_mut() {
p.insert("documents.max_characters", source);
}
}
if let Some(v) = overrides.overlap {
d.overlap = v;
if let Some(p) = provenance.as_mut() {
p.insert("documents.overlap", source);
}
}
if let Some(v) = overrides.embedding_preset.clone() {
d.embedding_preset = v;
if let Some(p) = provenance.as_mut() {
p.insert("documents.embedding_preset", source);
}
}
if let Some(v) = overrides.embed {
d.embed = v;
if let Some(p) = provenance.as_mut() {
p.insert("documents.embed", source);
}
}
if let Some(v) = overrides.language_auto_detect {
d.language.auto_detect = v;
if let Some(p) = provenance.as_mut() {
p.insert("documents.language.auto_detect", source);
}
}
if let Some(v) = overrides.language_min_confidence {
d.language.min_confidence = v;
if let Some(p) = provenance.as_mut() {
p.insert("documents.language.min_confidence", source);
}
}
if let Some(v) = overrides.language_detect_multiple {
d.language.detect_multiple = v;
if let Some(p) = provenance.as_mut() {
p.insert("documents.language.detect_multiple", source);
}
}
if let Some(v) = overrides.reranker_enabled {
d.reranker.enabled = v;
if let Some(p) = provenance.as_mut() {
p.insert("documents.reranker.enabled", source);
}
}
if let Some(v) = overrides.reranker_preset.clone() {
d.reranker.preset = v;
if let Some(p) = provenance.as_mut() {
p.insert("documents.reranker.preset", source);
}
}
if let Some(v) = overrides.reranker_top_k {
d.reranker.top_k = v;
if let Some(p) = provenance.as_mut() {
p.insert("documents.reranker.top_k", source);
}
}
if let Some(v) = overrides.keywords_enabled {
d.keywords.enabled = v;
if let Some(p) = provenance.as_mut() {
p.insert("documents.keywords.enabled", source);
}
}
if let Some(v) = overrides.keywords_max_keywords {
d.keywords.max_keywords = v;
if let Some(p) = provenance.as_mut() {
p.insert("documents.keywords.max_keywords", source);
}
}
if let Some(v) = overrides.keywords_min_score {
d.keywords.min_score = v;
if let Some(p) = provenance.as_mut() {
p.insert("documents.keywords.min_score", source);
}
}
if let Some(v) = overrides.ner_enabled {
d.ner.enabled = v;
if let Some(p) = provenance.as_mut() {
p.insert("documents.ner.enabled", source);
}
}
if let Some(v) = overrides.summarization_enabled {
d.summarization.enabled = v;
if let Some(p) = provenance.as_mut() {
p.insert("documents.summarization.enabled", source);
}
}
if let Some(v) = overrides.summarization_strategy.as_deref() {
let applied = match v.to_ascii_lowercase().as_str() {
"extractive" => {
d.summarization.strategy = SummarizationStrategy::Extractive;
true
}
"abstractive" => {
d.summarization.strategy = SummarizationStrategy::Abstractive;
true
}
_ => {
tracing::warn!(value = %v, "unknown summarization_strategy value; ignoring");
false
}
};
if applied && let Some(p) = provenance.as_mut() {
p.insert("documents.summarization.strategy", source);
}
}
if let Some(v) = overrides.summarization_max_tokens {
d.summarization.max_tokens = Some(v);
if let Some(p) = provenance.as_mut() {
p.insert("documents.summarization.max_tokens", source);
}
}
if let Some(v) = overrides.output_format.as_deref() {
match v.to_ascii_lowercase().as_str() {
"json" => d.output.format = OutputFormat::Json,
"toon" => d.output.format = OutputFormat::Toon,
_ => return,
}
if let Some(p) = provenance.as_mut() {
p.insert("documents.output.format", source);
}
}
apply_llm_overrides(config, overrides, source, provenance);
}
fn apply_llm_overrides(
config: &mut ConfigV1,
overrides: &DocumentsCliOverrides,
source: ConfigSource,
mut provenance: Option<&mut ProvenanceMap>,
) {
let llm = &mut config.llm;
if let Some(v) = overrides.llm_model.clone() {
llm.model = v;
if let Some(p) = provenance.as_mut() {
p.insert("llm.model", source);
}
}
if let Some(v) = overrides.llm_api_key.clone() {
llm.api_key = ApiKey::Literal(v);
if let Some(p) = provenance.as_mut() {
p.insert("llm.api_key", source);
}
}
if let Some(v) = overrides.llm_base_url.clone() {
llm.base_url = Some(v);
if let Some(p) = provenance.as_mut() {
p.insert("llm.base_url", source);
}
}
if let Some(v) = overrides.llm_temperature {
llm.temperature = Some(v);
if let Some(p) = provenance.as_mut() {
p.insert("llm.temperature", source);
}
}
if let Some(v) = overrides.llm_timeout_secs {
llm.timeout_secs = Some(v);
if let Some(p) = provenance.as_mut() {
p.insert("llm.timeout_secs", source);
}
}
if let Some(v) = overrides.llm_max_retries {
llm.max_retries = Some(v);
if let Some(p) = provenance.as_mut() {
p.insert("llm.max_retries", source);
}
}
if let Some(v) = overrides.llm_max_tokens {
llm.max_tokens = Some(v);
if let Some(p) = provenance.as_mut() {
p.insert("llm.max_tokens", source);
}
}
}