Skip to main content

weavatrix_semantic/
config.rs

1use crate::{Result, SemanticError};
2use weavatrix_graph::Confidence;
3
4/// Determines how directed top-K choices become semantic graph edges.
5#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
6pub enum SelectionMode {
7    /// Keep only pairs where both endpoints selected each other.
8    #[default]
9    Mutual,
10    /// Keep pairs where either endpoint selected the other.
11    Union,
12    /// Keep each source's selected targets as directional recommendations.
13    Directed,
14}
15
16impl SelectionMode {
17    pub(crate) const fn as_str(self) -> &'static str {
18        match self {
19            Self::Mutual => "mutual",
20            Self::Union => "union",
21            Self::Directed => "directed",
22        }
23    }
24}
25
26/// Model-specific semantic-linking configuration shared by every backend.
27#[derive(Debug, Clone, PartialEq)]
28pub struct LinkConfig {
29    model: String,
30    min_similarity: f64,
31    top_k: usize,
32    selection: SelectionMode,
33    confidence: Confidence,
34    max_vectors: usize,
35}
36
37impl LinkConfig {
38    /// Creates configuration with explicit model-specific threshold and top-K.
39    #[must_use]
40    pub fn new(model: impl Into<String>, min_similarity: f64, top_k: usize) -> Self {
41        Self {
42            model: model.into(),
43            min_similarity,
44            top_k,
45            selection: SelectionMode::Mutual,
46            confidence: Confidence::Low,
47            max_vectors: usize::MAX,
48        }
49    }
50
51    /// Changes pair selection semantics.
52    #[must_use]
53    pub const fn with_selection(mut self, selection: SelectionMode) -> Self {
54        self.selection = selection;
55        self
56    }
57
58    /// Sets the confidence attached to inferred graph evidence.
59    #[must_use]
60    pub const fn with_confidence(mut self, confidence: Confidence) -> Self {
61        self.confidence = confidence;
62        self
63    }
64
65    /// Sets an optional caller-defined safety bound on the input size.
66    ///
67    /// The default is [`usize::MAX`], so the linker does not impose a fixed
68    /// vector-count limit. This guard can still be useful for controlling the
69    /// quadratic comparison cost in latency-sensitive applications.
70    #[must_use]
71    pub const fn with_max_vectors(mut self, max_vectors: usize) -> Self {
72        self.max_vectors = max_vectors;
73        self
74    }
75
76    /// Embedding model identifier stored on every emitted edge.
77    #[must_use]
78    pub fn model(&self) -> &str {
79        &self.model
80    }
81
82    /// Inclusive cosine threshold in the range `[0, 1]`.
83    #[must_use]
84    pub const fn min_similarity(&self) -> f64 {
85        self.min_similarity
86    }
87
88    /// Maximum selected neighbors per vector before pair reconciliation.
89    #[must_use]
90    pub const fn top_k(&self) -> usize {
91        self.top_k
92    }
93
94    /// Pair selection mode.
95    #[must_use]
96    pub const fn selection(&self) -> SelectionMode {
97        self.selection
98    }
99
100    /// Confidence attached to all inferred edges.
101    #[must_use]
102    pub const fn confidence(&self) -> Confidence {
103        self.confidence
104    }
105
106    /// Maximum accepted vector count, or [`usize::MAX`] when unbounded.
107    #[must_use]
108    pub const fn max_vectors(&self) -> usize {
109        self.max_vectors
110    }
111
112    pub(crate) fn validate(&self) -> Result<()> {
113        if self.model.is_empty() {
114            return Err(SemanticError::EmptyModel);
115        }
116        if self.model.trim() != self.model {
117            return Err(SemanticError::ModelHasSurroundingWhitespace);
118        }
119        if !self.min_similarity.is_finite() || !(0.0..=1.0).contains(&self.min_similarity) {
120            return Err(SemanticError::InvalidSimilarityThreshold);
121        }
122        if self.top_k == 0 {
123            return Err(SemanticError::ZeroTopK);
124        }
125        if self.max_vectors == 0 {
126            return Err(SemanticError::ZeroMaxVectors);
127        }
128        Ok(())
129    }
130}