File size: 7,527 Bytes
6bc4afd 67fbecc 6bc4afd 67fbecc da51a9e 50cccdf 9543536 ed151e4 9543536 da51a9e 8855e7f ed151e4 8855e7f 9543536 ed151e4 50cccdf 9543536 50cccdf 8855e7f 9543536 50cccdf 9543536 50cccdf 9543536 50cccdf 9543536 50cccdf 6bc4afd da51a9e 3d2961d bf65d34 3d2961d da51a9e 6bc4afd ed151e4 6bc4afd ed151e4 9c70cda 50cccdf 6bc4afd 50cccdf 6bc4afd 50cccdf 8855e7f 6bc4afd ed151e4 67fbecc 6bc4afd c3e33f1 aa14d1f 6bc4afd 9c70cda 8855e7f 6bc4afd 67fbecc 6bc4afd 9543536 7fdbbb5 017769b 3d2961d 202450d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | from dataclasses import dataclass, field
from datetime import datetime, timezone
from uuid import uuid4
from typing import Any, Literal
from pydantic import BaseModel, Field, ValidationInfo, field_validator
"""
Core data models for the puppet theater simulation.
This module defines the structured objects used throughout a theater session,
including:
- Actors and their state (goals, secrets, mood, props, memory)
- Director decisions that guide story progression
- Actor responses for each scene beat
- Tool requests and tool execution results
- Individual beats in the transcript
- The overall theater session state and configuration
Pydantic models are used where validation is required for LLM-generated
outputs (e.g. actor responses and director decisions), while dataclasses
are used for runtime session state and domain entities.
In short: this file defines the data structures that represent the
story, characters, dialogue, stage actions, and session state of
the puppet theater engine.
"""
BeatType = Literal[
"setup",
"denial_or_contradiction",
"evidence_or_prop",
"secret_reveal",
"chaos_or_intervention",
"finale",
]
SimpleToolValue = str | int | float | bool | None
class ToolRequest(BaseModel):
"""
Represents a request to call a tool.
Pydantic automatically validates the input data when an
instance is created. Ensures the tool name and reason are
non-empty and that the reason remains concise.
"""
tool_name: str
arguments: Any = Field(default_factory=dict)
reason: str
@field_validator("tool_name", "reason")
@classmethod
def require_text(cls, value: str) -> str:
cleaned = " ".join(value.strip().split())
if not cleaned:
raise ValueError("field must not be empty")
return cleaned
@field_validator("reason")
@classmethod
def keep_reason_short(cls, value: str) -> str:
if len(value) > 140:
raise ValueError("reason must be 140 characters or fewer")
return value
class ActorResponse(BaseModel):
"""
Represents a structured response from a puppet actor for one scene beat.
Includes:
- intent: actor's short goal for this beat
- line: dialogue spoken on stage
- emotion: actor's emotional state
- gesture: physical action performed
- stage_effect: environmental/stage effect
- memory_update: optional note carried to future beats
- tool_request: optional request to use a tool
Validators enforce length limits, remove extra whitespace,
and prevent required fields from being empty.
"""
intent: str = Field(default="Keep the scene moving.", description="Short visible actor intention.")
line: str = Field(description="Short, stage-ready puppet dialogue.")
emotion: str
gesture: str
stage_effect: str
memory_update: str = Field(default="", description="Short visible memory note from this beat.")
tool_request: ToolRequest | None = None
@field_validator("intent", "line", "emotion", "gesture", "stage_effect", "memory_update")
@classmethod
def require_text(cls, value: str, info: ValidationInfo) -> str:
cleaned = " ".join(value.strip().split())
if not cleaned and info.field_name != "memory_update":
raise ValueError("field must not be empty")
return cleaned
@field_validator("intent")
@classmethod
def keep_intent_short(cls, value: str) -> str:
if len(value) > 90:
raise ValueError("intent must be 90 characters or fewer")
return value
@field_validator("line")
@classmethod
def keep_line_short(cls, value: str) -> str:
if not value:
raise ValueError("line must not be empty")
if len(value.split()) > 25:
raise ValueError("line must be 25 words or fewer")
if len(value) > 220:
raise ValueError("line must be 220 characters or fewer")
return value
@field_validator("memory_update")
@classmethod
def keep_memory_short(cls, value: str) -> str:
if len(value) > 140:
raise ValueError("memory_update must be 140 characters or fewer")
return value
class DirectorDecision(BaseModel):
next_speaker: str
beat_type: BeatType
instruction: str
stage_effect: str
uses_prop: bool = False
reveal_secret: bool = False
should_end_scene: bool = False
reason_summary: str
@field_validator("next_speaker", "instruction", "stage_effect", "reason_summary")
@classmethod
def require_text(cls, value: str) -> str:
cleaned = " ".join(value.strip().split())
if not cleaned:
raise ValueError("field must not be empty")
return cleaned
@field_validator("instruction", "reason_summary")
@classmethod
def keep_brief(cls, value: str) -> str:
if len(value) > 240:
raise ValueError("field must be 240 characters or fewer")
return value
@dataclass
class Actor:
name: str
avatar: str # Emoji or short label; used if avatar_image_url is unset/invalid.
goal: str
secret: str
speaking_style: str
tools: list[str] = field(default_factory=list)
avatar_image_url: str | None = None # Optional HTTPS portrait for the stage card.
held_prop: str | None = None
mood: str = "ready"
current_goal: str | None = None
goal_progress: str = "Waiting for the curtain."
held_props: list[str] = field(default_factory=list)
secret_status: Literal["hidden", "hinted", "revealed", "resolved"] = "hidden"
recent_memory: list[str] = field(default_factory=list)
@dataclass
class Beat:
speaker: str
intent: str
line: str
emotion: str
gesture: str
stage_effect: str
memory_update: str = ""
tool_request: ToolRequest | None = None
@dataclass
class ToolResult:
tool_name: str
result: str
actor_name: str
reason: str
arguments: dict[str, SimpleToolValue] = field(default_factory=dict)
stage_effect: str | None = None
@dataclass
class TheaterSession:
show_title: str
premise: str
setting: str
actors: list[Actor]
backdrop_image_url: str | None = None # Optional HTTPS image layered behind stage copy.
backdrop_description: str | None = None # LLM minimal art-direction text used to pick backdrop_image_url.
session_id: str = field(default_factory=lambda: uuid4().hex[:12])
created_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
beat_index: int = 0
min_beats: int = 7
target_beats: int = 10
max_beats: int = 12
show_length_mode: str = "standard"
transcript: list[Beat] = field(default_factory=list)
props: list[str] = field(default_factory=list)
latest_prop: str | None = None
latest_audience_action: str | None = None
latest_tool_result: ToolResult | None = None
recent_tool_results: list[ToolResult] = field(default_factory=list)
stage_lighting: str = "warm_spotlight"
director_log: list[str] = field(default_factory=list)
trace_events: list[dict[str, Any] | str] = field(default_factory=list)
finale_requested: bool = False
backend_name: str = "deterministic"
backend_model_id: str | None = None
backend_max_new_tokens: int = 120
backend_temperature: float = 0.75
director_mode: str = "deterministic"
# One-shot: show opening-curtain animation on the first stage render after create.
play_opening_curtain: bool = False
|