test / multiq /api.py
yousseftallal's picture
Feature: QuestionBank + game endpoints (/next, /prefill) + Groq provider
818b3aa
Raw History Blame Contribute Delete
5.53 kB
from __future__ import annotations
import logging
import os
from flask import Flask, jsonify, request
from .bank import QuestionBank
from .config import load_settings
from .generator import QuestionGenerator
logging.basicConfig(level=logging.INFO)
log = logging.getLogger("multiq.api")
_SMOKE = os.getenv("MQG_SMOKE", "false").lower() in ("1", "true", "yes")
_POINTS = {"easy": 10, "medium": 25, "hard": 50}
_BANK: QuestionBank | None = None
_GEN: QuestionGenerator | None = None
def _get_gen() -> QuestionGenerator:
global _GEN
if _GEN is None:
_GEN = QuestionGenerator(load_settings(), smoke=_SMOKE)
return _GEN
def _get_bank() -> QuestionBank:
global _BANK
if _BANK is None:
_BANK = QuestionBank(load_settings().bank_path)
return _BANK
def _game_meta(q: dict) -> dict:
return {
"points": _POINTS.get(q.get("difficulty", "easy"), 10),
"tags": [q.get("lang"), q.get("type", q.get("qtype")), q.get("difficulty")],
}
def create_app(name: str = "multiqgen") -> Flask:
app = Flask(name)
app.config["JSON_AS_ASCII"] = False
@app.get("/")
def root():
return jsonify({"service": "MultiQGen", "version": "0.1.0", "status": "ok"})
@app.route("/api/v1/health")
def health():
return jsonify({"status": "healthy", "bank": _get_bank().stats()})
@app.post("/api/v1/generate")
def generate_endpoint():
payload = request.get_json(force=True, silent=True) or {}
topic = payload.get("topic", "science")
lang = payload.get("lang", "en")
difficulty = payload.get("difficulty")
qtype = payload.get("type", "multiple_choice")
n_distractors = payload.get("distractors", 3)
temperature = payload.get("temperature")
q = _get_gen().generate(
topic=topic,
lang=lang,
difficulty=difficulty,
qtype=qtype,
n_distractors=n_distractors,
temperature=temperature,
)
d = q.to_dict()
return jsonify({"question": d, "game_meta": _game_meta(d)}), 200
@app.post("/api/v1/next")
def next_endpoint():
payload = request.get_json(force=True, silent=True) or {}
q = _get_bank().draw(
lang=payload.get("lang"),
difficulty=payload.get("difficulty"),
qtype=payload.get("type"),
topic=payload.get("topic"),
)
if q is None:
return jsonify({"error": "No pre-generated question matches. Run /api/v1/prefill first."}), 404
return jsonify({"question": q, "game_meta": _game_meta(q)}), 200
@app.post("/api/v1/prefill")
def prefill_endpoint():
payload = request.get_json(force=True, silent=True) or {}
topics = payload.get("topics", [])
langs = payload.get("langs", ["en", "ar"])
difficulties = payload.get("difficulties", ["easy", "medium", "hard"])
types = payload.get("types", ["multiple_choice"])
per_cell = int(payload.get("per_cell", 3))
gen = _get_gen()
bank = _get_bank()
generated = 0
failed = []
for topic in topics:
for lang in langs:
for diff in difficulties:
for qtype in types:
made = 0
attempts = 0
while made < per_cell and attempts < per_cell * 4:
attempts += 1
try:
q = gen.generate(topic=topic, lang=lang, difficulty=diff, qtype=qtype)
except Exception as e:
failed.append(
{"topic": topic, "lang": lang, "difficulty": diff, "error": str(e)[:200]}
)
break
if bank.add(q.to_dict()):
made += 1
generated += 1
else:
attempts -= 1
if made < per_cell:
failed.append(
{"topic": topic, "lang": lang, "difficulty": diff, "made": made, "target": per_cell}
)
return jsonify({"generated": generated, "failed": failed, "bank": bank.stats()})
@app.get("/api/v1/bank/stats")
def bank_stats_endpoint():
bank = _get_bank()
return jsonify({"stats": bank.stats(), "topics": bank.topics()})
@app.post("/api/v1/batch")
def batch_endpoint():
payload = request.get_json(force=True, silent=True) or {}
topics = payload.get("topics", [])
langs = payload.get("langs", ["en", "ar"])
difficulties = payload.get("difficulties", ["easy", "medium", "hard"])
gen = _get_gen()
results = []
failed = []
for t in topics:
for lang in langs:
for diff in difficulties:
try:
q = gen.generate(topic=t, lang=lang, difficulty=diff)
results.append(q.to_dict())
except Exception as e:
log.warning("Failed to gen Q for %s %s %s: %s", t, lang, diff, e)
failed.append({"topic": t, "lang": lang, "difficulty": diff, "error": str(e)})
return jsonify({"generated": results, "failed": failed})
return app