use crate::memory::Memory; use async_trait::async_trait; use std::fmt::Write; #[async_trait] pub trait MemoryLoader: Send + Sync { async fn load_context(&self, memory: &dyn Memory, user_message: &str) -> anyhow::Result; } pub struct DefaultMemoryLoader { limit: usize, min_relevance_score: f64, } impl Default for DefaultMemoryLoader { fn default() -> Self { Self { limit: 5, min_relevance_score: 0.4, } } } impl DefaultMemoryLoader { pub fn new(limit: usize, min_relevance_score: f64) -> Self { Self { limit: limit.max(1), min_relevance_score, } } } #[async_trait] impl MemoryLoader for DefaultMemoryLoader { async fn load_context( &self, memory: &dyn Memory, user_message: &str, ) -> anyhow::Result { let entries = memory.recall(user_message, self.limit, None).await?; if entries.is_empty() { return Ok(String::new()); } let mut context = String::from("[Memory context]\n"); for entry in entries { if let Some(score) = entry.score { if score < self.min_relevance_score { continue; } } let _ = writeln!(context, "- {}: {}", entry.key, entry.content); } // If all entries were below threshold, return empty if context == "[Memory context]\n" { return Ok(String::new()); } context.push('\n'); Ok(context) } } #[cfg(test)] mod tests { use super::*; use crate::memory::{Memory, MemoryCategory, MemoryEntry}; struct MockMemory; #[async_trait] impl Memory for MockMemory { async fn store( &self, _key: &str, _content: &str, _category: MemoryCategory, _session_id: Option<&str>, ) -> anyhow::Result<()> { Ok(()) } async fn recall( &self, _query: &str, limit: usize, _session_id: Option<&str>, ) -> anyhow::Result> { if limit == 0 { return Ok(vec![]); } Ok(vec![MemoryEntry { id: "1".into(), key: "k".into(), content: "v".into(), category: MemoryCategory::Conversation, timestamp: "now".into(), session_id: None, score: None, }]) } async fn get(&self, _key: &str) -> anyhow::Result> { Ok(None) } async fn list( &self, _category: Option<&MemoryCategory>, _session_id: Option<&str>, ) -> anyhow::Result> { Ok(vec![]) } async fn forget(&self, _key: &str) -> anyhow::Result { Ok(true) } async fn count(&self) -> anyhow::Result { Ok(0) } async fn health_check(&self) -> bool { true } fn name(&self) -> &str { "mock" } } #[tokio::test] async fn default_loader_formats_context() { let loader = DefaultMemoryLoader::default(); let context = loader.load_context(&MockMemory, "hello").await.unwrap(); assert!(context.contains("[Memory context]")); assert!(context.contains("- k: v")); } }