|
import gradio as gr |
|
from pathlib import Path |
|
from sentence_transformers import CrossEncoder |
|
import numpy as np |
|
from time import perf_counter |
|
from pydantic import BaseModel, Field |
|
from phi.agent import Agent |
|
from phi.model.groq import Groq |
|
import os |
|
import logging |
|
|
|
|
|
logging.basicConfig(level=logging.INFO) |
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
api_key = os.getenv("GROQ_API_KEY") |
|
if not api_key: |
|
gr.Warning("GROQ_API_KEY not found. Set it in 'Repository secrets'.") |
|
logger.error("GROQ_API_KEY not found.") |
|
else: |
|
os.environ["GROQ_API_KEY"] = api_key |
|
|
|
|
|
class QuizItem(BaseModel): |
|
question: str = Field(..., description="The quiz question") |
|
choices: list[str] = Field(..., description="List of 4 multiple-choice options") |
|
correct_answer: str = Field(..., description="The correct choice (e.g., 'C1')") |
|
|
|
class QuizOutput(BaseModel): |
|
items: list[QuizItem] = Field(..., description="List of 10 quiz items") |
|
|
|
|
|
groq_agent = Agent(model=Groq(model="llama3-70b-8192", api_key=api_key), markdown=True) |
|
|
|
quiz_generator = Agent( |
|
name="Quiz Generator", |
|
role="Generates structured quiz questions and answers", |
|
instructions=[ |
|
"Create 10 questions with 4 choices each based on the provided topic and documents.", |
|
"Use the specified difficulty level (easy, average, hard) to adjust question complexity.", |
|
"Ensure questions are derived only from the provided documents.", |
|
"Return the output in a structured format using the QuizOutput Pydantic model.", |
|
"Each question should have a unique correct answer from the choices (labeled C1, C2, C3, C4)." |
|
], |
|
model=Groq(id="llama3-70b-8192", api_key=api_key), |
|
response_model=QuizOutput, |
|
markdown=True |
|
) |
|
|
|
VECTOR_COLUMN_NAME = "vector" |
|
TEXT_COLUMN_NAME = "text" |
|
proj_dir = Path.cwd() |
|
|
|
|
|
from backend.semantic_search import table, retriever |
|
|
|
def generate_quiz_data(question_difficulty, topic, documents_str): |
|
prompt = f"""Generate a quiz with {question_difficulty} difficulty on topic '{topic}' using only the following documents:\n{documents_str}""" |
|
try: |
|
response = quiz_generator.run(prompt) |
|
return response.content |
|
except Exception as e: |
|
logger.error(f"Failed to generate quiz: {e}") |
|
return None |
|
|
|
def retrieve_and_generate_quiz(question_difficulty, topic): |
|
gr.Warning('Generating quiz may take 1-2 minutes. Please wait.', duration=60) |
|
top_k_rank = 10 |
|
documents = [] |
|
|
|
document_start = perf_counter() |
|
query_vec = retriever.encode(topic) |
|
documents = [doc[TEXT_COLUMN_NAME] for doc in table.search(query_vec, vector_column_name=VECTOR_COLUMN_NAME).limit(top_k_rank).to_list()] |
|
|
|
|
|
cross_encoder = CrossEncoder('BAAI/bge-reranker-base') |
|
query_doc_pair = [[topic, doc] for doc in documents] |
|
cross_scores = cross_encoder.predict(query_doc_pair) |
|
sim_scores_argsort = list(reversed(np.argsort(cross_scores))) |
|
documents = [documents[idx] for idx in sim_scores_argsort[:top_k_rank]] |
|
|
|
documents_str = '\n'.join(documents) |
|
quiz_data = generate_quiz_data(question_difficulty, topic, documents_str) |
|
return quiz_data |
|
|
|
def update_quiz_components(quiz_data): |
|
if not quiz_data or not quiz_data.items: |
|
return [gr.update(visible=False) for _ in range(10)] + [gr.update(value="Error: Failed to generate quiz.", visible=True)] |
|
|
|
radio_updates = [] |
|
for i, item in enumerate(quiz_data.items[:10]): |
|
choices = item.choices |
|
radio_update = gr.update(visible=True, choices=choices, label=item.question, value=None) |
|
radio_updates.append(radio_update) |
|
return radio_updates + [gr.update(value="Please select answers and click 'Check Score'.", visible=True)] |
|
|
|
|
|
def collect_answers_and_calculate(*all_inputs): |
|
print(f"Total inputs received: {len(all_inputs)}") |
|
|
|
|
|
radio_values = all_inputs[:10] |
|
quiz_data = all_inputs[10] |
|
|
|
print(f"Received radio_values: {radio_values}") |
|
print(f"Received quiz_data: {quiz_data}") |
|
|
|
|
|
score = 0 |
|
answered_questions = 0 |
|
|
|
for i, (user_answer, quiz_item) in enumerate(zip(radio_values, quiz_data.items[:10])): |
|
if user_answer is not None: |
|
answered_questions += 1 |
|
|
|
|
|
correct_answer_index = int(quiz_item.correct_answer[1]) - 1 |
|
correct_answer_text = quiz_item.choices[correct_answer_index] |
|
|
|
print(f"Q{i+1}: User='{user_answer}' vs Correct='{correct_answer_text}'") |
|
|
|
if user_answer == correct_answer_text: |
|
score += 1 |
|
|
|
print(f"Calculated score: {score}/{answered_questions}") |
|
|
|
|
|
if answered_questions == 0: |
|
html_message = """ |
|
<div style="text-align: center; padding: 20px; border-radius: 10px; background: linear-gradient(135deg, #ff6b6b, #ee5a24);"> |
|
<h2 style="color: white; margin: 0;">β οΈ Please answer at least one question!</h2> |
|
</div> |
|
""" |
|
elif score == answered_questions: |
|
html_message = f""" |
|
<div style="text-align: center; padding: 20px; border-radius: 10px; background: linear-gradient(135deg, #00d2d3, #54a0ff); box-shadow: 0 4px 15px rgba(0,0,0,0.2);"> |
|
<h1 style="color: white; margin: 0; text-shadow: 2px 2px 4px rgba(0,0,0,0.3);">π PERFECT SCORE! π</h1> |
|
<h2 style="color: #fff3cd; margin: 10px 0;">You got {score} out of {answered_questions} correct!</h2> |
|
<p style="color: white; font-size: 18px; margin: 0;">Outstanding performance! π</p> |
|
</div> |
|
""" |
|
elif score > answered_questions * 0.7: |
|
html_message = f""" |
|
<div style="text-align: center; padding: 20px; border-radius: 10px; background: linear-gradient(135deg, #2ed573, #7bed9f); box-shadow: 0 4px 15px rgba(0,0,0,0.2);"> |
|
<h1 style="color: white; margin: 0; text-shadow: 2px 2px 4px rgba(0,0,0,0.3);">π EXCELLENT! π</h1> |
|
<h2 style="color: #fff3cd; margin: 10px 0;">You got {score} out of {answered_questions} correct!</h2> |
|
<p style="color: white; font-size: 18px; margin: 0;">Great job! Keep it up! πͺ</p> |
|
</div> |
|
""" |
|
elif score > answered_questions * 0.5: |
|
html_message = f""" |
|
<div style="text-align: center; padding: 20px; border-radius: 10px; background: linear-gradient(135deg, #ffa726, #ffcc02); box-shadow: 0 4px 15px rgba(0,0,0,0.2);"> |
|
<h1 style="color: white; margin: 0; text-shadow: 2px 2px 4px rgba(0,0,0,0.3);">π GOOD JOB! π</h1> |
|
<h2 style="color: #fff3cd; margin: 10px 0;">You got {score} out of {answered_questions} correct!</h2> |
|
<p style="color: white; font-size: 18px; margin: 0;">Well done! Room for improvement! π</p> |
|
</div> |
|
""" |
|
else: |
|
html_message = f""" |
|
<div style="text-align: center; padding: 20px; border-radius: 10px; background: linear-gradient(135deg, #ff7675, #fd79a8); box-shadow: 0 4px 15px rgba(0,0,0,0.2);"> |
|
<h1 style="color: white; margin: 0; text-shadow: 2px 2px 4px rgba(0,0,0,0.3);">πͺ KEEP TRYING! πͺ</h1> |
|
<h2 style="color: #fff3cd; margin: 10px 0;">You got {score} out of {answered_questions} correct!</h2> |
|
<p style="color: white; font-size: 18px; margin: 0;">Don't worry! Practice makes perfect! πβ¨</p> |
|
</div> |
|
""" |
|
|
|
return html_message |
|
|
|
|
|
colorful_theme = gr.themes.Default(primary_hue="cyan", secondary_hue="yellow", neutral_hue="purple") |
|
|
|
with gr.Blocks(title="Quiz Maker", theme=colorful_theme) as QUIZBOT: |
|
|
|
with gr.Row(): |
|
with gr.Column(scale=2): |
|
gr.Image(value='logo.png', height=200, width=200) |
|
with gr.Column(scale=6): |
|
gr.HTML(""" |
|
<center> |
|
<h1><span style="color: purple;">GOVERNMENT HIGH SCHOOL,SUTHUKENY</span> STUDENTS QUIZBOT </h1> |
|
<h2>Generative AI-powered Capacity building for STUDENTS</h2> |
|
<i>β οΈ Students can create quiz from any topic from 10th Science and evaluate themselves! β οΈ</i> |
|
</center> |
|
""") |
|
|
|
topic = gr.Textbox(label="Enter the Topic for Quiz", placeholder="Write any CHAPTER NAME") |
|
|
|
with gr.Row(): |
|
difficulty_radio = gr.Radio(["easy", "average", "hard"], label="How difficult should the quiz be?") |
|
model_radio = gr.Radio(choices=['(ACCURATE) BGE reranker'], value='(ACCURATE) BGE reranker', label="Embeddings") |
|
|
|
generate_quiz_btn = gr.Button("Generate Quiz!π") |
|
quiz_msg = gr.Textbox(label="Status", interactive=False) |
|
|
|
|
|
question_radios = [gr.Radio(visible=False, label="", choices=[""], value=None) for _ in range(10)] |
|
quiz_data_state = gr.State(value=None) |
|
check_score_btn = gr.Button("Check Score", variant="primary", size="lg") |
|
|
|
|
|
score_output = gr.HTML(visible=False, label="Your Results") |
|
|
|
|
|
generate_quiz_btn.click( |
|
fn=retrieve_and_generate_quiz, |
|
inputs=[difficulty_radio, topic], |
|
outputs=[quiz_data_state] |
|
).then( |
|
fn=update_quiz_components, |
|
inputs=[quiz_data_state], |
|
outputs=question_radios + [quiz_msg] |
|
) |
|
|
|
|
|
check_score_btn.click( |
|
fn=collect_answers_and_calculate, |
|
inputs=question_radios + [quiz_data_state], |
|
outputs=[score_output], |
|
api_name="check_score" |
|
).then( |
|
fn=lambda: gr.update(visible=True), |
|
inputs=[], |
|
outputs=[score_output] |
|
) |
|
|
|
if __name__ == "__main__": |
|
QUIZBOT.queue().launch(server_name="0.0.0.0", server_port=7860) |