add nocmem: auto memory recall + ingest via NuoNuo hippocampal network
- nocmem Python service (mem/): FastAPI wrapper around NuoNuo's Hopfield-Hebbian memory, with /recall, /ingest, /store, /stats endpoints - NOC integration: auto recall after user message (injected as system msg), async ingest after LLM response (fire-and-forget) - Recall: cosine pre-filter (threshold 0.35) + Hopfield attention (β=32), top_k=3, KV-cache friendly (appended after user msg, not in system prompt) - Ingest: LLM extraction + paraphrase augmentation, heuristic fallback - Wired into main.rs, life.rs (agent done), http.rs (api chat) - Config: optional `nocmem.endpoint` in config.yaml - Includes benchmarks: LongMemEval (R@5=94.0%), efficiency, noise vs scale - Design doc: doc/nocmem.md
This commit is contained in:
69
src/nocmem.rs
Normal file
69
src/nocmem.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
//! nocmem client — auto-recall and async ingest via HTTP.
|
||||
|
||||
use tracing::{info, warn};
|
||||
|
||||
/// Recall relevant memories for the given text.
|
||||
/// Returns formatted memory string, or empty if none found / error / not configured.
|
||||
pub async fn recall(endpoint: &str, text: &str) -> String {
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_millis(500))
|
||||
.build()
|
||||
.unwrap();
|
||||
let url = format!("{}/recall", endpoint.trim_end_matches('/'));
|
||||
|
||||
match client
|
||||
.post(&url)
|
||||
.json(&serde_json::json!({"text": text, "top_k": 3, "hops": 2}))
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(resp) => {
|
||||
if let Ok(json) = resp.json::<serde_json::Value>().await {
|
||||
let count = json["count"].as_i64().unwrap_or(0);
|
||||
let memories = json["memories"].as_str().unwrap_or("");
|
||||
if count > 0 && !memories.is_empty() {
|
||||
let latency = json["latency_ms"].as_f64().unwrap_or(0.0);
|
||||
info!("nocmem recall: {count} memories, {latency:.1}ms");
|
||||
return memories.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("nocmem recall failed: {e:#}");
|
||||
}
|
||||
}
|
||||
String::new()
|
||||
}
|
||||
|
||||
/// Fire-and-forget ingest of a conversation turn.
|
||||
pub fn ingest_spawn(endpoint: String, user_msg: String, assistant_msg: String) {
|
||||
tokio::spawn(async move {
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(120))
|
||||
.build()
|
||||
.unwrap();
|
||||
let url = format!("{}/ingest", endpoint.trim_end_matches('/'));
|
||||
|
||||
match client
|
||||
.post(&url)
|
||||
.json(&serde_json::json!({
|
||||
"user_msg": user_msg,
|
||||
"assistant_msg": assistant_msg,
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(resp) => {
|
||||
if let Ok(json) = resp.json::<serde_json::Value>().await {
|
||||
let stored = json["stored"].as_i64().unwrap_or(0);
|
||||
if stored > 0 {
|
||||
info!("nocmem ingest: stored {stored} memories");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("nocmem ingest failed: {e:#}");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user