heerjtdev commited on
Commit
8cfed6d
·
verified ·
1 Parent(s): b879237

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +56 -118
app.py CHANGED
@@ -7,18 +7,14 @@ import google.generativeai as genai
7
  from sentence_transformers import SentenceTransformer, util
8
 
9
  # ============================================================
10
- # CONFIG
11
  # ============================================================
12
- GEMINI_API_KEY = "AIzaSyBrbLGXkSdXReb0lUucYqcNCNBkvS-RBFw"
13
- if not GEMINI_API_KEY:
14
- raise RuntimeError("Set GEMINI_API_KEY environment variable")
15
-
16
  genai.configure(api_key=GEMINI_API_KEY)
17
 
18
-
19
-
20
-
21
- MODEL = genai.GenerativeModel("gemini-2.0-flash")
22
 
23
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
24
  EMBED_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
@@ -29,130 +25,72 @@ embedder = SentenceTransformer(EMBED_MODEL, device=DEVICE)
29
  print("✅ Ready")
30
 
31
  # ============================================================
32
- # UTILS
33
  # ============================================================
34
- def split_sentences(text):
35
- return [s.strip() for s in re.split(r'(?<=[.!?])\s+', text) if len(s.strip()) > 5]
36
-
37
- def gemini(prompt, max_tokens=256):
38
- response = MODEL.generate_content(
39
- prompt,
40
- generation_config=genai.types.GenerationConfig(
41
- temperature=0.0,
42
- max_output_tokens=max_tokens
43
- )
44
- )
45
- return response.text.strip()
46
-
47
- def safe_json(text):
 
 
 
48
  try:
 
 
 
 
 
 
 
 
49
  return json.loads(text)
50
- except:
51
- start, end = text.find("{"), text.rfind("}") + 1
52
- if start != -1 and end != -1:
53
- try:
54
- return json.loads(text[start:end])
55
- except:
56
- return None
57
- return None
58
-
59
- # ============================================================
60
- # STEP 1: INTENT
61
- # ============================================================
62
- def detect_intent(question):
63
- prompt = f"""
64
- Classify the question intent. Choose ONE:
65
- FACTUAL, EXPLANATORY, CHARACTER_ARC, PROCESS, COMPARISON
66
-
67
- Question:
68
- {question}
69
-
70
- Output ONLY the label.
71
- """
72
- out = gemini(prompt, 20)
73
- return out if out in {
74
- "FACTUAL","EXPLANATORY","CHARACTER_ARC","PROCESS","COMPARISON"
75
- } else "EXPLANATORY"
76
-
77
- # ============================================================
78
- # STEP 2: RUBRIC GENERATION
79
- # ============================================================
80
- def generate_rubric(kb, question, intent):
81
- prompt = f"""
82
- You are an examiner.
83
-
84
- Using ONLY the knowledge base, create a grading rubric for the question.
85
- Each item must be an atomic idea a student must mention.
86
 
87
- Rules:
88
- - 3 to 6 criteria
89
- - No paraphrasing the question
90
- - No explanations
91
- - Capture progression if relevant
92
- - STRICT JSON ONLY
93
-
94
- Format:
95
- {{ "criteria": ["criterion 1", "criterion 2"] }}
96
-
97
- Knowledge Base:
98
- {kb}
99
-
100
- Question:
101
- {question}
102
-
103
- Intent:
104
- {intent}
105
- """
106
- raw = gemini(prompt, 300)
107
- parsed = safe_json(raw)
108
- return parsed["criteria"] if parsed and "criteria" in parsed else []
109
 
110
- # ============================================================
111
- # STEP 3: SEMANTIC MATCHING
112
- # ============================================================
113
- def score(answer, criteria):
114
- sents = split_sentences(answer)
115
  ans_emb = embedder.encode(sents, convert_to_tensor=True)
116
-
117
- results = []
118
- for crit in criteria:
119
  crit_emb = embedder.encode(crit, convert_to_tensor=True)
120
  sims = util.cos_sim(crit_emb, ans_emb)[0]
121
  best = float(torch.max(sims)) if sims.numel() else 0.0
 
122
 
123
- results.append({
124
- "criterion": crit,
125
- "score": round(best, 3),
126
- "satisfied": best >= SIM_THRESHOLD
127
- })
128
- return results
129
-
130
- # ============================================================
131
- # FINAL VERDICT
132
- # ============================================================
133
- def verdict(scored):
134
  hit = sum(c["satisfied"] for c in scored)
135
- total = len(scored)
136
-
137
- if hit == total:
138
- return "✅ CORRECT"
139
- if hit >= max(1, total // 2):
140
- return "⚠️ PARTIALLY CORRECT"
141
- return "❌ INCORRECT"
142
-
143
- # ============================================================
144
- # PIPELINE
145
- # ============================================================
146
- def evaluate(answer, question, kb):
147
- intent = detect_intent(question)
148
- rubric = generate_rubric(kb, question, intent)
149
- scored = score(answer, rubric) if rubric else []
150
 
151
  return {
152
  "intent": intent,
153
  "rubric": rubric,
154
- "scoring": scored,
155
- "final_verdict": verdict(scored) if rubric else "⚠️ NO RUBRIC"
156
  }
157
 
158
  # ============================================================
 
7
  from sentence_transformers import SentenceTransformer, util
8
 
9
  # ============================================================
10
+ # CONFIG - DO NOT LEAK YOUR KEY!
11
  # ============================================================
12
+ # Best practice: use os.environ.get("GEMINI_API_KEY")
13
+ GEMINI_API_KEY = "AIzaSyBrbLGXkSdXReb0lUucYqcNCNBkvS-RBFw"
 
 
14
  genai.configure(api_key=GEMINI_API_KEY)
15
 
16
+ # Use 1.5-flash for maximum stability on Free Tier
17
+ MODEL = genai.GenerativeModel("gemini-1.5-flash")
 
 
18
 
19
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
20
  EMBED_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
 
25
  print("✅ Ready")
26
 
27
  # ============================================================
28
+ # OPTIMIZED PIPELINE (ONE CALL ONLY)
29
  # ============================================================
30
+ def get_rubric_and_intent(kb, question):
31
+ """Combines intent detection and rubric generation to save API quota."""
32
+ prompt = f"""
33
+ You are an expert examiner. Analyze the provided Knowledge Base and Question.
34
+
35
+ 1. Classify the intent: FACTUAL, EXPLANATORY, CHARACTER_ARC, PROCESS, or COMPARISON.
36
+ 2. Create a grading rubric of 3-6 atomic criteria based ONLY on the Knowledge Base.
37
+
38
+ Knowledge Base: {kb}
39
+ Question: {question}
40
+
41
+ STRICT JSON OUTPUT ONLY:
42
+ {{
43
+ "intent": "YOUR_LABEL",
44
+ "criteria": ["criterion 1", "criterion 2", ...]
45
+ }}
46
+ """
47
  try:
48
+ response = MODEL.generate_content(prompt)
49
+ # Handle potential safety blocks or empty responses
50
+ if not response.candidates or not response.candidates[0].content.parts:
51
+ return {"intent": "ERROR", "criteria": []}
52
+
53
+ text = response.text.strip()
54
+ # Clean JSON if model adds markdown backticks
55
+ text = re.sub(r'^```json\s*|\s*```$', '', text, flags=re.MULTILINE)
56
  return json.loads(text)
57
+ except Exception as e:
58
+ print(f"API Error: {e}")
59
+ return {"intent": "EXPLANATORY", "criteria": []}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
+ def evaluate(answer, question, kb):
62
+ # STEP 1: Get logic from Gemini (Single Call)
63
+ data = get_rubric_and_intent(kb, question)
64
+ intent = data.get("intent", "EXPLANATORY")
65
+ rubric = data.get("criteria", [])
66
+
67
+ if not rubric:
68
+ return {"final_verdict": "⚠️ API ERROR: No rubric generated."}
69
+
70
+ # STEP 2: Semantic Matching (Local - No API cost)
71
+ sents = [s.strip() for s in re.split(r'(?<=[.!?])\s+', answer) if len(s.strip()) > 5]
72
+ if not sents:
73
+ return {"final_verdict": "❌ ANSWER TOO SHORT"}
 
 
 
 
 
 
 
 
 
74
 
 
 
 
 
 
75
  ans_emb = embedder.encode(sents, convert_to_tensor=True)
76
+ scored = []
77
+
78
+ for crit in rubric:
79
  crit_emb = embedder.encode(crit, convert_to_tensor=True)
80
  sims = util.cos_sim(crit_emb, ans_emb)[0]
81
  best = float(torch.max(sims)) if sims.numel() else 0.0
82
+ scored.append({"criterion": crit, "satisfied": best >= SIM_THRESHOLD})
83
 
84
+ # STEP 3: Verdict
 
 
 
 
 
 
 
 
 
 
85
  hit = sum(c["satisfied"] for c in scored)
86
+ if hit == len(scored): verdict_text = "✅ CORRECT"
87
+ elif hit >= max(1, len(scored) // 2): verdict_text = "⚠️ PARTIALLY CORRECT"
88
+ else: verdict_text = "❌ INCORRECT"
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
  return {
91
  "intent": intent,
92
  "rubric": rubric,
93
+ "final_verdict": verdict_text
 
94
  }
95
 
96
  # ============================================================