Improved validation based on story
This commit is contained in:
parent
e97db7f5b7
commit
5862254255
@ -51,7 +51,9 @@ class GameEngine:
|
|||||||
on_debug("config", {"model": model, "temperature": lm.get("temperature"), "max_tokens": lm.get("max_tokens"), "strategy": "tools"})
|
on_debug("config", {"model": model, "temperature": lm.get("temperature"), "max_tokens": lm.get("max_tokens"), "strategy": "tools"})
|
||||||
|
|
||||||
if player_action:
|
if player_action:
|
||||||
valid, reason = validate_action(player_action, on_debug=on_debug)
|
story = state.read_recent_book()
|
||||||
|
log = state.read_recent_log()
|
||||||
|
valid, reason = validate_action(player_action, story=story, log=log, on_debug=on_debug)
|
||||||
if valid:
|
if valid:
|
||||||
state.append_llm_log(f"\n[VALIDATION PASSED] {reason}")
|
state.append_llm_log(f"\n[VALIDATION PASSED] {reason}")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -8,7 +8,7 @@ from .paths import CHAR_PATH, WORLD_PATH
|
|||||||
from . import state
|
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.
|
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, story context, and story logic.
|
||||||
|
|
||||||
## Character
|
## Character
|
||||||
{character}
|
{character}
|
||||||
@ -16,6 +16,12 @@ VALIDATION_PROMPT = """You are a strict RPG game master validating whether a pla
|
|||||||
## World
|
## World
|
||||||
{world}
|
{world}
|
||||||
|
|
||||||
|
## Session Log
|
||||||
|
{log}
|
||||||
|
|
||||||
|
## Recent Story
|
||||||
|
{story}
|
||||||
|
|
||||||
## Player Action
|
## Player Action
|
||||||
{action}
|
{action}
|
||||||
|
|
||||||
@ -24,6 +30,7 @@ VALIDATION_PROMPT = """You are a strict RPG game master validating whether a pla
|
|||||||
- Are they asserting something that contradicts the state? -> invalid
|
- Are they asserting something that contradicts the state? -> invalid
|
||||||
- Is the action nonsensical given the situation? -> invalid
|
- Is the action nonsensical given the situation? -> invalid
|
||||||
- Does the action make sense given the character's abilities and resources? -> valid
|
- Does the action make sense given the character's abilities and resources? -> valid
|
||||||
|
- Pay close attention to the Recent Story section — entities like monsters, NPCs, and hazards currently present in the scene ARE valid targets for action.
|
||||||
- If valid, also check: if they're using a consumable item, note that it must be removed from inventory.
|
- 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. Examples:
|
Reply with ONLY the JSON object. Examples:
|
||||||
@ -39,6 +46,9 @@ or
|
|||||||
|
|
||||||
def validate_action(
|
def validate_action(
|
||||||
player_action: str,
|
player_action: str,
|
||||||
|
*,
|
||||||
|
story: str = "",
|
||||||
|
log: str = "",
|
||||||
on_debug: callable = None,
|
on_debug: callable = None,
|
||||||
) -> tuple[bool, str]:
|
) -> tuple[bool, str]:
|
||||||
"""Ask the LLM whether a player action is valid given the game state. Returns (valid, reason)."""
|
"""Ask the LLM whether a player action is valid given the game state. Returns (valid, reason)."""
|
||||||
@ -47,8 +57,10 @@ def validate_action(
|
|||||||
|
|
||||||
char = state.read_file(CHAR_PATH) or "*No character sheet.*"
|
char = state.read_file(CHAR_PATH) or "*No character sheet.*"
|
||||||
world = state.truncate_world(state.read_file(WORLD_PATH) or "") or "*No world state.*"
|
world = state.truncate_world(state.read_file(WORLD_PATH) or "") or "*No world state.*"
|
||||||
|
recent = story.strip() or state.read_recent_book() or "*No prior story.*"
|
||||||
|
log_entries = log.strip() or state.read_recent_log() or "*No recent events.*"
|
||||||
|
|
||||||
prompt = VALIDATION_PROMPT.format(character=char, world=world, action=player_action)
|
prompt = VALIDATION_PROMPT.format(character=char, world=world, log=log_entries, story=recent, action=player_action)
|
||||||
|
|
||||||
text = call_llm(
|
text = call_llm(
|
||||||
[{"role": "user", "content": prompt}],
|
[{"role": "user", "content": prompt}],
|
||||||
|
|||||||
@ -29,7 +29,7 @@ def test_valid_action(mock_call_llm, mock_truncate_world, mock_read_file):
|
|||||||
mock_truncate_world.return_value = "## Location\nTavern"
|
mock_truncate_world.return_value = "## Location\nTavern"
|
||||||
mock_call_llm.return_value = json.dumps({"valid": True, "reason": "ok"})
|
mock_call_llm.return_value = json.dumps({"valid": True, "reason": "ok"})
|
||||||
|
|
||||||
valid, reason = validate_action("I buy a drink")
|
valid, reason = validate_action("I buy a drink", story="At the tavern", log="- Entered the tavern")
|
||||||
|
|
||||||
assert valid is True
|
assert valid is True
|
||||||
assert reason == "ok"
|
assert reason == "ok"
|
||||||
@ -47,7 +47,7 @@ def test_invalid_action(mock_call_llm, mock_truncate_world, mock_read_file):
|
|||||||
mock_truncate_world.return_value = "## Location\nTavern"
|
mock_truncate_world.return_value = "## Location\nTavern"
|
||||||
mock_call_llm.return_value = json.dumps({"valid": False, "reason": "Not enough gold"})
|
mock_call_llm.return_value = json.dumps({"valid": False, "reason": "Not enough gold"})
|
||||||
|
|
||||||
valid, reason = validate_action("I buy a drink")
|
valid, reason = validate_action("I buy a drink", story="At the tavern", log="- Entered the tavern")
|
||||||
|
|
||||||
assert valid is False
|
assert valid is False
|
||||||
assert reason == "Not enough gold"
|
assert reason == "Not enough gold"
|
||||||
@ -64,7 +64,7 @@ def test_llm_returns_none(mock_call_llm, mock_truncate_world, mock_read_file):
|
|||||||
mock_truncate_world.return_value = "## Location\nTavern"
|
mock_truncate_world.return_value = "## Location\nTavern"
|
||||||
mock_call_llm.return_value = None
|
mock_call_llm.return_value = None
|
||||||
|
|
||||||
valid, reason = validate_action("I attack the dragon")
|
valid, reason = validate_action("I attack the dragon", story="A dragon appears!", log="- Dragon spotted")
|
||||||
|
|
||||||
assert valid is False
|
assert valid is False
|
||||||
assert reason == "Not sure"
|
assert reason == "Not sure"
|
||||||
@ -81,7 +81,7 @@ def test_llm_returns_bad_json(mock_call_llm, mock_truncate_world, mock_read_file
|
|||||||
mock_truncate_world.return_value = "## Location\nTavern"
|
mock_truncate_world.return_value = "## Location\nTavern"
|
||||||
mock_call_llm.return_value = "not valid json at all"
|
mock_call_llm.return_value = "not valid json at all"
|
||||||
|
|
||||||
valid, reason = validate_action("I cast a spell")
|
valid, reason = validate_action("I cast a spell", story="In a dungeon", log="- Found a weird altar")
|
||||||
|
|
||||||
assert valid is False
|
assert valid is False
|
||||||
assert reason == "Unrecognized"
|
assert reason == "Unrecognized"
|
||||||
@ -98,7 +98,7 @@ def test_missing_character_sheet(mock_truncate_world, mock_read_file):
|
|||||||
|
|
||||||
with patch("engine_lib.validation.call_llm") as mock_call_llm:
|
with patch("engine_lib.validation.call_llm") as mock_call_llm:
|
||||||
mock_call_llm.return_value = json.dumps({"valid": True, "reason": "ok"})
|
mock_call_llm.return_value = json.dumps({"valid": True, "reason": "ok"})
|
||||||
valid, reason = validate_action("I look around")
|
valid, reason = validate_action("I look around", story="In a dark room", log="- Entered the room")
|
||||||
|
|
||||||
assert valid is True
|
assert valid is True
|
||||||
print("✓ handles missing character sheet gracefully")
|
print("✓ handles missing character sheet gracefully")
|
||||||
@ -118,7 +118,7 @@ def test_on_debug_called(mock_call_llm, mock_truncate_world, mock_read_file):
|
|||||||
def debug_cb(key, data):
|
def debug_cb(key, data):
|
||||||
events.append((key, data))
|
events.append((key, data))
|
||||||
|
|
||||||
valid, reason = validate_action("I open the door", on_debug=debug_cb)
|
valid, reason = validate_action("I open the door", story="In a hallway", log="- Heard noises", on_debug=debug_cb)
|
||||||
|
|
||||||
assert valid is True
|
assert valid is True
|
||||||
assert len(events) == 1
|
assert len(events) == 1
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user