#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use chaotic_semantic_memory::prelude::*;
use chaotic_semantic_memory::retrieval::GraphRagConfig;
const NS: &str = "_default";
async fn setup_framework() -> ChaoticSemanticFramework {
ChaoticSemanticFramework::builder()
.without_persistence()
.build()
.await
.unwrap()
}
#[tokio::test]
async fn test_graph_rag_synthetic_structure() {
let framework = setup_framework().await;
let v0 = HVec10240::new_seeded(0);
let v1 = HVec10240::new_seeded(1);
let v2 = HVec10240::new_seeded(2);
let v3 = HVec10240::new_seeded(3);
framework.inject_concept("c0", v0).await.unwrap();
framework.inject_concept("c1", v1).await.unwrap();
framework.inject_concept("c2", v2).await.unwrap();
framework.inject_concept("c3", v3).await.unwrap();
framework.associate("c0", "c1", 0.8).await.unwrap();
framework.associate("c1", "c2", 0.6).await.unwrap();
let config = GraphRagConfig {
anchor_top_k: 1,
max_hops: 2,
min_assoc_strength: 0.1,
similarity_weight: 0.5,
graph_weight: 0.5,
final_top_k: 10,
};
let results = framework.probe_with_graph(v0, config).await.unwrap();
assert!(!results.is_empty());
assert_eq!(results[0].id, "c0");
assert_eq!(results[0].hop_distance, 0);
let ids: Vec<String> = results.iter().map(|r| r.id.clone()).collect();
assert!(ids.contains(&"c1".to_string()));
assert!(ids.contains(&"c2".to_string()));
assert!(!ids.contains(&"c3".to_string()));
let c2_res = results.iter().find(|r| r.id == "c2").unwrap();
assert!((c2_res.assoc_strength - 0.6).abs() < f32::EPSILON);
}
#[tokio::test]
async fn test_graph_rag_connected_outranks_similarity() {
let framework = setup_framework().await;
let v_query = HVec10240::new_seeded(100);
let v_anchor = v_query;
let v_neighbor = HVec10240::new_seeded(200);
let v_high_sim = v_query;
framework.inject_concept("anchor", v_anchor).await.unwrap();
framework
.inject_concept("neighbor", v_neighbor)
.await
.unwrap();
framework
.inject_concept("high_sim", v_high_sim)
.await
.unwrap();
framework
.associate("anchor", "neighbor", 0.9)
.await
.unwrap();
let config = GraphRagConfig {
anchor_top_k: 1, max_hops: 1,
similarity_weight: 0.1,
graph_weight: 0.9,
final_top_k: 5,
..Default::default()
};
let results = framework.probe_with_graph(v_query, config).await.unwrap();
let ids: Vec<String> = results.iter().map(|r| r.id.clone()).collect();
assert!(ids.contains(&"anchor".to_string()) || ids.contains(&"high_sim".to_string()));
}
#[tokio::test]
async fn test_graph_rag_cycles() {
let framework = setup_framework().await;
framework
.inject_concept("c0", HVec10240::random())
.await
.unwrap();
framework
.inject_concept("c1", HVec10240::random())
.await
.unwrap();
framework.associate("c0", "c1", 0.8).await.unwrap();
framework.associate("c1", "c0", 0.8).await.unwrap();
let config = GraphRagConfig {
anchor_top_k: 1,
max_hops: 5,
..Default::default()
};
let results = framework
.probe_with_graph(HVec10240::random(), config)
.await
.unwrap();
assert!(results.len() <= 2);
}
#[tokio::test]
async fn test_graph_rag_empty_isolated() {
let framework = setup_framework().await;
let results = framework
.probe_with_graph(HVec10240::random(), GraphRagConfig::default())
.await
.unwrap();
assert!(results.is_empty());
framework
.inject_concept("c0", HVec10240::random())
.await
.unwrap();
let results = framework
.probe_with_graph(HVec10240::random(), GraphRagConfig::default())
.await
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].id, "c0");
assert_eq!(results[0].hop_distance, 0);
}
#[tokio::test]
async fn test_graph_rag_boundary_conditions() {
let framework = setup_framework().await;
framework
.inject_concept("c0", HVec10240::new_seeded(0))
.await
.unwrap();
framework
.inject_concept("c1", HVec10240::new_seeded(1))
.await
.unwrap();
framework
.inject_concept("c2", HVec10240::new_seeded(2))
.await
.unwrap();
framework.associate("c0", "c1", 0.5).await.unwrap();
framework.associate("c0", "c2", 0.4).await.unwrap();
let config = GraphRagConfig {
anchor_top_k: 1,
max_hops: 1,
min_assoc_strength: 0.5, ..Default::default()
};
let results = framework
.probe_with_graph(HVec10240::new_seeded(0), config)
.await
.unwrap();
let ids: Vec<String> = results.iter().map(|r| r.id.clone()).collect();
assert!(ids.contains(&"c1".to_string()));
assert!(
!ids.contains(&"c2".to_string()),
"c2 should be filtered by min_assoc_strength"
);
let config_scoring = GraphRagConfig {
anchor_top_k: 1,
max_hops: 1,
similarity_weight: 0.0,
graph_weight: 1.0,
..Default::default()
};
let results_scoring = framework
.probe_with_graph(HVec10240::new_seeded(0), config_scoring)
.await
.unwrap();
let c0_res = results_scoring.iter().find(|r| r.id == "c0").unwrap();
let c1_res = results_scoring.iter().find(|r| r.id == "c1").unwrap();
assert!((c0_res.score - 1.0).abs() < f32::EPSILON);
assert!((c1_res.score - 0.25).abs() < f32::EPSILON);
}
#[tokio::test]
async fn test_graph_rag_max_results_boundary() {
let framework = setup_framework().await;
framework
.inject_concept("c0", HVec10240::new_seeded(0))
.await
.unwrap();
for i in 1..=1001 {
let id = format!("n{i}");
framework
.inject_concept(&id, HVec10240::new_seeded(i as u64))
.await
.unwrap();
framework.associate("c0", &id, 0.9).await.unwrap();
}
let config = GraphRagConfig {
anchor_top_k: 1,
max_hops: 1,
final_top_k: 2000,
..Default::default()
};
let results = framework
.probe_with_graph(HVec10240::new_seeded(0), config)
.await
.unwrap();
assert_eq!(
results.len(),
1001,
"Should have anchor plus exactly 1000 neighbors"
);
}
#[tokio::test]
async fn test_graph_rag_truncation_logic() {
let framework = setup_framework().await;
for i in 0..5 {
framework
.inject_concept(&format!("c{i}"), HVec10240::new_seeded(i as u64))
.await
.unwrap();
}
framework.associate("c0", "c1", 0.9).await.unwrap();
framework.associate("c1", "c2", 0.8).await.unwrap();
let config_zero = GraphRagConfig {
anchor_top_k: 0,
final_top_k: 0,
..Default::default()
};
let results_zero = framework
.probe_with_graph(HVec10240::new_seeded(0), config_zero)
.await
.unwrap();
assert!(
results_zero.is_empty(),
"top_k=0 should return empty results"
);
let config_one = GraphRagConfig {
anchor_top_k: 1,
max_hops: 0,
final_top_k: 1,
..Default::default()
};
let results_one = framework
.probe_with_graph(HVec10240::new_seeded(0), config_one)
.await
.unwrap();
assert_eq!(
results_one.len(),
1,
"top_k=1 should return exactly 1 result"
);
assert_eq!(results_one[0].id, "c0");
let config_trunc = GraphRagConfig {
anchor_top_k: 1, max_hops: 2, final_top_k: 2,
similarity_weight: 0.5,
graph_weight: 0.5,
..Default::default()
};
let results_trunc = framework
.probe_with_graph(HVec10240::new_seeded(0), config_trunc)
.await
.unwrap();
assert_eq!(results_trunc.len(), 2, "Should truncate to final_top_k");
assert!(results_trunc[0].score >= results_trunc[1].score);
assert_eq!(results_trunc[0].id, "c0");
assert_eq!(results_trunc[1].id, "c1");
let config_anchors = GraphRagConfig {
anchor_top_k: 2,
max_hops: 0,
final_top_k: 10,
..Default::default()
};
let results_anchors = framework
.probe_with_graph(HVec10240::new_seeded(0), config_anchors)
.await
.unwrap();
assert_eq!(
results_anchors.len(),
2,
"Should have exactly anchor_top_k results when hops=0"
);
assert_eq!(results_anchors[0].id, "c0");
assert!(
results_anchors.iter().any(|r| r.id == "c1")
|| results_anchors.iter().any(|r| r.id == "c2")
);
let config_exact = GraphRagConfig {
anchor_top_k: 5,
max_hops: 0,
final_top_k: 5,
..Default::default()
};
let results_exact = framework
.probe_with_graph(HVec10240::new_seeded(0), config_exact)
.await
.unwrap();
assert_eq!(
results_exact.len(),
5,
"Should return all 5 elements when len == top_k"
);
for i in 0..4 {
assert!(results_exact[i].score >= results_exact[i + 1].score);
}
let config_huge_final = GraphRagConfig {
final_top_k: 100_001, ..Default::default()
};
assert!(
framework
.probe_with_graph(HVec10240::new_seeded(0), config_huge_final)
.await
.is_err()
);
let config_huge_anchor = GraphRagConfig {
anchor_top_k: 100_001,
..Default::default()
};
assert!(
framework
.probe_with_graph(HVec10240::new_seeded(0), config_huge_anchor)
.await
.is_err()
);
let config_zero_anchor = GraphRagConfig {
anchor_top_k: 0,
final_top_k: 10,
..Default::default()
};
let results_zero_anchor = framework
.probe_with_graph(HVec10240::new_seeded(0), config_zero_anchor)
.await
.unwrap();
assert!(
results_zero_anchor.is_empty(),
"anchor_top_k=0 should return empty results"
);
let config_zero_final = GraphRagConfig {
anchor_top_k: 5,
final_top_k: 0,
..Default::default()
};
let results_zero_final = framework
.probe_with_graph(HVec10240::new_seeded(0), config_zero_final)
.await
.unwrap();
assert!(
results_zero_final.is_empty(),
"final_top_k=0 should return empty results"
);
let config_strict = GraphRagConfig {
anchor_top_k: 2,
max_hops: 0,
final_top_k: 10,
..Default::default()
};
let results_strict = framework
.probe_with_graph(HVec10240::new_seeded(0), config_strict)
.await
.unwrap();
assert_eq!(results_strict.len(), 2);
assert_eq!(results_strict[0].id, "c0");
let mut all_sims = Vec::new();
for i in 0..5 {
let v = HVec10240::new_seeded(i as u64);
let sim = HVec10240::new_seeded(0).cosine_similarity(&v);
all_sims.push((format!("c{i}"), sim));
}
all_sims.sort_by(|a, b| b.1.total_cmp(&a.1));
assert_eq!(results_strict[1].id, all_sims[1].0);
}