use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum MemoryType {
#[default]
ShortTerm,
LongTerm,
Episodic,
Semantic,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryEntry {
pub id: String,
pub content: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub embedding: Option<Vec<f32>>,
#[serde(default)]
pub memory_type: MemoryType,
#[serde(default)]
pub metadata: HashMap<String, Value>,
pub created_at: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_accessed: Option<DateTime<Utc>>,
#[serde(default = "default_importance")]
pub importance: f32,
#[serde(default)]
pub access_count: u32,
}
fn default_importance() -> f32 {
0.5
}
impl MemoryEntry {
pub fn new(content: impl Into<String>) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
content: content.into(),
embedding: None,
memory_type: MemoryType::default(),
metadata: HashMap::new(),
created_at: Utc::now(),
last_accessed: None,
importance: 0.5,
access_count: 0,
}
}
pub fn with_type(mut self, memory_type: MemoryType) -> Self {
self.memory_type = memory_type;
self
}
pub fn with_embedding(mut self, embedding: Vec<f32>) -> Self {
self.embedding = Some(embedding);
self
}
pub fn with_importance(mut self, importance: f32) -> Self {
self.importance = importance.clamp(0.0, 1.0);
self
}
pub fn with_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
self.metadata.insert(key.into(), value);
self
}
pub fn record_access(&mut self) {
self.access_count += 1;
self.last_accessed = Some(Utc::now());
}
pub fn relevance_score(&self) -> f32 {
let age_hours = (Utc::now() - self.created_at).num_hours() as f32;
let recency_factor = (-age_hours / 24.0 / 7.0).exp();
let access_factor = 1.0 + (self.access_count as f32).ln().max(0.0) * 0.1;
self.importance * recency_factor * access_factor
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memory_entry_creation() {
let entry = MemoryEntry::new("Test memory content");
assert_eq!(entry.content, "Test memory content");
assert_eq!(entry.memory_type, MemoryType::ShortTerm);
assert_eq!(entry.importance, 0.5);
}
#[test]
fn test_memory_entry_builder() {
let entry = MemoryEntry::new("Test")
.with_type(MemoryType::LongTerm)
.with_importance(0.9)
.with_metadata("source", Value::String("user".to_string()));
assert_eq!(entry.memory_type, MemoryType::LongTerm);
assert_eq!(entry.importance, 0.9);
assert!(entry.metadata.contains_key("source"));
}
#[test]
fn test_record_access() {
let mut entry = MemoryEntry::new("Test");
assert_eq!(entry.access_count, 0);
entry.record_access();
assert_eq!(entry.access_count, 1);
assert!(entry.last_accessed.is_some());
}
}