use hippmem_core::config::AlgoParams;
use hippmem_core::model::enums::ContentType;
use hippmem_core::model::unit::WriteContext;
use hippmem_engine::{Engine, EngineConfig, RetrieveContext, RetrieveInput, WriteMemoryInput};
use tempfile::tempdir;
fn ctx() -> WriteContext {
WriteContext {
conversation_id: Some(1),
session_id: Some(1),
project_id: None,
task_id: None,
user_id: None,
local_time: hippmem_core::time::Timestamp(1_700_000_000_000),
preceding_memory_ids: vec![],
source_refs: vec![],
}
}
fn chain_engine(store_dir: &std::path::Path) -> Engine {
Engine::open(EngineConfig {
store_dir: store_dir.into(),
algo: AlgoParams {
rrf_w_semantic_binary: 0.0,
rrf_w_semantic_dense: 0.0,
rrf_w_recent: 0.0,
min_propagation_energy: 0.005,
..Default::default()
},
..Default::default()
})
.unwrap()
}
fn write(engine: &Engine, text: &str) {
engine
.write(WriteMemoryInput {
content: text.into(),
content_type: Some(ContentType::UserStatement),
context: ctx(),
importance_hint: None,
source_refs: vec![],
})
.unwrap();
}
fn retrieve_with_hops(
engine: &Engine,
query: &str,
max_hops: Option<usize>,
) -> hippmem_engine::RetrieveOutput {
engine
.retrieve(RetrieveInput {
query: query.into(),
context: RetrieveContext::default(),
top_k: 10,
max_hops,
retrieval_mode: hippmem_core::model::links::RetrievalMode::Balanced,
})
.unwrap()
}
fn max_hop_in_trace(out: &hippmem_engine::RetrieveOutput) -> u8 {
out.trace.steps.iter().map(|s| s.hop).max().unwrap_or(0)
}
#[test]
fn max_hops_controls_traversal_depth() {
let dir = tempdir().unwrap();
let engine = chain_engine(&dir.path().join("hippmem.redb"));
write(&engine, "张伟喜欢打篮球,经常去球场训练。"); write(
&engine,
"王芳和张伟计划优化数据库性能,上周开会讨论了方案。",
); write(
&engine,
"赵磊和王芳计划优化数据库性能,上周开会讨论了方案。",
);
let out1 = retrieve_with_hops(&engine, "赵磊在哪里上学?", Some(1));
assert_eq!(
out1.trace.hops_used, 1,
"max_hops=1 时 hops_used 必须为 1(实际 0 = 报告硬编码 bug 未修)"
);
assert_eq!(max_hop_in_trace(&out1), 1, "max_hops=1 时不得出现 2 跳节点");
let out3 = retrieve_with_hops(&engine, "赵磊在哪里上学?", Some(3));
assert!(
out3.trace.hops_used >= 2,
"max_hops=3 时两跳链必须被执行, 实际 hops_used={}",
out3.trace.hops_used
);
assert!(
max_hop_in_trace(&out3) >= 2,
"max_hops=3 时 trace 中必须包含 hop=2 的扩散步"
);
assert!(
out3.results
.iter()
.any(|r| r.memory.content.raw.contains("张伟喜欢打篮球")),
"两跳邻居必须出现在 top-10 结果中"
);
engine.close().unwrap();
}
#[test]
fn hops_used_zero_when_no_propagation() {
let dir = tempdir().unwrap();
let engine = chain_engine(&dir.path().join("hippmem.redb"));
write(&engine, "一个人独自在房间里看书。");
let out = retrieve_with_hops(&engine, "完全无关的查询词,没有匹配。", None);
assert_eq!(out.trace.hops_used, 0);
engine.close().unwrap();
}