Spaces:
Build error
Build error
| //! Memory Entry Definition | |
| //! | |
| //! Core data structure for memory entries with serialization support. | |
| use serde::{Deserialize, Serialize}; | |
| use chrono::{DateTime, Utc}; | |
| use std::collections::HashMap; | |
| /// Memory types as defined in CoALA paper | |
| pub enum MemoryType { | |
| /// Specific events/interactions | |
| Episodic = 0, | |
| /// General knowledge/facts | |
| Semantic = 1, | |
| /// Learned patterns/skills | |
| Procedural = 2, | |
| /// Time-aware context | |
| Temporal = 3, | |
| } | |
| impl From<u8> for MemoryType { | |
| fn from(value: u8) -> Self { | |
| match value { | |
| 0 => MemoryType::Episodic, | |
| 1 => MemoryType::Semantic, | |
| 2 => MemoryType::Procedural, | |
| 3 => MemoryType::Temporal, | |
| _ => MemoryType::Episodic, | |
| } | |
| } | |
| } | |
| impl std::fmt::Display for MemoryType { | |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
| match self { | |
| MemoryType::Episodic => write!(f, "episodic"), | |
| MemoryType::Semantic => write!(f, "semantic"), | |
| MemoryType::Procedural => write!(f, "procedural"), | |
| MemoryType::Temporal => write!(f, "temporal"), | |
| } | |
| } | |
| } | |
| /// A single memory entry | |
| pub struct MemoryEntry { | |
| /// Unique identifier | |
| pub id: String, | |
| /// Memory type category | |
| pub memory_type: MemoryType, | |
| /// The actual content/text | |
| pub content: String, | |
| /// Vector embedding for similarity search | |
| pub embedding: Option<Vec<f32>>, | |
| /// Additional structured metadata | |
| pub metadata: HashMap<String, serde_json::Value>, | |
| /// When this memory was created | |
| pub timestamp: DateTime<Utc>, | |
| /// Time-to-live in seconds (None = no expiration) | |
| pub ttl_seconds: Option<u64>, | |
| /// Confidence score (0.0 - 1.0) | |
| pub confidence: f64, | |
| /// User this memory belongs to | |
| pub user_id: String, | |
| /// Session this memory was created in | |
| pub session_id: String, | |
| /// Source of this memory | |
| pub source: String, | |
| /// Tags for categorization | |
| pub tags: Vec<String>, | |
| /// Version for optimistic concurrency | |
| pub version: i32, | |
| /// Dependencies on other memories | |
| pub dependencies: Vec<String>, | |
| } | |
| impl MemoryEntry { | |
| /// Create a new memory entry with sensible defaults | |
| pub fn new( | |
| id: String, | |
| memory_type: MemoryType, | |
| content: String, | |
| user_id: String, | |
| session_id: String, | |
| ) -> Self { | |
| Self { | |
| id, | |
| memory_type, | |
| content, | |
| embedding: None, | |
| metadata: HashMap::new(), | |
| timestamp: Utc::now(), | |
| ttl_seconds: None, | |
| confidence: 0.8, | |
| user_id, | |
| session_id, | |
| source: "conversation".to_string(), | |
| tags: Vec::new(), | |
| version: 1, | |
| dependencies: Vec::new(), | |
| } | |
| } | |
| /// Check if this entry has expired | |
| pub fn is_expired(&self) -> bool { | |
| if let Some(ttl) = self.ttl_seconds { | |
| let expiry = self.timestamp + chrono::Duration::seconds(ttl as i64); | |
| Utc::now() > expiry | |
| } else { | |
| false | |
| } | |
| } | |
| /// Calculate expiry time if TTL is set | |
| pub fn expires_at(&self) -> Option<DateTime<Utc>> { | |
| self.ttl_seconds.map(|ttl| { | |
| self.timestamp + chrono::Duration::seconds(ttl as i64) | |
| }) | |
| } | |
| /// Calculate age in hours | |
| pub fn age_hours(&self) -> f64 { | |
| let duration = Utc::now() - self.timestamp; | |
| duration.num_seconds() as f64 / 3600.0 | |
| } | |
| /// Set embedding | |
| pub fn with_embedding(mut self, embedding: Vec<f32>) -> Self { | |
| self.embedding = Some(embedding); | |
| self | |
| } | |
| /// Set TTL | |
| pub fn with_ttl(mut self, seconds: u64) -> Self { | |
| self.ttl_seconds = Some(seconds); | |
| self | |
| } | |
| /// Set confidence | |
| pub fn with_confidence(mut self, confidence: f64) -> Self { | |
| self.confidence = confidence; | |
| self | |
| } | |
| /// Set source | |
| pub fn with_source(mut self, source: String) -> Self { | |
| self.source = source; | |
| self | |
| } | |
| /// Add tags | |
| pub fn with_tags(mut self, tags: Vec<String>) -> Self { | |
| self.tags = tags; | |
| self | |
| } | |
| /// Add metadata | |
| pub fn with_metadata(mut self, key: String, value: serde_json::Value) -> Self { | |
| self.metadata.insert(key, value); | |
| self | |
| } | |
| /// Calculate similarity with another entry using embeddings | |
| pub fn similarity(&self, other: &MemoryEntry) -> Option<f64> { | |
| match (&self.embedding, &other.embedding) { | |
| (Some(a), Some(b)) => Some(cosine_similarity(a, b)), | |
| _ => None, | |
| } | |
| } | |
| } | |
| /// Calculate cosine similarity between two vectors | |
| fn cosine_similarity(a: &[f32], b: &[f32]) -> f64 { | |
| if a.len() != b.len() { | |
| return 0.0; | |
| } | |
| let mut dot_product = 0.0f64; | |
| let mut norm_a = 0.0f64; | |
| let mut norm_b = 0.0f64; | |
| for i in 0..a.len() { | |
| dot_product += (a[i] as f64) * (b[i] as f64); | |
| norm_a += (a[i] as f64).powi(2); | |
| norm_b += (b[i] as f64).powi(2); | |
| } | |
| if norm_a == 0.0 || norm_b == 0.0 { | |
| return 0.0; | |
| } | |
| dot_product / (norm_a.sqrt() * norm_b.sqrt()) | |
| } | |
| mod tests { | |
| use super::*; | |
| fn test_entry_creation() { | |
| let entry = MemoryEntry::new( | |
| "test-1".to_string(), | |
| MemoryType::Episodic, | |
| "Test content".to_string(), | |
| "user-1".to_string(), | |
| "session-1".to_string(), | |
| ); | |
| assert_eq!(entry.id, "test-1"); | |
| assert_eq!(entry.memory_type, MemoryType::Episodic); | |
| assert!(!entry.is_expired()); | |
| } | |
| fn test_cosine_similarity() { | |
| let a = vec![1.0, 0.0, 0.0]; | |
| let b = vec![1.0, 0.0, 0.0]; | |
| assert!((cosine_similarity(&a, &b) - 1.0).abs() < 0.0001); | |
| let c = vec![0.0, 1.0, 0.0]; | |
| assert!(cosine_similarity(&a, &c).abs() < 0.0001); | |
| } | |
| } | |