1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
//! Semantic search and vector index operations for Node
use ipfrs_core::{Cid, Result};
use ipfrs_semantic::{QueryFilter, SearchResult};
use std::path::Path;
use super::{Node, SemanticStats};
impl Node {
/// Get semantic router statistics
///
/// Returns comprehensive statistics about the semantic index including
/// vector count, dimension, distance metric, and cache performance.
///
/// # Returns
/// Statistics about the semantic router
///
/// # Errors
/// Returns error if semantic routing is not enabled
///
/// # Example
/// ```rust,no_run
/// use ipfrs::{Node, NodeConfig};
///
/// # async fn example() -> ipfrs::Result<()> {
/// let mut node = Node::new(NodeConfig::default())?;
/// node.start().await?;
///
/// let stats = node.semantic_stats()?;
/// println!("Indexed vectors: {}", stats.num_vectors);
/// println!("Vector dimension: {}", stats.dimension);
/// println!("Cache size: {}/{}", stats.cache_size, stats.cache_capacity);
/// # Ok(())
/// # }
/// ```
pub fn semantic_stats(&self) -> Result<SemanticStats> {
let semantic = self.semantic()?;
let router_stats = semantic.stats();
let cache_stats = semantic.cache_stats();
Ok(SemanticStats {
num_vectors: router_stats.num_vectors,
dimension: router_stats.dimension,
metric: router_stats.metric,
cache_size: cache_stats.size,
cache_capacity: cache_stats.capacity,
})
}
/// Index content with its semantic embedding
///
/// Adds content to the semantic index for similarity search. The embedding
/// should be a vector representation of the content (e.g., from a sentence
/// transformer model).
///
/// # Arguments
/// * `cid` - Content identifier to index
/// * `embedding` - Vector embedding of the content
///
/// # Example
/// ```rust,no_run
/// use ipfrs::{Node, NodeConfig};
///
/// # async fn example() -> ipfrs::Result<()> {
/// let mut node = Node::new(NodeConfig::default())?;
/// node.start().await?;
///
/// # let cid = ipfrs_core::Cid::default();
/// // Index content with 768-dimensional embedding (e.g., from BERT)
/// let embedding = vec![0.5; 768];
/// node.index_content(&cid, &embedding).await?;
/// # Ok(())
/// # }
/// ```
pub async fn index_content(&self, cid: &Cid, embedding: &[f32]) -> Result<()> {
let semantic = self.semantic()?;
semantic.add(cid, embedding)
}
/// Search for similar content by semantic similarity
///
/// Performs k-nearest neighbor search over indexed content using vector
/// similarity. Returns the top k most similar items.
///
/// # Arguments
/// * `query_embedding` - Query vector to search for
/// * `k` - Number of results to return
///
/// # Returns
/// Vector of search results ordered by similarity (highest first)
///
/// # Example
/// ```rust,no_run
/// use ipfrs::{Node, NodeConfig};
///
/// # async fn example() -> ipfrs::Result<()> {
/// let mut node = Node::new(NodeConfig::default())?;
/// node.start().await?;
///
/// // Search for top 10 similar documents
/// let query_embedding = vec![0.3; 768];
/// let results = node.search_similar(&query_embedding, 10).await?;
///
/// for result in results {
/// println!("CID: {}, Score: {}", result.cid, result.score);
/// }
/// # Ok(())
/// # }
/// ```
pub async fn search_similar(
&self,
query_embedding: &[f32],
k: usize,
) -> Result<Vec<SearchResult>> {
let semantic = self.semantic()?;
semantic.query(query_embedding, k).await
}
/// Search with advanced filtering options
///
/// Performs semantic search with additional filters like minimum score
/// threshold, CID prefix matching, and result limits.
///
/// # Arguments
/// * `query_embedding` - Query vector to search for
/// * `k` - Number of results to return
/// * `filter` - Query filter options
///
/// # Returns
/// Vector of filtered search results ordered by similarity
///
/// # Example
/// ```rust,no_run
/// use ipfrs::{Node, NodeConfig, QueryFilter};
///
/// # async fn example() -> ipfrs::Result<()> {
/// let mut node = Node::new(NodeConfig::default())?;
/// node.start().await?;
///
/// // Search with filters
/// let query_embedding = vec![0.3; 768];
/// let filter = QueryFilter {
/// min_score: Some(0.8), // Only results with score >= 0.8
/// max_score: None, // No max score filter
/// max_results: Some(5), // Limit to 5 results
/// cid_prefix: None, // No CID filtering
/// };
///
/// let results = node.search_hybrid(&query_embedding, 20, filter).await?;
///
/// for result in results {
/// println!("High-confidence match: {} ({})", result.cid, result.score);
/// }
/// # Ok(())
/// # }
/// ```
pub async fn search_hybrid(
&self,
query_embedding: &[f32],
k: usize,
filter: QueryFilter,
) -> Result<Vec<SearchResult>> {
let semantic = self.semantic()?;
semantic.query_with_filter(query_embedding, k, filter).await
}
/// Save the semantic index to disk
///
/// Persists the entire HNSW index including all vectors and CID mappings
/// to a file for later loading.
///
/// # Arguments
/// * `path` - Path to save the index file
///
/// # Example
/// ```rust,no_run
/// use ipfrs::{Node, NodeConfig};
///
/// # async fn example() -> ipfrs::Result<()> {
/// let mut node = Node::new(NodeConfig::default())?;
/// node.start().await?;
///
/// // Save the semantic index
/// node.save_semantic_index("semantic.index").await?;
/// println!("Semantic index saved");
/// # Ok(())
/// # }
/// ```
pub async fn save_semantic_index(&self, path: impl AsRef<Path>) -> Result<()> {
let semantic = self.semantic()?;
semantic.save_index(path).await
}
/// Load a semantic index from disk
///
/// Loads a previously saved HNSW index from disk, replacing the current index.
///
/// # Arguments
/// * `path` - Path to the saved index file
///
/// # Example
/// ```rust,no_run
/// use ipfrs::{Node, NodeConfig};
///
/// # async fn example() -> ipfrs::Result<()> {
/// let mut node = Node::new(NodeConfig::default())?;
/// node.start().await?;
///
/// // Load the semantic index
/// node.load_semantic_index("semantic.index").await?;
/// println!("Semantic index loaded");
/// # Ok(())
/// # }
/// ```
pub async fn load_semantic_index(&self, path: impl AsRef<Path>) -> Result<()> {
let semantic = self.semantic()?;
semantic.load_index(path).await
}
}