knowledge_runtime/evidence/
support.rs1use semantic_memory::SearchResult;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct SearchEvidenceBundle {
11 pub id: String,
13 pub items: Vec<EvidenceItem>,
15 pub aggregate_confidence: f32,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct EvidenceItem {
22 pub content: String,
24 pub source: semantic_memory::SearchSource,
26 pub score: f64,
28 pub relevance: EvidenceRelevance,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "snake_case")]
35pub enum EvidenceRelevance {
36 Direct,
38 Supporting,
40 Contrasting,
42 Tangential,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct AnswerBundle {
49 pub answer: String,
51 pub evidence: SearchEvidenceBundle,
53 pub confidence: f32,
55}
56
57pub fn bundle_from_results(results: &[SearchResult]) -> SearchEvidenceBundle {
62 let items: Vec<EvidenceItem> = results
63 .iter()
64 .map(|r| EvidenceItem {
65 content: r.content.clone(),
66 source: r.source.clone(),
67 score: r.score,
68 relevance: EvidenceRelevance::Direct,
69 })
70 .collect();
71
72 let aggregate_confidence = if items.is_empty() {
73 0.0
74 } else {
75 let sum: f64 = items.iter().map(|i| i.score).sum();
76 (sum / items.len() as f64).min(1.0) as f32
77 };
78
79 SearchEvidenceBundle {
80 id: uuid::Uuid::new_v4().to_string(),
81 items,
82 aggregate_confidence,
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use semantic_memory::{SearchResult, SearchSource};
90
91 #[test]
92 fn bundle_from_empty_results() {
93 let bundle = bundle_from_results(&[]);
94 assert!(bundle.items.is_empty());
95 assert_eq!(bundle.aggregate_confidence, 0.0);
96 }
97
98 #[test]
99 fn bundle_preserves_order() {
100 let results = vec![
101 SearchResult {
102 content: "first".into(),
103 source: SearchSource::Fact {
104 fact_id: "f1".into(),
105 namespace: "test".into(),
106 },
107 score: 0.9,
108 bm25_rank: None,
109 vector_rank: None,
110 cosine_similarity: None,
111 },
112 SearchResult {
113 content: "second".into(),
114 source: SearchSource::Fact {
115 fact_id: "f2".into(),
116 namespace: "test".into(),
117 },
118 score: 0.7,
119 bm25_rank: None,
120 vector_rank: None,
121 cosine_similarity: None,
122 },
123 ];
124 let bundle = bundle_from_results(&results);
125 assert_eq!(bundle.items.len(), 2);
126 assert_eq!(bundle.items[0].content, "first");
127 assert_eq!(bundle.items[1].content, "second");
128 }
129}