LitDigitalTwin / engine /loader.py
jmisak's picture
Update engine/loader.py
43a6973 verified
import yaml
import os
def load_persona(path):
"""
Load a LitDigitalTwin persona from YAML file.
Ensures required fields and initializes missing defaults.
"""
if not os.path.exists(path):
raise FileNotFoundError(f"Persona file not found: {path}")
with open(path, "r", encoding="utf-8") as f:
persona = yaml.safe_load(f)
# Required top-level keys
required_keys = [
"persona_name",
"age",
"role",
"system_prompt",
"facts"
]
for key in required_keys:
if key not in persona:
raise ValueError(f"Missing required key in persona: {key}")
# Ensure facts is a list
if not isinstance(persona.get("facts"), list):
print("Warning: 'facts' should be a list. Converting.")
facts = persona.get("facts", [])
if isinstance(facts, dict):
persona["facts"] = list(facts.values())
else:
persona["facts"] = []
# Initialize default_state if missing
if "default_state" not in persona:
persona["default_state"] = {}
state = persona["default_state"]
default_state_keys = {
"anxiety": 0.5,
"trust": 0.5,
"openness": 0.5,
"mode": "baseline",
"emotional_memory": []
}
for key, default in default_state_keys.items():
if key not in state:
print(f"Warning: Missing state key '{key}' in persona. Using default value.")
state[key] = default
# Ensure tone_guidance exists and has at least 'baseline'
if "tone_guidance" not in persona:
persona["tone_guidance"] = {
"baseline": {
"voice": "Natural and authentic",
"example": "I'm doing okay today, thanks for asking."
}
}
return persona
def validate_persona(persona):
"""
Validate that a persona has all necessary components for simulation.
Returns (is_valid, error_message)
"""
if not persona.get("persona_name"):
return False, "Persona must have a name"
if not persona.get("system_prompt"):
return False, "Persona must have a system_prompt"
if not isinstance(persona.get("facts"), list):
return False, "Persona 'facts' must be a list"
state = persona.get("default_state", {})
for key in ["anxiety", "trust", "openness"]:
value = state.get(key)
if value is None:
return False, f"default_state missing required key: {key}"
if not isinstance(value, (int, float)):
return False, f"default_state.{key} must be numeric"
if not 0 <= value <= 1:
return False, f"default_state.{key} must be between 0 and 1"
return True, "Persona is valid"
def save_persona(persona, path):
"""
Save a persona to YAML file.
"""
with open(path, "w", encoding="utf-8") as f:
yaml.dump(persona, f, sort_keys=False, default_flow_style=False)
return path
def create_default_persona(name, age, role):
"""
Create a basic LitDigitalTwin persona template.
"""
persona = {
"persona_name": name,
"age": age,
"role": role,
"system_prompt": f"You are {name}, a {age}-year-old {role}. Respond naturally and stay in character.",
"facts": [
f"{name} is {age} years old.",
f"{name} works as a {role}."
],
"tone_guidance": {
"baseline": {
"voice": "Natural and authentic",
"example": "I'm doing okay today, thanks for asking."
}
},
"default_state": {
"anxiety": 0.5,
"trust": 0.5,
"openness": 0.5,
"mode": "baseline",
"emotional_memory": []
}
}
return persona
def list_available_personas(persona_dir="./personas"):
"""
List all available persona files.
"""
if not os.path.exists(persona_dir):
return []
personas = []
for filename in os.listdir(persona_dir):
if filename.endswith(".yml") or filename.endswith(".yaml"):
path = os.path.join(persona_dir, filename)
try:
persona = load_persona(path)
personas.append({
"filename": filename,
"name": persona.get("persona_name", "Unknown"),
"age": persona.get("age", ""),
"role": persona.get("role", "")
})
except Exception as e:
print(f"Error loading {filename}: {e}")
return personas