import gradio as gr from transformers import pipeline, AutoTokenizer, AutoModelForSeq2SeqLM TITLE = "Bloom’s Taxonomy Helper (Classify + Generate + Rewrite)" # ------------------ Bloom labels ------------------ LABELS = ["Remember", "Understand", "Apply", "Analyze", "Evaluate", "Create"] # ------------------ Generation templates by Bloom ------------------ PROMPT_TEMPLATES = { "Remember": ( "Write {n} distinct factual recall questions for college students.\n" "Topic: {topic}\n" "Cognitive focus: Remember (recognize/recall facts and terms).\n" "Style: Use 'What', 'When', 'Define', or 'List'.\n" "Do not include answers. Number each on its own line." ), "Understand": ( "Write {n} distinct comprehension questions for college students.\n" "Topic: {topic}\n" "Cognitive focus: Understand (explain, summarize, interpret).\n" "Style: Use 'Explain', 'Summarize', 'Describe', or 'Give an example of'.\n" "Do not include answers. Number each on its own line." ), "Apply": ( "Write {n} distinct application questions for college students.\n" "Topic: {topic}\n" "Cognitive focus: Apply (use procedures, compute, demonstrate use).\n" "Style: Ask students to calculate, implement, or solve with concrete data.\n" "Avoid 'define' or 'explain' prompts. Do not include answers. Number each on its own line." ), "Analyze": ( "Write {n} distinct analysis questions for college students.\n" "Topic: {topic}\n" "Cognitive focus: Analyze (compare/contrast, break down relationships, cause–effect).\n" "Style: Use 'Compare', 'Differentiate', 'Explain why', 'Break down', or 'Trace'.\n" "Do not include answers. Number each on its own line." ), "Evaluate": ( "Write {n} distinct evaluation questions for college students.\n" "Topic: {topic}\n" "Cognitive focus: Evaluate (judge, critique, justify with criteria and evidence).\n" "Style: Use 'Argue', 'Defend', 'Critique', 'Which is better and why', or 'Assess'.\n" "Do not include answers. Number each on its own line." ), "Create": ( "Write {n} distinct creation/synthesis tasks for college students.\n" "Topic: {topic}\n" "Cognitive focus: Create (design, plan, invent, propose, produce a novel artifact).\n" "Style: Use 'Design', 'Propose', 'Develop', 'Compose', or 'Build'. Include realistic constraints.\n" "Do not include answers. Number each on its own line." ), } # ------------------ Pipelines ------------------ # Zero-shot classifier (no training) clf = pipeline("zero-shot-classification", model="typeform/distilbert-base-uncased-mnli") # Higher-quality generator (CPU-friendly but better than *small*) GEN_MODEL = "google/flan-t5-base" gen_tok = AutoTokenizer.from_pretrained(GEN_MODEL) gen_mdl = AutoModelForSeq2SeqLM.from_pretrained(GEN_MODEL) gen = pipeline("text2text-generation", model=gen_mdl, tokenizer=gen_tok) # ------------------ Helpers ------------------ def clean_numbering(text: str, n: int) -> str: """Force clean 1..n numbering, each on its own line.""" lines = [ln.strip(" -*\t") for ln in text.splitlines() if ln.strip()] # If model returned a paragraph, split by sentence end if len(lines) == 1 and "1." not in lines[0]: import re parts = [p.strip() for p in re.split(r"(?<=[.?!])\s+", lines[0]) if p.strip()] lines = parts lines = [ln for ln in lines if any(c.isalpha() for c in ln)] lines = lines[:max(1, n)] return "\n".join(f"{i+1}. {ln.lstrip('0123456789. ').strip()}" for i, ln in enumerate(lines)) # ------------------ Functions ------------------ def classify_bloom(question: str): question = (question or "").strip() if not question: return "", "" res = clf(question, LABELS) labels = res["labels"] scores = [round(float(s), 3) for s in res["scores"]] top = labels[0] if labels else "" table = "\n".join(f"{l}: {s}" for l, s in zip(labels, scores)) return top, table def generate_questions(topic: str, level: str, n: int, creativity: float): topic = (topic or "").strip() if not topic: return "Please enter a topic." template = PROMPT_TEMPLATES.get(level, PROMPT_TEMPLATES["Understand"]) prompt = template.format(n=int(n), topic=topic) out = gen( prompt, max_new_tokens=180, do_sample=(creativity > 0.01), temperature=max(0.01, min(1.2, creativity)), top_p=0.9, num_beams=1, )[0]["generated_text"] return clean_numbering(out, int(n)) def rewrite_level(question: str, target_level: str): question = (question or "").strip() if not question: return "Paste a question to rewrite." # Leverage the template for target level to steer rewriting template = PROMPT_TEMPLATES.get(target_level, PROMPT_TEMPLATES["Understand"]) prompt = ( f"{template}\n\n" f"Transform the following single question to match the level above. Keep it concise and do not include the answer.\n" f"Original: {question}\n" f"Return exactly 1 numbered question." ) out = gen(prompt, max_new_tokens=100, do_sample=False)[0]["generated_text"] return clean_numbering(out, 1) # ------------------ UI ------------------ with gr.Blocks(title=TITLE) as demo: gr.Markdown(f"# {TITLE}") gr.Markdown( "Classify questions by Bloom level, generate new questions aligned to a level, " "and rewrite a question to a different level. Runs fully on open models." ) with gr.Tab("Classify"): q = gr.Textbox( label="Enter a question", lines=4, placeholder="e.g., Explain why randomized controlled trials reduce bias." ) top = gr.Textbox(label="Predicted Bloom level", interactive=False) scores = gr.Textbox(label="All scores", interactive=False) gr.Button("Classify").click(classify_bloom, [q], [top, scores]) with gr.Tab("Generate"): with gr.Row(): topic = gr.Textbox(label="Topic", value="binary numbers in computer science") level = gr.Dropdown(LABELS, value="Apply", label="Bloom level") with gr.Row(): n = gr.Slider(1, 10, value=5, step=1, label="How many questions") creativity = gr.Slider(0.0, 1.2, value=0.6, step=0.1, label="Creativity (temperature)") out = gr.Textbox(label="Generated questions", lines=12) gr.Button("Generate").click(generate_questions, [topic, level, n, creativity], out) with gr.Tab("Rewrite"): q2 = gr.Textbox(label="Original question", lines=4, value="Define binary number.") target = gr.Dropdown(LABELS, value="Analyze", label="Target Bloom level") out2 = gr.Textbox(label="Rewritten question", lines=4) gr.Button("Rewrite").click(rewrite_level, [q2, target], out2) demo.launch()