first commit

This commit is contained in:
2025-11-24 23:10:55 -06:00
commit 8315fa51c9
279 changed files with 74600 additions and 0 deletions

View File

@@ -0,0 +1,160 @@
"""
Response parser for AI narrative responses.
This module handles AI response parsing. Game state changes (items, gold, XP)
are now handled exclusively through predetermined dice check outcomes in
action templates, not through AI-generated JSON.
"""
from dataclasses import dataclass, field
from typing import Any, Optional
import structlog
logger = structlog.get_logger(__name__)
@dataclass
class ItemGrant:
"""
Represents an item granted by the AI during gameplay.
The AI can grant items in two ways:
1. By item_id - References an existing item from game data
2. By name/type/description - Creates a generic item
"""
item_id: Optional[str] = None # For existing items
name: Optional[str] = None # For generic items
item_type: Optional[str] = None # consumable, weapon, armor, quest_item
description: Optional[str] = None
value: int = 0
quantity: int = 1
def is_existing_item(self) -> bool:
"""Check if this references an existing item by ID."""
return self.item_id is not None
def is_generic_item(self) -> bool:
"""Check if this is a generic item created by the AI."""
return self.item_id is None and self.name is not None
@dataclass
class GameStateChanges:
"""
Structured game state changes extracted from AI response.
These changes are validated and applied to the character after
the AI generates its narrative response.
"""
items_given: list[ItemGrant] = field(default_factory=list)
items_taken: list[str] = field(default_factory=list) # item_ids to remove
gold_given: int = 0
gold_taken: int = 0
experience_given: int = 0
quest_offered: Optional[str] = None # quest_id
quest_completed: Optional[str] = None # quest_id
location_change: Optional[str] = None
@dataclass
class ParsedAIResponse:
"""
Complete parsed AI response with narrative and game state changes.
Attributes:
narrative: The narrative text to display to the player
game_changes: Structured game state changes to apply
raw_response: The original unparsed response from AI
parse_success: Whether parsing succeeded
parse_errors: Any errors encountered during parsing
"""
narrative: str
game_changes: GameStateChanges
raw_response: str
parse_success: bool = True
parse_errors: list[str] = field(default_factory=list)
class ResponseParserError(Exception):
"""Exception raised when response parsing fails critically."""
pass
def parse_ai_response(response_text: str) -> ParsedAIResponse:
"""
Parse an AI response to extract the narrative text.
Game state changes (items, gold, XP) are now handled exclusively through
predetermined dice check outcomes, not through AI-generated structured data.
Args:
response_text: The raw AI response text
Returns:
ParsedAIResponse with narrative (game_changes will be empty)
"""
logger.debug("Parsing AI response", response_length=len(response_text))
# Return the full response as narrative
# Game state changes come from predetermined check_outcomes, not AI
return ParsedAIResponse(
narrative=response_text.strip(),
game_changes=GameStateChanges(),
raw_response=response_text,
parse_success=True,
parse_errors=[]
)
def _parse_game_actions(data: dict[str, Any]) -> GameStateChanges:
"""
Parse the game actions dictionary into a GameStateChanges object.
Args:
data: Dictionary from parsed JSON
Returns:
GameStateChanges object with parsed data
"""
changes = GameStateChanges()
# Parse items_given
if "items_given" in data and data["items_given"]:
for item_data in data["items_given"]:
if isinstance(item_data, dict):
item_grant = ItemGrant(
item_id=item_data.get("item_id"),
name=item_data.get("name"),
item_type=item_data.get("type"),
description=item_data.get("description"),
value=item_data.get("value", 0),
quantity=item_data.get("quantity", 1)
)
changes.items_given.append(item_grant)
elif isinstance(item_data, str):
# Simple string format - treat as item_id
changes.items_given.append(ItemGrant(item_id=item_data))
# Parse items_taken
if "items_taken" in data and data["items_taken"]:
changes.items_taken = [
item_id for item_id in data["items_taken"]
if isinstance(item_id, str)
]
# Parse gold changes
changes.gold_given = int(data.get("gold_given", 0))
changes.gold_taken = int(data.get("gold_taken", 0))
# Parse experience
changes.experience_given = int(data.get("experience_given", 0))
# Parse quest changes
changes.quest_offered = data.get("quest_offered")
changes.quest_completed = data.get("quest_completed")
# Parse location change
changes.location_change = data.get("location_change")
return changes