"""Modal endpoint for Photo Archaeology Simulator text generation. Deploy with: modal deploy modal_app.py Then set the Hugging Face Space secret MODAL_EXCAVATE_URL to the printed /excavate URL. """ import modal MODEL_ID = "Qwen/Qwen3-4B-Instruct-2507" image = ( modal.Image.debian_slim(python_version="3.11") .pip_install("fastapi[standard]", "torch", "transformers>=4.51.0", "accelerate", "sentencepiece") ) app = modal.App("photo-archaeology-simulator") @app.cls(image=image, gpu="A10G", timeout=240, scaledown_window=300) class ArchaeologyWriter: @modal.enter() def load(self): from transformers import AutoModelForCausalLM, AutoTokenizer self.tokenizer = AutoTokenizer.from_pretrained(MODEL_ID) self.model = AutoModelForCausalLM.from_pretrained(MODEL_ID, device_map="auto", torch_dtype="auto") @modal.method() def write(self, artifacts, tone): prompt = f""" You are a comic academic archaeologist studying messy modern rooms. Write a faux-scholarly excavation report in markdown. Tone: {tone} Use numbered Clue entries matching these detected artifacts: {artifacts} Invent playful carbon-dating methods for digital and household objects. Keep it funny, kind, and under 700 words. Do not give real cleaning advice except as a joke. """.strip() messages = [ {"role": "system", "content": "You produce humorous pseudo-academic reports, never serious scientific claims."}, {"role": "user", "content": prompt}, ] text = self.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) inputs = self.tokenizer([text], return_tensors="pt").to(self.model.device) output = self.model.generate(**inputs, max_new_tokens=900, temperature=0.85, do_sample=True) return self.tokenizer.decode(output[0][inputs.input_ids.shape[1]:], skip_special_tokens=True) @app.function(image=image, timeout=300) @modal.fastapi_endpoint(method="POST") def excavate(payload: dict): report = ArchaeologyWriter().write.remote(payload.get("artifacts", []), payload.get("tone", "Very academic")) return {"model": MODEL_ID, "report": report}