writerslogic_warm/core/agency/
questions.rs1use crate::core::agency::goals::GoalStore;
11use crate::core::atom_memory::AtomMemory;
12use crate::core::cognition::gaps::KnowledgeGap;
13use crate::core::cognition::hypothesis::Hypothesis;
14use crate::core::entangled::EntangledHVec;
15
16#[derive(Clone, Debug)]
18pub struct Question {
19 pub text: String,
20 pub source: QuestionSource,
21 pub goal_relevance: f64,
22}
23
24#[derive(Clone, Debug)]
26pub enum QuestionSource {
27 Gap {
28 entity: String,
29 relation: String,
30 },
31 Hypothesis {
32 entity: String,
33 relation: String,
34 proposed: String,
35 },
36 Exploration {
37 relation: String,
38 },
39}
40
41pub struct QuestionGenerator;
43
44impl QuestionGenerator {
45 pub fn from_gaps(
47 gaps: &[KnowledgeGap],
48 atom_memory: &AtomMemory,
49 goal_store: &GoalStore,
50 ) -> Vec<Question> {
51 gaps.iter()
52 .map(|gap| {
53 let text = format!("What is the {} of {}?", gap.missing_relation, gap.entity);
54 let relevance = Self::score_relevance(&text, atom_memory, goal_store);
55 Question {
56 text,
57 source: QuestionSource::Gap {
58 entity: gap.entity.clone(),
59 relation: gap.missing_relation.clone(),
60 },
61 goal_relevance: relevance,
62 }
63 })
64 .collect()
65 }
66
67 pub fn from_hypotheses(
69 hypotheses: &[Hypothesis],
70 atom_memory: &AtomMemory,
71 goal_store: &GoalStore,
72 ) -> Vec<Question> {
73 hypotheses
74 .iter()
75 .map(|hyp| {
76 let text = format!(
77 "Is {} the {} of {}? (confidence: {:.0}%)",
78 hyp.proposed_filler,
79 hyp.relation,
80 hyp.entity,
81 hyp.confidence * 100.0
82 );
83 let relevance = Self::score_relevance(&text, atom_memory, goal_store);
84 Question {
85 text,
86 source: QuestionSource::Hypothesis {
87 entity: hyp.entity.clone(),
88 relation: hyp.relation.clone(),
89 proposed: hyp.proposed_filler.clone(),
90 },
91 goal_relevance: relevance,
92 }
93 })
94 .collect()
95 }
96
97 pub fn from_relations(
99 relations: &[String],
100 atom_memory: &AtomMemory,
101 goal_store: &GoalStore,
102 ) -> Vec<Question> {
103 relations
104 .iter()
105 .map(|rel| {
106 let text = format!("What entities have the {} relation?", rel);
107 let relevance = Self::score_relevance(&text, atom_memory, goal_store);
108 Question {
109 text,
110 source: QuestionSource::Exploration {
111 relation: rel.clone(),
112 },
113 goal_relevance: relevance,
114 }
115 })
116 .collect()
117 }
118
119 pub fn prioritize(mut questions: Vec<Question>) -> Vec<Question> {
121 questions.sort_by(|a, b| {
122 b.goal_relevance
123 .partial_cmp(&a.goal_relevance)
124 .unwrap_or(std::cmp::Ordering::Equal)
125 });
126 questions
127 }
128
129 fn score_relevance(text: &str, atom_memory: &AtomMemory, goal_store: &GoalStore) -> f64 {
130 let vecs: Vec<EntangledHVec> = text
132 .split_whitespace()
133 .filter_map(|w| atom_memory.get(&w.to_lowercase()))
134 .collect();
135 if vecs.is_empty() {
136 return 0.0;
137 }
138 let question_vec = EntangledHVec::bundle(&vecs);
139 if question_vec.indices().is_empty() {
140 return 0.0;
141 }
142 goal_store.goal_relevance(&question_vec)
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use crate::core::agency::goals::Goal;
150 use crate::core::cognition::gaps::KnowledgeGap;
151 use crate::core::cognition::hypothesis::Hypothesis;
152
153 fn make_test_env() -> (AtomMemory, GoalStore) {
154 let dim = 16384;
155 let atom_mem = AtomMemory::new(dim, 3.0);
156 let goal_store = GoalStore::new();
157
158 atom_mem.get_or_insert("located_in");
159 atom_mem.get_or_insert("tokyo");
160 atom_mem.get_or_insert("europe");
161
162 goal_store.add(Goal {
163 name: "find_locations".to_string(),
164 description: "Find where things are located".to_string(),
165 vector: atom_mem.get("located_in").unwrap(),
166 relevance: 1.0,
167 urgency: 1.0,
168 cost: 0.0,
169 active: true,
170 });
171
172 (atom_mem, goal_store)
173 }
174
175 #[test]
176 fn test_from_gaps() {
177 let (atom_mem, goal_store) = make_test_env();
178 let gaps = vec![KnowledgeGap {
179 entity: "tokyo".to_string(),
180 missing_relation: "located_in".to_string(),
181 peers_with_relation: vec!["paris".to_string()],
182 peer_coverage: 1.0,
183 }];
184
185 let questions = QuestionGenerator::from_gaps(&gaps, &atom_mem, &goal_store);
186 assert_eq!(questions.len(), 1);
187 assert!(questions[0].text.contains("located_in"));
188 assert!(questions[0].text.contains("tokyo"));
189 }
190
191 #[test]
192 fn test_from_hypotheses() {
193 let (atom_mem, goal_store) = make_test_env();
194 let hypotheses = vec![Hypothesis {
195 entity: "tokyo".to_string(),
196 relation: "located_in".to_string(),
197 proposed_filler: "asia".to_string(),
198 confidence: 0.85,
199 evidence_count: 3,
200 confirmed: false,
201 }];
202
203 let questions = QuestionGenerator::from_hypotheses(&hypotheses, &atom_mem, &goal_store);
204 assert_eq!(questions.len(), 1);
205 assert!(questions[0].text.contains("asia"));
206 assert!(questions[0].text.contains("85%"));
207 }
208
209 #[test]
210 fn test_prioritize() {
211 let q1 = Question {
212 text: "low".to_string(),
213 source: QuestionSource::Exploration {
214 relation: "r".to_string(),
215 },
216 goal_relevance: 0.1,
217 };
218 let q2 = Question {
219 text: "high".to_string(),
220 source: QuestionSource::Exploration {
221 relation: "r".to_string(),
222 },
223 goal_relevance: 0.9,
224 };
225 let ranked = QuestionGenerator::prioritize(vec![q1, q2]);
226 assert_eq!(ranked[0].text, "high");
227 assert_eq!(ranked[1].text, "low");
228 }
229
230 #[test]
231 fn test_no_goals_zero_relevance() {
232 let atom_mem = AtomMemory::new(16384, 3.0);
233 let goal_store = GoalStore::new(); let gaps = vec![KnowledgeGap {
235 entity: "x".to_string(),
236 missing_relation: "r".to_string(),
237 peers_with_relation: vec![],
238 peer_coverage: 1.0,
239 }];
240 let questions = QuestionGenerator::from_gaps(&gaps, &atom_mem, &goal_store);
241 assert_eq!(questions[0].goal_relevance, 0.0);
242 }
243}