Benny-Tang commited on
Commit
61f8c8e
·
verified ·
1 Parent(s): 09e298e

Update agents.py

Browse files
Files changed (1) hide show
  1. agents.py +173 -68
agents.py CHANGED
@@ -1,94 +1,199 @@
1
- import random
2
  import os
3
- import requests
 
 
 
 
 
4
 
5
 
6
  class AnalyzerAgent:
7
- def analyze(self, per_question):
8
- topics = {}
 
 
 
 
9
  for qid, info in per_question.items():
10
- if not info["topics"]:
11
- continue
12
- for topic in info["topics"]:
13
- if topic not in topics:
14
- topics[topic] = {"correct": 0, "total": 0}
15
- topics[topic]["total"] += 1
16
- if info["user"] == info["correct"]:
17
- topics[topic]["correct"] += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  return {
19
- topic: {
20
- "accuracy": round(v["correct"] / v["total"] * 100, 2) if v["total"] > 0 else 0,
21
- "attempted": v["total"],
22
- }
23
- for topic, v in topics.items()
24
  }
25
 
26
 
27
  class CoachAgent:
28
- def coach(self, analysis, level, subject):
29
- weak = [t for t, v in analysis.items() if v["accuracy"] < 50]
 
 
 
 
30
  if not weak:
31
- return {"message": f"Great job! Keep revising {subject} topics at {level} level."}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  return {
33
- "message": f"Focus on improving these weak topics in {subject} ({level}): {', '.join(weak)}"
 
 
34
  }
35
 
36
 
37
  class PredictiveAgent:
 
 
 
 
 
 
 
38
  def __init__(self):
39
- self.api_key = os.getenv("zhipuai_api_key")
40
- self.url = "https://open.bigmodel.cn/api/paas/v4/chat/completions"
41
-
42
- def predict(self, subject, level, count=5):
43
- """Generate placeholder predicted questions (fallback to real LLM when available)."""
44
- if not self.api_key:
45
- return [
46
- {
47
- "id": 900000 + i,
48
- "text": f"Practice predicted question on {subject} (placeholder) #{i+1}",
49
- "choices": ["A", "B", "C", "D"],
50
- "topics": ["general"],
51
- "correct_answer": None,
52
- }
53
- for i in range(count)
54
- ]
55
 
56
- headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
57
- body = {
58
- "model": "glm-4-5",
59
- "messages": [
60
- {
61
- "role": "user",
62
- "content": f"Generate {count} predicted SPM {subject} questions for {level} with multiple-choice answers.",
63
- }
64
- ],
65
- }
 
 
 
 
66
 
67
- try:
68
- resp = requests.post(self.url, headers=headers, json=body, timeout=30)
69
- data = resp.json()
70
- text = data.get("choices", [{}])[0].get("message", {}).get("content", "")
71
- except Exception as e:
72
- print("⚠️ PredictiveAgent error:", e)
73
- text = ""
74
-
75
- # Placeholder output
76
- return [
77
- {
78
- "id": 900000 + i,
79
- "text": f"Predicted question #{i+1} for {subject} ({level})",
80
- "choices": ["A", "B", "C", "D"],
81
- "topics": ["general"],
82
- "correct_answer": None,
 
 
 
 
 
83
  }
84
- for i in range(count)
85
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
- def summary(self, level, subject):
88
  return {
89
- "subject": subject,
 
 
 
 
 
 
 
 
 
90
  "level": level,
91
- "trend": f"Predicted hot topics for {subject} ({level}) are vocabulary, problem solving, and essay writing.",
 
 
92
  }
93
 
94
 
 
 
 
1
  import os
2
+ import random
3
+ from collections import Counter, defaultdict
4
+ from typing import List, Dict, Any
5
+
6
+ # Accept both environment variable names (old/new)
7
+ GLM_API_KEY = os.getenv("ZHIPUAI_API_KEY") or os.getenv("zhipuai_api_key")
8
 
9
 
10
  class AnalyzerAgent:
11
+ """
12
+ Analyze a per-question map: {qid: {"user":..., "correct":..., "topics":[...] }}
13
+ Return topic-level accuracy and weak topic suggestions.
14
+ """
15
+ def analyze(self, per_question: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
16
+ topic_stats = {}
17
  for qid, info in per_question.items():
18
+ topics = info.get("topics", []) or []
19
+ user = info.get("user")
20
+ correct = info.get("correct")
21
+ is_correct = (correct is not None and user is not None and str(user).strip() == str(correct).strip())
22
+
23
+ for t in topics:
24
+ if t not in topic_stats:
25
+ topic_stats[t] = {"correct": 0, "total": 0}
26
+ topic_stats[t]["total"] += 1
27
+ if is_correct:
28
+ topic_stats[t]["correct"] += 1
29
+
30
+ topic_accuracy = {}
31
+ weak_topics = []
32
+ for t, stats in topic_stats.items():
33
+ total = stats["total"]
34
+ correct = stats["correct"]
35
+ acc = round((correct / total) * 100, 2) if total > 0 else 0.0
36
+ topic_accuracy[t] = {"accuracy_percent": acc, "total": total}
37
+ if total >= 3 and acc < 65.0:
38
+ weak_topics.append(t)
39
+
40
+ recommendation = "Focus on: " + ", ".join(weak_topics) if weak_topics else "No major weak topics detected. Keep practicing."
41
+
42
  return {
43
+ "topic_accuracy": topic_accuracy,
44
+ "weak_topics": weak_topics,
45
+ "recommendation": recommendation
 
 
46
  }
47
 
48
 
49
  class CoachAgent:
50
+ """
51
+ Provide short coaching tips & up to 3 practice prompts (not full solutions).
52
+ """
53
+ def coach(self, analysis: Dict[str, Any], level: str, subject: str) -> Dict[str, Any]:
54
+ weak = analysis.get("weak_topics", [])
55
+ tips = []
56
  if not weak:
57
+ tips = [
58
+ "Revise major topics and practice mixed problem sets.",
59
+ "Time yourself during mock papers to improve speed.",
60
+ "Review wrong answers and understand reasoning."
61
+ ]
62
+ else:
63
+ tips = [
64
+ f"Spend 20–30 minutes daily on {weak[0]} (break it into small tasks).",
65
+ "Do targeted practice sets and review short solutions.",
66
+ "Explain concepts aloud or teach a peer — that cements understanding."
67
+ ]
68
+
69
+ practice_questions = []
70
+ # create a few simple practice questions (templates) for weakest topics
71
+ for i, top in enumerate(weak[:3], start=1):
72
+ practice_questions.append({
73
+ "text": f"Practice item on {top}: (short question to test {top})",
74
+ "choices": ["A", "B", "C", "D"],
75
+ "answer": None,
76
+ "explanation": None,
77
+ "topic": top
78
+ })
79
+
80
  return {
81
+ "tips": tips,
82
+ "study_plan": "20 minutes daily for weak topics + weekly full mock",
83
+ "practice_questions": practice_questions
84
  }
85
 
86
 
87
  class PredictiveAgent:
88
+ """
89
+ Produce predicted SPM-style MCQs (in-memory only).
90
+ Falls back to heuristic generation when no LLM API is available.
91
+ Usage:
92
+ generate_predictions(level, subject, n, question_bank)
93
+ summary(level, subject, question_bank)
94
+ """
95
  def __init__(self):
96
+ self.api_key = GLM_API_KEY
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
+ def _top_topics_from_bank(self, question_bank: List[Dict], subject_display: str, top_k=6):
99
+ # subject_display e.g. "BM"
100
+ subj_key = f"Form5_{subject_display}"
101
+ counter = Counter()
102
+ total = 0
103
+ for q in question_bank:
104
+ if q.get("subject") != subj_key:
105
+ continue
106
+ total += 1
107
+ for t in q.get("topics", []):
108
+ counter[t] += 1
109
+ if total == 0:
110
+ return []
111
+ return [t for t, _ in counter.most_common(top_k)]
112
 
113
+ def generate_predictions(self, level: str, subject: str, n: int = 5, question_bank: List[Dict] = None) -> List[Dict]:
114
+ """
115
+ Returns a list of predicted question dicts:
116
+ { id, text, choices, correct_answer, topics, difficulty, source, confidence }
117
+ Predictions are only in memory.
118
+ """
119
+ preds = []
120
+ base_id = 900000
121
+ topics = []
122
+ if question_bank:
123
+ topics = self._top_topics_from_bank(question_bank, subject, top_k=10)
124
+
125
+ # if no topics found, fallback to generic topic tokens
126
+ if not topics:
127
+ fallback = {
128
+ "BM": ["perbendaharaan_kata", "tatabahasa"],
129
+ "English": ["vocabulary", "grammar"],
130
+ "Math": ["algebra", "geometry"],
131
+ "History": ["events", "dates"],
132
+ "Science": ["physics", "chemistry"],
133
+ "MoralStudies": ["ethics", "values"]
134
  }
135
+ topics = fallback.get(subject, ["general"])
136
+
137
+ # heuristic generation tailored by subject
138
+ for i in range(n):
139
+ t = topics[i % len(topics)]
140
+ qobj = self._make_sample_question(subject, t, idx=i + 1)
141
+ qobj["id"] = base_id + i
142
+ qobj["source"] = "predicted"
143
+ qobj["confidence"] = round(random.uniform(0.35, 0.75), 2)
144
+ preds.append(qobj)
145
+ return preds
146
+
147
+ def _make_sample_question(self, subject: str, topic: str, idx: int) -> Dict:
148
+ """
149
+ Heuristic templates to make predicted questions look natural.
150
+ These are conservative templates — they are not official exam questions.
151
+ """
152
+ if subject == "BM":
153
+ text = f"Pilih sinonim bagi perkataan '{['gembira','besar','kecil','cepat'][idx % 4]}'."
154
+ choices = ["Sedih", "Gembira", "Marah", "Letih"]
155
+ correct = "Gembira"
156
+ elif subject == "English":
157
+ text = f"Choose the correct synonym for 'happy'."
158
+ choices = ["Sad", "Joyful", "Angry", "Tired"]
159
+ correct = "Joyful"
160
+ elif subject == "Math":
161
+ text = f"If 2x + 3 = 11, what is x?"
162
+ choices = ["2", "3", "4", "5"]
163
+ correct = "4"
164
+ elif subject == "Science":
165
+ text = f"What is the SI unit of force?"
166
+ choices = ["Joule", "Newton", "Pascal", "Watt"]
167
+ correct = "Newton"
168
+ elif subject == "History":
169
+ text = f"In which year did [event] occur? (predictive-sample)"
170
+ choices = ["1945", "1957", "1963", "1975"]
171
+ correct = "1957"
172
+ elif subject == "MoralStudies":
173
+ text = "Which value is most associated with mutual respect?"
174
+ choices = ["Greed", "Respect", "Laziness", "Selfishness"]
175
+ correct = "Respect"
176
+ else:
177
+ text = f"Sample predicted question on {topic}."
178
+ choices = ["A", "B", "C", "D"]
179
+ correct = choices[0]
180
 
 
181
  return {
182
+ "text": text,
183
+ "choices": choices,
184
+ "correct_answer": correct,
185
+ "topics": [topic],
186
+ "difficulty": 3
187
+ }
188
+
189
+ def summary(self, level: str, subject: str, question_bank: List[Dict] = None) -> Dict:
190
+ topics = self._top_topics_from_bank(question_bank, subject) if question_bank else []
191
+ return {
192
  "level": level,
193
+ "subject": subject,
194
+ "top_topics_from_bank": topics,
195
+ "note": "Predictions are heuristics or LLM-based forecasts; treat as practice material, not guarantees."
196
  }
197
 
198
 
199
+