Repository navigation
Expand file tree
/
Copy pathstreamlit_app.py
More file actions
187 lines (155 loc) 路 9.53 KB
/
Copy pathstreamlit_app.py
File metadata and controls
187 lines (155 loc) 路 9.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
import streamlit as st
import os
import litellm
import random
import uuid
import json
# import mysql.connector
# import re
from helper_functions import parse_score_from_feedback, log_event_to_mysql, st_rtl_write
from knowledge_base import knowledge_base
try:
api_key = st.secrets["GEMINI_API_KEY"]
os.environ['GEMINI_API_KEY'] = api_key
except KeyError:
st.error("API key for Gemini is missing. Please check your .streamlit/secrets.toml file.")
st.stop()
litellm.set_verbose = True
# --- PAGE LAYOUT AND STATE MANAGEMENT ---
st.title("馃帗 讛诪讜专讛 讛驻专讟讬 砖诇讱")
if 'session_id' not in st.session_state:
st.session_state.session_id = str(uuid.uuid4())
if 'current_question_unit' not in st.session_state:
current_unit = random.choice(knowledge_base)
st.session_state.current_question_unit = current_unit
else:
current_unit = st.session_state.current_question_unit
if 'last_logged_topic' not in st.session_state or st.session_state.last_logged_topic != current_unit['topic']:
log_event_to_mysql(
session_id=st.session_state.session_id,
event_type="QUESTION_PRESENTED",
details_dict={"questionText": current_unit['question']},
topic=current_unit['topic'],
difficulty=current_unit['difficulty'],
scope=current_unit['scope']
)
st.session_state.last_logged_topic = current_unit['topic']
st.header(f"谞讜砖讗: {current_unit['topic']}")
st.subheader(f"砖讗诇讛 诇讚讜讙诪讛 (诪注诪讜讚 {current_unit['page_number']}):")
# Use st.markdown to create an RTL container for the question
#rtl_question = f'<div style="direction: rtl; text-align: right;">{current_unit["question"]}</div>'
#st.markdown(rtl_question, unsafe_allow_html=True)
st_rtl_write(current_unit['question'])
st.divider()
student_answer = st.text_area("讛拽诇讬讚讬 讗转 转砖讜讘转讱 讻讗谉:", height=150)
# --- THE CORRECTED AGENTIC WORKFLOW ---
if st.button("讛注专讱 讗转 转砖讜讘转讬"):
session_id = st.session_state.session_id
try:
log_event_to_mysql(
session_id=session_id,
event_type="SUBMISSION_ATTEMPT",
details_dict={"questionText": current_unit['question'], "studentAnswer": student_answer},
topic=current_unit['topic'],
difficulty=current_unit['difficulty'],
scope=current_unit['scope']
)
except Exception as e:
st.error(f"An error occurred: {e}")
try:
if not student_answer.strip():
st.warning("讗谞讗 讛拽诇讬讚讬 转砖讜讘讛 诇驻谞讬 讛诇讞讬爪讛 注诇 讛讻驻转讜专.")
log_event_to_mysql(session_id, "TRIAGE_RESULT", {"classification": "empty_answer"})
else:
# --- AGENT 1: Triage Agent ---
with st.spinner("...拽讜专讗 讗转 讛转砖讜讘讛"):
triage_prompt = f"""
You are a text classification agent. Classify the student's answer into one of these three categories:
- `valid_attempt`: The student is trying to answer the question.
- `no_knowledge`: The student states they don't know the answer or are unsure.
- `gibberish`: The answer is nonsense or random characters.
Student's Answer: "{student_answer}"
---
Respond with ONLY ONE WORD: valid_attempt, no_knowledge, or gibberish.
"""
triage_response = litellm.completion(model="gemini/gemini-1.5-flash-latest", messages=[{"role": "user", "content": triage_prompt}])
classification = triage_response.choices[0].message.content.strip().lower()
log_event_to_mysql(session_id, "TRIAGE_RESULT", {"classification": classification})
# --- CORRECTED LOGIC: The evaluation is now NESTED inside the 'valid_attempt' block ---
if "valid_attempt" in classification:
# --- AGENT 2: Evaluator Agent ---
with st.spinner("讛诪注专讻转 诪注专讬讻讛 讗转 转砖讜讘转讱..."):
evaluation_prompt = f"""
You are an assistant that evaluates a student's answer against an ideal answer from a textbook. The interaction must be in HEBREW, Female form (You are a male trainer, and the student is female).
**Sample of an Ideal Answer (in Hebrew) to this question:** {current_unit['ideal_answer']}
**Key Concepts the student should mention (in Hebrew):** {current_unit['key_concepts']}
**Student's Answer (in Hebrew):** {student_answer}
---
Based ONLY on the information above, perform the following tasks in HEBREW:
1. Provide a score from 1 (completely wrong) to 5 (perfect).
2. Provide a short, one-sentence justification for your score.
3. Provide friendly and constructive feedback to help the student learn.
Format your response as a single, valid JSON object with ONLY the following keys:
- "score": An integer from 1 to 5.
- "justification": A string containing the justification.
- "feedback": A string containing the feedback.
"""
evaluation_response = litellm.completion(model="gemini/gemini-1.5-flash-latest", messages=[{"role": "user", "content": evaluation_prompt}],response_format={"type": "json_object"})
feedback_json_string = evaluation_response.choices[0].message.content
# --- THIS IS THE NEW BLOCK ---
try:
# Parse the JSON string from the LLM into a Python dictionary
evaluation_data = json.loads(feedback_json_string)
# Safely get the data. .get() is safer than [] because it won't crash if a key is missing.
numeric_score = evaluation_data.get('score')
justification_text = evaluation_data.get('justification', "诇讗 住讜驻拽 谞讬诪讜拽.")
feedback_text = evaluation_data.get('feedback', "诇讗 住讜驻拽 诪砖讜讘.")
# Display the structured feedback
st.markdown("---")
st.markdown('<h3 style="direction: rtl; text-align: right;">讛注专讻讛 砖诇 转砖讜讘转讱:</h3>', unsafe_allow_html=True)
# Construct the final HTML string with bold tags and line breaks
final_feedback_html = f"""
<div style="direction: rtl; text-align: right;">
<b>爪讬讜谉:</b> {numeric_score}/5<br>
<b>谞讬诪讜拽:</b> {justification_text}<br>
<b>诪砖讜讘:</b> {feedback_text}
</div>
"""
st.markdown(final_feedback_html, unsafe_allow_html=True)
# Log the event with the clean, numeric score
log_event_to_mysql(
session_id=session_id,
event_type="EVALUATION_RESULT",
details_dict={"rawFeedback": evaluation_data}, # Log the whole structured object
topic=current_unit['topic'],
difficulty=current_unit['difficulty'],
scope=current_unit['scope'],
score=numeric_score # Use the direct numeric score
)
except (json.JSONDecodeError, AttributeError):
# This is a fallback in case the LLM fails to return valid JSON
st.error("砖讙讬讗讛 讘注讬讘讜讚 转砖讜讘转 讛诪注专讻转. 诪爪讬讙 讗转 讛转砖讜讘讛 讛讙讜诇诪讬转:")
st_rtl_write(feedback_json_string) # Display the raw text so nothing is lost
log_event_to_mysql(
session_id=session_id, event_type="ERROR",
details_dict={"source": "json_parsing", "rawResponse": feedback_json_string},
topic=current_unit['topic']
)
# --- END OF NEW BLOCK ---
elif "no_knowledge" in classification:
response_text = (
"讗讬谉 砖讜诐 讘注讬讛! 讝讜 讛专讙砖讛 讟讘注讬转 诇讙诪专讬 讘转讛诇讬讱 诇诪讬讚讛. "
f"讛谞讜砖讗 讛讝讛 诪讜驻讬注 讘注诪讜讚 {current_unit['page_number']}. 谞住讬 诇拽专讜讗 砖讜讘 讗转 讛讞诇拽 讛专诇讜讜谞讟讬 讜诇谞住讜转 砖讜讘!"
)
st.info(response_text)
log_event_to_mysql(session_id, "SYSTEM_RESPONSE", {"type": "hint_and_encourage", "text": response_text})
elif "gibberish" in classification:
response_text = "谞专讗讛 砖讛转砖讜讘讛 砖讛讜拽诇讚讛 讗讬谞讛 讘专讜专讛. 讗谞讗 谞住讬 诇谞住讞 转砖讜讘讛 诪诇讗讛."
st.warning(response_text)
log_event_to_mysql(session_id, "SYSTEM_RESPONSE", {"type": "request_clearer_answer", "text": response_text})
else: # Fallback for unknown classification from Triage Agent
st.error("讛转专讞砖讛 砖讙讬讗讛 讘谞讬转讜讞 讛转砖讜讘讛. 讗谞讗 谞住讬 砖讜讘.")
log_event_to_mysql(session_id, "ERROR", {"source": "triage_logic", "message": "Unknown classification"})
except Exception as e:
st.error(f"An error occurred: {e}")
log_event_to_mysql(session_id, "ERROR", {"source": "main_logic_block", "message": str(e)})