Spaces:
Running
Running
| """mist-qg-1.5b demo — passage in, questions out, 25 languages. | |
| Run locally with: python app.py | |
| On HF Spaces: this file + requirements.txt + README.md (with the | |
| Spaces YAML header) is the whole deployment. | |
| """ | |
| import json | |
| import torch | |
| import gradio as gr | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| MODEL_ID = "olaverse/mist-qg-1.5b" | |
| LANG_NAMES = { | |
| "eng": "English", "fra": "French", "deu": "German", "spa": "Spanish", | |
| "por": "Portuguese", "ita": "Italian", "nld": "Dutch", "rus": "Russian", | |
| "pol": "Polish", "tur": "Turkish", "vie": "Vietnamese", "ind": "Indonesian", | |
| "hin": "Hindi", "jpn": "Japanese", "kor": "Korean", | |
| "yor": "Yoruba", "ibo": "Igbo", "hau": "Hausa", "swh": "Swahili", | |
| "amh": "Amharic", "zul": "Zulu", "xho": "Xhosa", "sna": "Shona", | |
| "som": "Somali", "afr": "Afrikaans", | |
| } | |
| # languages flagged on the model card as lower-confidence — shown as a caption | |
| # in the UI when selected, not hidden, so people can still try them | |
| WEAK_LANGS = {"amh", "som", "sna"} | |
| TEACHER_SYSTEM = "You write search-style questions that a passage directly answers." | |
| TEACHER_TEMPLATE = """You are given a passage. Write {n} questions that the passage directly answers. | |
| Rules: | |
| - Every question MUST be answerable using ONLY this passage. | |
| - NEVER copy or repeat a sentence from the passage. | |
| - Rewrite the information into a natural question. | |
| - Questions should sound like something a real person would ask in a search engine. | |
| - Do not quote the passage. | |
| - Vary the question types: factual, yes/no, why/how, comparison. | |
| - Write all questions in {language}. | |
| - Return ONLY valid JSON: | |
| {{"questions": [{slots}]}} | |
| Passage: {passage}""" | |
| EXAMPLES = [ | |
| ["Tides are caused by the gravitational pull of the moon and, to a lesser extent, the sun, acting on Earth's oceans.", "eng", 3], | |
| ["Les marées sont causées par l'attraction gravitationnelle de la lune et, dans une moindre mesure, du soleil, agissant sur les océans de la Terre.", "fra", 3], | |
| ["Photosynthesis converts sunlight into chemical energy in plants, using carbon dioxide and water to produce glucose and oxygen.", "eng", 3], | |
| ["Ìjì máa ń wáyé nítorí agbára ìfàmọ́ra òṣùpá àti oòrùn lórí omi inú òkun ayé.", "yor", 3], | |
| ] | |
| _model = None | |
| _tok = None | |
| def _load(): | |
| global _model, _tok | |
| if _model is None: | |
| _tok = AutoTokenizer.from_pretrained(MODEL_ID) | |
| _model = AutoModelForCausalLM.from_pretrained( | |
| MODEL_ID, | |
| dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32, | |
| device_map="auto" if torch.cuda.is_available() else None, | |
| ) | |
| _model.eval() | |
| return _model, _tok | |
| def generate(passage: str, language_code: str, n: int): | |
| passage = (passage or "").strip() | |
| if not passage: | |
| return "", "⚠️ Enter a passage first." | |
| if len(passage) < 20: | |
| return "", "⚠️ Passage is very short — results may be poor." | |
| model, tok = _load() | |
| lang_name = LANG_NAMES.get(language_code, language_code) | |
| slots = ", ".join(['"..."'] * int(n)) | |
| user = TEACHER_TEMPLATE.format( | |
| n=int(n), | |
| language=lang_name, | |
| slots=slots, | |
| passage=passage, | |
| ) | |
| messages = [ | |
| {"role": "system", "content": TEACHER_SYSTEM}, | |
| {"role": "user", "content": user}, | |
| ] | |
| # Build model inputs | |
| inputs = tok.apply_chat_template( | |
| messages, | |
| tokenize=True, | |
| add_generation_prompt=True, | |
| return_tensors="pt", | |
| return_dict=True, | |
| ) | |
| inputs = {k: v.to(model.device) for k, v in inputs.items()} | |
| with torch.no_grad(): | |
| out = model.generate( | |
| **inputs, | |
| max_new_tokens=250, | |
| do_sample=False, | |
| pad_token_id=tok.pad_token_id | |
| if tok.pad_token_id is not None | |
| else tok.eos_token_id, | |
| ) | |
| prompt_len = inputs["input_ids"].shape[1] | |
| text = tok.decode( | |
| out[0][prompt_len:], | |
| skip_special_tokens=True, | |
| ) | |
| try: | |
| start = text.index("{") | |
| end = text.rindex("}") + 1 | |
| obj = json.loads(text[start:end]) | |
| questions = [ | |
| q | |
| for q in obj.get("questions", []) | |
| if isinstance(q, str) and q.strip() | |
| ] | |
| except (ValueError, json.JSONDecodeError): | |
| questions = [] | |
| note = "" | |
| if language_code in WEAK_LANGS: | |
| note = ( | |
| f"\n\n⚠️ {lang_name} is one of this model's lower-confidence languages " | |
| f"(see the [model card](https://huggingface.co/{MODEL_ID}) for benchmark numbers)." | |
| ) | |
| if not questions: | |
| return ( | |
| text, | |
| f"⚠️ Couldn't parse valid questions from the model's output. Raw output shown alongside.{note}", | |
| ) | |
| formatted = "\n".join( | |
| f"{i + 1}. {q}" for i, q in enumerate(questions) | |
| ) | |
| return formatted, ("✅ Generated." + note) if note else "✅ Generated." | |
| with gr.Blocks(title="mist-qg-1.5b — multilingual question generator") as demo: | |
| gr.Markdown( | |
| "# mist-qg-1.5b\n" | |
| "Passage in, search-style questions out — across 25 languages. " | |
| f"[Model card](https://huggingface.co/{MODEL_ID}) · " | |
| "[Training data](https://huggingface.co/datasets/olaverse/qg-passages-multi)" | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| passage_in = gr.Textbox( | |
| label="Passage", lines=6, | |
| placeholder="Paste a paragraph of text here...", | |
| ) | |
| lang_in = gr.Dropdown( | |
| choices=[(name, code) for code, name in sorted(LANG_NAMES.items(), key=lambda x: x[1])], | |
| value="eng", label="Language", | |
| ) | |
| n_in = gr.Slider(1, 5, value=3, step=1, label="Number of questions") | |
| btn = gr.Button("Generate Questions", variant="primary") | |
| with gr.Column(): | |
| questions_out = gr.Textbox(label="Generated questions", lines=8, interactive=False) | |
| status_out = gr.Markdown() | |
| gr.Examples( | |
| examples=EXAMPLES, | |
| inputs=[passage_in, lang_in, n_in], | |
| label="Try an example", | |
| ) | |
| btn.click(generate, inputs=[passage_in, lang_in, n_in], outputs=[questions_out, status_out]) | |
| if __name__ == "__main__": | |
| demo.launch() | |