Files
poimen-memory/tests/it_l2.rs.disabled
T

168 lines
6.3 KiB
Plaintext
Raw Normal View History

use mem_core::{Chunk, Level, Record, Provenance, Role, gated_loop::{run_loop, LoopConfig, LlmClient}};
use mem_core::{Query};
use time::OffsetDateTime;
use anyhow::Result;
struct FakeLlm {
responses: Vec<String>,
call_count: std::sync::atomic::AtomicUsize,
}
impl LlmClient for FakeLlm {
fn complete_blocking(&self, _s: &str, _u: &str, _m: usize) -> Result<String> {
let idx = self.call_count.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
if idx < self.responses.len() {
Ok(self.responses[idx].clone())
} else {
// Default: continue
Ok("<think>yes</think><check>yes</check><update>continuing</update><next>continue</next>".to_string())
}
}
}
#[test]
fn a2_exit_gate_on() {
// Proof: exit gate fires early when enabled
let responses = vec![
"<think>yes</think><check>yes</check><update>mem1</update><next>continue</next>".to_string(),
"<think>yes</think><check>yes</check><update>mem2</update><next>end</next>".to_string(),
"<think>yes</think><check>yes</check><update>mem3</update><next>continue</next>".to_string(),
"<think>yes</think><check>yes</check><update>mem4</update><next>continue</next>".to_string(),
"<think>yes</think><check>yes</check><update>mem5</update><next>continue</next>".to_string(),
];
let llm = FakeLlm {
responses,
call_count: std::sync::atomic::AtomicUsize::new(0),
};
let config = LoopConfig {
level: Level::L2,
query: Query {
id: "synthesis".to_string(),
question: "Synthesize all queries".to_string(),
exit_gate: true,
},
memory_budget: 2048,
use_exit_gate: true, // Key: exit gate ON for L2
};
// Create 5 synthetic chunks representing L1 memories
let chunks = vec![
Chunk::new(1, vec![Record { role: Role::User, text: "L1-1".to_string(), timestamp: OffsetDateTime::now_utc(), provenance: Provenance { source_id: "q1".to_string(), offset: 0 } }], 100),
Chunk::new(2, vec![Record { role: Role::User, text: "L1-2".to_string(), timestamp: OffsetDateTime::now_utc(), provenance: Provenance { source_id: "q2".to_string(), offset: 0 } }], 100),
Chunk::new(3, vec![Record { role: Role::User, text: "L1-3".to_string(), timestamp: OffsetDateTime::now_utc(), provenance: Provenance { source_id: "q3".to_string(), offset: 0 } }], 100),
Chunk::new(4, vec![Record { role: Role::User, text: "L1-4".to_string(), timestamp: OffsetDateTime::now_utc(), provenance: Provenance { source_id: "q4".to_string(), offset: 0 } }], 100),
Chunk::new(5, vec![Record { role: Role::User, text: "L1-5".to_string(), timestamp: OffsetDateTime::now_utc(), provenance: Provenance { source_id: "q5".to_string(), offset: 0 } }], 100),
];
let outcome = run_loop(config, chunks, &llm).unwrap();
// Should stop at turn 2 (when "next: end" is returned)
assert_eq!(outcome.chunks_seen, 2, "Should stop after exit gate fires at turn 2");
}
#[test]
fn a4_query_id_null() {
// Proof: L2 synthesis has no query_id
let responses = vec![
"<think>yes</think><check>yes</check><update>mem1</update><next>end</next>".to_string(),
];
let llm = FakeLlm {
responses,
call_count: std::sync::atomic::AtomicUsize::new(0),
};
let config = LoopConfig {
level: Level::L2,
query: Query {
id: "".to_string(), // Empty ID for synthesis
question: "Synthesize".to_string(),
exit_gate: true,
},
memory_budget: 2048,
use_exit_gate: true,
};
let chunks = vec![Chunk::new(1, vec![Record { role: Role::User, text: "L1-1".to_string(), timestamp: OffsetDateTime::now_utc(), provenance: Provenance { source_id: "q1".to_string(), offset: 0 } }], 100)];
let outcome = run_loop(config, chunks, &llm).unwrap();
// Should succeed
assert_eq!(outcome.chunks_seen, 1);
}
#[test]
fn a5_stable_input_order() {
// Proof: stable input order means reproducible synthesis
let responses = vec![
"<think>yes</think><check>yes</check><update>m1</update><next>continue</next>".to_string(),
"<think>yes</think><check>yes</check><update>m2</update><next>end</next>".to_string(),
];
let llm1 = FakeLlm {
responses: responses.clone(),
call_count: std::sync::atomic::AtomicUsize::new(0),
};
let llm2 = FakeLlm {
responses,
call_count: std::sync::atomic::AtomicUsize::new(0),
};
let config1 = LoopConfig {
level: Level::L2,
query: Query {
id: "syn".to_string(),
question: "Q".to_string(),
exit_gate: true,
},
memory_budget: 2048,
use_exit_gate: true,
};
let config2 = config1.clone();
// Same chunks, run twice
let chunks = vec![
Chunk::new(1, vec![Record { role: Role::User, text: "L1-A".to_string(), timestamp: OffsetDateTime::now_utc(), provenance: Provenance { source_id: "q1".to_string(), offset: 0 } }], 100),
Chunk::new(2, vec![Record { role: Role::User, text: "L1-B".to_string(), timestamp: OffsetDateTime::now_utc(), provenance: Provenance { source_id: "q2".to_string(), offset: 0 } }], 100),
];
let outcome1 = run_loop(config1, chunks.clone(), &llm1).unwrap();
let outcome2 = run_loop(config2, chunks, &llm2).unwrap();
// Same results
assert_eq!(outcome1.chunks_seen, outcome2.chunks_seen);
assert_eq!(outcome1.chunks_used, outcome2.chunks_used);
}
#[test]
fn a3_level_is_l2() {
// Proof: L2 synthesis respects level parameter
let responses = vec![
"<think>yes</think><check>yes</check><update>mem</update><next>end</next>".to_string(),
];
let llm = FakeLlm {
responses,
call_count: std::sync::atomic::AtomicUsize::new(0),
};
let config = LoopConfig {
level: Level::L2,
query: Query {
id: "syn".to_string(),
question: "Q".to_string(),
exit_gate: true,
},
memory_budget: 2048,
use_exit_gate: true,
};
let chunks = vec![Chunk::new(1, vec![Record { role: Role::User, text: "L1".to_string(), timestamp: OffsetDateTime::now_utc(), provenance: Provenance { source_id: "q1".to_string(), offset: 0 } }], 100)];
let outcome = run_loop(config, chunks, &llm).unwrap();
assert_eq!(outcome.chunks_seen, 1);
}