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;