- 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
70 lines
2.3 KiB
Rust
70 lines
2.3 KiB
Rust
//! 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:#}");
|
|
}
|
|
}
|
|
});
|
|
}
|