File size: 4,618 Bytes
a865db3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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