Falln87 commited on
Commit
fdcd0a4
Β·
verified Β·
1 Parent(s): 4f70a26

CLERK synthetic evolving-session generator: personas, updates/supersessions/tombstones, gold ledger transitions, seeded

Browse files
Files changed (1) hide show
  1. clerk/generator.py +178 -0
clerk/generator.py ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CLERK synthetic evolving-session generator.
2
+
3
+ Generates personas with evolving facts across multiple dialogue sessions.
4
+ Because every fact event (ADD / UPDATE / TOMBSTONE) is generated
5
+ programmatically, the gold ledger state after every session is known by
6
+ construction, which yields exact supervision for the consolidation policy β€”
7
+ no LLM judge anywhere in the pipeline.
8
+
9
+ Everything is seeded: two runs with the same --seed produce byte-identical
10
+ data.
11
+
12
+ Author: Justin Wolcott (fallnai-research.org)
13
+ License: Apache-2.0
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import json
20
+ import random
21
+ from typing import Any, Dict, List, Optional, Tuple
22
+
23
+ from clerk.common import (
24
+ LEDGER_BUDGET_DEFAULT,
25
+ Slot,
26
+ apply_ops,
27
+ empty_ledger,
28
+ enforce_budget,
29
+ serialize_ledger,
30
+ salience,
31
+ )
32
+
33
+ # ---------------------------------------------------------------- predicates
34
+
35
+ # (predicate key, human phrase, value pool, statement template, update
36
+ # template, tombstone template)
37
+ PREDICATES: List[Dict[str, Any]] = [
38
+ {"p": "job", "h": "job", "v": ["barista", "chef", "nurse", "high school teacher", "librarian", "electrician", "accountant", "software engineer", "pharmacist", "bus driver"],
39
+ "add": "Big news β€” I started a new job as a {v}.", "upd": "I actually switched jobs again. I'm a {v} now.", "tmb": "I left my job β€” between things right now."},
40
+ {"p": "city", "h": "city", "v": ["Denver", "Portland", "Nashville", "Austin", "Madison", "Boise", "Tucson", "Salem", "Reno", "Spokane"],
41
+ "add": "I just moved to {v}!", "upd": "We relocated again β€” I'm in {v} now.", "tmb": "I'm between cities at the moment, staying with family."},
42
+ {"p": "pet_kind", "h": "pet", "v": ["cat", "dog", "rabbit", "parakeet", "hamster", "iguana"],
43
+ "add": "I adopted a {v}.", "upd": "Long story, but I rehomed the {v_old} and got a {v} instead.", "tmb": "My pet passed away last month. It's been rough."},
44
+ {"p": "partner", "h": "partner", "v": ["Sam", "Jordan", "Priya", "Diego", "Nina", "Marcus", "Ellie", "Omar"],
45
+ "add": "I've been seeing someone β€” their name is {v}.", "upd": "That ended, and I'm dating {v} now.", "tmb": "We broke up, so I'm single again."},
46
+ {"p": "car", "h": "car", "v": ["a red Civic", "a blue Outback", "a gray Silverado", "a used Miata", "an old Corolla", "a white Jetta"],
47
+ "add": "I finally bought a car β€” {v}.", "upd": "I traded it in for {v}.", "tmb": "I sold the car β€” biking everywhere now."},
48
+ {"p": "hobby", "h": "main hobby", "v": ["bouldering", "watercolor painting", "chess", "birdwatching", "pottery", "kickboxing", "sourdough baking", "trail running"],
49
+ "add": "I've gotten really into {v}.", "upd": "I switched hobbies β€” now it's all about {v}.", "tmb": "I had to drop the hobby, no time lately."},
50
+ {"p": "favorite_food", "h": "favorite food", "v": ["pad thai", "dumplings", "tacos", "ramen", "lasagna", "pho", "shawarma", "pancakes"],
51
+ "add": "If I had to pick a favorite food, it's {v}.", "upd": "I've changed my favorite food to {v}, believe it or not.", "tmb": "I can't eat that anymore β€” no favorite food these days."},
52
+ {"p": "gym", "h": "gym", "v": ["RiseFit", "IronWorks", "FlexCity", "PeakBarre", "the Strength Lab", "Northside Athletic"],
53
+ "add": "I joined a new gym called {v}.", "upd": "I switched gyms β€” now I'm at {v}.", "tmb": "I cancelled my gym membership."},
54
+ {"p": "boss", "h": "boss", "v": ["Dana", "Victor", "Renee", "Kofi", "Beatrice", "Hank"],
55
+ "add": "My new boss's name is {v}.", "upd": "There's a new boss over me now: {v}.", "tmb": "My boss left and they haven't replaced them."},
56
+ {"p": "coworker", "h": "closest coworker", "v": ["Lena", "Theo", "Yusuf", "Marge", "Pete", "Iris"],
57
+ "add": "I've become friends with a coworker named {v}.", "upd": "On a new team now β€” my closest coworker there is {v}.", "tmb": "That coworker transferred away."},
58
+ {"p": "project", "h": "big work project", "v": ["the website redesign", "the database migration", "the product launch", "the security audit", "the onboarding revamp"],
59
+ "add": "I'm leading {v} at work.", "upd": "I got moved off that and onto {v}.", "tmb": "The project got cancelled, honestly."},
60
+ {"p": "deadline", "h": "big deadline", "v": ["March 14", "April 2", "May 9", "June 21", "July 3", "August 30"],
61
+ "add": "My big deadline is {v}.", "upd": "The deadline moved β€” it's {v} now.", "tmb": "The deadline got pushed indefinitely."},
62
+ {"p": "instrument", "h": "instrument", "v": ["guitar", "violin", "accordion", "cello", "drums", "trumpet"],
63
+ "add": "I've been learning the {v}.", "upd": "I switched instruments β€” playing the {v} now.", "tmb": "I gave up the instrument, wrists couldn't take it."},
64
+ {"p": "team", "h": "sports team I follow", "v": ["the Larks", "the Comets", "the Harbors", "the Northside Pistons", "the Rovers"],
65
+ "add": "I've started following {v}.", "upd": "I switched allegiances β€” it's {v} now.", "tmb": "I stopped following sports entirely."},
66
+ {"p": "allergy", "h": "allergy", "v": ["shellfish", "peanuts", "latex", "penicillin", "kiwi"],
67
+ "add": "Heads up: I'm allergic to {v}.", "upd": "New allergy diagnosis β€” {v} this time.", "tmb": "Good news, the allergy testing came back clear."},
68
+ {"p": "vacation_plan", "h": "next vacation spot", "v": ["Lisbon", "Kyoto", "Banff", "Oaxaca", "the Azores", "Marrakesh"],
69
+ "add": "I'm planning a trip to {v}.", "upd": "Change of plans β€” we're going to {v} instead.", "tmb": "The trip is off."},
70
+ {"p": "hobby_class", "h": "evening class", "v": ["a pottery class", "a Spanish class", "a jazz piano class", "a photography class", "a welding class"],
71
+ "add": "I signed up for {v}.", "upd": "I dropped that and enrolled in {v}.", "tmb": "I withdrew from the class."},
72
+ {"p": "neighbor", "h": "neighbor", "v": ["Mr. Alvarez", "Ms. Chen", "the Kowalskis", "Pastor Jim", "Dr. Patel"],
73
+ "add": "My new neighbor is {v}.", "upd": "They moved out β€” my neighbor now is {v}.", "tmb": "My neighbor moved away recently."},
74
+ ]
75
+
76
+ TRAIN_NAMES = ["Alex", "Bailey", "Casey", "Drew", "Emery", "Frankie", "Gray", "Harper",
77
+ "Indigo", "Jesse", "Kai", "Logan", "Marley", "Noel", "Oakley", "Parker",
78
+ "Quinn", "Reese", "Sage", "Tatum", "Undra", "Val", "Wren", "Xander"]
79
+ TEST_NAMES = ["Ari", "Bellamy", "Crew", "Dallas", "East", "Flynn", "Greer", "Hollis",
80
+ "Ira", "Jules", "Kit", "Lennon", "Morgan", "Nico", "Onyx", "Perry",
81
+ "Quince", "Rory", "Sailor", "Tao", "Uri", "Vesper", "Wilder", "Yael"]
82
+
83
+ CHATTER = [
84
+ "Anyway β€” how's your week going?",
85
+ "Sorry, I just needed to vent a little.",
86
+ "This weather is something else lately.",
87
+ "Did you catch the game last night?",
88
+ "I should probably get more sleep.",
89
+ "Anyway, enough about that.",
90
+ "My commute was ridiculous today.",
91
+ "Coffee count today: three. Send help.",
92
+ ]
93
+ ACKS = ["Got it β€” noted.", "That's good to hear.", "Thanks for the update!",
94
+ "Ah, interesting.", "Makes sense.", "Oh wow, okay.", "Sounds like a plan.",
95
+ "Noted, thanks for telling me."]
96
+
97
+ Q_TEMPLATES = {
98
+ "recent": ["Do you remember what my {h} is?", "Quick quiz: what's my {h} again?"],
99
+ "stale": ["Do you still remember my {h} from before?", "You remember what my {h} is, right?"],
100
+ "superseded": ["Wait β€” what is my {h} these days?", "Remind me: what's my current {h}?"],
101
+ "negated": ["Do I still have a {h}?", "What about my {h} β€” still a thing?"],
102
+ "unknown": ["What's my {h}?", "Do you happen to know my {h}?"],
103
+ "temporal": ["What was my {h} back then, before it changed?", "What did my {h} used to be?"],
104
+ }
105
+
106
+ ANSWER_OK = {
107
+ "value": "{v}",
108
+ "negated": "You don't have that anymore β€” you mentioned it's no longer the case.",
109
+ "unknown": "I don't know β€” you haven't told me about that yet.",
110
+ "temporal": "{v}",
111
+ }
112
+
113
+
114
+ def _fill(t: str, v: Optional[str] = None, h: Optional[str] = None, s: Optional[str] = None) -> str:
115
+ out = t
116
+ if v is not None:
117
+ out = out.replace("{v}", v)
118
+ if h is not None:
119
+ out = out.replace("{h}", h)
120
+ if s is not None:
121
+ out = out.replace("{s}", s)
122
+ return out
123
+
124
+
125
+ # ---------------------------------------------------------------- generation
126
+
127
+ def make_persona(rng: random.Random, name: str, n_init: int = 8) -> Dict[str, Any]:
128
+ preds = rng.sample(PREDICATES, n_init)
129
+ facts = []
130
+ for spec in preds:
131
+ facts.append({
132
+ "pred": spec["p"], "value": rng.choice(spec["v"]),
133
+ "born_session": 0, "state": "active", # active | updated | tombstoned
134
+ "history": [],
135
+ })
136
+ return {"name": name, "facts": facts}
137
+
138
+
139
+ def find_fact(persona: Dict[str, Any], pred: str) -> Optional[Dict[str, Any]]:
140
+ for f in persona["facts"]:
141
+ if f["pred"] == pred:
142
+ return f
143
+ return None
144
+
145
+
146
+ def spec_of(pred: str) -> Dict[str, Any]:
147
+ return next(p for p in PREDICATES if p["p"] == pred)
148
+
149
+
150
+ def render_session(
151
+ rng: random.Random,
152
+ persona: Dict[str, Any],
153
+ session_idx: int,
154
+ n_add: int, n_upd: int, n_tmb: int, n_q: int,
155
+ ) -> Tuple[List[Dict[str, str]], List[Dict[str, Any]], List[Dict[str, Any]]]:
156
+ """Render one session. Returns (turns, write_events, qas).
157
+
158
+ write_events: [{"kind": ADD/UPDATE/TOMBSTONE, "pred":..., "value":...}]
159
+ qas: [{"q":..., "a":..., "type":..., "pred":...}]
160
+ """
161
+ name = persona["name"]
162
+ turns: List[Dict[str, Any]] = []
163
+ events: List[Dict[str, Any]] = []
164
+
165
+ # --- pick events
166
+ updatable = [f for f in persona["facts"] if f["state"] == "active"
167
+ and f["born_session"] < session_idx]
168
+ spec_pool = [p for p in PREDICATES if find_fact(persona, p["p"]) is None]
169
+
170
+ for _ in range(n_add):
171
+ if not spec_pool_left(spec_pool_left_spec := spec_pool_left := None) and False:
172
+ pass # placeholder removed
173
+ del spec_pool_left
174
+
175
+ for _ in range(n_add):
176
+ if not spec_pool_left_check(spec_pool_left_spec := None) and False:
177
+ pass
178
+ return [], [], []