1use crate::types::*;
4use crate::Result;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
9#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
10pub enum Priority {
11 Low,
13 Medium,
15 High,
17 Critical,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
24pub enum ResolutionStrategy {
25 SearchMemory,
27 AskUser,
29 Assume,
31 ExternalLookup,
33 DeriveFromContext,
35 Unresolvable,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41#[serde(rename_all = "camelCase")]
42pub struct KnownFact {
43 pub fact: String,
45 pub source: KnowledgeSource,
47 pub confidence: f32,
49 pub timestamp: i64,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
56pub enum KnowledgeSource {
57 Memory,
59 Context,
61 Character,
63 RecentMessages,
65 Inferred,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71#[serde(rename_all = "camelCase")]
72pub struct KnowledgeGap {
73 pub description: String,
75 pub priority: Priority,
77 pub resolvable: bool,
79 pub resolution_strategy: Option<ResolutionStrategy>,
81 pub impact: String,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87#[serde(rename_all = "camelCase")]
88pub struct Assumption {
89 pub assumption: String,
91 pub confidence: f32,
93 pub risk_level: RiskLevel,
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
99#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
100pub enum RiskLevel {
101 Low,
103 Medium,
105 High,
107 Critical,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113#[serde(rename_all = "camelCase")]
114pub struct KnowledgeState {
115 pub known_facts: Vec<KnownFact>,
117 pub unknown_gaps: Vec<KnowledgeGap>,
119 pub assumptions: Vec<Assumption>,
121 pub confidence_score: f32,
123 pub summary: String,
125}
126
127pub struct KnowledgeAnalyzer;
129
130impl KnowledgeAnalyzer {
131 pub fn new() -> Self {
133 Self
134 }
135
136 pub async fn analyze(&self, message: &Memory, state: &State) -> Result<KnowledgeState> {
138 let mut known_facts = Vec::new();
139 let mut unknown_gaps = Vec::new();
140
141 let entities = self.extract_entities(&message.content.text);
143
144 known_facts.extend(self.extract_facts_from_state(state));
146
147 if let Some(recent) = state.data.get("recentMessages") {
149 known_facts.extend(self.extract_facts_from_recent(recent));
150 }
151
152 for entity in &entities {
154 if !self.is_entity_known(entity, &known_facts) {
155 let gap = self.create_knowledge_gap(entity, &message.content.text);
156 unknown_gaps.push(gap);
157 }
158 }
159
160 let contextual_gaps = self.analyze_contextual_requirements(&message.content.text, state);
162 unknown_gaps.extend(contextual_gaps);
163
164 let assumptions = self.generate_assumptions(&unknown_gaps, state);
166
167 let confidence_score = self.calculate_confidence(&known_facts, &unknown_gaps);
169
170 let summary = self.generate_summary(&known_facts, &unknown_gaps, &assumptions);
172
173 Ok(KnowledgeState {
174 known_facts,
175 unknown_gaps,
176 assumptions,
177 confidence_score,
178 summary,
179 })
180 }
181
182 fn extract_entities(&self, text: &str) -> Vec<String> {
184 let mut entities = Vec::new();
185
186 let words: Vec<&str> = text.split_whitespace().collect();
188
189 for window in words.windows(2) {
190 if window[0]
192 .chars()
193 .next()
194 .map(|c| c.is_uppercase())
195 .unwrap_or(false)
196 {
197 entities.push(window[0].to_string());
198 }
199
200 if window[0]
202 .chars()
203 .next()
204 .map(|c| c.is_uppercase())
205 .unwrap_or(false)
206 && window[1]
207 .chars()
208 .next()
209 .map(|c| c.is_uppercase())
210 .unwrap_or(false)
211 {
212 entities.push(format!("{} {}", window[0], window[1]));
213 }
214 }
215
216 let technical_patterns = [
218 "algorithm",
219 "function",
220 "code",
221 "system",
222 "database",
223 "api",
224 "service",
225 "module",
226 "component",
227 "framework",
228 ];
229
230 for pattern in &technical_patterns {
231 if text.to_lowercase().contains(pattern) {
232 entities.push(pattern.to_string());
233 }
234 }
235
236 let lower = text.to_lowercase();
238 for lang in ["rust", "python", "java", "javascript", "go", "c++", "c"].iter() {
239 if lower.contains(lang) {
240 let name = match *lang {
241 "javascript" => "JavaScript".to_string(),
242 "c++" => "C++".to_string(),
243 _ => {
244 let mut s = lang.to_string();
245 if let Some(first) = s.chars().next() {
246 s.replace_range(0..1, &first.to_uppercase().to_string());
247 }
248 s
249 }
250 };
251 entities.push(name);
252 }
253 }
254
255 entities.sort();
257 entities.dedup();
258
259 entities
260 }
261
262 fn extract_facts_from_state(&self, state: &State) -> Vec<KnownFact> {
264 let mut facts = Vec::new();
265 let now = chrono::Utc::now().timestamp();
266
267 if let Some(name) = state.data.get("agentName") {
269 if let Some(name_str) = name.as_str() {
270 facts.push(KnownFact {
271 fact: format!("Agent name is {}", name_str),
272 source: KnowledgeSource::Context,
273 confidence: 1.0,
274 timestamp: now,
275 });
276 }
277 }
278
279 if let Some(user) = state.data.get("userName") {
281 if let Some(user_str) = user.as_str() {
282 facts.push(KnownFact {
283 fact: format!("User name is {}", user_str),
284 source: KnowledgeSource::Context,
285 confidence: 1.0,
286 timestamp: now,
287 });
288 }
289 }
290
291 if let Some(goals) = state.data.get("goals") {
293 if let Some(goals_arr) = goals.as_array() {
294 for goal in goals_arr {
295 if let Some(goal_str) = goal.as_str() {
296 facts.push(KnownFact {
297 fact: format!("Current goal: {}", goal_str),
298 source: KnowledgeSource::Context,
299 confidence: 0.9,
300 timestamp: now,
301 });
302 }
303 }
304 }
305 }
306
307 facts
308 }
309
310 fn extract_facts_from_recent(&self, recent: &serde_json::Value) -> Vec<KnownFact> {
312 let mut facts = Vec::new();
313 let now = chrono::Utc::now().timestamp();
314
315 if let Some(messages) = recent.as_array() {
316 for msg in messages.iter().take(5) {
317 if let Some(content) = msg.get("content").and_then(|c| c.get("text")) {
318 if let Some(text) = content.as_str() {
319 facts.push(KnownFact {
321 fact: format!(
322 "Recent context: {}",
323 text.chars().take(100).collect::<String>()
324 ),
325 source: KnowledgeSource::RecentMessages,
326 confidence: 0.8,
327 timestamp: now,
328 });
329 }
330 }
331 }
332 }
333
334 facts
335 }
336
337 fn is_entity_known(&self, entity: &str, known_facts: &[KnownFact]) -> bool {
339 let entity_lower = entity.to_lowercase();
340 known_facts
341 .iter()
342 .any(|fact| fact.fact.to_lowercase().contains(&entity_lower))
343 }
344
345 fn create_knowledge_gap(&self, entity: &str, context: &str) -> KnowledgeGap {
347 let priority = if context
349 .to_lowercase()
350 .contains(&format!("what is {}", entity.to_lowercase()))
351 || context
352 .to_lowercase()
353 .contains(&format!("who is {}", entity.to_lowercase()))
354 {
355 Priority::Critical
356 } else if context.to_lowercase().contains("explain") {
357 Priority::High
358 } else {
359 Priority::Medium
360 };
361
362 KnowledgeGap {
363 description: format!("Unknown entity: {}", entity),
364 priority,
365 resolvable: true,
366 resolution_strategy: Some(ResolutionStrategy::SearchMemory),
367 impact: format!("May affect understanding of {}", entity),
368 }
369 }
370
371 fn analyze_contextual_requirements(&self, text: &str, state: &State) -> Vec<KnowledgeGap> {
373 let mut gaps = Vec::new();
374 let lower = text.to_lowercase();
375
376 if (lower.contains(" it ") || lower.contains("this") || lower.contains("that"))
378 && !state.data.contains_key("recentMessages")
379 {
380 gaps.push(KnowledgeGap {
381 description: "Unclear reference - missing context".to_string(),
382 priority: Priority::High,
383 resolvable: false,
384 resolution_strategy: Some(ResolutionStrategy::AskUser),
385 impact: "May misunderstand what user is referring to".to_string(),
386 });
387 }
388
389 if lower.contains("previous") || lower.contains("earlier") || lower.contains("last time") {
391 gaps.push(KnowledgeGap {
392 description: "Reference to previous conversation or event".to_string(),
393 priority: Priority::High,
394 resolvable: true,
395 resolution_strategy: Some(ResolutionStrategy::SearchMemory),
396 impact: "Missing historical context".to_string(),
397 });
398 }
399
400 if (lower.contains("implement") || lower.contains("build"))
402 && !lower.contains("how")
403 && lower.split_whitespace().count() < 10
404 {
405 gaps.push(KnowledgeGap {
406 description: "Insufficient implementation details".to_string(),
407 priority: Priority::High,
408 resolvable: true,
409 resolution_strategy: Some(ResolutionStrategy::AskUser),
410 impact: "May provide generic solution instead of specific one".to_string(),
411 });
412 }
413
414 gaps
415 }
416
417 fn generate_assumptions(&self, gaps: &[KnowledgeGap], state: &State) -> Vec<Assumption> {
419 let mut assumptions = Vec::new();
420
421 for gap in gaps {
423 if gap.priority <= Priority::Medium && gap.resolvable {
424 let assumption = match gap.resolution_strategy {
426 Some(ResolutionStrategy::DeriveFromContext) => Assumption {
427 assumption: format!("Assuming typical context for: {}", gap.description),
428 confidence: 0.6,
429 risk_level: RiskLevel::Low,
430 },
431 Some(ResolutionStrategy::Assume) => Assumption {
432 assumption: format!(
433 "Assuming standard interpretation: {}",
434 gap.description
435 ),
436 confidence: 0.5,
437 risk_level: RiskLevel::Medium,
438 },
439 _ => continue,
440 };
441 assumptions.push(assumption);
442 }
443 }
444
445 if state.data.get("intent").and_then(|i| i.as_str()) == Some("question") {
447 assumptions.push(Assumption {
448 assumption: "User wants informative, helpful response".to_string(),
449 confidence: 0.9,
450 risk_level: RiskLevel::Low,
451 });
452 }
453
454 assumptions
455 }
456
457 fn calculate_confidence(&self, known_facts: &[KnownFact], gaps: &[KnowledgeGap]) -> f32 {
459 if known_facts.is_empty() && gaps.is_empty() {
460 return 0.5; }
462
463 let gap_penalty: f32 = gaps
465 .iter()
466 .map(|g| match g.priority {
467 Priority::Critical => 0.3,
468 Priority::High => 0.2,
469 Priority::Medium => 0.1,
470 Priority::Low => 0.05,
471 })
472 .sum();
473
474 let fact_boost = (known_facts.len() as f32 * 0.1).min(0.4);
476
477 let confidence = 0.5 + fact_boost - gap_penalty;
479
480 confidence.max(0.1).min(1.0)
481 }
482
483 fn generate_summary(
485 &self,
486 known_facts: &[KnownFact],
487 gaps: &[KnowledgeGap],
488 assumptions: &[Assumption],
489 ) -> String {
490 let critical_gaps = gaps
491 .iter()
492 .filter(|g| g.priority == Priority::Critical)
493 .count();
494 let high_gaps = gaps.iter().filter(|g| g.priority == Priority::High).count();
495
496 format!(
497 "Known: {} facts | Unknown: {} gaps ({} critical, {} high) | Assumptions: {}",
498 known_facts.len(),
499 gaps.len(),
500 critical_gaps,
501 high_gaps,
502 assumptions.len()
503 )
504 }
505}
506
507impl Default for KnowledgeAnalyzer {
508 fn default() -> Self {
509 Self::new()
510 }
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516 use uuid::Uuid;
517
518 fn create_test_message(text: &str) -> Memory {
519 Memory {
520 id: Uuid::new_v4(),
521 entity_id: Uuid::new_v4(),
522 agent_id: Uuid::new_v4(),
523 room_id: Uuid::new_v4(),
524 content: Content {
525 text: text.to_string(),
526 ..Default::default()
527 },
528 embedding: None,
529 metadata: None,
530 created_at: chrono::Utc::now().timestamp(),
531 unique: None,
532 similarity: None,
533 }
534 }
535
536 #[tokio::test]
537 async fn test_knowledge_analysis_simple() {
538 let analyzer = KnowledgeAnalyzer::new();
539 let message = create_test_message("Hello, how are you?");
540 let state = State::new();
541
542 let knowledge = analyzer.analyze(&message, &state).await.unwrap();
543 assert!(knowledge.confidence_score > 0.0);
544 }
545
546 #[tokio::test]
547 async fn test_entity_extraction() {
548 let analyzer = KnowledgeAnalyzer::new();
549 let entities = analyzer.extract_entities("Tell me about Rust programming and Python");
550
551 assert!(entities.contains(&"Rust".to_string()));
552 assert!(entities.contains(&"Python".to_string()));
553 }
554
555 #[tokio::test]
556 async fn test_contextual_gaps() {
557 let analyzer = KnowledgeAnalyzer::new();
558 let message = create_test_message("Can you continue from where we left off last time?");
559 let state = State::new();
560
561 let knowledge = analyzer.analyze(&message, &state).await.unwrap();
562 assert!(!knowledge.unknown_gaps.is_empty());
563 assert!(knowledge
564 .unknown_gaps
565 .iter()
566 .any(|g| g.priority >= Priority::High));
567 }
568
569 #[tokio::test]
570 async fn test_known_facts_from_state() {
571 let analyzer = KnowledgeAnalyzer::new();
572 let message = create_test_message("Hello");
573 let mut state = State::new();
574 state.data.insert(
575 "agentName".to_string(),
576 serde_json::Value::String("TestAgent".to_string()),
577 );
578
579 let knowledge = analyzer.analyze(&message, &state).await.unwrap();
580 assert!(!knowledge.known_facts.is_empty());
581 assert!(knowledge
582 .known_facts
583 .iter()
584 .any(|f| f.fact.contains("TestAgent")));
585 }
586}