use hippmem_core::model::enums::ContentType;
use hippmem_core::model::links::LinkType;
use hippmem_core::model::unit::WriteContext;
use hippmem_engine::{
Engine, EngineConfig, FeedbackInput, InspectQuery, InspectReport, RetrieveContext,
RetrieveInput, UsageSignal, 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 write(engine: &Engine, text: &str) -> hippmem_core::ids::MemoryId {
engine
.write(WriteMemoryInput {
content: text.into(),
content_type: Some(ContentType::UserStatement),
context: ctx(),
importance_hint: None,
source_refs: vec![],
})
.unwrap()
.memory_id
}
fn retrieve(engine: &Engine, query: &str) -> hippmem_engine::RetrieveOutput {
engine
.retrieve(RetrieveInput {
query: query.into(),
context: RetrieveContext::default(),
top_k: 5,
max_hops: None,
retrieval_mode: hippmem_core::model::links::RetrievalMode::Balanced,
})
.unwrap()
}
fn usage_of(engine: &Engine, id: hippmem_core::ids::MemoryId) -> f32 {
match engine.inspect(InspectQuery::Memory(id)).unwrap() {
InspectReport::Memory(m) => m.unit.activation.usage_score.value(),
_ => panic!("expected Memory inspect report"),
}
}
fn assert_usage_close(engine: &Engine, id: hippmem_core::ids::MemoryId, expected: f32) {
let actual = usage_of(engine, id);
assert!(
(actual - expected).abs() < 1e-5,
"usage_score 应为 {expected}, 实际 {actual}"
);
}
#[test]
fn usage_score_updates_on_feedback_signals() {
let dir = tempdir().unwrap();
let engine = Engine::open(EngineConfig {
store_dir: dir.path().join("hippmem.redb"),
..Default::default()
})
.unwrap();
let mid = write(&engine, "小明和李华是高中同学。");
let out = retrieve(&engine, "小明和李华之间有什么关系?");
assert_eq!(
usage_of(&engine, mid),
0.5,
"初始 usage_score 应为 0.5(中性)"
);
let feedback = |signal: UsageSignal| {
engine
.feedback(FeedbackInput {
retrieval_id: out.retrieval_id,
used_memory_ids: vec![mid],
signal,
})
.unwrap();
};
feedback(UsageSignal::Referenced); assert_usage_close(&engine, mid, 0.55);
feedback(UsageSignal::UserConfirmedCorrect); assert_usage_close(&engine, mid, 0.65);
feedback(UsageSignal::TaskSucceeded); assert_usage_close(&engine, mid, 0.73);
feedback(UsageSignal::UserRejected); assert_usage_close(&engine, mid, 0.63);
for _ in 0..10 {
feedback(UsageSignal::UserConfirmedCorrect);
}
assert_eq!(usage_of(&engine, mid), 1.0);
for _ in 0..10 {
feedback(UsageSignal::UserRejected);
}
assert_eq!(usage_of(&engine, mid), 0.0);
engine.close().unwrap();
}
#[test]
fn user_rejected_demotes_instead_of_boosting() {
use hippmem_core::config::AlgoParams;
let dir = tempdir().unwrap();
let engine = Engine::open(EngineConfig {
store_dir: dir.path().join("hippmem.redb"),
algo: AlgoParams {
rrf_w_recent: 0.0,
..Default::default()
},
..Default::default()
})
.unwrap();
write(&engine, "小明和李华是高中同学。");
write(&engine, "李华和王芳在同一家公司工作。");
write(&engine, "王芳和张伟是一对夫妻。");
let rejected = write(&engine, "张伟是北京大学的教授。");
let out1 = retrieve(&engine, "张伟在哪里工作?");
let target_before = out1
.results
.iter()
.find(|r| r.memory.id.0 == rejected.0)
.map(|r| r.final_score)
.expect("被拒记忆应在结果中");
std::thread::sleep(std::time::Duration::from_millis(2));
engine
.feedback(FeedbackInput {
retrieval_id: out1.retrieval_id,
used_memory_ids: vec![rejected],
signal: UsageSignal::UserRejected,
})
.unwrap();
let out2 = retrieve(&engine, "张伟在哪里工作?");
let target_after = out2
.results
.iter()
.find(|r| r.memory.id.0 == rejected.0)
.map(|r| r.final_score)
.expect("被拒记忆仍应在结果中");
assert!(
target_after < target_before,
"拒绝后被拒记忆分数必须下降(usage 降权),{target_before} → {target_after}"
);
for r in &out2.results {
if r.memory.id.0 == rejected.0 {
continue;
}
let before = out1
.results
.iter()
.find(|b| b.memory.id == r.memory.id)
.map(|b| b.final_score)
.expect("其它记忆应在两轮结果中");
assert!(
r.final_score <= before + 1e-6,
"拒绝不得提升其它记忆的分数,{:?} {} → {}",
r.memory.id,
before,
r.final_score
);
}
engine.close().unwrap();
}
#[test]
fn user_rejected_does_not_create_coactivation_edges() {
let dir = tempdir().unwrap();
let engine = Engine::open(EngineConfig {
store_dir: dir.path().join("hippmem.redb"),
..Default::default()
})
.unwrap();
let a = write(&engine, "小明和李华是高中同学。");
let b = write(&engine, "李华和王芳在同一家公司工作。");
let out = retrieve(&engine, "小明和李华之间有什么关系?");
for _ in 0..2 {
std::thread::sleep(std::time::Duration::from_millis(2));
engine
.feedback(FeedbackInput {
retrieval_id: out.retrieval_id,
used_memory_ids: vec![a, b],
signal: UsageSignal::UserRejected,
})
.unwrap();
}
engine
.consolidate(hippmem_engine::ConsolidationScope::Incremental)
.unwrap();
let report = match engine.inspect(InspectQuery::Memory(a)).unwrap() {
InspectReport::Memory(m) => m,
_ => panic!("expected Memory inspect report"),
};
for edge in &report.out_edges {
if edge.to == b {
assert_eq!(
edge.activation_count, 0,
"拒绝信号不得强化 a→b 边(activation_count={})",
edge.activation_count
);
assert_ne!(
edge.link_type,
LinkType::Causal,
"拒绝信号不得创建 CoActivation 边(Causal)"
);
}
}
engine.close().unwrap();
}