Quiz-Generator / app.py
SerotoninRonin's picture
Adding CTA to error response
9ccf303 verified
import json
from typing import List, Any, Dict, Tuple, Union
import gradio as gr
from huggingface_hub import InferenceClient
MAX_QUESTIONS = 20
def parse_quiz_json(content: str) -> Union[List[Dict[str, Any]], Dict[str, Any]]:
"""Parse the quiz JSON content robustly."""
try:
if content.startswith("{") or content.startswith("["):
quiz = json.loads(content)
else:
quiz = json.loads(content.lstrip().removeprefix("```json").removesuffix("```"))
if isinstance(quiz, dict) and "questions" in quiz:
return quiz["questions"]
return quiz
except Exception:
try:
quiz = json.loads(json.loads(content))
if isinstance(quiz, dict) and "questions" in quiz:
return quiz["questions"]
return quiz
except Exception as e:
return {"error": f"Could not parse JSON: {e}", "raw_output": content}
def generate_quiz(
topic: str, num_questions: int, q_type: str
) -> Tuple[List[dict[str, Any]], List[str]]:
"""Generate quiz questions and correct answers."""
if not topic:
updates = [
gr.update(visible=True, value="Please enter a topic."),
*[gr.update(visible=False, value="", interactive=False) for _ in range((MAX_QUESTIONS * 2) - 1)]
]
return updates, []
system_msg = "You are an expert quiz creator."
user_msg = (
f"Create a quiz containing {num_questions} {q_type} questions based on the following topic:\n"
f"{topic}\n"
"Respond with a JSON array of objects, each containing a question, options, and the answer.\n"
)
if q_type == "Multiple Choice":
user_msg += "Each question should have 4 answer choices, unlabeled, and one correct answer."
client = InferenceClient()
content = ""
# Stream the response from the model
for chunk in client.chat.completions.create(
model="openai/gpt-oss-20b",
messages=[
{"role": "system", "content": system_msg},
{"role": "user", "content": user_msg}
],
max_tokens=1500,
temperature=0.7,
stream=True,
# response_format={
# "type": "json_schema",
# "json_schema": {
# "name": "questions",
# "description": "A list of questions, answer choices, and the correct answer",
# "schema": {
# "type": "array",
# "items": {
# "type": "object",
# "properties": {
# "question": {"type": "string"},
# "options": {
# "type": "array",
# "items": {"type": "string"}
# },
# "answer": {"type": "string"}
# },
# }
# },
# "strict": True
# }
# },
):
if chunk.choices and chunk.choices[0].delta and chunk.choices[0].delta.content:
content += chunk.choices[0].delta.content
quiz = parse_quiz_json(content)
updates = []
correct_answers = []
for i in range(MAX_QUESTIONS):
if isinstance(quiz, dict) and "error" in quiz:
if i == 0:
updates.append(gr.update(visible=True, value="Oops! An error occured. Please try again."))
else:
updates.append(gr.update(visible=False, value="", interactive=False))
updates.append(gr.update(visible=False, choices=[], value=None, interactive=False))
elif isinstance(quiz, list) and i < len(quiz):
question = quiz[i].get("question", "No question provided")
options = ["True", "False"] if q_type == "True/False" else quiz[i].get("options", [])
answer = quiz[i].get("answer", "No answer provided")
correct_answers.append(answer)
updates.append(gr.update(visible=True, value=f"{question}"))
updates.append(gr.update(visible=True, choices=options, interactive=True, value=None))
else:
updates.append(gr.update(visible=False, value="", interactive=False))
updates.append(gr.update(visible=False, choices=[], value=None, interactive=False))
return updates, correct_answers
def score_quiz(*args) -> str:
"""Score the quiz based on user answers and correct answers."""
*user_answers, correct_answers = args
if not correct_answers or not isinstance(correct_answers, list):
return "No quiz generated yet. Please generate a quiz first."
score = 0
total = 0
for i, user_ans in enumerate(user_answers):
if i < len(correct_answers) and user_ans is not None:
if user_ans == correct_answers[i]:
score += 1
total += 1
return f"Your score: {score} / {total}"
with gr.Blocks() as demo:
gr.Markdown("# AI Quiz Generator")
gr.Markdown("### Instructions")
gr.Markdown(
"""1. Enter a topic for the quiz in the textbox above.
2. Select the number of questions you want.
3. Choose the type of questions (Multiple Choice or True/False).
4. Click 'Generate Quiz' to create your quiz.
5. The generated quiz will appear in the output box.
6. After answering, click 'Submit Answers' to see your score."""
)
topic_input = gr.Textbox(label="Quiz Topic / Prompt", lines=4, placeholder="Enter the topic for the quiz")
num_questions_input = gr.Slider(minimum=1, maximum=MAX_QUESTIONS, step=1, label="Number of Questions", value=5)
q_type_input = gr.Radio(choices=["Multiple Choice", "True/False"], label="Question Type", value="Multiple Choice")
generate_button = gr.Button("Generate Quiz")
quiz_components = []
for i in range(MAX_QUESTIONS):
quiz_components.append(gr.Textbox(label=f"Q{i+1}", visible=False, interactive=False))
quiz_components.append(gr.Radio(choices=[], label=f"Options", visible=False))
score_output = gr.Textbox(label="Score", visible=True, interactive=False)
submit_button = gr.Button("Submit Answers")
correct_answers_state = gr.State([])
def generate_and_store(*args):
updates, correct_answers = generate_quiz(*args)
return (*updates, correct_answers, 0)
generate_button.click(
fn=generate_and_store,
inputs=[topic_input, num_questions_input, q_type_input],
outputs=[*quiz_components, correct_answers_state, score_output],
)
radio_outputs = [quiz_components[i] for i in range(1, len(quiz_components), 2)]
def score_with_state(*args):
*user_answers, correct_answers = args
return score_quiz(*user_answers, correct_answers)
submit_button.click(
fn=score_with_state,
inputs=[*radio_outputs, correct_answers_state],
outputs=[score_output],
)
with gr.Row():
generate_button
submit_button
score_output
if __name__ == "__main__":
demo.launch()