{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%pip install -q transformers accelerate bitsandbytes torch gradio\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import torch\n", "import json\n", "import pandas as pd\n", "import gradio as gr\n", "from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig\n", "from huggingface_hub import login\n", "from google.colab import userdata\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Authenticate with HuggingFace\n", "hf_token = userdata.get('HF_TOKEN')\n", "login(hf_token, add_to_git_credential=True)\n", "print(\"Successfully authenticated with HuggingFace\")\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Model configuration\n", "MODEL_NAME = \"meta-llama/Meta-Llama-3.1-8B-Instruct\"\n", "\n", "# 4-bit quantization for efficiency on T4 GPU\n", "quant_config = BitsAndBytesConfig(\n", " load_in_4bit=True,\n", " bnb_4bit_use_double_quant=True,\n", " bnb_4bit_compute_dtype=torch.bfloat16,\n", " bnb_4bit_quant_type=\"nf4\"\n", ")\n", "\n", "# Load tokenizer and model\n", "tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)\n", "tokenizer.pad_token = tokenizer.eos_token\n", "\n", "model = AutoModelForCausalLM.from_pretrained(\n", " MODEL_NAME,\n", " device_map=\"auto\",\n", " quantization_config=quant_config\n", ")\n", "\n", "print(\"Model loaded successfully!\")\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Topic definitions based on course content\n", "TOPICS = {\n", " \"Week 1: LLM APIs & Prompting\": {\n", " \"concepts\": [\n", " \"OpenAI API usage and parameters\",\n", " \"Prompt engineering techniques\",\n", " \"Temperature and top_p parameters\",\n", " \"System vs user messages\",\n", " \"JSON mode and structured outputs\",\n", " \"Token counting and pricing\",\n", " \"Chat completions vs completions\",\n", " \"Few-shot learning\"\n", " ]\n", " },\n", " \"Week 2: Function Calling & Agents\": {\n", " \"concepts\": [\n", " \"Function calling syntax and format\",\n", " \"Tool definitions and schemas\",\n", " \"Parallel function calling\",\n", " \"Function calling best practices\",\n", " \"Agent patterns and workflows\",\n", " \"Structured outputs with Pydantic\",\n", " \"Error handling in function calls\"\n", " ]\n", " },\n", " \"Week 3: Transformers & Models\": {\n", " \"concepts\": [\n", " \"Tokenizers and tokenization strategies\",\n", " \"BPE, WordPiece, and SentencePiece\",\n", " \"HuggingFace pipelines\",\n", " \"AutoModel and AutoTokenizer\",\n", " \"Model quantization (4-bit, 8-bit)\",\n", " \"Speech-to-text with Whisper\",\n", " \"Local vs cloud model inference\",\n", " \"Model architectures (encoder, decoder, encoder-decoder)\"\n", " ]\n", " }\n", "}\n", "\n", "# Difficulty level descriptions\n", "DIFFICULTY_LEVELS = {\n", " \"Beginner\": \"Basic understanding of concepts and definitions\",\n", " \"Intermediate\": \"Application of concepts with some technical depth\",\n", " \"Advanced\": \"Edge cases, optimization, and deep technical understanding\"\n", "}\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def generate_questions(topic, difficulty, num_questions):\n", " \"\"\"\n", " Generate educational Q&A questions using the LLM.\n", " \n", " Args:\n", " topic: Topic category to generate questions for\n", " difficulty: Difficulty level (Beginner/Intermediate/Advanced)\n", " num_questions: Number of questions to generate\n", " \n", " Returns:\n", " List of dictionaries containing questions and answers\n", " \"\"\"\n", " \n", " # Get topic details\n", " topic_info = TOPICS[topic]\n", " concepts = \", \".join(topic_info[\"concepts\"])\n", " \n", " # Build the prompt using Llama's chat format\n", " system_message = \"\"\"You are an expert educator creating high-quality multiple-choice questions for an LLM Engineering course.\n", "\n", "Format each question EXACTLY as shown below:\n", "\n", "QUESTION: [question text]\n", "A) [option A]\n", "B) [option B]\n", "C) [option C]\n", "D) [option D]\n", "ANSWER: [correct letter]\n", "EXPLANATION: [brief explanation]\n", "---\"\"\"\n", "\n", " user_prompt = f\"\"\"Create {num_questions} multiple-choice questions about: {topic}\n", "\n", "Difficulty Level: {difficulty}\n", "\n", "Cover these concepts: {concepts}\n", "\n", "Requirements:\n", "- Questions should be practical and relevant to real LLM engineering\n", "- All 4 options should be plausible\n", "- Explanations should be clear and educational\n", "- Vary the correct answer position\n", "\n", "Generate {num_questions} questions now:\"\"\"\n", "\n", " # Prepare messages for Llama\n", " messages = [\n", " {\"role\": \"system\", \"content\": system_message},\n", " {\"role\": \"user\", \"content\": user_prompt}\n", " ]\n", " \n", " # Tokenize using Llama's chat template\n", " input_ids = tokenizer.apply_chat_template(\n", " messages,\n", " return_tensors=\"pt\",\n", " add_generation_prompt=True\n", " ).to(model.device)\n", " \n", " attention_mask = torch.ones_like(input_ids).to(model.device)\n", " \n", " # Generate\n", " print(f\"Generating {num_questions} questions...\")\n", " max_tokens = min(2500, num_questions * 200)\n", " \n", " with torch.no_grad():\n", " outputs = model.generate(\n", " input_ids,\n", " attention_mask=attention_mask,\n", " max_new_tokens=max_tokens,\n", " temperature=0.7,\n", " do_sample=True,\n", " top_p=0.9,\n", " pad_token_id=tokenizer.eos_token_id\n", " )\n", " \n", " # Decode\n", " response = tokenizer.decode(outputs[0], skip_special_tokens=True)\n", " \n", " # Extract just the assistant's response\n", " if \"assistant\" in response:\n", " response = response.split(\"assistant\")[-1].strip()\n", " \n", " # Debug: print what we got\n", " print(\"Generated text preview:\")\n", " print(response[:500] + \"...\" if len(response) > 500 else response)\n", " print()\n", " \n", " # Parse the questions\n", " questions = parse_questions(response, topic, difficulty)\n", " \n", " print(f\"Successfully generated {len(questions)} questions\")\n", " return questions\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def parse_questions(text, topic, difficulty):\n", " \"\"\"\n", " Parse the generated text into structured question objects.\n", " More robust parsing that handles various formats.\n", " \"\"\"\n", " questions = []\n", " \n", " # Split by \"QUESTION:\" to get individual question blocks\n", " blocks = text.split(\"QUESTION:\")\n", " \n", " for i, block in enumerate(blocks):\n", " if not block.strip() or i == 0 and len(block) < 20:\n", " continue\n", " \n", " try:\n", " # Extract components\n", " question_text = \"\"\n", " options = {}\n", " answer = \"\"\n", " explanation = \"\"\n", " \n", " lines = block.strip().split(\"\\n\")\n", " \n", " for line in lines:\n", " line = line.strip()\n", " if not line or line == \"---\":\n", " continue\n", " \n", " # Handle question text (first non-empty line before options)\n", " if not question_text and not any(line.startswith(x) for x in [\"A)\", \"B)\", \"C)\", \"D)\", \"ANSWER:\", \"EXPLANATION:\", \"Answer:\", \"Explanation:\"]):\n", " question_text = line\n", " \n", " # Handle options - be flexible with formatting\n", " elif line.startswith(\"A)\") or line.startswith(\"A.\"):\n", " options[\"A\"] = line[2:].strip()\n", " elif line.startswith(\"B)\") or line.startswith(\"B.\"):\n", " options[\"B\"] = line[2:].strip()\n", " elif line.startswith(\"C)\") or line.startswith(\"C.\"):\n", " options[\"C\"] = line[2:].strip()\n", " elif line.startswith(\"D)\") or line.startswith(\"D.\"):\n", " options[\"D\"] = line[2:].strip()\n", " \n", " # Handle answer\n", " elif line.upper().startswith(\"ANSWER:\"):\n", " answer = line.split(\":\", 1)[1].strip()\n", " \n", " # Handle explanation\n", " elif line.upper().startswith(\"EXPLANATION:\"):\n", " explanation = line.split(\":\", 1)[1].strip()\n", " elif explanation and len(explanation) < 200:\n", " # Continue multi-line explanation (up to reasonable length)\n", " explanation += \" \" + line\n", " \n", " # Extract just the letter from answer\n", " if answer:\n", " answer_letter = \"\"\n", " for char in answer.upper():\n", " if char in [\"A\", \"B\", \"C\", \"D\"]:\n", " answer_letter = char\n", " break\n", " answer = answer_letter\n", " \n", " # Only add if we have minimum required components\n", " if question_text and len(options) >= 3 and answer:\n", " # Fill missing option if needed\n", " if len(options) == 3:\n", " for letter in [\"A\", \"B\", \"C\", \"D\"]:\n", " if letter not in options:\n", " options[letter] = \"Not applicable\"\n", " break\n", " \n", " # Use placeholder explanation if none provided\n", " if not explanation:\n", " explanation = f\"The correct answer is {answer}.\"\n", " \n", " questions.append({\n", " \"id\": len(questions) + 1,\n", " \"topic\": topic,\n", " \"difficulty\": difficulty,\n", " \"question\": question_text,\n", " \"options\": options,\n", " \"correct_answer\": answer,\n", " \"explanation\": explanation.strip()\n", " })\n", " print(f\"Parsed question {len(questions)}\")\n", " else:\n", " print(f\"Skipped incomplete block: Q={bool(question_text)}, Opts={len(options)}, Ans={bool(answer)}\")\n", " \n", " except Exception as e:\n", " print(f\"Error parsing block {i+1}: {str(e)}\")\n", " continue\n", " \n", " return questions\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def format_questions_display(questions):\n", " \"\"\"Format questions for display in Gradio.\"\"\"\n", " if not questions:\n", " return \"No questions generated.\"\n", " \n", " output = f\"# Generated Questions\\n\\n\"\n", " output += f\"**Total Questions:** {len(questions)}\\n\\n\"\n", " output += \"---\\n\\n\"\n", " \n", " for q in questions:\n", " output += f\"## Question {q['id']}\\n\\n\"\n", " output += f\"**Topic:** {q['topic']} \\n\"\n", " output += f\"**Difficulty:** {q['difficulty']} \\n\\n\"\n", " output += f\"**Q:** {q['question']}\\n\\n\"\n", " \n", " for letter in ['A', 'B', 'C', 'D']:\n", " prefix = \"✅ \" if letter == q['correct_answer'] else \"\"\n", " output += f\"{prefix}{letter}) {q['options'][letter]}\\n\\n\"\n", " \n", " output += f\"**Answer:** {q['correct_answer']}\\n\\n\"\n", " output += f\"**Explanation:** {q['explanation']}\\n\\n\"\n", " output += \"---\\n\\n\"\n", " \n", " return output\n", "\n", "\n", "def export_to_json(questions):\n", " \"\"\"Export questions to JSON file.\"\"\"\n", " if not questions:\n", " return None\n", " \n", " filename = \"educational_qa_dataset.json\"\n", " with open(filename, 'w') as f:\n", " json.dump(questions, f, indent=2)\n", " \n", " return filename\n", "\n", "\n", "def export_to_csv(questions):\n", " \"\"\"Export questions to CSV file.\"\"\"\n", " if not questions:\n", " return None\n", " \n", " # Flatten the data for CSV\n", " flattened = []\n", " for q in questions:\n", " flattened.append({\n", " 'id': q['id'],\n", " 'topic': q['topic'],\n", " 'difficulty': q['difficulty'],\n", " 'question': q['question'],\n", " 'option_A': q['options']['A'],\n", " 'option_B': q['options']['B'],\n", " 'option_C': q['options']['C'],\n", " 'option_D': q['options']['D'],\n", " 'correct_answer': q['correct_answer'],\n", " 'explanation': q['explanation']\n", " })\n", " \n", " filename = \"educational_qa_dataset.csv\"\n", " df = pd.DataFrame(flattened)\n", " df.to_csv(filename, index=False)\n", " \n", " return filename\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def gradio_generate(topic, difficulty, num_questions):\n", " \"\"\"\n", " Wrapper function for Gradio interface.\n", " Generates questions and returns formatted output plus download files.\n", " \"\"\"\n", " try:\n", " # Generate questions\n", " questions = generate_questions(topic, difficulty, num_questions)\n", " \n", " if not questions:\n", " return \"Failed to generate questions. Please try again.\", None, None\n", " \n", " # Format for display\n", " display_text = format_questions_display(questions)\n", " \n", " # Export files\n", " json_file = export_to_json(questions)\n", " csv_file = export_to_csv(questions)\n", " \n", " return display_text, json_file, csv_file\n", " \n", " except Exception as e:\n", " return f\"Error: {str(e)}\", None, None\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Build the Gradio UI\n", "with gr.Blocks(title=\"Educational Q&A Generator\", theme=gr.themes.Soft()) as demo:\n", " \n", " gr.Markdown(\"\"\"\n", " # 📚 Educational Q&A Dataset Generator\n", " Generate high-quality multiple-choice questions for LLM Engineering topics\n", " \"\"\")\n", " \n", " with gr.Row():\n", " with gr.Column(scale=1):\n", " gr.Markdown(\"### ⚙️ Configuration\")\n", " \n", " topic_dropdown = gr.Dropdown(\n", " choices=list(TOPICS.keys()),\n", " value=\"Week 3: Transformers & Models\",\n", " label=\"Select Topic\",\n", " info=\"Choose which week's content to generate questions for\"\n", " )\n", " \n", " difficulty_dropdown = gr.Dropdown(\n", " choices=[\"Beginner\", \"Intermediate\", \"Advanced\"],\n", " value=\"Intermediate\",\n", " label=\"Difficulty Level\",\n", " info=\"Select the difficulty of the questions\"\n", " )\n", " \n", " num_questions_slider = gr.Slider(\n", " minimum=5,\n", " maximum=20,\n", " value=10,\n", " step=5,\n", " label=\"Number of Questions\",\n", " info=\"How many questions to generate (5-20)\"\n", " )\n", " \n", " generate_btn = gr.Button(\"🚀 Generate Questions\", variant=\"primary\", size=\"lg\")\n", " \n", " gr.Markdown(\"\"\"\n", " ---\n", " ### 📥 Download Files\n", " After generation, download your dataset in JSON or CSV format\n", " \"\"\")\n", " \n", " with gr.Row():\n", " json_download = gr.File(label=\"JSON File\", interactive=False)\n", " csv_download = gr.File(label=\"CSV File\", interactive=False)\n", " \n", " with gr.Column(scale=2):\n", " gr.Markdown(\"### 📝 Generated Questions\")\n", " \n", " output_display = gr.Markdown(\n", " value=\"Click 'Generate Questions' to start...\",\n", " label=\"Questions\"\n", " )\n", " \n", " # Connect the generate button\n", " generate_btn.click(\n", " fn=gradio_generate,\n", " inputs=[topic_dropdown, difficulty_dropdown, num_questions_slider],\n", " outputs=[output_display, json_download, csv_download]\n", " )\n", " \n", " gr.Markdown(\"\"\"\n", " ---\n", " ### 💡 Tips:\n", " - Start with 5 questions to test the system\n", " - Beginner questions cover definitions and basic concepts\n", " - Intermediate questions test application and understanding\n", " - Advanced questions explore edge cases and optimization\n", " - Generation takes ~30-60 seconds depending on number of questions\n", " \n", " ### 📊 Output Formats:\n", " - **JSON**: Structured data for programmatic use\n", " - **CSV**: Easy to view in spreadsheets or import into other tools\n", " \"\"\")\n", "\n", "print(\"✅ Gradio interface configured!\")\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Launch the Gradio app\n", "demo.launch(share=True, debug=True)\n" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 2 }