use anyhow::Result;
use async_trait::async_trait;
use mr_common::{
Memory, MemoryEdge, MemoryFacet, MemoryType, PropagationDelta, RetrievalRule, RuleStats,
};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[async_trait]
pub trait MemoryStorage: Send + Sync {
async fn add(&self, memory: &Memory) -> Result<()> {
self.save(memory).await
}
async fn save(&self, memory: &Memory) -> Result<()>;
async fn get(&self, id: &Uuid) -> Result<Option<Memory>>;
async fn update(&self, memory: &Memory) -> Result<()>;
async fn delete(&self, id: &Uuid) -> Result<bool>;
async fn list(&self, limit: usize) -> Result<Vec<Memory>>;
async fn list_by_type(&self, memory_type: MemoryType, limit: usize) -> Result<Vec<Memory>>;
async fn list_by_tag(&self, tag: &str, limit: usize) -> Result<Vec<Memory>>;
async fn list_by_importance(&self, min: f32, max: f32) -> Result<Vec<Memory>>;
async fn list_deleted(&self) -> Result<Vec<Memory>>;
async fn count(&self) -> Result<usize>;
async fn count_deleted(&self) -> Result<usize>;
async fn list_by_project(&self, project_id: &Uuid) -> Result<Vec<Memory>>;
async fn get_chunks_by_group(&self, chunk_group_id: &Uuid) -> Result<Vec<Memory>>;
async fn list_older_than(&self, cutoff: &str, limit: usize) -> Result<Vec<Memory>>;
}
#[async_trait]
pub trait ProjectStorage: Send + Sync {
async fn save(&self, project: &mr_common::Project) -> Result<()>;
async fn get(&self, id: &Uuid) -> Result<Option<mr_common::Project>>;
async fn get_by_name(&self, name: &str) -> Result<Option<mr_common::Project>>;
async fn delete(&self, id: &Uuid) -> Result<bool>;
async fn list(&self) -> Result<Vec<mr_common::Project>>;
}
#[async_trait]
pub trait ConfigStorage: Send + Sync {
async fn get(&self, key: &str) -> Result<Option<String>>;
async fn set(&self, key: &str, value: &str) -> Result<()>;
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct VectorPayload {
pub project_id: Option<Uuid>,
pub memory_type: String,
pub tags: Vec<String>,
pub content_preview: String,
pub importance: f32,
pub chunk_group_id: Option<Uuid>,
pub chunk_index: Option<u32>,
pub chunk_total: Option<u32>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SearchFilter {
pub project_id: Option<Uuid>,
pub include_global: bool,
pub memory_type: Option<String>,
pub min_score: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchHit {
pub memory_id: Uuid,
pub score: f32,
pub payload: VectorPayload,
}
#[async_trait]
pub trait VectorStorage: Send + Sync {
async fn add(&self, id: &Uuid, embedding: &[f32], payload: VectorPayload) -> Result<()>;
async fn remove(&self, id: &Uuid) -> Result<bool>;
async fn search(
&self,
query: &[f32],
filter: SearchFilter,
top_k: usize,
) -> Result<Vec<SearchHit>>;
async fn get(&self, id: &Uuid) -> Result<Option<Vec<f32>>>;
async fn count(&self) -> Result<usize>;
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct FtsPayload {
pub project_id: Option<Uuid>,
pub memory_type: String,
pub tags: Vec<String>,
pub importance: f32,
}
#[async_trait]
pub trait FtsStorage: Send + Sync {
async fn add(&self, id: &Uuid, text: &str, payload: FtsPayload) -> Result<()>;
async fn remove(&self, id: &Uuid) -> Result<bool>;
async fn search(
&self,
query: &str,
filter: SearchFilter,
top_k: usize,
) -> Result<Vec<SearchHit>>;
async fn count(&self) -> Result<usize>;
fn reload(&self) -> Result<()> {
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HybridSearchRequest {
pub query: String,
pub query_embedding: Vec<f32>,
pub filter: SearchFilter,
pub top_k: usize,
pub hybrid_alpha: f32,
pub mmr_lambda: f32,
pub mmr_enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HybridSearchResult {
pub hits: Vec<SearchHit>,
pub vec_count: usize,
pub fts_count: usize,
}
#[async_trait]
pub trait HybridStorage: Send + Sync {
async fn search(&self, req: HybridSearchRequest) -> Result<HybridSearchResult>;
async fn add(
&self,
id: &Uuid,
embedding: &[f32],
text: &str,
payload: VectorPayload,
) -> Result<()>;
async fn remove(&self, id: &Uuid) -> Result<bool>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FacetSearchHit {
pub facet_id: Uuid,
pub memory_id: Uuid,
pub score: f32,
pub theme: String,
pub confidence: f32,
pub keywords: Vec<String>,
}
#[async_trait]
pub trait FacetStorage: Send + Sync {
async fn save(&self, facet: &MemoryFacet) -> Result<()>;
async fn get(&self, id: &Uuid) -> Result<Option<MemoryFacet>>;
async fn delete(&self, id: &Uuid) -> Result<bool>;
async fn list_by_memory(&self, memory_id: &Uuid) -> Result<Vec<MemoryFacet>>;
async fn count(&self) -> Result<usize>;
async fn search_by_theme(
&self,
query_embedding: &[f32],
top_k: usize,
min_score: f32,
) -> Result<Vec<FacetSearchHit>>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphTraversalParams {
pub max_depth: usize,
pub decay: f32,
pub min_score: f32,
pub max_results: usize,
pub edge_types: Option<Vec<mr_common::EdgeType>>,
}
impl Default for GraphTraversalParams {
fn default() -> Self {
Self {
max_depth: 3,
decay: 0.6,
min_score: 0.3,
max_results: 50,
edge_types: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphSearchHit {
pub memory_id: Uuid,
pub score: f32,
pub path: Vec<Uuid>,
pub path_types: Vec<mr_common::EdgeType>,
}
#[async_trait]
pub trait EdgeStorage: Send + Sync {
async fn save(&self, edge: &MemoryEdge) -> Result<()>;
async fn get(&self, id: &Uuid) -> Result<Option<MemoryEdge>>;
async fn delete(&self, id: &Uuid) -> Result<bool>;
async fn list_out_edges(&self, source_id: &Uuid) -> Result<Vec<MemoryEdge>>;
async fn list_in_edges(&self, target_id: &Uuid) -> Result<Vec<MemoryEdge>>;
async fn list_by_type(&self, edge_type: mr_common::EdgeType) -> Result<Vec<MemoryEdge>>;
async fn count(&self) -> Result<usize>;
async fn neighbors(&self, memory_id: &Uuid) -> Result<Vec<MemoryEdge>>;
async fn traverse(
&self,
seed_ids: &[Uuid],
params: GraphTraversalParams,
) -> Result<Vec<GraphSearchHit>>;
}
#[async_trait]
pub trait PropagationStorage: Send + Sync {
async fn save(&self, delta: &PropagationDelta) -> Result<()>;
async fn get(&self, id: &Uuid) -> Result<Option<PropagationDelta>>;
async fn delete(&self, id: &Uuid) -> Result<bool>;
async fn list_pending(&self, target_id: &Uuid) -> Result<Vec<PropagationDelta>>;
async fn list_by_source(&self, source_id: &Uuid) -> Result<Vec<PropagationDelta>>;
async fn mark_applied(&self, id: &Uuid) -> Result<bool>;
async fn has_applied(&self, source_id: &Uuid, target_id: &Uuid) -> Result<bool>;
async fn count_pending(&self) -> Result<usize>;
async fn save_batch(&self, deltas: &[PropagationDelta]) -> Result<()> {
for delta in deltas {
self.save(delta).await?;
}
Ok(())
}
}
#[async_trait]
pub trait RuleStorage: Send + Sync {
async fn save(&self, rule: &RetrievalRule) -> Result<()>;
async fn get(&self, id: &Uuid) -> Result<Option<RetrievalRule>>;
async fn delete(&self, id: &Uuid) -> Result<bool>;
async fn list(&self, enabled_only: bool) -> Result<Vec<RetrievalRule>>;
async fn list_by_priority(&self) -> Result<Vec<RetrievalRule>>;
async fn update_stats(&self, stats: &RuleStats) -> Result<()>;
async fn get_stats(&self, rule_id: &Uuid) -> Result<Option<RuleStats>>;
async fn record_hit(&self, rule_id: &Uuid) -> Result<()>;
}
#[async_trait]
pub trait TieredStorage: Send + Sync {
async fn save_summary(&self, memory_id: &Uuid, summary: &str) -> Result<()>;
async fn get_summary(&self, memory_id: &Uuid) -> Result<Option<String>>;
async fn delete_summary(&self, memory_id: &Uuid) -> Result<bool>;
async fn save_keywords(&self, memory_id: &Uuid, keywords: &[String]) -> Result<()>;
async fn get_keywords(&self, memory_id: &Uuid) -> Result<Option<Vec<String>>>;
async fn delete_keywords(&self, memory_id: &Uuid) -> Result<bool>;
async fn save_summaries_batch(&self, items: &[(Uuid, String)]) -> Result<()> {
for (id, summary) in items {
self.save_summary(id, summary).await?;
}
Ok(())
}
async fn save_keywords_batch(&self, items: &[(Uuid, Vec<String>)]) -> Result<()> {
for (id, keywords) in items {
self.save_keywords(id, keywords).await?;
}
Ok(())
}
}