1use crate::providers::ProviderName;
10use serde::{Deserialize, Serialize};
11
12use super::{BeliefRevisionConfig, ConflictRecencyConfig, RpeConfig, WriteGateConfig};
13
14fn default_graph_max_entities_per_message() -> usize {
15 10
16}
17
18fn default_graph_max_edges_per_message() -> usize {
19 15
20}
21
22fn default_graph_community_refresh_interval() -> usize {
23 100
24}
25
26fn default_graph_community_summary_max_prompt_bytes() -> usize {
27 8192
28}
29
30fn default_graph_community_summary_concurrency() -> usize {
31 4
32}
33
34fn default_lpa_edge_chunk_size() -> usize {
35 10_000
36}
37
38fn default_graph_entity_similarity_threshold() -> f32 {
39 0.85
40}
41
42fn default_graph_entity_ambiguous_threshold() -> f32 {
43 0.70
44}
45
46fn default_graph_extraction_timeout_secs() -> u64 {
47 15
48}
49
50fn default_graph_max_hops() -> u32 {
51 2
52}
53
54fn default_graph_recall_limit() -> usize {
55 10
56}
57
58fn default_graph_expired_edge_retention_days() -> u32 {
59 90
60}
61
62fn default_graph_temporal_decay_rate() -> f64 {
63 0.0
64}
65
66fn default_graph_edge_history_limit() -> usize {
67 100
68}
69
70fn default_spreading_activation_decay_lambda() -> f32 {
71 0.85
72}
73
74fn default_spreading_activation_max_hops() -> u32 {
75 3
76}
77
78fn default_spreading_activation_activation_threshold() -> f32 {
79 0.1
80}
81
82fn default_spreading_activation_inhibition_threshold() -> f32 {
83 0.8
84}
85
86fn default_spreading_activation_max_activated_nodes() -> usize {
87 50
88}
89
90fn default_spreading_activation_recall_timeout_ms() -> u64 {
91 1000
92}
93
94fn default_benna_alpha() -> f32 {
95 0.3
96}
97
98fn default_benna_fast_rate() -> f32 {
99 0.5
100}
101
102fn default_benna_slow_rate() -> f32 {
103 0.05
104}
105
106fn default_note_linking_similarity_threshold() -> f32 {
107 0.85
108}
109
110fn default_note_linking_top_k() -> usize {
111 10
112}
113
114fn default_note_linking_timeout_secs() -> u64 {
115 5
116}
117
118fn validate_temporal_decay_rate<'de, D>(deserializer: D) -> Result<f64, D::Error>
119where
120 D: serde::Deserializer<'de>,
121{
122 let value = <f64 as serde::Deserialize>::deserialize(deserializer)?;
123 if value.is_nan() || value.is_infinite() {
124 return Err(serde::de::Error::custom(
125 "temporal_decay_rate must be a finite number",
126 ));
127 }
128 if !(0.0..=10.0).contains(&value) {
129 return Err(serde::de::Error::custom(
130 "temporal_decay_rate must be in [0.0, 10.0]",
131 ));
132 }
133 Ok(value)
134}
135
136#[derive(Debug, Clone, Deserialize, Serialize)]
150#[serde(default)]
151pub struct SpreadingActivationConfig {
152 pub enabled: bool,
154 #[serde(deserialize_with = "crate::de_helpers::de_unit_open")]
156 pub decay_lambda: f32,
157 #[serde(deserialize_with = "validate_max_hops")]
159 pub max_hops: u32,
160 pub activation_threshold: f32,
162 pub inhibition_threshold: f32,
164 pub max_activated_nodes: usize,
166 #[serde(default = "default_seed_structural_weight")]
168 pub seed_structural_weight: f32,
169 #[serde(default = "default_seed_community_cap")]
171 pub seed_community_cap: usize,
172 #[serde(default = "default_spreading_activation_recall_timeout_ms")]
176 pub recall_timeout_ms: u64,
177 #[serde(
182 default = "default_benna_alpha",
183 deserialize_with = "validate_benna_alpha"
184 )]
185 pub alpha: f32,
186 #[serde(
190 default = "default_benna_fast_rate",
191 deserialize_with = "validate_benna_rate"
192 )]
193 pub benna_fast_rate: f32,
194 #[serde(
198 default = "default_benna_slow_rate",
199 deserialize_with = "validate_benna_rate"
200 )]
201 pub benna_slow_rate: f32,
202}
203
204fn validate_max_hops<'de, D>(deserializer: D) -> Result<u32, D::Error>
205where
206 D: serde::Deserializer<'de>,
207{
208 let value = <u32 as serde::Deserialize>::deserialize(deserializer)?;
209 if value == 0 {
210 return Err(serde::de::Error::custom("max_hops must be >= 1"));
211 }
212 Ok(value)
213}
214
215fn validate_benna_alpha<'de, D>(deserializer: D) -> Result<f32, D::Error>
216where
217 D: serde::Deserializer<'de>,
218{
219 let value = <f32 as serde::Deserialize>::deserialize(deserializer)?;
220 if !value.is_finite() {
221 return Err(serde::de::Error::custom("alpha must be a finite number"));
222 }
223 if !(0.0..=1.0).contains(&value) {
224 return Err(serde::de::Error::custom("alpha must be in [0.0, 1.0]"));
225 }
226 Ok(value)
227}
228
229fn validate_benna_rate<'de, D>(deserializer: D) -> Result<f32, D::Error>
230where
231 D: serde::Deserializer<'de>,
232{
233 let value = <f32 as serde::Deserialize>::deserialize(deserializer)?;
234 if !value.is_finite() {
235 return Err(serde::de::Error::custom(
236 "benna_fast_rate/benna_slow_rate must be a finite number",
237 ));
238 }
239 if !(value > 0.0 && value <= 1.0) {
240 return Err(serde::de::Error::custom(
241 "benna_fast_rate/benna_slow_rate must be in (0.0, 1.0]",
242 ));
243 }
244 Ok(value)
245}
246
247impl SpreadingActivationConfig {
248 #[must_use = "validation result must be checked"]
254 pub fn validate(&self) -> Result<(), String> {
255 if self.activation_threshold >= self.inhibition_threshold {
256 return Err(format!(
257 "activation_threshold ({}) must be < inhibition_threshold ({})",
258 self.activation_threshold, self.inhibition_threshold
259 ));
260 }
261 Ok(())
262 }
263}
264
265fn default_seed_structural_weight() -> f32 {
266 0.4
267}
268
269fn default_seed_community_cap() -> usize {
270 3
271}
272
273impl Default for SpreadingActivationConfig {
274 fn default() -> Self {
275 Self {
276 enabled: false,
277 decay_lambda: default_spreading_activation_decay_lambda(),
278 max_hops: default_spreading_activation_max_hops(),
279 activation_threshold: default_spreading_activation_activation_threshold(),
280 inhibition_threshold: default_spreading_activation_inhibition_threshold(),
281 max_activated_nodes: default_spreading_activation_max_activated_nodes(),
282 seed_structural_weight: default_seed_structural_weight(),
283 seed_community_cap: default_seed_community_cap(),
284 recall_timeout_ms: default_spreading_activation_recall_timeout_ms(),
285 alpha: default_benna_alpha(),
286 benna_fast_rate: default_benna_fast_rate(),
287 benna_slow_rate: default_benna_slow_rate(),
288 }
289 }
290}
291
292#[derive(Debug, Clone, Deserialize, Serialize)]
298#[serde(default)]
299pub struct NoteLinkingConfig {
300 pub enabled: bool,
302 #[serde(deserialize_with = "crate::de_helpers::de_unit_closed")]
304 pub similarity_threshold: f32,
305 pub top_k: usize,
307 pub timeout_secs: u64,
309}
310
311impl Default for NoteLinkingConfig {
312 fn default() -> Self {
313 Self {
314 enabled: false,
315 similarity_threshold: default_note_linking_similarity_threshold(),
316 top_k: default_note_linking_top_k(),
317 timeout_secs: default_note_linking_timeout_secs(),
318 }
319 }
320}
321
322#[derive(Debug, Clone, Deserialize, Serialize)]
338#[serde(default)]
339pub struct EmGraphConfig {
340 pub enabled: bool,
342 pub extract_provider: ProviderName,
345 pub max_chain_depth: u32,
347}
348
349impl Default for EmGraphConfig {
350 fn default() -> Self {
351 Self {
352 enabled: false,
353 extract_provider: ProviderName::default(),
354 max_chain_depth: 3,
355 }
356 }
357}
358
359#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
364#[serde(rename_all = "snake_case")]
365#[non_exhaustive]
366pub enum GraphRetrievalStrategy {
367 #[default]
369 Synapse,
370 Bfs,
372 #[serde(rename = "astar")]
374 AStar,
375 WaterCircles,
377 BeamSearch,
379 Hybrid,
381}
382
383fn default_beam_width() -> usize {
384 10
385}
386
387#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
392pub struct BeamSearchConfig {
393 #[serde(default = "default_beam_width")]
395 pub beam_width: usize,
396}
397
398impl Default for BeamSearchConfig {
399 fn default() -> Self {
400 Self {
401 beam_width: default_beam_width(),
402 }
403 }
404}
405
406#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
410pub struct WaterCirclesConfig {
411 #[serde(default)]
413 pub ring_limit: usize,
414}
415
416fn default_evolution_sweep_interval() -> usize {
417 50
418}
419
420fn default_confidence_prune_threshold() -> f32 {
421 0.1
422}
423
424#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
428pub struct ExperienceConfig {
429 #[serde(default)]
431 pub enabled: bool,
432 #[serde(default)]
434 pub evolution_sweep_enabled: bool,
435 #[serde(default = "default_confidence_prune_threshold")]
437 pub confidence_prune_threshold: f32,
438 #[serde(default = "default_evolution_sweep_interval")]
440 pub evolution_sweep_interval: usize,
441}
442
443impl Default for ExperienceConfig {
444 fn default() -> Self {
445 Self {
446 enabled: false,
447 evolution_sweep_enabled: false,
448 confidence_prune_threshold: default_confidence_prune_threshold(),
449 evolution_sweep_interval: default_evolution_sweep_interval(),
450 }
451 }
452}
453
454#[derive(Debug, Clone, Deserialize, Serialize)]
463#[serde(default)]
464#[allow(clippy::struct_excessive_bools)]
465pub struct GraphConfig {
466 pub enabled: bool,
467 pub extract_model: String,
468 #[serde(default = "default_graph_max_entities_per_message")]
469 pub max_entities_per_message: usize,
470 #[serde(default = "default_graph_max_edges_per_message")]
471 pub max_edges_per_message: usize,
472 #[serde(default = "default_graph_community_refresh_interval")]
473 pub community_refresh_interval: usize,
474 #[serde(default = "default_graph_entity_similarity_threshold")]
475 pub entity_similarity_threshold: f32,
476 #[serde(default = "default_graph_extraction_timeout_secs")]
477 pub extraction_timeout_secs: u64,
478 #[serde(default)]
479 pub use_embedding_resolution: bool,
480 #[serde(default = "default_graph_entity_ambiguous_threshold")]
481 pub entity_ambiguous_threshold: f32,
482 #[serde(default = "default_graph_max_hops")]
483 pub max_hops: u32,
484 #[serde(default = "default_graph_recall_limit")]
485 pub recall_limit: usize,
486 #[serde(default = "default_graph_expired_edge_retention_days")]
488 pub expired_edge_retention_days: u32,
489 #[serde(default)]
491 pub max_entities: usize,
492 #[serde(default = "default_graph_community_summary_max_prompt_bytes")]
494 pub community_summary_max_prompt_bytes: usize,
495 #[serde(default = "default_graph_community_summary_concurrency")]
497 pub community_summary_concurrency: usize,
498 #[serde(default = "default_lpa_edge_chunk_size")]
501 pub lpa_edge_chunk_size: usize,
502 #[serde(
508 default = "default_graph_temporal_decay_rate",
509 deserialize_with = "validate_temporal_decay_rate"
510 )]
511 pub temporal_decay_rate: f64,
512 #[serde(default = "default_graph_edge_history_limit")]
518 pub edge_history_limit: usize,
519 #[serde(default)]
525 pub note_linking: NoteLinkingConfig,
526 #[serde(default)]
531 pub spreading_activation: SpreadingActivationConfig,
532 #[serde(default)]
537 pub retrieval_strategy: GraphRetrievalStrategy,
538 #[serde(default)]
550 pub extract_provider: ProviderName,
551 #[serde(default)]
554 pub strategy_classifier_provider: Option<ProviderName>,
555 #[serde(default)]
557 pub beam_search: BeamSearchConfig,
558 #[serde(default)]
560 pub watercircles: WaterCirclesConfig,
561 #[serde(default)]
563 pub experience: ExperienceConfig,
564 #[serde(
567 default = "default_link_weight_decay_lambda",
568 deserialize_with = "crate::de_helpers::de_unit_open"
569 )]
570 pub link_weight_decay_lambda: f64,
571 #[serde(default = "default_link_weight_decay_interval_secs")]
573 pub link_weight_decay_interval_secs: u64,
574 #[serde(default)]
580 pub belief_revision: BeliefRevisionConfig,
581 #[serde(default)]
586 pub rpe: RpeConfig,
587 #[serde(default = "default_graph_pool_size")]
593 pub pool_size: u32,
594 #[serde(default)]
599 pub apex_mem: ApexMemConfig,
600 #[serde(default = "default_graph_llm_timeout_secs")]
602 pub llm_timeout_secs: u64,
603 #[serde(default)]
617 pub query_sensitive_cost: bool,
618
619 #[serde(default)]
624 pub implicit_conflict: ImplicitConflictConfig,
625 #[serde(default)]
630 pub write_gate: WriteGateConfig,
631 #[serde(default)]
635 pub conflict_recency: ConflictRecencyConfig,
636 #[serde(default = "default_true")]
645 pub recall_include_imported: bool,
646}
647
648#[derive(
650 Debug,
651 Clone,
652 Copy,
653 PartialEq,
654 Eq,
655 Default,
656 serde::Serialize,
657 serde::Deserialize,
658 schemars::JsonSchema,
659)]
660#[serde(rename_all = "snake_case")]
661#[non_exhaustive]
662pub enum SimilarityMethod {
663 #[default]
665 Levenshtein,
666 Embedding,
668 Both,
670}
671
672#[derive(
674 Debug,
675 Clone,
676 Copy,
677 PartialEq,
678 Eq,
679 Default,
680 serde::Serialize,
681 serde::Deserialize,
682 schemars::JsonSchema,
683)]
684#[serde(rename_all = "snake_case")]
685#[non_exhaustive]
686pub enum ConflictResolutionStrategy {
687 #[default]
689 FlagOnly,
690 Recency,
692 Confidence,
694 Llm,
696}
697
698#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
700#[serde(default)]
701pub struct ConsolidationDaemonConfig {
702 pub enabled: bool,
704 #[serde(default = "default_ic_daemon_interval_secs")]
706 pub interval_seconds: u64,
707 #[serde(default = "default_ic_daemon_batch_size")]
709 pub batch_size: usize,
710}
711
712impl Default for ConsolidationDaemonConfig {
713 fn default() -> Self {
714 Self {
715 enabled: false,
716 interval_seconds: default_ic_daemon_interval_secs(),
717 batch_size: default_ic_daemon_batch_size(),
718 }
719 }
720}
721
722fn default_ic_daemon_interval_secs() -> u64 {
723 7200
724}
725
726fn default_ic_daemon_batch_size() -> usize {
727 100
728}
729
730#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
749#[serde(default)]
750pub struct ImplicitConflictConfig {
751 pub enabled: bool,
753 #[serde(default)]
755 pub similarity_method: SimilarityMethod,
756 #[serde(default = "default_ic_similarity_threshold")]
758 pub conflict_similarity_threshold: f64,
759 #[serde(default)]
761 pub resolution_strategy: ConflictResolutionStrategy,
762 #[serde(default)]
764 pub implicit_conflict_provider: crate::providers::ProviderName,
765 #[serde(default = "default_ic_llm_timeout_ms")]
767 pub conflict_llm_timeout_ms: u64,
768 #[serde(default = "default_ic_candidate_ttl_days")]
770 pub candidate_ttl_days: u32,
771 #[serde(default = "default_ic_propagation_depth")]
773 pub propagation_depth: u32,
774 #[serde(default)]
776 pub consolidation_daemon: ConsolidationDaemonConfig,
777}
778
779impl Default for ImplicitConflictConfig {
780 fn default() -> Self {
781 Self {
782 enabled: false,
783 similarity_method: SimilarityMethod::default(),
784 conflict_similarity_threshold: default_ic_similarity_threshold(),
785 resolution_strategy: ConflictResolutionStrategy::default(),
786 implicit_conflict_provider: crate::providers::ProviderName::default(),
787 conflict_llm_timeout_ms: default_ic_llm_timeout_ms(),
788 candidate_ttl_days: default_ic_candidate_ttl_days(),
789 propagation_depth: default_ic_propagation_depth(),
790 consolidation_daemon: ConsolidationDaemonConfig::default(),
791 }
792 }
793}
794
795fn default_ic_similarity_threshold() -> f64 {
796 0.80
797}
798
799fn default_ic_llm_timeout_ms() -> u64 {
800 800
801}
802
803fn default_ic_candidate_ttl_days() -> u32 {
804 30
805}
806
807fn default_ic_propagation_depth() -> u32 {
808 2
809}
810
811fn default_graph_pool_size() -> u32 {
812 3
813}
814
815fn default_graph_llm_timeout_secs() -> u64 {
816 30
817}
818
819#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
827#[serde(default)]
828pub struct ApexMemConfig {
829 pub enabled: bool,
831}
832
833fn default_quality_gate_threshold() -> f32 {
834 0.55
835}
836
837fn default_quality_gate_recent_window() -> usize {
838 32
839}
840
841fn default_quality_gate_contradiction_grace_seconds() -> u64 {
842 300
843}
844
845fn default_quality_gate_information_value_weight() -> f32 {
846 0.4
847}
848
849fn default_quality_gate_reference_completeness_weight() -> f32 {
850 0.3
851}
852
853fn default_quality_gate_contradiction_weight() -> f32 {
854 0.3
855}
856
857fn default_quality_gate_rejection_rate_alarm_ratio() -> f32 {
858 0.35
859}
860
861fn default_quality_gate_llm_timeout_ms() -> u64 {
862 500
863}
864
865fn default_quality_gate_llm_weight() -> f32 {
866 0.5
867}
868
869fn default_quality_gate_reference_check_lang_en() -> bool {
870 true
871}
872
873#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
881#[serde(default)]
882pub struct WriteQualityGateConfig {
883 pub enabled: bool,
885 #[serde(default = "default_quality_gate_threshold")]
887 pub threshold: f32,
888 #[serde(default = "default_quality_gate_recent_window")]
890 pub recent_window: usize,
891 #[serde(default = "default_quality_gate_contradiction_grace_seconds")]
893 pub contradiction_grace_seconds: u64,
894 #[serde(default = "default_quality_gate_information_value_weight")]
896 pub information_value_weight: f32,
897 #[serde(default = "default_quality_gate_reference_completeness_weight")]
899 pub reference_completeness_weight: f32,
900 #[serde(default = "default_quality_gate_contradiction_weight")]
902 pub contradiction_weight: f32,
903 #[serde(default = "default_quality_gate_rejection_rate_alarm_ratio")]
905 pub rejection_rate_alarm_ratio: f32,
906 #[serde(default)]
908 pub quality_gate_provider: ProviderName,
909 #[serde(default = "default_quality_gate_llm_timeout_ms")]
911 pub llm_timeout_ms: u64,
912 #[serde(default = "default_quality_gate_llm_weight")]
914 pub llm_weight: f32,
915 #[serde(default = "default_quality_gate_reference_check_lang_en")]
917 pub reference_check_lang_en: bool,
918}
919
920impl Default for WriteQualityGateConfig {
921 fn default() -> Self {
922 Self {
923 enabled: false,
924 threshold: default_quality_gate_threshold(),
925 recent_window: default_quality_gate_recent_window(),
926 contradiction_grace_seconds: default_quality_gate_contradiction_grace_seconds(),
927 information_value_weight: default_quality_gate_information_value_weight(),
928 reference_completeness_weight: default_quality_gate_reference_completeness_weight(),
929 contradiction_weight: default_quality_gate_contradiction_weight(),
930 rejection_rate_alarm_ratio: default_quality_gate_rejection_rate_alarm_ratio(),
931 quality_gate_provider: ProviderName::default(),
932 llm_timeout_ms: default_quality_gate_llm_timeout_ms(),
933 llm_weight: default_quality_gate_llm_weight(),
934 reference_check_lang_en: default_quality_gate_reference_check_lang_en(),
935 }
936 }
937}
938
939impl Default for GraphConfig {
940 fn default() -> Self {
941 Self {
942 enabled: false,
943 extract_model: String::new(),
944 max_entities_per_message: default_graph_max_entities_per_message(),
945 max_edges_per_message: default_graph_max_edges_per_message(),
946 community_refresh_interval: default_graph_community_refresh_interval(),
947 entity_similarity_threshold: default_graph_entity_similarity_threshold(),
948 extraction_timeout_secs: default_graph_extraction_timeout_secs(),
949 use_embedding_resolution: false,
950 entity_ambiguous_threshold: default_graph_entity_ambiguous_threshold(),
951 max_hops: default_graph_max_hops(),
952 recall_limit: default_graph_recall_limit(),
953 expired_edge_retention_days: default_graph_expired_edge_retention_days(),
954 max_entities: 0,
955 community_summary_max_prompt_bytes: default_graph_community_summary_max_prompt_bytes(),
956 community_summary_concurrency: default_graph_community_summary_concurrency(),
957 lpa_edge_chunk_size: default_lpa_edge_chunk_size(),
958 temporal_decay_rate: default_graph_temporal_decay_rate(),
959 edge_history_limit: default_graph_edge_history_limit(),
960 note_linking: NoteLinkingConfig::default(),
961 spreading_activation: SpreadingActivationConfig::default(),
962 retrieval_strategy: GraphRetrievalStrategy::default(),
963 extract_provider: ProviderName::default(),
964 strategy_classifier_provider: None,
965 beam_search: BeamSearchConfig::default(),
966 watercircles: WaterCirclesConfig::default(),
967 experience: ExperienceConfig::default(),
968 link_weight_decay_lambda: default_link_weight_decay_lambda(),
969 link_weight_decay_interval_secs: default_link_weight_decay_interval_secs(),
970 belief_revision: BeliefRevisionConfig::default(),
971 rpe: RpeConfig::default(),
972 pool_size: default_graph_pool_size(),
973 apex_mem: ApexMemConfig::default(),
974 llm_timeout_secs: default_graph_llm_timeout_secs(),
975 query_sensitive_cost: false,
976 implicit_conflict: ImplicitConflictConfig::default(),
977 write_gate: WriteGateConfig::default(),
978 conflict_recency: ConflictRecencyConfig::default(),
979 recall_include_imported: true,
980 }
981 }
982}
983
984fn default_true() -> bool {
985 true
986}
987
988fn default_link_weight_decay_lambda() -> f64 {
989 0.95
990}
991
992fn default_link_weight_decay_interval_secs() -> u64 {
993 86400
994}