import json import re import gradio as gr from llama_cpp import Llama, LlamaGrammar from huggingface_hub import hf_hub_download # ========================================== # 1. LOAD THE MODEL # ========================================== print("Checking LLM status...") # Using a global variable for the model to handle Hugging Face's persistence llm = None def get_llm(): global llm if llm is None: print("Downloading Gemma Model...") model_path = hf_hub_download( repo_id="bartowski/google_gemma-2-9b-it-GGUF", # Corrected typical ID or keeping user choice filename="google_gemma-2-9b-it-Q4_K_M.gguf" # Using a known valid filename, user had '4-E4B' which might be a typo ) print("Loading LLM into RAM...") llm = Llama(model_path=model_path, n_ctx=4096, n_threads=2, verbose=False) print("Model Loaded Successfully!") return llm # ========================================== # 2. TEMPLATES & GRAMMARS # ========================================== TEMPLATES = { "Fill-in-the-blank": { "dna": [{"question": "If she ___ (miss) the flight, she would buy a new ticket.", "answer": "missed"}], "grammar": r""" root ::= "[" ws item ("," ws item)* ws "]" item ::= "{" ws "\"question\"" ws ":" ws string "," ws "\"answer\"" ws ":" ws string ws "}" string ::= "\"" ([^"\\\n] | "\\" (["\\/bfnrt] | "u"[0-9a-fA-F]{4}))* "\"" ws ::= [ \t\n\r]* """ }, "Multiple Choice": { "dna": [{"question": "The company ___ a profit last year.", "options": ["make", "made", "makes", "making"], "answer": "made"}], "grammar": r""" root ::= "[" ws item ("," ws item)* ws "]" item ::= "{" ws "\"question\"" ws ":" ws string "," ws "\"options\"" ws ":" ws "[" ws string ("," ws string)* ws "]" "," ws "\"answer\"" ws ":" ws string ws "}" string ::= "\"" ([^"\\\n] | "\\" (["\\/bfnrt] | "u"[0-9a-fA-F]{4}))* "\"" ws ::= [ \t\n\r]* """ }, "Sentence Reorder": { "dna": [{"scrambled": "yesterday / went / to / the / store / I", "correct_order": "I went to the store yesterday."}], "grammar": r""" root ::= "[" ws item ("," ws item)* ws "]" item ::= "{" ws "\"scrambled\"" ws ":" ws string "," ws "\"correct_order\"" ws ":" ws string ws "}" string ::= "\"" ([^"\\\n] | "\\" (["\\/bfnrt] | "u"[0-9a-fA-F]{4}))* "\"" ws ::= [ \t\n\r]* """ }, "Free QA": { "dna": [{"question": "What is the main advantage of open-source software?", "suggested_answer": "It allows anyone to inspect, modify, and enhance the source code."}], "grammar": r""" root ::= "[" ws item ("," ws item)* ws "]" item ::= "{" ws "\"question\"" ws ":" ws string "," ws "\"suggested_answer\"" ws ":" ws string ws "}" string ::= "\"" ([^"\\\n] | "\\" (["\\/bfnrt] | "u"[0-9a-fA-F]{4}))* "\"" ws ::= [ \t\n\r]* """ } } # ========================================== # 3. GENERATION LOGIC # ========================================== def generate_expansion(form_choice, user_requirement): model = get_llm() selected_dna = TEMPLATES[form_choice]["dna"] quiz_grammar = LlamaGrammar.from_string(TEMPLATES[form_choice]["grammar"]) prompt = f"""user You are an expert educational content creator. ### TASK REQUIREMENTS {user_requirement} ### STYLE GUIDE (DNA) Follow this exact JSON structure and keys: {json.dumps(selected_dna, indent=2)} ### INSTRUCTIONS - Generate the exact questions requested. - Output ONLY a raw JSON array matching the format above. model """ output = model( prompt, max_tokens=1500, temperature=0.5, stop=[""], grammar=quiz_grammar, echo=False ) try: raw_text = output['choices'][0]['text'].strip() quiz_data = json.loads(raw_text) json_str = json.dumps(quiz_data, indent=2, ensure_ascii=False) return quiz_data, form_choice, json_str except Exception as e: return [], form_choice, f"Error: {e}\n\nRaw Output:\n{output['choices'][0]['text']}" # ========================================== # 4. GRADIO INTERFACE # ========================================== with gr.Blocks(theme=gr.themes.Soft()) as demo: gr.Markdown(""" # 🚀 RA-ICL: Generative Expansion & Template Engine Welcome! This Space demonstrates a highly advanced application of **Retrieval-Augmented In-Context Learning (RA-ICL)** known as **1-to-N Generative Expansion**. ### 🧠 How this works: 1. **DNA Injection:** We inject a single JSON sample (the "DNA") into the prompt. 2. **Grammar Constraints:** We apply a strict GBNF Grammar to force valid JSON output. 3. **Generative Expansion:** The model scales your "Creative Instructions" into a brand-new dataset. """) # State variables to hold the dynamic data quiz_state = gr.State([]) form_state = gr.State("") with gr.Row(): with gr.Column(scale=1): form_choice = gr.Radio( choices=["Multiple Choice", "Fill-in-the-blank", "Sentence Reorder", "Free QA"], value="Multiple Choice", label="1. Output Structure" ) user_requirement = gr.Textbox( label="2. Content Requirement", placeholder="e.g., Generate 3 questions about German past tense verbs. Theme: Holiday.", lines=5 ) generate_btn = gr.Button("Generate Interactive Quiz", variant="primary", size="lg") with gr.Column(scale=2): with gr.Tabs(): with gr.TabItem("🎮 Playable Quiz"): @gr.render(inputs=[quiz_state, form_state]) def render_quiz(q_data, f_type): if not q_data: gr.Markdown("*Generate a quiz to see it here.*") return gr.Markdown(f"### {f_type} Quiz") for i, q in enumerate(q_data): with gr.Group(): if f_type == "Multiple Choice": gr.Markdown(f"**Q{i+1}:** {q['question']}") user_ans = gr.Radio(choices=q['options'], label="Your Answer") check_btn = gr.Button("Check Answer", size="sm") feedback = gr.Markdown() def check_mcq(ans, correct=q['answer']): if not ans: return "⚠️ Please select an answer." if ans == correct: return "✅ **Correct!**" return f"❌ **Incorrect.** The correct answer is: {correct}" check_btn.click(fn=check_mcq, inputs=[user_ans], outputs=[feedback]) elif f_type == "Fill-in-the-blank": gr.Markdown(f"**Q{i+1}:** {q['question']}") user_ans = gr.Textbox(label="Your Answer", placeholder="Type here...") check_btn = gr.Button("Check Answer", size="sm") feedback = gr.Markdown() def check_fitb(ans, correct=q['answer']): if not ans: return "⚠️ Please type an answer." if ans.strip().lower() == correct.strip().lower(): return "✅ **Correct!**" return f"❌ **Incorrect.** The correct answer is: {correct}" check_btn.click(fn=check_fitb, inputs=[user_ans], outputs=[feedback]) elif f_type == "Sentence Reorder": gr.Markdown(f"**Q{i+1} (Rearrange):**\n*{q['scrambled']}*") user_ans = gr.Textbox(label="Correct Order", placeholder="Type the full sentence here...") check_btn = gr.Button("Check Answer", size="sm") feedback = gr.Markdown() def check_reorder(ans, correct=q['correct_order']): if not ans: return "⚠️ Please type an answer." clean_ans = re.sub(r'[^a-z0-9äöüß]', '', ans.lower()) clean_correct = re.sub(r'[^a-z0-9äöüß]', '', correct.lower()) if clean_ans == clean_correct: return "✅ **Correct!**" return f"❌ **Incorrect.** The correct sentence is:\n{correct}" check_btn.click(fn=check_reorder, inputs=[user_ans], outputs=[feedback]) elif f_type == "Free QA": gr.Markdown(f"**Q{i+1}:** {q['question']}") user_ans = gr.Textbox(label="Your Answer", lines=2) show_btn = gr.Button("Show Suggested Answer", size="sm") feedback = gr.Markdown(visible=False) def show_ans(correct=q['suggested_answer']): return gr.update(value=f"**Suggested Answer:** {correct}", visible=True) show_btn.click(fn=show_ans, outputs=[feedback]) with gr.TabItem("⚙️ Raw Data (JSON)"): output_json = gr.Code(label="Generated JSON Output", language="json") generate_btn.click( fn=generate_expansion, inputs=[form_choice, user_requirement], outputs=[quiz_state, form_state, output_json] ) if __name__ == "__main__": demo.launch(server_name="0.0.0.0", server_port=7860)