Spaces:
Runtime error
Runtime error
| """The Oracle โ custom frontend via gradio.Server. | |
| Architecture (deterministic core + LLM only for phrasing): | |
| 1. engine.filter_candidates โ narrow the JSON candidate list by the answers | |
| 2. engine.choose_attribute โ pick the attribute that best splits the set | |
| 3. question_maker.make_question โ LLM turns that attribute into a natural question | |
| The LLM never decides elimination; the engine does, so filtering is always exact. | |
| Outcomes: 1 left -> guess; 0 left -> "I don't know it yet" (discovery hook); | |
| "I am not sure" answers don't filter and aren't re-asked. | |
| Run: python server.py | |
| ORACLE_QUESTION_LLM=1 python server.py # natural questions via local LLM | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| from gradio import Server | |
| from fastapi.responses import HTMLResponse | |
| import engine | |
| import discovery | |
| import question_maker | |
| import subprocess | |
| subprocess.run("pip install -V llama_cpp_python==0.3.0", shell=True) | |
| MAX_QUESTIONS = int(os.environ.get("ORACLE_MAX_QUESTIONS", "20")) | |
| app = Server() | |
| HERE = os.path.dirname(os.path.abspath(__file__)) | |
| try: | |
| from fastapi.staticfiles import StaticFiles | |
| app.mount("/images", StaticFiles(directory=os.path.join(HERE, "images")), name="images") | |
| except Exception as exc: # noqa: BLE001 | |
| print(f"[oracle] could not mount /images: {exc}") | |
| # At boot (background): load the model, then pre-generate & cache every question | |
| # once. After this, gameplay is instant dict lookups โ no per-turn model calls โ | |
| # so it stays snappy even on a weak CPU. No-op if the LLM is disabled/unavailable. | |
| if os.environ.get("ORACLE_QUESTION_LLM", "1") == "1": | |
| import threading | |
| def _boot_warm(): | |
| import llm | |
| import question_maker | |
| llm.warmup() | |
| question_maker.prewarm_questions() | |
| threading.Thread(target=_boot_warm, daemon=True).start() | |
| def next_turn(category: str = "animal", history_json: str = "[]", asked: int = 0) -> dict: | |
| try: | |
| history = json.loads(history_json) if history_json else [] | |
| except (json.JSONDecodeError, ValueError): | |
| history = [] | |
| # STEP 1 โ deterministic filter by the answers so far | |
| facts = [{"attribute": h.get("attribute"), "answer": h.get("answer")} for h in history] | |
| items = engine.filter_candidates(engine.load_items(category), facts) | |
| names = [it["name"] for it in items] | |
| print(f"[oracle] {category}: {len(names)} remain -> {names[:20]}", flush=True) | |
| base = {"asked": asked, "max": MAX_QUESTIONS, "remaining": len(names), "items": names} | |
| # STEP 2 โ outcomes decided in code | |
| if not names: | |
| # discovery: nothing in the DB matches -> ask the player to teach us | |
| return {"action": "giveup", | |
| "text": "Hmm, I don't know this one yet. What were you thinking of?", | |
| **base} | |
| if len(names) == 1 or asked >= MAX_QUESTIONS: | |
| yes_attrs = [h.get("attribute") for h in history | |
| if str(h.get("answer", "")).strip().lower() == "yes" and h.get("attribute")] | |
| reveal = question_maker.make_reveal(category, yes_attrs) | |
| return {"action": "guess", "text": names[0], "reveal": reveal, **base} | |
| # STEP 3 โ pick the best attribute, then have the LLM phrase the question | |
| asked_attrs = [h.get("attribute") for h in history if h.get("attribute")] | |
| attr = engine.choose_attribute(category, items, asked_attrs) | |
| if attr is None: | |
| return {"action": "guess", "text": names[0], **base} # can't split further | |
| question = question_maker.make_question(category, attr, asked_attrs) | |
| return {"action": "ask", "attribute": attr, "text": question, "options": ["Yes", "No"], **base} | |
| def learn(category: str = "animal", name: str = "", history_json: str = "[]") -> dict: | |
| """Discovery mode: the player tells us what it was; we derive its attributes, | |
| add it to the JSON DB, and explain any answers that contradicted reality.""" | |
| try: | |
| history = json.loads(history_json) if history_json else [] | |
| except (json.JSONDecodeError, ValueError): | |
| history = [] | |
| try: | |
| return discovery.learn_item(category, name, history) | |
| except Exception as exc: # noqa: BLE001 โ never crash the game on a teach | |
| print(f"[oracle] learn failed: {exc}") | |
| return {"status": "error", "message": str(exc)} | |
| async def homepage(): | |
| with open(os.path.join(HERE, "index.html"), "r", encoding="utf-8") as f: | |
| return f.read() | |
| if __name__ == "__main__": | |
| # On Hugging Face Spaces the app must listen on 0.0.0.0:7860. Gradio reads | |
| # these env vars; setdefault keeps local runs unchanged. | |
| os.environ.setdefault("GRADIO_SERVER_NAME", "0.0.0.0") | |
| os.environ.setdefault("GRADIO_SERVER_PORT", "7860") | |
| app.launch(show_error=True) | |