"""Print the exact prompts the LLM would see for several sample inputs. No network call — this just builds the system + user messages and dumps them to stdout. Useful for reading what the model actually sees with a given oracle / theme / language combo. Usage: cd oracles_app ../.venv/bin/python scripts/mock_prompts.py """ from __future__ import annotations import json import sys from pathlib import Path _HERE = Path(__file__).resolve().parent _APP_ROOT = _HERE.parent if str(_APP_ROOT) not in sys.path: sys.path.insert(0, str(_APP_ROOT)) from oracles.state import GameState, Oracle, Obstacle, NARRATION_LENGTHS from oracles.themes import get_theme from oracles.resolution import ( _load_resolution_prompt, _apply_theme, _wrap_with_language_force, _safe_oracle_for_template, ) def build_resolution_prompt( obstacle_setup: str, oracle_text: str, hero_name: str, village_name: str, language: str, theme_key: str, narration_length: str = "medium", ) -> tuple[str, str]: """Return (system, user) — exactly what would be sent to the LLM. This mirrors ``resolve_trial`` up to the ``client.complete_json`` call, then bails out and returns the prompt. """ _, n_min, n_max, _max_tokens = NARRATION_LENGTHS.get( narration_length, NARRATION_LENGTHS["medium"] ) template = _load_resolution_prompt() safe_oracle = _safe_oracle_for_template(oracle_text) th = get_theme(theme_key) system = _apply_theme(template, th) system = ( system .replace("{hero_name}", hero_name) .replace("{village_name}", village_name) .replace("{obstacle_setup}", obstacle_setup) .replace("{oracle_text}", safe_oracle) .replace("{language}", language) .replace("{narration_min}", str(n_min)) .replace("{narration_max}", str(n_max)) ) system = _wrap_with_language_force(system, language) user = "Pick one of modes A/B/C and write the resolution now." return system, user # --------------------------------------------------------------------------- # Sample scenarios — pretend the player has just inscribed these oracles # and the apprentice has reached trial 1. # --------------------------------------------------------------------------- SCENARIOS = [ { "label": "Fantasy / English — single noun", "obstacle_setup": ( "A grey sphinx with the body of a lion and the cracked face of an " "old woman blocks the mountain pass. She offers a single riddle in " "a voice like wind through dry reeds — and warns that all who " "answer wrong have already been eaten." ), "oracle_text": "phone", "hero_name": "Tobin", "village_name": "the Hollow", "language": "English", "theme_key": "fantasy", "narration_length": "medium", }, { "label": "Fantasy / English — gibberish", "obstacle_setup": ( "A grey sphinx with the body of a lion and the cracked face of an " "old woman blocks the mountain pass. She offers a single riddle." ), "oracle_text": "qwerty", "hero_name": "Tobin", "village_name": "the Hollow", "language": "English", "theme_key": "fantasy", "narration_length": "medium", }, { "label": "Space-Cowboy / English — emoji", "obstacle_setup": ( "The mayor's hired gun stands in the dust street outside the " "saloon, hand near his holster. He calls the apprentice's name. " "The town watches from shaded porches; no one moves." ), "oracle_text": "🍕", "hero_name": "Cal", "village_name": "Dry Creek", "language": "English", "theme_key": "space_cowboy", "narration_length": "short", }, { "label": "Mistgate / Simplified Chinese — short Chinese phrase", "obstacle_setup": ( "雾门前立着一道无形的屏障。守门人沉默不语," "只用一根食指点向地面的符印——错答者即化为雾。" ), "oracle_text": "蓝色", "hero_name": "小明", "village_name": "凡人村", "language": "Simplified Chinese (use 简体中文 throughout; do not write in English)", "theme_key": "mistgate", "narration_length": "medium", }, { "label": "Fantasy / English — silly compound", "obstacle_setup": ( "Three hornets the size of small cats hum a foot above the path. " "Their wings rattle like a held breath. There is nowhere to flank." ), "oracle_text": "wooden spoon", "hero_name": "Tobin", "village_name": "the Hollow", "language": "English", "theme_key": "fantasy", "narration_length": "long", }, ] def _hr(char: str = "─", width: int = 78) -> str: return char * width def main() -> int: print(_hr("═")) print("THE APPRENTICE — mock prompt inspection") print(_hr("═")) print() print( "For each scenario below, the system + user messages are exactly\n" "what the LLM client sends. No network call is made; the LoRA's\n" "response would slot in after the user message." ) print() for i, sc in enumerate(SCENARIOS, 1): print(_hr("═")) print(f" SCENARIO {i}: {sc['label']}") print(_hr("═")) print() print(f" Theme: {sc['theme_key']}") print(f" Hero / village: {sc['hero_name']!r} / {sc['village_name']!r}") print(f" Language: {sc['language']}") print(f" Narration length: {sc['narration_length']}") print(f" Oracle inscribed: {sc['oracle_text']!r}") print() print(" OBSTACLE SETUP:") print(f" > {sc['obstacle_setup']}") print() system, user = build_resolution_prompt( obstacle_setup=sc["obstacle_setup"], oracle_text=sc["oracle_text"], hero_name=sc["hero_name"], village_name=sc["village_name"], language=sc["language"], theme_key=sc["theme_key"], narration_length=sc["narration_length"], ) print(_hr("─")) print(" SYSTEM PROMPT (sent as role=system)") print(_hr("─")) print(system) print() print(_hr("─")) print(" USER PROMPT (sent as role=user)") print(_hr("─")) print(user) print() print(f" ↳ Expected response shape: JSON") print(f" {{\"narration\": str (~{NARRATION_LENGTHS[sc['narration_length']][1]}-" f"{NARRATION_LENGTHS[sc['narration_length']][2]} words / chars), " f"\"tactic\": str (~1 line)}}") print() print(_hr("═")) print(f" {len(SCENARIOS)} scenarios printed.") print(_hr("═")) return 0 if __name__ == "__main__": sys.exit(main())