use super::{DistanceMetric, VelesCollection, VelesDatabase, VelesError, VelesPoint};
#[derive(Debug, Clone, uniffi::Record)]
pub struct SemanticResult {
pub id: u64,
pub score: f32,
pub content: String,
}
#[derive(uniffi::Object)]
pub struct VelesSemanticMemory {
collection: std::sync::Arc<VelesCollection>,
}
impl VelesSemanticMemory {
fn content_from_payload(payload: Option<&String>) -> String {
payload
.and_then(|p| serde_json::from_str::<serde_json::Value>(p).ok())
.as_ref()
.and_then(|v| v.get("content"))
.and_then(serde_json::Value::as_str)
.unwrap_or_default()
.to_string()
}
}
#[uniffi::export]
impl VelesSemanticMemory {
#[uniffi::constructor]
pub fn new(db: &VelesDatabase, dimension: u32) -> Result<Self, VelesError> {
let collection_name = "_semantic_memory";
let collection = match db.get_collection(collection_name.to_string())? {
Some(coll) => coll,
None => {
db.create_collection(
collection_name.to_string(),
dimension,
DistanceMetric::Cosine,
)?;
db.get_collection(collection_name.to_string())?
.ok_or(VelesError::database(
"Failed to retrieve collection after creation".to_string(),
))?
}
};
Ok(Self { collection })
}
pub fn store(&self, id: u64, content: String, embedding: Vec<f32>) -> Result<(), VelesError> {
let payload = serde_json::to_string(&serde_json::json!({ "content": content }))
.map_err(|e| VelesError::database(format!("Failed to encode content payload: {e}")))?;
let point = VelesPoint {
id,
vector: embedding,
payload: Some(payload),
};
self.collection.upsert(point)?;
Ok(())
}
pub fn query(
&self,
embedding: Vec<f32>,
top_k: u32,
) -> Result<Vec<SemanticResult>, VelesError> {
let results = self.collection.search(embedding, top_k)?;
let ids: Vec<u64> = results.iter().map(|r| r.id).collect();
let contents: std::collections::HashMap<u64, String> = self
.collection
.get(ids)
.into_iter()
.map(|p| (p.id, Self::content_from_payload(p.payload.as_ref())))
.collect();
Ok(results
.into_iter()
.map(|r| SemanticResult {
id: r.id,
score: r.score,
content: contents.get(&r.id).cloned().unwrap_or_default(),
})
.collect())
}
pub fn len(&self) -> Result<u64, VelesError> {
Ok(self.collection.count())
}
pub fn is_empty(&self) -> Result<bool, VelesError> {
Ok(self.len()? == 0)
}
pub fn delete(&self, id: u64) -> Result<(), VelesError> {
self.collection.delete(id)
}
pub fn remove(&self, id: u64) -> Result<(), VelesError> {
self.delete(id)
}
pub fn clear(&self) -> Result<(), VelesError> {
for id in self.collection.all_ids() {
let _ = self.collection.delete(id);
}
Ok(())
}
pub fn dimension(&self) -> u32 {
self.collection.dimension()
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn create_test_db() -> (TempDir, std::sync::Arc<VelesDatabase>) {
let dir = TempDir::new().expect("test: create temp dir");
let db = VelesDatabase::open(dir.path().to_string_lossy().to_string())
.expect("test: open database");
(dir, db)
}
#[test]
fn test_semantic_memory_new() {
let (_dir, db) = create_test_db();
let memory = VelesSemanticMemory::new(&db, 4).expect("test: construct semantic memory");
assert_eq!(memory.dimension(), 4);
assert!(memory.is_empty().expect("test: is_empty on fresh memory"));
}
#[test]
fn test_semantic_memory_store_and_query() {
let (_dir, db) = create_test_db();
let memory = VelesSemanticMemory::new(&db, 4).expect("test: construct semantic memory");
memory
.store(1, "Test content".to_string(), vec![0.1, 0.2, 0.3, 0.4])
.expect("test: store knowledge fact");
assert_eq!(memory.len().expect("test: read len"), 1);
assert!(!memory.is_empty().expect("test: is_empty after store"));
let results = memory
.query(vec![0.1, 0.2, 0.3, 0.4], 5)
.expect("test: query semantic memory");
assert_eq!(results.len(), 1);
assert_eq!(results[0].id, 1);
assert_eq!(results[0].content, "Test content");
}
#[test]
fn test_semantic_memory_delete() {
let (_dir, db) = create_test_db();
let memory = VelesSemanticMemory::new(&db, 4).expect("test: construct semantic memory");
memory
.store(1, "Content".to_string(), vec![0.1, 0.2, 0.3, 0.4])
.expect("test: store knowledge fact");
assert_eq!(memory.len().expect("test: read len"), 1);
memory.delete(1).expect("test: delete knowledge fact");
assert!(memory.is_empty().expect("test: is_empty after delete"));
}
#[test]
fn test_semantic_memory_clear() {
let (_dir, db) = create_test_db();
let memory = VelesSemanticMemory::new(&db, 4).expect("test: construct semantic memory");
memory
.store(1, "First".to_string(), vec![0.1, 0.2, 0.3, 0.4])
.expect("test: store first fact");
memory
.store(2, "Second".to_string(), vec![0.5, 0.6, 0.7, 0.8])
.expect("test: store second fact");
assert_eq!(memory.len().expect("test: read len"), 2);
memory.clear().expect("test: clear knowledge facts");
assert!(memory.is_empty().expect("test: is_empty after clear"));
}
#[test]
fn test_semantic_memory_content_survives_reload() {
let dir = TempDir::new().expect("test: create temp dir");
let path = dir.path().to_string_lossy().to_string();
{
let db = VelesDatabase::open(path.clone()).expect("test: open database");
let memory = VelesSemanticMemory::new(&db, 4).expect("test: construct semantic memory");
memory
.store(
7,
"Paris is the capital of France".to_string(),
vec![0.1, 0.2, 0.3, 0.4],
)
.expect("test: store knowledge fact");
}
let db = VelesDatabase::open(path).expect("test: re-open database");
let memory = VelesSemanticMemory::new(&db, 4).expect("test: re-construct semantic memory");
let results = memory
.query(vec![0.1, 0.2, 0.3, 0.4], 5)
.expect("test: query after reload");
assert_eq!(results.len(), 1);
assert_eq!(results[0].id, 7);
assert_eq!(results[0].content, "Paris is the capital of France");
}
}