ceylon_next/memory/vector/mod.rs
1//! Vector memory and semantic search capabilities.
2//!
3//! This module provides vector embeddings and semantic search functionality
4//! for agent memories. It enables similarity-based memory retrieval using
5//! vector embeddings rather than simple keyword matching.
6//!
7//! # Features
8//!
9//! - **Embedding Generation**: Convert text to vector embeddings using various providers
10//! - **Semantic Search**: Find similar memories based on meaning, not just keywords
11//! - **Hybrid Search**: Combine semantic similarity with traditional keyword search
12//! - **Multiple Backends**: Support for local and cloud vector databases
13//!
14//! # Architecture
15//!
16//! The vector memory system is built around three core traits:
17//!
18//! - [`EmbeddingProvider`] - Generates vector embeddings from text
19//! - [`VectorStore`] - Stores and retrieves vectors with metadata
20//! - [`VectorMemory`] - High-level interface combining embeddings and storage
21//!
22//! # Available Backends
23//!
24//! - [`LocalVectorStore`] - In-memory vector store with cosine similarity search
25//!
26//! # Example
27//!
28//! ```rust,no_run
29//! use ceylon_next::memory::vector::{EmbeddingProvider, VectorMemory, LocalVectorStore};
30//! use std::sync::Arc;
31//!
32//! #[tokio::main]
33//! async fn main() {
34//! // Create an embedding provider (e.g., OpenAI, Ollama, etc.)
35//! // let embedder = OpenAIEmbeddings::new("api-key");
36//!
37//! // Create a vector store
38//! let store = LocalVectorStore::new(384); // 384-dimensional embeddings
39//!
40//! // Use for semantic search
41//! // let results = store.search(&query_vector, 5).await.unwrap();
42//! }
43//! ```
44
45use async_trait::async_trait;
46use serde::{Deserialize, Serialize};
47
48// ============================================
49// Core Types
50// ============================================
51
52/// A vector embedding with associated metadata.
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct VectorEntry {
55 /// Unique identifier for this vector entry
56 pub id: String,
57 /// The vector embedding
58 pub vector: Vec<f32>,
59 /// Associated memory entry ID
60 pub memory_id: String,
61 /// Agent ID for filtering
62 pub agent_id: String,
63 /// Original text that was embedded
64 pub text: String,
65 /// Optional metadata
66 pub metadata: Option<serde_json::Value>,
67 /// Unix timestamp when created
68 pub created_at: u64,
69}
70
71impl VectorEntry {
72 /// Creates a new vector entry.
73 pub fn new(
74 memory_id: String,
75 agent_id: String,
76 text: String,
77 vector: Vec<f32>,
78 metadata: Option<serde_json::Value>,
79 ) -> Self {
80 Self {
81 id: uuid::Uuid::new_v4().to_string(),
82 vector,
83 memory_id,
84 agent_id,
85 text,
86 metadata,
87 created_at: Self::current_timestamp(),
88 }
89 }
90
91 fn current_timestamp() -> u64 {
92 std::time::SystemTime::now()
93 .duration_since(std::time::UNIX_EPOCH)
94 .unwrap()
95 .as_secs()
96 }
97}
98
99/// A search result with similarity score.
100#[derive(Debug, Clone)]
101pub struct SearchResult {
102 /// The vector entry that matched
103 pub entry: VectorEntry,
104 /// Similarity score (typically 0.0 to 1.0, higher is more similar)
105 pub score: f32,
106}
107
108// ============================================
109// Embedding Provider Trait
110// ============================================
111
112/// Trait for generating vector embeddings from text.
113///
114/// Implementations can use different embedding models:
115/// - OpenAI's text-embedding models
116/// - Local models via Ollama
117/// - Hugging Face models
118/// - Custom embedding models
119///
120/// # Example
121///
122/// ```rust,no_run
123/// use ceylon_next::memory::vector::EmbeddingProvider;
124/// use async_trait::async_trait;
125///
126/// struct MyEmbedder;
127///
128/// #[async_trait]
129/// impl EmbeddingProvider for MyEmbedder {
130/// async fn embed(&self, text: &str) -> Result<Vec<f32>, String> {
131/// // Generate embedding...
132/// Ok(vec![0.1, 0.2, 0.3])
133/// }
134///
135/// fn dimension(&self) -> usize {
136/// 3
137/// }
138/// }
139/// ```
140#[async_trait]
141pub trait EmbeddingProvider: Send + Sync {
142 /// Generates an embedding vector for the given text.
143 ///
144 /// # Arguments
145 ///
146 /// * `text` - The text to embed
147 ///
148 /// # Returns
149 ///
150 /// A vector of floats representing the embedding, or an error message.
151 async fn embed(&self, text: &str) -> Result<Vec<f32>, String>;
152
153 /// Generates embeddings for multiple texts in batch.
154 ///
155 /// Default implementation calls `embed` for each text sequentially.
156 /// Providers can override this for more efficient batch processing.
157 async fn embed_batch(&self, texts: &[String]) -> Result<Vec<Vec<f32>>, String> {
158 let mut embeddings = Vec::with_capacity(texts.len());
159 for text in texts {
160 embeddings.push(self.embed(text).await?);
161 }
162 Ok(embeddings)
163 }
164
165 /// Returns the dimensionality of the embeddings produced by this provider.
166 fn dimension(&self) -> usize;
167
168 /// Returns the model name or identifier.
169 fn model_name(&self) -> &str {
170 "unknown"
171 }
172}
173
174// ============================================
175// Vector Store Trait
176// ============================================
177
178/// Trait for storing and retrieving vector embeddings.
179///
180/// Vector stores handle the low-level storage and similarity search
181/// of vector embeddings. Different implementations can use different
182/// backends like in-memory storage, vector databases, etc.
183#[async_trait]
184pub trait VectorStore: Send + Sync {
185 /// Stores a vector entry.
186 async fn store(&self, entry: VectorEntry) -> Result<String, String>;
187
188 /// Stores multiple vector entries in batch.
189 async fn store_batch(&self, entries: Vec<VectorEntry>) -> Result<Vec<String>, String> {
190 let mut ids = Vec::with_capacity(entries.len());
191 for entry in entries {
192 ids.push(self.store(entry).await?);
193 }
194 Ok(ids)
195 }
196
197 /// Retrieves a vector entry by ID.
198 async fn get(&self, id: &str) -> Result<Option<VectorEntry>, String>;
199
200 /// Searches for similar vectors using cosine similarity.
201 ///
202 /// # Arguments
203 ///
204 /// * `query_vector` - The query vector to search for
205 /// * `agent_id` - Optional agent ID to filter results
206 /// * `limit` - Maximum number of results to return
207 /// * `threshold` - Optional minimum similarity score threshold (0.0 to 1.0)
208 ///
209 /// # Returns
210 ///
211 /// A vector of search results ordered by similarity (highest first).
212 async fn search(
213 &self,
214 query_vector: &[f32],
215 agent_id: Option<&str>,
216 limit: usize,
217 threshold: Option<f32>,
218 ) -> Result<Vec<SearchResult>, String>;
219
220 /// Deletes all vectors for an agent.
221 async fn clear_agent_vectors(&self, agent_id: &str) -> Result<(), String>;
222
223 /// Returns the number of vectors stored.
224 async fn count(&self) -> Result<usize, String>;
225
226 /// Returns the dimension of vectors in this store.
227 fn dimension(&self) -> usize;
228}
229
230// ============================================
231// Vector Memory Trait
232// ============================================
233
234/// High-level interface for semantic memory operations.
235///
236/// This trait combines embedding generation and vector storage to provide
237/// a complete semantic memory system. It handles the conversion of text
238/// memories into vector embeddings and provides semantic search capabilities.
239#[async_trait]
240pub trait VectorMemory: Send + Sync {
241 /// Stores a text memory by generating its embedding and storing it.
242 ///
243 /// # Arguments
244 ///
245 /// * `memory_id` - ID of the associated memory entry
246 /// * `agent_id` - ID of the agent
247 /// * `text` - The text content to embed and store
248 /// * `metadata` - Optional metadata to associate with the vector
249 async fn store_text(
250 &self,
251 memory_id: String,
252 agent_id: String,
253 text: String,
254 metadata: Option<serde_json::Value>,
255 ) -> Result<String, String>;
256
257 /// Searches for similar memories using semantic search.
258 ///
259 /// # Arguments
260 ///
261 /// * `agent_id` - ID of the agent to search within
262 /// * `query` - The search query text
263 /// * `limit` - Maximum number of results
264 /// * `threshold` - Optional minimum similarity threshold
265 async fn semantic_search(
266 &self,
267 agent_id: &str,
268 query: &str,
269 limit: usize,
270 threshold: Option<f32>,
271 ) -> Result<Vec<SearchResult>, String>;
272
273 /// Clears all vector memories for an agent.
274 async fn clear(&self, agent_id: &str) -> Result<(), String>;
275}
276
277// ============================================
278// Module Declarations
279// ============================================
280
281mod embedding;
282mod local_store;
283mod utils;
284
285#[cfg(feature = "vector-openai")]
286mod openai;
287
288#[cfg(feature = "vector-ollama")]
289mod ollama;
290
291#[cfg(feature = "vector-huggingface")]
292mod huggingface;
293
294#[cfg(feature = "vector-huggingface-local")]
295mod huggingface_local;
296
297#[cfg(test)]
298mod tests;
299
300// ============================================
301// Public Exports
302// ============================================
303
304pub use embedding::CachedEmbeddings;
305pub use local_store::LocalVectorStore;
306pub use utils::{cosine_similarity, normalize_vector};
307
308#[cfg(feature = "vector-openai")]
309pub use openai::{OpenAIEmbeddings, OpenAIModel};
310
311#[cfg(feature = "vector-ollama")]
312pub use ollama::{get_model_dimension, OllamaEmbeddings};
313
314#[cfg(feature = "vector-huggingface")]
315pub use huggingface::{HuggingFaceEmbeddings, HuggingFaceModel};
316
317#[cfg(feature = "vector-huggingface-local")]
318pub use huggingface_local::HuggingFaceLocalEmbeddings;