File size: 5,534 Bytes
d544dca
 
 
 
 
 
 
818b3aa
d544dca
 
 
 
 
 
 
 
818b3aa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d544dca
 
 
 
 
 
 
 
 
 
 
818b3aa
d544dca
 
 
 
 
 
 
 
 
 
 
818b3aa
d544dca
 
 
 
 
 
 
818b3aa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d544dca
818b3aa
 
 
 
d544dca
 
 
 
 
 
 
 
818b3aa
d544dca
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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