Spaces:
Runtime error
Runtime error
| """Asterism Relay author model: MiniCPM4.1-8B (OpenBMB) on Modal via vLLM. | |
| The model AUTHORS each sector: it selects which real phenomenon is present, names | |
| the body, invents the vanished-civilization transmission, and picks a seed. vLLM | |
| guided decoding forces schema-valid JSON every call. The backend then COMPOSES the | |
| fact-layer explanation from the curated knowledge base so the science is guaranteed | |
| accurate (never free-generated) — satisfying the hackathon's factual-accuracy rule. | |
| modal deploy scripts/minicpm_modal.py # -> stable https web endpoint /sector | |
| modal run scripts/minicpm_modal.py # quick 3-sample smoke test | |
| """ | |
| import json | |
| import random | |
| import time | |
| import modal | |
| MODEL_ID = "openbmb/MiniCPM4.1-8B" | |
| SECTOR_SCHEMA = { | |
| "type": "object", | |
| "properties": { | |
| "concept_id": {"type": "string"}, | |
| "fact_ids": {"type": "array", "items": {"type": "integer"}}, | |
| "body_name": {"type": "string"}, | |
| "phenomenon_label": {"type": "string"}, | |
| "civilization": {"type": "string"}, | |
| "transmission": {"type": "string"}, | |
| "seed": {"type": "integer"}, | |
| }, | |
| "required": ["concept_id", "fact_ids", "body_name", "civilization", "transmission", "seed"], | |
| } | |
| app = modal.App("asterism-author") | |
| hf_cache = modal.Volume.from_name("hf-cache", create_if_missing=True) | |
| author_image = ( | |
| modal.Image.debian_slim(python_version="3.11") | |
| # 4.56.2 is the version MiniCPM4.1 targets and that loads cleanly with transformers. | |
| .pip_install("torch==2.6.0", "transformers==4.56.2", "accelerate", "sentencepiece", | |
| "einops", "huggingface_hub[hf_transfer]", "fastapi[standard]") | |
| .env({"HF_HUB_ENABLE_HF_TRANSFER": "1"}) | |
| .add_local_file("data/astro_facts.json", "/root/astro_facts.json", copy=True) | |
| ) | |
| def _parse_json(text): | |
| import re | |
| text = re.sub(r"<think>.*?</think>", "", text, flags=re.DOTALL) | |
| text = re.sub(r"```(?:json)?", "", text).strip() | |
| for candidate in (text, text.encode("utf-8").decode("unicode_escape", "ignore")): | |
| try: | |
| return json.loads(candidate) | |
| except Exception: | |
| m = re.search(r"\{.*\}", candidate, flags=re.DOTALL) | |
| if m: | |
| try: | |
| return json.loads(m.group(0)) | |
| except Exception: | |
| pass | |
| return {} | |
| class Author: | |
| def load(self): | |
| import torch | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| self.torch = torch | |
| self.facts = json.load(open("/root/astro_facts.json", encoding="utf-8")) | |
| self.by_id = {f["id"]: f for f in self.facts} | |
| self.tok = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True) | |
| self.model = AutoModelForCausalLM.from_pretrained( | |
| MODEL_ID, trust_remote_code=True, torch_dtype=torch.bfloat16, device_map="auto") | |
| self.model.eval() | |
| # ---- prompt ------------------------------------------------------------- | |
| def _messages(self, sector_key, position, concept): | |
| return [ | |
| {"role": "system", "content": ( | |
| "You are the author of a first-person cosmic exploration game. The player is approaching " | |
| "a real astrophysical phenomenon. Your job is the FICTION around it: a vanished interstellar " | |
| "civilization whose final transmissions are scattered across the universe.\n" | |
| "Output ONLY a single JSON object, no markdown, no commentary, with EXACTLY these keys: " | |
| '{"concept_id": str, "fact_ids": [int], "body_name": str, "phenomenon_label": str, ' | |
| '"civilization": str, "transmission": str, "seed": int}.')}, | |
| {"role": "user", "content": json.dumps({ | |
| "task": "Author this sector.", | |
| "sector_key": sector_key, "position": position, | |
| "phenomenon": { | |
| "concept_id": concept["id"], "name": concept["name"], | |
| "body_type": concept["body_type"], "one_line": concept["one_line"], | |
| "facts": concept["facts"], | |
| }, | |
| "instructions": [ | |
| f"concept_id: must be exactly '{concept['id']}'.", | |
| "fact_ids: 2-3 zero-based indices into the facts array above (the ones to surface to the player).", | |
| "body_name: an evocative proper name for this body (e.g. 'Vesper Lighthouse', 'The Far Choir').", | |
| "phenomenon_label: a short poetic label for what it is (<= 5 words).", | |
| "civilization: the name of the vanished civilization tied to this fragment.", | |
| "transmission: ONE haunting fragment (1-2 sentences, >=60 chars) of their final message, " | |
| "thematically tied to this phenomenon. Original fiction — evocative, mysterious, never explaining the science.", | |
| "seed: a random 6-7 digit integer.", | |
| ]}, ensure_ascii=True)}, | |
| ] | |
| # ---- compose the trustworthy fact layer + final payload ----------------- | |
| def _finalize(self, sector_key, position, m): | |
| concept = self.by_id.get(m.get("concept_id")) or random.choice(self.facts) | |
| n = len(concept["facts"]) | |
| ids = [i for i in m.get("fact_ids", []) if isinstance(i, int) and 0 <= i < n] | |
| if not ids: | |
| ids = list(range(min(3, n))) | |
| explanation = " ".join([concept["one_line"]] + [concept["facts"][i] for i in ids]) | |
| vc = concept["visual_cues"] | |
| rng = random.Random(m.get("seed") or sector_key) | |
| return { | |
| "sector_key": sector_key, | |
| "authored_by": "minicpm4.1-8b", | |
| "body": { | |
| "id": f"{concept['id']}-{rng.randrange(100, 999)}", | |
| "name": m.get("body_name") or concept["name"], | |
| "type": concept["body_type"], | |
| "category": concept.get("category", "star"), | |
| "phenomenon": m.get("phenomenon_label") or concept["name"], | |
| "position": position, | |
| }, | |
| "fact_layer": { | |
| "source": "curated_local_knowledge_base", | |
| "concept_id": concept["id"], | |
| "title": concept["name"], | |
| "fact_ids": ids, | |
| "explanation": explanation, | |
| }, | |
| "fiction_layer": { | |
| "civilization": m.get("civilization") or "the Last Cartographers", | |
| "fragment_id": f"transmission-{rng.randrange(1, 99):02d}", | |
| "transmission": m.get("transmission") or random.choice(concept["transmissions"]), | |
| }, | |
| "shader": { | |
| "seed": int(m.get("seed") or rng.randrange(100000, 9999999)), | |
| "render": vc["render"], | |
| "primary": vc["primary_color"], | |
| "secondary": vc["secondary_color"], | |
| "emissive": vc["emissive"], | |
| "radius": vc["radius"], | |
| "beam": vc.get("beam", False), | |
| "corona": vc.get("corona", False), | |
| "rings": vc.get("rings", False), | |
| "jets": vc.get("jets", False), | |
| "particles": vc.get("particles", vc["secondary_color"]), | |
| "noise_scale": round(1.8 + rng.random() * 3.0, 3), | |
| "atmosphere": round(0.3 + rng.random() * 0.5, 3), | |
| }, | |
| } | |
| def _fallback(self, sector_key, position): | |
| concept = random.choice(self.facts) | |
| return self._finalize(sector_key, position, { | |
| "concept_id": concept["id"], "fact_ids": [0, 1], | |
| "body_name": concept["name"], "civilization": "the Last Cartographers", | |
| "transmission": random.choice(concept["transmissions"]), "seed": random.randrange(100000, 9999999), | |
| }) | |
| def _author(self, sector_key, position, concept_id=""): | |
| concept = self.by_id.get(concept_id) or random.choice(self.facts) | |
| messages = self._messages(sector_key, position, concept) | |
| prompt = self.tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True, | |
| enable_thinking=False) | |
| inputs = self.tok(prompt, return_tensors="pt").to(self.model.device) | |
| with self.torch.no_grad(): | |
| out = self.model.generate(**inputs, max_new_tokens=420, do_sample=True, | |
| temperature=0.9, top_p=0.95, pad_token_id=self.tok.eos_token_id) | |
| text = self.tok.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True) | |
| m = _parse_json(text) | |
| m["concept_id"] = concept["id"] # enforce the sector's assigned phenomenon | |
| return self._finalize(sector_key, position, m) | |
| # ---- public surfaces ---------------------------------------------------- | |
| def sector(self, sector_key: str = "0:0:-1", position: str = "0,20,-450", concept_id: str = ""): | |
| try: | |
| x, y, z = (float(v) for v in position.split(",")) | |
| except Exception: | |
| x, y, z = 0.0, 20.0, -450.0 | |
| pos = {"x": x, "y": y, "z": z} | |
| try: | |
| return self._author(sector_key, pos, concept_id) | |
| except Exception as exc: | |
| payload = self._fallback(sector_key, pos) | |
| payload["authored_by"] = f"fallback ({type(exc).__name__})" | |
| return payload | |
| def smoke(self, runs: int = 3): | |
| results = [] | |
| for i in range(runs): | |
| t = time.perf_counter() | |
| p = self._author("0:0:-1", {"x": 0, "y": 20, "z": -450}) | |
| dt = time.perf_counter() - t | |
| results.append((dt, p)) | |
| print(f"[RUN {i+1}] {dt:.2f}s {p['body']['name']} / {p['fact_layer']['title']}", flush=True) | |
| print(" FACT:", p["fact_layer"]["explanation"][:160], flush=True) | |
| print(" FICTION:", p["fiction_layer"]["civilization"], "—", p["fiction_layer"]["transmission"][:160], flush=True) | |
| return [(round(d, 2), p) for d, p in results] | |
| def main(): | |
| Author().smoke.remote(3) | |