Skip to main content

nexus_memory_vectors/
lib.rs

1//! Nexus Vectors - Semantic search over storage-backed embeddings
2//!
3//! This crate provides semantic search capabilities for vector embeddings,
4//! used by the cognition engine (`nexus-agent`) to retrieve relevant memories.
5//!
6//! ## Runtime Retrieval Path
7//! The live cognition path uses **`SemanticSearch`** over `VectorEntry` slices
8//! fetched from `nexus-storage`. `SemanticSearch` performs cosine similarity
9//! ranking with optional graph-tree-based score boosting.
10//!
11//! ## Internal/Test Abstractions
12//! `VectorDatabase` is an in-memory vector store with its own HashMap-based
13//! storage. It is **not** used by the shipped retrieval path and exists for
14//! testing and development. It is deprecated and should not be used for
15//! runtime retrieval.
16//!
17//! ## Features
18//! - **384-dimensional embeddings**: Compatible with all-MiniLM-L6-v2
19//! - **Cosine similarity search**: Fast semantic search
20//! - **Graph tree organization**: Hierarchical memory management with relevance boosting
21//! - **Priority-based boosting**: High-priority memories get boosted scores
22//!
23//! ## Performance Targets
24//! - Search latency: <10ms for 1k vectors
25//! - Memory efficiency: In-memory storage with indexing
26//!
27//! ## Usage (Runtime Path)
28//! ```ignore
29//! use nexus_memory_vectors::{SemanticSearch, SearchOptions, VectorEntry};
30//!
31//! // Vectors come from storage, not an in-memory DB
32//! let vectors: Vec<VectorEntry> = /* fetched from nexus-storage */;
33//! let search = SemanticSearch::new();
34//! let options = SearchOptions::with_limit(10).with_threshold(0.5);
35//! let (results, latency) = search.search(&query_embedding, &vectors, &options).unwrap();
36//! ```
37
38pub mod database;
39pub mod graph;
40pub mod search;
41
42#[allow(deprecated)]
43pub use database::{
44    batch_cosine_similarity, cosine_similarity, dot_product, euclidean_distance, normalize_vector,
45    top_k_similar, VectorDatabase, VectorDatabaseStats, VectorSearchResult, DEFAULT_SEARCH_LIMIT,
46    DEFAULT_SIMILARITY_THRESHOLD,
47};
48pub use graph::{GraphNode, GraphNode as Node, GraphTree, NodeType, TreeNode, TreeStats};
49pub use search::{SearchOptions, SearchResult, SemanticSearch};
50
51use serde::{Deserialize, Serialize};
52
53/// Result type for vector operations
54pub type Result<T> = std::result::Result<T, nexus_core::NexusError>;
55
56/// Embedding dimension (all-MiniLM-L6-v2 uses 384 dimensions)
57pub const EMBEDDING_DIMENSION: usize = 384;
58
59/// Vector embedding type
60pub type Embedding = Vec<f32>;
61
62/// Vector with metadata
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct VectorEntry {
65    /// Unique identifier (memory ID)
66    pub id: i64,
67
68    /// The embedding vector
69    pub embedding: Embedding,
70
71    /// Category for filtering
72    pub category: String,
73
74    /// Optional memory lane type
75    pub memory_lane_type: Option<String>,
76
77    /// Namespace ID for isolation
78    pub namespace_id: i64,
79
80    /// Timestamp for freshness
81    pub created_at: chrono::DateTime<chrono::Utc>,
82}
83
84impl VectorEntry {
85    /// Create a new vector entry
86    pub fn new(id: i64, embedding: Embedding, category: String, namespace_id: i64) -> Self {
87        Self {
88            id,
89            embedding,
90            category,
91            memory_lane_type: None,
92            namespace_id,
93            created_at: chrono::Utc::now(),
94        }
95    }
96
97    /// Create with all fields
98    pub fn with_memory_lane_type(mut self, memory_lane_type: impl Into<String>) -> Self {
99        self.memory_lane_type = Some(memory_lane_type.into());
100        self
101    }
102}
103
104/// Search latency information
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct SearchLatency {
107    /// Total search time in milliseconds
108    pub total_ms: u64,
109
110    /// Vector comparison time in milliseconds
111    pub vector_comparison_ms: u64,
112
113    /// Graph traversal time in milliseconds (if applicable)
114    pub graph_traversal_ms: Option<u64>,
115}
116
117impl SearchLatency {
118    /// Check if search meets the <10ms target
119    pub fn meets_target(&self) -> bool {
120        self.total_ms < 10
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn test_vector_entry_new() {
130        let embedding = vec![0.1; EMBEDDING_DIMENSION];
131        let entry = VectorEntry::new(1, embedding.clone(), "general".to_string(), 1);
132
133        assert_eq!(entry.id, 1);
134        assert_eq!(entry.embedding, embedding);
135        assert_eq!(entry.category, "general");
136        assert_eq!(entry.namespace_id, 1);
137        assert!(entry.memory_lane_type.is_none());
138    }
139
140    #[test]
141    fn test_vector_entry_with_memory_lane_type() {
142        let embedding = vec![0.1; EMBEDDING_DIMENSION];
143        let entry = VectorEntry::new(1, embedding, "general".to_string(), 1)
144            .with_memory_lane_type("correction");
145
146        assert_eq!(entry.memory_lane_type, Some("correction".to_string()));
147    }
148
149    #[test]
150    fn test_search_latency_meets_target() {
151        let good = SearchLatency {
152            total_ms: 5,
153            vector_comparison_ms: 3,
154            graph_traversal_ms: Some(1),
155        };
156        assert!(good.meets_target());
157
158        let bad = SearchLatency {
159            total_ms: 15,
160            vector_comparison_ms: 10,
161            graph_traversal_ms: Some(4),
162        };
163        assert!(!bad.meets_target());
164    }
165}