splinter-keep/tools/engine_lib/validation.py
2026-06-30 21:18:35 +02:00

76 lines
2.3 KiB
Python

from __future__ import annotations
import json
from .llm import call_llm
from .paths import CHAR_PATH, WORLD_PATH
from . import state
VALIDATION_PROMPT = """You are a strict RPG game master validating whether a player's action is possible given the game state. Be thorough — check inventory, stats, location, NPCs, and story logic.
Respond with JSON only:
{{"valid": true, "reason": "ok"}}
or
{{"valid": false, "reason": "brief explanation of why the action is impossible"}}
## Character
{character}
## World
{world}
## Player Action
{action}
## Instructions
- Is the player trying to use an item they don't have? -> invalid
- Are they asserting something that contradicts the state? -> invalid
- Is the action nonsensical given the situation? -> invalid
- Does the action make sense given the character's abilities and resources? -> valid
- If valid, also check: if they're using a consumable item, note that it must be removed from inventory.
Reply with ONLY the JSON object."""
def validate_action(
player_action: str,
on_debug: callable = None,
) -> tuple[bool, str]:
"""Ask the LLM whether a player action is valid given the game state. Returns (valid, reason)."""
if not player_action:
return True, ""
char = state.read_file(CHAR_PATH) or "*No character sheet.*"
world = state.truncate_world(state.read_file(WORLD_PATH) or "") or "*No world state.*"
prompt = VALIDATION_PROMPT.format(character=char, world=world, action=player_action)
text = call_llm(
[{"role": "user", "content": prompt}],
max_tokens=256,
temperature=0.2,
label="Action validation",
on_debug=on_debug,
)
if not text:
return True, ""
try:
data = json.loads(text.strip())
valid = data.get("valid", True)
reason = data.get("reason", "")
if on_debug:
on_debug("action_validation", {"valid": valid, "reason": reason, "action": player_action})
return valid, reason
except (json.JSONDecodeError, ValueError):
if on_debug:
on_debug("action_validation", {"valid": True, "reason": "parse_failed", "raw": text[:200]})
return True, ""
def auto_prompt(book_log: str = "") -> str:
"""Fallback player prompt."""
return "**What do you do?**"