namish10 commited on
Commit
86b5863
·
verified ·
1 Parent(s): e86ba0b

Upload agents.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. agents.py +445 -0
agents.py ADDED
@@ -0,0 +1,445 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ContextFlow Multi-Agent Integration
3
+
4
+ Brings together the core ContextFlow agents for the OpenEnv environment:
5
+ - DoubtPredictorAgent: RL-based confusion prediction
6
+ - BehavioralAgent: Behavior signal analysis
7
+ - HandGestureAgent: Gesture-based learning signals
8
+ """
9
+
10
+ import numpy as np
11
+ from typing import Dict, List, Any, Optional, Tuple
12
+ from dataclasses import dataclass, field
13
+ from datetime import datetime
14
+ from enum import Enum
15
+ import json
16
+
17
+
18
+ class ConfusionLevel(str, Enum):
19
+ LOW = "low"
20
+ MEDIUM = "medium"
21
+ HIGH = "high"
22
+ CRITICAL = "critical"
23
+
24
+
25
+ class InterventionType(str, Enum):
26
+ HINT = "hint"
27
+ SIMPLIFY = "simplify"
28
+ BREAKDOWN = "breakdown"
29
+ EXAMPLE = "example"
30
+ SCAFFOLD = "scaffold"
31
+ PEER_CONNECT = "peer_connect"
32
+ BREAK = "break"
33
+ ENCOURAGE = "encourage"
34
+
35
+
36
+ @dataclass
37
+ class LearningState:
38
+ topic: str
39
+ subtopic: str
40
+ progress_percentage: float
41
+ time_spent_seconds: int
42
+ confusion_signals: float
43
+ eye_tracking_confidence: float
44
+ scroll_reversals: int
45
+ selection_count: int
46
+ previous_doubts_count: int
47
+ mastery_level: float
48
+ difficulty_rating: float
49
+ time_of_day: int
50
+ streak_days: int
51
+
52
+
53
+ @dataclass
54
+ class BehavioralSignal:
55
+ signal_type: str
56
+ value: float
57
+ timestamp: datetime
58
+ source: str
59
+ metadata: Dict = field(default_factory=dict)
60
+
61
+
62
+ @dataclass
63
+ class GestureTemplate:
64
+ gesture_id: str
65
+ name: str
66
+ description: str
67
+ samples: List[List[float]] = field(default_factory=list)
68
+ centroid: Optional[List[float]] = None
69
+ threshold: float = 0.3
70
+ trained: bool = False
71
+
72
+
73
+ @dataclass
74
+ class AgentPrediction:
75
+ confusion_probability: float
76
+ confusion_level: ConfusionLevel
77
+ confidence: float
78
+ recommended_intervention: InterventionType
79
+ intervention_intensity: float
80
+ reasoning: str
81
+ supporting_signals: Dict[str, float]
82
+
83
+
84
+ class MultiModalFusion:
85
+ """Fuses signals from multiple modalities"""
86
+
87
+ def __init__(self):
88
+ self.weights = {
89
+ "behavioral": 0.25,
90
+ "gesture": 0.25,
91
+ "biometric": 0.20,
92
+ "temporal": 0.15,
93
+ "content": 0.15,
94
+ }
95
+
96
+ def fuse(
97
+ self,
98
+ behavioral: float,
99
+ gesture: float,
100
+ biometric: float,
101
+ temporal: float,
102
+ content: float,
103
+ ) -> float:
104
+ return (
105
+ self.weights["behavioral"] * behavioral +
106
+ self.weights["gesture"] * gesture +
107
+ self.weights["biometric"] * biometric +
108
+ self.weights["temporal"] * temporal +
109
+ self.weights["content"] * content
110
+ )
111
+
112
+
113
+ class ContextFlowAgent:
114
+ """
115
+ Integrated ContextFlow agent combining:
116
+ - RL-based doubt prediction
117
+ - Behavioral signal analysis
118
+ - Gesture recognition
119
+ - Multi-modal fusion
120
+ """
121
+
122
+ def __init__(self, config: Optional[Dict] = None):
123
+ self.config = config or {}
124
+
125
+ self.doubt_predictor = RLBasedPredictor()
126
+ self.behavioral_analyzer = BehavioralAnalyzer()
127
+ self.gesture_recognizer = GestureRecognizer()
128
+ self.fusion = MultiModalFusion()
129
+
130
+ self.history: List[Dict] = []
131
+ self.episode_rewards: List[float] = []
132
+
133
+ self.epsilon = 1.0
134
+ self.epsilon_decay = 0.995
135
+ self.epsilon_min = 0.01
136
+
137
+ def predict(
138
+ self,
139
+ observation: Dict[str, Any],
140
+ use_exploration: bool = True,
141
+ ) -> AgentPrediction:
142
+ behavioral_signal = self.behavioral_analyzer.analyze(observation)
143
+ gesture_signal = self.gesture_recognizer.recognize(observation)
144
+ biometric_signal = self._extract_biometric_signal(observation)
145
+ temporal_signal = self._extract_temporal_signal(observation)
146
+ content_signal = self._extract_content_signal(observation)
147
+
148
+ fused_signal = self.fusion.fuse(
149
+ behavioral=behavioral_signal,
150
+ gesture=gesture_signal,
151
+ biometric=biometric_signal,
152
+ temporal=temporal_signal,
153
+ content=content_signal,
154
+ )
155
+
156
+ if use_exploration and np.random.random() < self.epsilon:
157
+ confusion_prob = np.random.uniform(0.3, 0.8)
158
+ else:
159
+ confusion_prob = self.doubt_predictor.predict(fused_signal)
160
+
161
+ confusion_level = self._get_confusion_level(confusion_prob)
162
+ intervention, intensity = self._get_recommendation(confusion_prob, confusion_level)
163
+
164
+ return AgentPrediction(
165
+ confusion_probability=confusion_prob,
166
+ confusion_level=confusion_level,
167
+ confidence=0.85,
168
+ recommended_intervention=intervention,
169
+ intervention_intensity=intensity,
170
+ reasoning=self._generate_reasoning(behavioral_signal, gesture_signal, biometric_signal),
171
+ supporting_signals={
172
+ "behavioral": behavioral_signal,
173
+ "gesture": gesture_signal,
174
+ "biometric": biometric_signal,
175
+ "temporal": temporal_signal,
176
+ "content": content_signal,
177
+ "fused": fused_signal,
178
+ }
179
+ )
180
+
181
+ def update(self, reward: float, observation: Dict[str, Any]):
182
+ self.episode_rewards.append(reward)
183
+ self.epsilon = max(self.epsilon_min, self.epsilon * self.epsilon_decay)
184
+
185
+ if len(self.episode_rewards) > 100:
186
+ recent_avg = np.mean(self.episode_rewards[-100:])
187
+ self.doubt_predictor.update_q_value(recent_avg)
188
+
189
+ def _extract_biometric_signal(self, obs: Dict) -> float:
190
+ biometric = obs.get("biometric_features", [])
191
+ if not biometric:
192
+ return 0.5
193
+
194
+ hr = biometric[0] if len(biometric) > 0 else 70.0
195
+ gsr = biometric[1] if len(biometric) > 1 else 0.5
196
+
197
+ hr_signal = min(1.0, max(0.0, (hr - 60) / 40))
198
+ gsr_signal = min(1.0, max(0.0, gsr * 2))
199
+
200
+ return (hr_signal + gsr_signal) / 2
201
+
202
+ def _extract_temporal_signal(self, obs: Dict) -> float:
203
+ time_spent = obs.get("learning_context", {}).get("time_spent", 0)
204
+
205
+ if time_spent < 300:
206
+ return 0.2
207
+ elif time_spent < 900:
208
+ return 0.4
209
+ elif time_spent < 1800:
210
+ return 0.6
211
+ else:
212
+ return 0.8 + min(0.2, (time_spent - 1800) / 3600)
213
+
214
+ def _extract_content_signal(self, obs: Dict) -> float:
215
+ difficulty = obs.get("learning_context", {}).get("difficulty", "medium")
216
+ difficulty_map = {"easy": 0.2, "medium": 0.5, "hard": 0.8}
217
+ return difficulty_map.get(difficulty, 0.5)
218
+
219
+ def _get_confusion_level(self, prob: float) -> ConfusionLevel:
220
+ if prob < 0.25:
221
+ return ConfusionLevel.LOW
222
+ elif prob < 0.5:
223
+ return ConfusionLevel.MEDIUM
224
+ elif prob < 0.75:
225
+ return ConfusionLevel.HIGH
226
+ else:
227
+ return ConfusionLevel.CRITICAL
228
+
229
+ def _get_recommendation(self, prob: float, level: ConfusionLevel) -> Tuple[InterventionType, float]:
230
+ recommendations = {
231
+ ConfusionLevel.LOW: (InterventionType.ENCOURAGE, 0.3),
232
+ ConfusionLevel.MEDIUM: (InterventionType.HINT, 0.5),
233
+ ConfusionLevel.HIGH: (InterventionType.SIMPLIFY, 0.7),
234
+ ConfusionLevel.CRITICAL: (InterventionType.SCAFFOLD, 0.9),
235
+ }
236
+ return recommendations[level]
237
+
238
+ def _generate_reasoning(self, behavioral: float, gesture: float, biometric: float) -> str:
239
+ reasons = []
240
+
241
+ if behavioral > 0.6:
242
+ reasons.append("High scroll reversals and hesitation detected")
243
+ if gesture > 0.6:
244
+ reasons.append("Confusion-related gestures identified")
245
+ if biometric > 0.6:
246
+ reasons.append("Elevated physiological stress indicators")
247
+
248
+ if not reasons:
249
+ reasons.append("All signals within normal range")
250
+
251
+ return "; ".join(reasons)
252
+
253
+
254
+ class RLBasedPredictor:
255
+ """Q-learning based confusion predictor"""
256
+
257
+ def __init__(self):
258
+ self.q_values: Dict[float, float] = {}
259
+ self.gamma = 0.95
260
+ self.learning_rate = 0.1
261
+
262
+ def predict(self, state: float) -> float:
263
+ if state not in self.q_values:
264
+ self.q_values[state] = 0.5
265
+
266
+ base = self.q_values[state]
267
+ noise = np.random.normal(0, 0.05)
268
+ return np.clip(base + noise, 0.0, 1.0)
269
+
270
+ def update_q_value(self, reward: float, state: float = 0.5):
271
+ if state not in self.q_values:
272
+ self.q_values[state] = 0.5
273
+
274
+ self.q_values[state] += self.learning_rate * (
275
+ reward - self.q_values[state]
276
+ )
277
+
278
+
279
+ class BehavioralAnalyzer:
280
+ """Analyzes behavioral signals for confusion indicators"""
281
+
282
+ def __init__(self):
283
+ self.baseline_scroll_speed = 1.0
284
+ self.baseline_click_rate = 1.0
285
+
286
+ def analyze(self, observation: Dict[str, Any]) -> float:
287
+ behavioral = observation.get("behavioral_features", [])
288
+
289
+ if not behavioral or len(behavioral) < 4:
290
+ return 0.5
291
+
292
+ scroll_reversal = behavioral[0]
293
+ hesitation = behavioral[1]
294
+ click_pattern = behavioral[2]
295
+ time_on_task = behavioral[3]
296
+
297
+ signals = [
298
+ min(1.0, scroll_reversal * 2),
299
+ min(1.0, hesitation * 2),
300
+ min(1.0, click_pattern * 2),
301
+ min(1.0, time_on_task / 1800),
302
+ ]
303
+
304
+ return np.mean(signals)
305
+
306
+
307
+ class GestureRecognizer:
308
+ """Recognizes confusion-related gestures"""
309
+
310
+ CONFUCSION_GESTURES = {
311
+ "head_scratch": {"pattern": [0.7, 0.8, 0.9], "confidence": 0.85},
312
+ "brow_furrow": {"pattern": [0.6, 0.7, 0.8], "confidence": 0.75},
313
+ "hand_wave": {"pattern": [0.5, 0.6, 0.7], "confidence": 0.70},
314
+ "thinking": {"pattern": [0.4, 0.5, 0.6], "confidence": 0.65},
315
+ }
316
+
317
+ def __init__(self):
318
+ self.last_gesture = None
319
+ self.gesture_duration = 0
320
+
321
+ def recognize(self, observation: Dict[str, Any]) -> float:
322
+ gesture_features = observation.get("gesture_features", [])
323
+
324
+ if not gesture_features or len(gesture_features) < 21:
325
+ return 0.3
326
+
327
+ hand_variance = np.var(gesture_features[:21])
328
+ movement_intensity = np.mean(np.abs(np.diff(gesture_features[:21])))
329
+
330
+ confusion_score = min(1.0, (hand_variance * 5 + movement_intensity * 3))
331
+
332
+ return confusion_score
333
+
334
+
335
+ class KnowledgeGraphAgent:
336
+ """Tracks concept relationships and prerequisite chains"""
337
+
338
+ def __init__(self):
339
+ self.concepts: Dict[str, Dict] = {}
340
+ self.prerequisites: Dict[str, List[str]] = {}
341
+
342
+ def add_concept(self, concept: str, mastery: float, prerequisites: List[str]):
343
+ self.concepts[concept] = {
344
+ "mastery": mastery,
345
+ "last_accessed": datetime.now(),
346
+ }
347
+ self.prerequisites[concept] = prerequisites
348
+
349
+ def get_prerequisite_mastery(self, concept: str) -> float:
350
+ prereqs = self.prerequisites.get(concept, [])
351
+ if not prereqs:
352
+ return 1.0
353
+
354
+ masteries = [self.concepts.get(p, {}).get("mastery", 0.0) for p in prereqs]
355
+ return min(masteries) if masteries else 1.0
356
+
357
+ def predict_confusion_risk(self, concept: str) -> float:
358
+ mastery = self.concepts.get(concept, {}).get("mastery", 0.0)
359
+ prereq_mastery = self.get_prerequisite_mastery(concept)
360
+
361
+ risk = (1 - mastery) * 0.6 + (1 - prereq_mastery) * 0.4
362
+ return risk
363
+
364
+
365
+ class PeerLearningAgent:
366
+ """Connects learners with similar struggles"""
367
+
368
+ def __init__(self):
369
+ self.learners: Dict[str, Dict] = {}
370
+ self.doubt_patterns: Dict[str, List[str]] = {}
371
+
372
+ def register_doubt(self, user_id: str, doubt: str):
373
+ if user_id not in self.doubt_patterns:
374
+ self.doubt_patterns[user_id] = []
375
+ self.doubt_patterns[user_id].append(doubt)
376
+
377
+ def find_similar_learners(self, doubt: str, top_k: int = 3) -> List[Dict]:
378
+ matches = []
379
+ for user_id, doubts in self.doubt_patterns.items():
380
+ overlap = len(set(doubts) & {doubt})
381
+ if overlap > 0:
382
+ matches.append({
383
+ "user_id": user_id,
384
+ "overlap": overlap,
385
+ "solutions_shared": len(doubts) - overlap,
386
+ })
387
+
388
+ matches.sort(key=lambda x: x["overlap"], reverse=True)
389
+ return matches[:top_k]
390
+
391
+
392
+ class RecallAgent:
393
+ """Spaced repetition for concept reinforcement"""
394
+
395
+ def __init__(self):
396
+ self.cards: Dict[str, Dict] = {}
397
+
398
+ def add_card(self, concept: str, quality: int = 0):
399
+ self.cards[concept] = {
400
+ "interval": 1,
401
+ "ease_factor": 2.5,
402
+ "repetitions": 0,
403
+ "next_review": datetime.now(),
404
+ "quality": quality,
405
+ }
406
+
407
+ def process_review(self, concept: str, quality: int) -> Dict:
408
+ if concept not in self.cards:
409
+ self.add_card(concept, quality)
410
+ return {"interval": 1, "message": "New card added"}
411
+
412
+ card = self.cards[concept]
413
+
414
+ if quality < 3:
415
+ card["repetitions"] = 0
416
+ card["interval"] = 1
417
+ else:
418
+ if card["repetitions"] == 0:
419
+ card["interval"] = 1
420
+ elif card["repetitions"] == 1:
421
+ card["interval"] = 6
422
+ else:
423
+ card["interval"] = int(card["interval"] * card["ease_factor"])
424
+
425
+ card["repetitions"] += 1
426
+
427
+ card["ease_factor"] = max(1.3, card["ease_factor"] + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02)))
428
+ card["next_review"] = datetime.now()
429
+
430
+ return {"interval": card["interval"], "ease_factor": card["ease_factor"]}
431
+
432
+
433
+ __all__ = [
434
+ "ContextFlowAgent",
435
+ "RLBasedPredictor",
436
+ "BehavioralAnalyzer",
437
+ "GestureRecognizer",
438
+ "KnowledgeGraphAgent",
439
+ "PeerLearningAgent",
440
+ "RecallAgent",
441
+ "MultiModalFusion",
442
+ "ConfusionLevel",
443
+ "InterventionType",
444
+ "AgentPrediction",
445
+ ]