use crate::providers::ProviderName;
use serde::{Deserialize, Serialize};
use super::{BeliefRevisionConfig, ConflictRecencyConfig, RpeConfig, WriteGateConfig};
fn default_graph_max_entities_per_message() -> usize {
10
}
fn default_graph_max_edges_per_message() -> usize {
15
}
fn default_graph_community_refresh_interval() -> usize {
100
}
fn default_graph_community_summary_max_prompt_bytes() -> usize {
8192
}
fn default_graph_community_summary_concurrency() -> usize {
4
}
fn default_lpa_edge_chunk_size() -> usize {
10_000
}
fn default_graph_entity_similarity_threshold() -> f32 {
0.85
}
fn default_graph_entity_ambiguous_threshold() -> f32 {
0.70
}
fn default_graph_extraction_timeout_secs() -> u64 {
15
}
fn default_graph_max_hops() -> u32 {
2
}
fn default_graph_recall_limit() -> usize {
10
}
fn default_graph_expired_edge_retention_days() -> u32 {
90
}
fn default_graph_temporal_decay_rate() -> f64 {
0.0
}
fn default_graph_edge_history_limit() -> usize {
100
}
fn default_spreading_activation_decay_lambda() -> f32 {
0.85
}
fn default_spreading_activation_max_hops() -> u32 {
3
}
fn default_spreading_activation_activation_threshold() -> f32 {
0.1
}
fn default_spreading_activation_inhibition_threshold() -> f32 {
0.8
}
fn default_spreading_activation_max_activated_nodes() -> usize {
50
}
fn default_spreading_activation_recall_timeout_ms() -> u64 {
1000
}
fn default_benna_alpha() -> f32 {
0.3
}
fn default_benna_fast_rate() -> f32 {
0.5
}
fn default_benna_slow_rate() -> f32 {
0.05
}
fn default_note_linking_similarity_threshold() -> f32 {
0.85
}
fn default_note_linking_top_k() -> usize {
10
}
fn default_note_linking_timeout_secs() -> u64 {
5
}
fn validate_temporal_decay_rate<'de, D>(deserializer: D) -> Result<f64, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = <f64 as serde::Deserialize>::deserialize(deserializer)?;
if value.is_nan() || value.is_infinite() {
return Err(serde::de::Error::custom(
"temporal_decay_rate must be a finite number",
));
}
if !(0.0..=10.0).contains(&value) {
return Err(serde::de::Error::custom(
"temporal_decay_rate must be in [0.0, 10.0]",
));
}
Ok(value)
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default)]
pub struct SpreadingActivationConfig {
pub enabled: bool,
#[serde(deserialize_with = "crate::de_helpers::de_unit_open")]
pub decay_lambda: f32,
#[serde(deserialize_with = "validate_max_hops")]
pub max_hops: u32,
pub activation_threshold: f32,
pub inhibition_threshold: f32,
pub max_activated_nodes: usize,
#[serde(default = "default_seed_structural_weight")]
pub seed_structural_weight: f32,
#[serde(default = "default_seed_community_cap")]
pub seed_community_cap: usize,
#[serde(default = "default_spreading_activation_recall_timeout_ms")]
pub recall_timeout_ms: u64,
#[serde(
default = "default_benna_alpha",
deserialize_with = "validate_benna_alpha"
)]
pub alpha: f32,
#[serde(
default = "default_benna_fast_rate",
deserialize_with = "validate_benna_rate"
)]
pub benna_fast_rate: f32,
#[serde(
default = "default_benna_slow_rate",
deserialize_with = "validate_benna_rate"
)]
pub benna_slow_rate: f32,
}
fn validate_max_hops<'de, D>(deserializer: D) -> Result<u32, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = <u32 as serde::Deserialize>::deserialize(deserializer)?;
if value == 0 {
return Err(serde::de::Error::custom("max_hops must be >= 1"));
}
Ok(value)
}
fn validate_benna_alpha<'de, D>(deserializer: D) -> Result<f32, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = <f32 as serde::Deserialize>::deserialize(deserializer)?;
if !value.is_finite() {
return Err(serde::de::Error::custom("alpha must be a finite number"));
}
if !(0.0..=1.0).contains(&value) {
return Err(serde::de::Error::custom("alpha must be in [0.0, 1.0]"));
}
Ok(value)
}
fn validate_benna_rate<'de, D>(deserializer: D) -> Result<f32, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = <f32 as serde::Deserialize>::deserialize(deserializer)?;
if !value.is_finite() {
return Err(serde::de::Error::custom(
"benna_fast_rate/benna_slow_rate must be a finite number",
));
}
if !(value > 0.0 && value <= 1.0) {
return Err(serde::de::Error::custom(
"benna_fast_rate/benna_slow_rate must be in (0.0, 1.0]",
));
}
Ok(value)
}
impl SpreadingActivationConfig {
#[must_use = "validation result must be checked"]
pub fn validate(&self) -> Result<(), String> {
if self.activation_threshold >= self.inhibition_threshold {
return Err(format!(
"activation_threshold ({}) must be < inhibition_threshold ({})",
self.activation_threshold, self.inhibition_threshold
));
}
Ok(())
}
}
fn default_seed_structural_weight() -> f32 {
0.4
}
fn default_seed_community_cap() -> usize {
3
}
impl Default for SpreadingActivationConfig {
fn default() -> Self {
Self {
enabled: false,
decay_lambda: default_spreading_activation_decay_lambda(),
max_hops: default_spreading_activation_max_hops(),
activation_threshold: default_spreading_activation_activation_threshold(),
inhibition_threshold: default_spreading_activation_inhibition_threshold(),
max_activated_nodes: default_spreading_activation_max_activated_nodes(),
seed_structural_weight: default_seed_structural_weight(),
seed_community_cap: default_seed_community_cap(),
recall_timeout_ms: default_spreading_activation_recall_timeout_ms(),
alpha: default_benna_alpha(),
benna_fast_rate: default_benna_fast_rate(),
benna_slow_rate: default_benna_slow_rate(),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default)]
pub struct NoteLinkingConfig {
pub enabled: bool,
#[serde(deserialize_with = "crate::de_helpers::de_unit_closed")]
pub similarity_threshold: f32,
pub top_k: usize,
pub timeout_secs: u64,
}
impl Default for NoteLinkingConfig {
fn default() -> Self {
Self {
enabled: false,
similarity_threshold: default_note_linking_similarity_threshold(),
top_k: default_note_linking_top_k(),
timeout_secs: default_note_linking_timeout_secs(),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default)]
pub struct EmGraphConfig {
pub enabled: bool,
pub extract_provider: ProviderName,
pub max_chain_depth: u32,
}
impl Default for EmGraphConfig {
fn default() -> Self {
Self {
enabled: false,
extract_provider: ProviderName::default(),
max_chain_depth: 3,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum GraphRetrievalStrategy {
#[default]
Synapse,
Bfs,
#[serde(rename = "astar")]
AStar,
WaterCircles,
BeamSearch,
Hybrid,
}
fn default_beam_width() -> usize {
10
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct BeamSearchConfig {
#[serde(default = "default_beam_width")]
pub beam_width: usize,
}
impl Default for BeamSearchConfig {
fn default() -> Self {
Self {
beam_width: default_beam_width(),
}
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct WaterCirclesConfig {
#[serde(default)]
pub ring_limit: usize,
}
fn default_evolution_sweep_interval() -> usize {
50
}
fn default_confidence_prune_threshold() -> f32 {
0.1
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ExperienceConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub evolution_sweep_enabled: bool,
#[serde(default = "default_confidence_prune_threshold")]
pub confidence_prune_threshold: f32,
#[serde(default = "default_evolution_sweep_interval")]
pub evolution_sweep_interval: usize,
}
impl Default for ExperienceConfig {
fn default() -> Self {
Self {
enabled: false,
evolution_sweep_enabled: false,
confidence_prune_threshold: default_confidence_prune_threshold(),
evolution_sweep_interval: default_evolution_sweep_interval(),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default)]
#[allow(clippy::struct_excessive_bools)]
pub struct GraphConfig {
pub enabled: bool,
pub extract_model: String,
#[serde(default = "default_graph_max_entities_per_message")]
pub max_entities_per_message: usize,
#[serde(default = "default_graph_max_edges_per_message")]
pub max_edges_per_message: usize,
#[serde(default = "default_graph_community_refresh_interval")]
pub community_refresh_interval: usize,
#[serde(default = "default_graph_entity_similarity_threshold")]
pub entity_similarity_threshold: f32,
#[serde(default = "default_graph_extraction_timeout_secs")]
pub extraction_timeout_secs: u64,
#[serde(default)]
pub use_embedding_resolution: bool,
#[serde(default = "default_graph_entity_ambiguous_threshold")]
pub entity_ambiguous_threshold: f32,
#[serde(default = "default_graph_max_hops")]
pub max_hops: u32,
#[serde(default = "default_graph_recall_limit")]
pub recall_limit: usize,
#[serde(default = "default_graph_expired_edge_retention_days")]
pub expired_edge_retention_days: u32,
#[serde(default)]
pub max_entities: usize,
#[serde(default = "default_graph_community_summary_max_prompt_bytes")]
pub community_summary_max_prompt_bytes: usize,
#[serde(default = "default_graph_community_summary_concurrency")]
pub community_summary_concurrency: usize,
#[serde(default = "default_lpa_edge_chunk_size")]
pub lpa_edge_chunk_size: usize,
#[serde(
default = "default_graph_temporal_decay_rate",
deserialize_with = "validate_temporal_decay_rate"
)]
pub temporal_decay_rate: f64,
#[serde(default = "default_graph_edge_history_limit")]
pub edge_history_limit: usize,
#[serde(default)]
pub note_linking: NoteLinkingConfig,
#[serde(default)]
pub spreading_activation: SpreadingActivationConfig,
#[serde(default)]
pub retrieval_strategy: GraphRetrievalStrategy,
#[serde(default)]
pub extract_provider: ProviderName,
#[serde(default)]
pub strategy_classifier_provider: Option<ProviderName>,
#[serde(default)]
pub beam_search: BeamSearchConfig,
#[serde(default)]
pub watercircles: WaterCirclesConfig,
#[serde(default)]
pub experience: ExperienceConfig,
#[serde(
default = "default_link_weight_decay_lambda",
deserialize_with = "crate::de_helpers::de_unit_open"
)]
pub link_weight_decay_lambda: f64,
#[serde(default = "default_link_weight_decay_interval_secs")]
pub link_weight_decay_interval_secs: u64,
#[serde(default)]
pub belief_revision: BeliefRevisionConfig,
#[serde(default)]
pub rpe: RpeConfig,
#[serde(default = "default_graph_pool_size")]
pub pool_size: u32,
#[serde(default)]
pub apex_mem: ApexMemConfig,
#[serde(default = "default_graph_llm_timeout_secs")]
pub llm_timeout_secs: u64,
#[serde(default)]
pub query_sensitive_cost: bool,
#[serde(default)]
pub implicit_conflict: ImplicitConflictConfig,
#[serde(default)]
pub write_gate: WriteGateConfig,
#[serde(default)]
pub conflict_recency: ConflictRecencyConfig,
#[serde(default = "default_true")]
pub recall_include_imported: bool,
}
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Default,
serde::Serialize,
serde::Deserialize,
schemars::JsonSchema,
)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum SimilarityMethod {
#[default]
Levenshtein,
Embedding,
Both,
}
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Default,
serde::Serialize,
serde::Deserialize,
schemars::JsonSchema,
)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ConflictResolutionStrategy {
#[default]
FlagOnly,
Recency,
Confidence,
Llm,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
#[serde(default)]
pub struct ConsolidationDaemonConfig {
pub enabled: bool,
#[serde(default = "default_ic_daemon_interval_secs")]
pub interval_seconds: u64,
#[serde(default = "default_ic_daemon_batch_size")]
pub batch_size: usize,
}
impl Default for ConsolidationDaemonConfig {
fn default() -> Self {
Self {
enabled: false,
interval_seconds: default_ic_daemon_interval_secs(),
batch_size: default_ic_daemon_batch_size(),
}
}
}
fn default_ic_daemon_interval_secs() -> u64 {
7200
}
fn default_ic_daemon_batch_size() -> usize {
100
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct ImplicitConflictConfig {
pub enabled: bool,
#[serde(default)]
pub similarity_method: SimilarityMethod,
#[serde(default = "default_ic_similarity_threshold")]
pub conflict_similarity_threshold: f64,
#[serde(default)]
pub resolution_strategy: ConflictResolutionStrategy,
#[serde(default)]
pub implicit_conflict_provider: crate::providers::ProviderName,
#[serde(default = "default_ic_llm_timeout_ms")]
pub conflict_llm_timeout_ms: u64,
#[serde(default = "default_ic_candidate_ttl_days")]
pub candidate_ttl_days: u32,
#[serde(default = "default_ic_propagation_depth")]
pub propagation_depth: u32,
#[serde(default)]
pub consolidation_daemon: ConsolidationDaemonConfig,
}
impl Default for ImplicitConflictConfig {
fn default() -> Self {
Self {
enabled: false,
similarity_method: SimilarityMethod::default(),
conflict_similarity_threshold: default_ic_similarity_threshold(),
resolution_strategy: ConflictResolutionStrategy::default(),
implicit_conflict_provider: crate::providers::ProviderName::default(),
conflict_llm_timeout_ms: default_ic_llm_timeout_ms(),
candidate_ttl_days: default_ic_candidate_ttl_days(),
propagation_depth: default_ic_propagation_depth(),
consolidation_daemon: ConsolidationDaemonConfig::default(),
}
}
}
fn default_ic_similarity_threshold() -> f64 {
0.80
}
fn default_ic_llm_timeout_ms() -> u64 {
800
}
fn default_ic_candidate_ttl_days() -> u32 {
30
}
fn default_ic_propagation_depth() -> u32 {
2
}
fn default_graph_pool_size() -> u32 {
3
}
fn default_graph_llm_timeout_secs() -> u64 {
30
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
#[serde(default)]
pub struct ApexMemConfig {
pub enabled: bool,
}
fn default_quality_gate_threshold() -> f32 {
0.55
}
fn default_quality_gate_recent_window() -> usize {
32
}
fn default_quality_gate_contradiction_grace_seconds() -> u64 {
300
}
fn default_quality_gate_information_value_weight() -> f32 {
0.4
}
fn default_quality_gate_reference_completeness_weight() -> f32 {
0.3
}
fn default_quality_gate_contradiction_weight() -> f32 {
0.3
}
fn default_quality_gate_rejection_rate_alarm_ratio() -> f32 {
0.35
}
fn default_quality_gate_llm_timeout_ms() -> u64 {
500
}
fn default_quality_gate_llm_weight() -> f32 {
0.5
}
fn default_quality_gate_reference_check_lang_en() -> bool {
true
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct WriteQualityGateConfig {
pub enabled: bool,
#[serde(default = "default_quality_gate_threshold")]
pub threshold: f32,
#[serde(default = "default_quality_gate_recent_window")]
pub recent_window: usize,
#[serde(default = "default_quality_gate_contradiction_grace_seconds")]
pub contradiction_grace_seconds: u64,
#[serde(default = "default_quality_gate_information_value_weight")]
pub information_value_weight: f32,
#[serde(default = "default_quality_gate_reference_completeness_weight")]
pub reference_completeness_weight: f32,
#[serde(default = "default_quality_gate_contradiction_weight")]
pub contradiction_weight: f32,
#[serde(default = "default_quality_gate_rejection_rate_alarm_ratio")]
pub rejection_rate_alarm_ratio: f32,
#[serde(default)]
pub quality_gate_provider: ProviderName,
#[serde(default = "default_quality_gate_llm_timeout_ms")]
pub llm_timeout_ms: u64,
#[serde(default = "default_quality_gate_llm_weight")]
pub llm_weight: f32,
#[serde(default = "default_quality_gate_reference_check_lang_en")]
pub reference_check_lang_en: bool,
}
impl Default for WriteQualityGateConfig {
fn default() -> Self {
Self {
enabled: false,
threshold: default_quality_gate_threshold(),
recent_window: default_quality_gate_recent_window(),
contradiction_grace_seconds: default_quality_gate_contradiction_grace_seconds(),
information_value_weight: default_quality_gate_information_value_weight(),
reference_completeness_weight: default_quality_gate_reference_completeness_weight(),
contradiction_weight: default_quality_gate_contradiction_weight(),
rejection_rate_alarm_ratio: default_quality_gate_rejection_rate_alarm_ratio(),
quality_gate_provider: ProviderName::default(),
llm_timeout_ms: default_quality_gate_llm_timeout_ms(),
llm_weight: default_quality_gate_llm_weight(),
reference_check_lang_en: default_quality_gate_reference_check_lang_en(),
}
}
}
impl Default for GraphConfig {
fn default() -> Self {
Self {
enabled: false,
extract_model: String::new(),
max_entities_per_message: default_graph_max_entities_per_message(),
max_edges_per_message: default_graph_max_edges_per_message(),
community_refresh_interval: default_graph_community_refresh_interval(),
entity_similarity_threshold: default_graph_entity_similarity_threshold(),
extraction_timeout_secs: default_graph_extraction_timeout_secs(),
use_embedding_resolution: false,
entity_ambiguous_threshold: default_graph_entity_ambiguous_threshold(),
max_hops: default_graph_max_hops(),
recall_limit: default_graph_recall_limit(),
expired_edge_retention_days: default_graph_expired_edge_retention_days(),
max_entities: 0,
community_summary_max_prompt_bytes: default_graph_community_summary_max_prompt_bytes(),
community_summary_concurrency: default_graph_community_summary_concurrency(),
lpa_edge_chunk_size: default_lpa_edge_chunk_size(),
temporal_decay_rate: default_graph_temporal_decay_rate(),
edge_history_limit: default_graph_edge_history_limit(),
note_linking: NoteLinkingConfig::default(),
spreading_activation: SpreadingActivationConfig::default(),
retrieval_strategy: GraphRetrievalStrategy::default(),
extract_provider: ProviderName::default(),
strategy_classifier_provider: None,
beam_search: BeamSearchConfig::default(),
watercircles: WaterCirclesConfig::default(),
experience: ExperienceConfig::default(),
link_weight_decay_lambda: default_link_weight_decay_lambda(),
link_weight_decay_interval_secs: default_link_weight_decay_interval_secs(),
belief_revision: BeliefRevisionConfig::default(),
rpe: RpeConfig::default(),
pool_size: default_graph_pool_size(),
apex_mem: ApexMemConfig::default(),
llm_timeout_secs: default_graph_llm_timeout_secs(),
query_sensitive_cost: false,
implicit_conflict: ImplicitConflictConfig::default(),
write_gate: WriteGateConfig::default(),
conflict_recency: ConflictRecencyConfig::default(),
recall_include_imported: true,
}
}
}
fn default_true() -> bool {
true
}
fn default_link_weight_decay_lambda() -> f64 {
0.95
}
fn default_link_weight_decay_interval_secs() -> u64 {
86400
}