mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
perf(memory): drop O(n^2) position scan in episodic relevance (#3782)
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
This commit is contained in:
co-authored by
Steven Enamakel
parent
b0fa43c89d
commit
62fb5beab1
@@ -252,14 +252,10 @@ impl UnifiedMemory {
|
||||
}
|
||||
}
|
||||
|
||||
for entry in &episodic_hits {
|
||||
for (position_idx, entry) in episodic_hits.iter().enumerate() {
|
||||
let freshness = Self::recency_score(entry.timestamp, now);
|
||||
// Episodic FTS5 returns results ordered by rank (best first).
|
||||
// Normalize position to a 0-1 relevance score.
|
||||
let position_idx = episodic_hits
|
||||
.iter()
|
||||
.position(|e| e.id == entry.id)
|
||||
.unwrap_or(0);
|
||||
let fts_relevance = 1.0 - (position_idx as f64 / episodic_hits.len().max(1) as f64);
|
||||
|
||||
let episodic_score = (fts_relevance * 0.7) + (freshness * 0.3);
|
||||
|
||||
@@ -264,6 +264,63 @@ async fn query_episodic_hits_have_correct_kind() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Episodic FTS relevance is derived from each hit's rank position
|
||||
/// (`1.0 - idx / len`). With two equally-fresh matches the only
|
||||
/// differentiator is rank, so the relevance scores must be exactly the
|
||||
/// per-position values {1.0, 0.5}. This pins the position-indexing math
|
||||
/// for n > 1 — the single-entry tests above cannot, since idx is always 0.
|
||||
#[tokio::test]
|
||||
async fn query_episodic_relevance_tracks_rank_position() {
|
||||
use crate::openhuman::memory_store::fts5::{self, EpisodicEntry};
|
||||
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let memory = UnifiedMemory::new(tmp.path(), Arc::new(NoopEmbedding), None).unwrap();
|
||||
|
||||
// Two distinct entries, identical timestamp (equal freshness), both
|
||||
// matching the query so episodic_hits has len == 2.
|
||||
for content in [
|
||||
"I have been using Tokio for async Rust development",
|
||||
"Tokio async runtime powers our backend services",
|
||||
] {
|
||||
fts5::episodic_insert(
|
||||
&memory.conn,
|
||||
&EpisodicEntry {
|
||||
id: None,
|
||||
session_id: "sess-rank".into(),
|
||||
timestamp: 1000.0,
|
||||
role: "user".into(),
|
||||
content: content.into(),
|
||||
lesson: None,
|
||||
tool_calls_json: None,
|
||||
cost_microdollars: 0,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let hits = memory
|
||||
.query_namespace_hits("global", "Tokio async", 10)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut relevances: Vec<f64> = hits
|
||||
.iter()
|
||||
.filter(|h| h.kind == crate::openhuman::memory_store::MemoryItemKind::Episodic)
|
||||
.map(|h| h.score_breakdown.episodic_relevance)
|
||||
.collect();
|
||||
relevances.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
|
||||
assert_eq!(
|
||||
relevances.len(),
|
||||
2,
|
||||
"expected exactly two episodic hits, got {relevances:?}"
|
||||
);
|
||||
assert!(
|
||||
(relevances[0] - 0.5).abs() < 1e-9 && (relevances[1] - 1.0).abs() < 1e-9,
|
||||
"episodic relevance must be {{0.5, 1.0}} for two-element rank order, got {relevances:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn query_supporting_relations_contain_entity_types() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user