reflex/
constants.rs

1//! Cross-cutting, shared constants.
2//!
3//! Prefer deriving secondary constants (e.g. byte sizes) from primary ones to avoid drift.
4//!
5//! # Dimension Invariants
6//!
7//! The embedding dimension values are treated as compile-time invariants across many modules
8//! (embedding, vectordb, cache, scoring). If you need runtime-configurable dimensions:
9//!
10//! 1. Use [`DimConfig`] to pass dimensions through initialization
11//! 2. Use [`validate_embedding_dim`] at module boundaries to catch mismatches early
12//! 3. The compile-time constants remain as defaults and for static size calculations
13
14/// Default embedding vector dimension.
15pub const DEFAULT_EMBEDDING_DIM: usize = 1536;
16/// Default embedding size in bytes when stored as f16.
17pub const EMBEDDING_F16_BYTES: usize = DEFAULT_EMBEDDING_DIM * 2;
18/// Default embedding size in bytes when stored as f32.
19pub const EMBEDDING_F32_BYTES: usize = DEFAULT_EMBEDDING_DIM * 4;
20/// Default embedding size in bytes when stored as binary quantization (1 bit/dim).
21pub const EMBEDDING_BQ_BYTES: usize = DEFAULT_EMBEDDING_DIM / 8;
22
23/// Default vector size exported as `u64` for Qdrant APIs.
24pub const DEFAULT_VECTOR_SIZE_U64: u64 = DEFAULT_EMBEDDING_DIM as u64;
25
26/// Default L3 verification threshold.
27pub const DEFAULT_VERIFICATION_THRESHOLD: f32 = 0.70;
28
29/// Default maximum sequence length for models that support truncation.
30pub const DEFAULT_MAX_SEQ_LEN: usize = 8192;
31
32/// Runtime dimension configuration for modules that support dynamic embedding sizes.
33///
34/// Use this when initializing modules that need to agree on vector dimensions at runtime.
35/// The [`validate`](DimConfig::validate) method ensures consistency with the default constants.
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct DimConfig {
38    /// The embedding vector dimension (number of floats).
39    pub embedding_dim: usize,
40}
41
42impl Default for DimConfig {
43    fn default() -> Self {
44        Self {
45            embedding_dim: DEFAULT_EMBEDDING_DIM,
46        }
47    }
48}
49
50impl DimConfig {
51    /// Creates a new dimension configuration with the specified embedding dimension.
52    pub fn new(embedding_dim: usize) -> Self {
53        Self { embedding_dim }
54    }
55
56    /// Validates that this configuration is internally consistent and reasonable.
57    ///
58    /// Returns an error if:
59    /// - `embedding_dim` is zero
60    /// - `embedding_dim` is not divisible by 8 (required for binary quantization)
61    pub fn validate(&self) -> Result<(), DimValidationError> {
62        if self.embedding_dim == 0 {
63            return Err(DimValidationError::ZeroDimension);
64        }
65        if !self.embedding_dim.is_multiple_of(8) {
66            return Err(DimValidationError::NotDivisibleBy8 {
67                dim: self.embedding_dim,
68            });
69        }
70        Ok(())
71    }
72
73    /// Returns the number of bytes needed for F16 representation.
74    pub fn f16_bytes(&self) -> usize {
75        self.embedding_dim * 2
76    }
77
78    /// Returns the number of bytes needed for F32 representation.
79    pub fn f32_bytes(&self) -> usize {
80        self.embedding_dim * 4
81    }
82
83    /// Returns the number of bytes needed for binary quantization.
84    pub fn bq_bytes(&self) -> usize {
85        self.embedding_dim / 8
86    }
87}
88
89/// Error returned when dimension validation fails.
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub enum DimValidationError {
92    /// Embedding dimension cannot be zero.
93    ZeroDimension,
94    /// Embedding dimension must be divisible by 8 for binary quantization.
95    NotDivisibleBy8 {
96        /// Actual dimension.
97        dim: usize,
98    },
99    /// Runtime dimension does not match expected dimension.
100    DimensionMismatch {
101        /// Expected dimension.
102        expected: usize,
103        /// Actual dimension.
104        actual: usize,
105    },
106}
107
108impl std::fmt::Display for DimValidationError {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        match self {
111            Self::ZeroDimension => write!(f, "embedding dimension cannot be zero"),
112            Self::NotDivisibleBy8 { dim } => {
113                write!(
114                    f,
115                    "embedding dimension {} is not divisible by 8 (required for BQ)",
116                    dim
117                )
118            }
119            Self::DimensionMismatch { expected, actual } => {
120                write!(
121                    f,
122                    "dimension mismatch: expected {}, got {}",
123                    expected, actual
124                )
125            }
126        }
127    }
128}
129
130impl std::error::Error for DimValidationError {}
131
132/// Validates that a runtime embedding dimension matches the expected dimension.
133///
134/// Use this at module boundaries to catch dimension mismatches early, rather than
135/// encountering silent data corruption or panics deep in the processing pipeline.
136///
137/// # Example
138///
139/// ```
140/// use reflex::constants::{validate_embedding_dim, DEFAULT_EMBEDDING_DIM};
141///
142/// // At module boundary, validate incoming dimension matches expected
143/// let embedder_dim = 1536;
144/// validate_embedding_dim(embedder_dim, DEFAULT_EMBEDDING_DIM).unwrap();
145/// ```
146pub fn validate_embedding_dim(actual: usize, expected: usize) -> Result<(), DimValidationError> {
147    if actual != expected {
148        return Err(DimValidationError::DimensionMismatch { expected, actual });
149    }
150    Ok(())
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_dim_config_default() {
159        let config = DimConfig::default();
160        assert_eq!(config.embedding_dim, DEFAULT_EMBEDDING_DIM);
161    }
162
163    #[test]
164    fn test_dim_config_validate_success() {
165        let config = DimConfig::new(1536);
166        assert!(config.validate().is_ok());
167    }
168
169    #[test]
170    fn test_dim_config_validate_zero() {
171        let config = DimConfig::new(0);
172        assert_eq!(config.validate(), Err(DimValidationError::ZeroDimension));
173    }
174
175    #[test]
176    fn test_dim_config_validate_not_divisible_by_8() {
177        let config = DimConfig::new(1537);
178        assert_eq!(
179            config.validate(),
180            Err(DimValidationError::NotDivisibleBy8 { dim: 1537 })
181        );
182    }
183
184    #[test]
185    fn test_dim_config_byte_calculations() {
186        let config = DimConfig::new(1536);
187        assert_eq!(config.f16_bytes(), EMBEDDING_F16_BYTES);
188        assert_eq!(config.f32_bytes(), EMBEDDING_F32_BYTES);
189        assert_eq!(config.bq_bytes(), EMBEDDING_BQ_BYTES);
190    }
191
192    #[test]
193    fn test_validate_embedding_dim_match() {
194        assert!(validate_embedding_dim(1536, 1536).is_ok());
195    }
196
197    #[test]
198    fn test_validate_embedding_dim_mismatch() {
199        assert_eq!(
200            validate_embedding_dim(768, 1536),
201            Err(DimValidationError::DimensionMismatch {
202                expected: 1536,
203                actual: 768
204            })
205        );
206    }
207
208    #[test]
209    fn test_error_display() {
210        let err = DimValidationError::ZeroDimension;
211        assert_eq!(err.to_string(), "embedding dimension cannot be zero");
212
213        let err = DimValidationError::NotDivisibleBy8 { dim: 1537 };
214        assert!(err.to_string().contains("1537"));
215        assert!(err.to_string().contains("divisible by 8"));
216
217        let err = DimValidationError::DimensionMismatch {
218            expected: 1536,
219            actual: 768,
220        };
221        assert!(err.to_string().contains("1536"));
222        assert!(err.to_string().contains("768"));
223    }
224}