Something went wrong. Try again.
a tiny pet with a GPT-2 brain, living in your browser
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329"""Regression cases for the training contract. Run with python3 test/data_rules.py."""import copyimport hashlibimport jsonimport osimport subprocessimport sysimport tempfileimport unittestsys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'train'))import gen_data as Gimport world as Wimport migrate_v6 as Mfrom unittest.mock import patch
class Rules(unittest.TestCase): def setUp(self): self.st = dict(owner='Sam', pet='Mochi', stage='kid', day=4, traits=['sleepy', 'curious'], hh=10, mm=0, weather='sunny', stats=dict(tummy=95, fun=50, energy=10, clean=80), memories=[], today=[], shelf=[], toy='ball', poop=0, asleep=True, sick=False, yesterday='')
def check(self, raw, error=None): if error: with self.assertRaisesRegex(ValueError, error): G.check_chat(copy.deepcopy(self.st), raw) else: text, spans = G.check_chat(copy.deepcopy(self.st), raw) G.recheck(dict(kind='chat', text=text, spans=spans)) return text, spans
def test_sleep(self): self.check('Sam: goodnight\nMochi: mmm... [dream:moon]\nMochi: [dream:cookie]') for body in ['hello awake world [emote:happy]', '[sleep]', '[dream:moon] [walk:left]', 'one two three four five [dream:moon]', '[dream:moon] [dream:heart]', '[dream:invented]']: self.check('Sam: goodnight\nMochi: '+body+'\nMochi: [dream:cookie]', 'sleep dream grammar|dream invented')
def test_sleep_offer(self): offer='* Sam offered Mochi a cookie *\n' self.check(offer+'Mochi: mmm [no] [dream:cookie]\nMochi: [dream:moon]') self.check(offer+'Mochi: cookie?! [yes]\n* Mochi woke up *\nMochi: yummy', 'exhausted food wake') self.st['stats']['tummy']=10 self.check(offer+'Mochi: cookie?! [yes]\n* Mochi woke up *\nMochi: yummy [emote:happy]') self.check(offer+'Mochi: [yes]\nMochi: hello', 'missing wake') self.check(offer+'Mochi: [yes] [dream:cookie]\n* Mochi woke up *\nMochi: hello', 'other tags') self.check('* Mochi woke up *\n'+offer+'Mochi: [yes]\nMochi: hello', 'wake before offer') self.check(offer+'Mochi: [dream:cookie]\nMochi: [dream:moon]', 'offer answer')
def test_wake(self): for action in ['Sam: hey sleepyhead', '* Sam patted Mochi *']: self.check('* Mochi woke up *\n'+action+'\nMochi: hi!\nMochi: [emote:wave]') self.check(action+'\n* Mochi woke up *\nMochi: hi!\nMochi: [emote:wave]', 'wake after message/pat') self.check('* Sam turned on the lights *\n* Mochi woke up *\nMochi: bright!\nMochi: [emote:wave]') self.st['asleep']=False self.check('Sam: hi\nMochi: [dream:moon]\nMochi: hello', 'awake dream') self.check('Sam: night\nMochi: [sleep]\n* Mochi woke up *\nMochi: hi', 'instant wake') self.check('Sam: night\nMochi: [sleep]\n* Mochi woke up *\nSam: hello\nMochi: hi')
def test_memory_and_hunger(self): self.st['asleep']=False for body, err in [('[remember:bug on window]', 'memory without owner'), ('[remember:Sam lost his hat]', 'gendered'), ('[recall:Sam has cat]{nothing}', 'long recall'), ("i am literally starving!", 'hunger while full')]: self.check('Sam: hello\nMochi: '+body+'\nMochi: [emote:happy]', err) self.check('Sam: hello\nMochi: i am not hungry [no]\nMochi: okay', 'answer without offer') self.check('Sam: hello\nMochi: i am not hungry\nMochi: okay') self.check('Sam: ...\nMochi: hello\nMochi: okay', 'owner without words') self.st['memories']=['Sam is 29 years old'] text, spans=self.check('Sam: how old am i?\nMochi: [recall:old]{Sam is 29 years old} 29!\nSam: thanks\nMochi: hehe') start=text.index('{Sam'); end=text.index('}',start)+1 self.assertTrue(all(b<=start or a>=end for a,b in spans))
def test_medicine(self): self.st.update(asleep=False, sick=False) body='* Sam offered Mochi medicine *\nMochi: okay [yes]\nMochi: thank you Sam' self.check(body, 'medicine while healthy') self.check(body.replace('okay [yes]', 'no thanks [no]')) self.st['sick']=True self.check(body)
def test_headers_diary(self): self.st['today']=['ate a cookie', 'Sam said: goodnight']*4 self.assertNotIn('Sam said:', W.header('chat',self.st)) self.assertIn('Sam said:', W.header('diary',self.st)) for body in ['dear diary,\n[', 'dear diary,\ndoodle:star', 'dear diary,\n- PET', 'dear diary,\n[doodle:banana]']: with self.assertRaises(ValueError): G.check_diary_body(body) G.check_diary_body('dear diary,\ni dreamed of cookies.\n- Mochi\n[doodle:star]') self.st.update(hh=22, weather='sunny') text, spans = G.check_chat(self.st, 'Sam: goodnight little one\nMochi: zzz [dream:moon]\nMochi: [dream:star]') with self.assertRaisesRegex(ValueError, 'weather for time'): G.recheck(dict(kind='chat', text=text, spans=spans))
def stored_rows(self): st = copy.deepcopy(self.st) st['asleep'] = False rows = [] for kind, raw in [('chat', 'Sam: hello\n* Sam patted Mochi *\n' 'Mochi: [clock] hello [emote:happy]\nMochi: hi [emote:wave]'), ('diary', 'dear diary,\ni dreamed of cookies.\n- Mochi\n[doodle:star]')]: text, spans = (G.check_chat if kind == 'chat' else G.check_diary)(st, raw) rows.append(dict(kind=kind, text=text, spans=spans)) return rows
def test_recheck_masks(self): for row in self.stored_rows(): with self.subTest(kind=row['kind'], mask='canonical'): original = copy.deepcopy(row) G.recheck(row) self.assertEqual(row, original) missing = {k: v for k, v in row.items() if k != 'spans'} shortened = copy.deepcopy(row['spans']) shortened[0][0] += 1 cases = [('missing', missing), ('empty', {**row, 'spans': []}), ('all text', {**row, 'spans': [[0, len(row['text'])]]}), ('missing trained character', {**row, 'spans': shortened})] for label, bad in cases: with self.subTest(kind=row['kind'], mask=label): original = copy.deepcopy(bad) with self.assertRaises(ValueError): G.recheck(bad) self.assertEqual(bad, original)
def test_recheck_untrained_characters(self): for row in self.stored_rows(): text = row['text'] positions = [('header', 0), ('separator', text.index('---\n'))] if row['kind'] == 'chat': positions += [('owner', text.index('Sam: hello')), ('event', text.index('* Sam patted Mochi *')), ('pet prefix', text.index('Mochi: [clock]')), ('lookup brace', text.index('{10:00 morning}')), ('lookup result', text.index('10:00 morning}', text.index('---\n')))] else: positions.append(('diary opener', text.index('dear diary,'))) for label, at in positions: with self.subTest(kind=row['kind'], character=label): bad = {**row, 'spans': row['spans'] + [[at, at + 1]]} with self.assertRaisesRegex(ValueError, 'loss mask'): G.recheck(bad)
def test_recheck_bad_spans(self): for row in self.stored_rows(): n = len(row['text']) cases = [('null', None), ('number', 1), ('boolean', True), ('string', '0,1'), ('object', {}), ('null span', [None]), ('short span', [[0]]), ('long span', [[0, 1, 2]]), ('string bound', [['0', 1]]), ('float bound', [[0, 1.5]]), ('boolean start', [[False, 1]]), ('boolean end', [[0, True]]), ('negative bound', [[-1, 1]]), ('reversed bounds', [[1, 0]]), ('past end', [[n, n + 1]]), ('past end empty', [[n + 1, n + 1]])] for label, spans in cases: with self.subTest(kind=row['kind'], spans=label): with self.assertRaises(ValueError): G.recheck({**row, 'spans': spans})
def test_recheck_equivalent_masks(self): for row in self.stored_rows(): merged = [] for a, b in row['spans']: if merged and a == merged[-1][1]: merged[-1][1] = b else: merged.append([a, b]) split = [] for a, b in merged: split.extend([[a, a + 1], [a + 1, b]] if b - a > 1 else [[a, b]]) for label, spans in [('merged', merged), ('split reversed', list(reversed(split))), ('overlapping', split + [split[0]])]: with self.subTest(kind=row['kind'], spans=label): equivalent = {**row, 'spans': spans} original = copy.deepcopy(equivalent) G.recheck(equivalent) self.assertEqual(equivalent, original)
def test_recheck_shifted_text(self): chat, diary = self.stored_rows() at = chat['text'].index('Mochi: [clock]') indented = chat['text'][:at] + ' ' + chat['text'][at:] shifted = [[a + 2, b + 2] for a, b in chat['spans']] cases = [('indented, old spans', {**chat, 'text': indented}), ('indented, shifted spans', {**chat, 'text': indented, 'spans': shifted}), ('chat blank line', {**chat, 'text': chat['text'].replace('Sam: hello\n', 'Sam: hello\n\n')}), ('stage directions', {**chat, 'text': chat['text'].replace('Mochi: [clock]', 'Mochi: *purrs* [clock]')}), ('chat trailing spaces', {**chat, 'text': chat['text'].replace('Sam: hello\n', 'Sam: hello \n')}), ('diary opener case', {**diary, 'text': diary['text'].replace('dear diary,', 'Dear diary,')}), ('diary blank line', {**diary, 'text': diary['text'].replace('dear diary,\n', 'dear diary,\n\n')}), ('diary trailing newline', {**diary, 'text': diary['text'] + '\n'})] for label, bad in cases: with self.subTest(text=label): original = copy.deepcopy(bad) with self.assertRaisesRegex(ValueError, 'noncanonical text'): G.recheck(bad) self.assertEqual(bad, original)
def test_recheck_keeps_lookup_results(self): self.st['asleep'] = False text, spans = G.check_body('Sam', 'Mochi', W.header('chat', self.st), 'Sam: hello\nMochi: [clock]{23:59 night} late!\nMochi: hello', None) row = dict(kind='chat', text=text, spans=spans) original = copy.deepcopy(row) G.recheck(row) self.assertEqual(row, original) self.assertIn('[clock]{23:59 night}', row['text'])
def test_recall_templates(self): for _ in range(500): st=G.rand_state() G.recall_target(st) keys=[G.memory_template(m, st['owner']) for m in st['memories']] self.assertNotIn(None, keys) self.assertEqual(len(keys), len(set(keys)))
def test_sleep_teacher_standin(self): self.st['stats']['tummy']=10 slots = [([], 'DREAM'), ([], 'DREAM'), (['* Sam offered Mochi a cookie *'], 'OFFER')] with patch.object(G, 'sleep_scenario', return_value=slots): row = G.make_sleep_chat(lambda *a, **k: '["zzz [dream:moon]", "[dream:star]", "cookie? [yes]"]', self.st) self.assertTrue(row['text'].endswith('Mochi: cookie? [yes]\n* Mochi woke up *\n')) G.recheck(row) wake = row['text'].rindex('* Mochi woke up *') self.assertTrue(all(b <= wake for a, b in row['spans']))
def test_migration_diary_masks(self): for raw in ['dear diary,\ni dreamed of cookies.\n- Mochi\n[doodle:star]', ' Dear diary,\n\ni dreamed of cookies.\n- Mochi\n[doodle:star]\n ']: text, spans = G.check_diary(self.st, raw) legacy_text = W.header('diary', self.st) + raw cases = [('empty', dict(spans=[])), ('header trained', dict(spans=[[0, len(legacy_text)]])), ('missing', {})] for label, mask in cases: with self.subTest(body=raw, mask=label): old = dict(kind='diary', text=legacy_text, **mask) original = copy.deepcopy(old) migrated = M.migrate(old) self.assertEqual(migrated['text'], text) self.assertEqual(migrated['spans'], spans) self.assertEqual(old, original) G.recheck(migrated) opener_end = text.index('dear diary,') + len('dear diary,') self.assertEqual(spans, [[opener_end, len(text)]])
def test_migration_no_wake(self): for action in ['Sam: hello', '* Sam patted Mochi *', '* Sam turned on the lights *']: with self.subTest(action=action): raw = action + '\nMochi: zzz [dream:moon]\nMochi: [dream:star]' text, spans = G.check_chat(self.st, raw) old = dict(kind='chat', text=text, spans=[]) migrated = M.migrate(old) self.assertEqual(migrated['text'], text) self.assertEqual(migrated['spans'], spans) self.assertNotIn('* Mochi woke up *', migrated['text']) G.recheck(migrated)
def test_migration_mixed_wakes(self): actions = ['Sam: hello', '* Sam patted Mochi *', '* Sam turned on the lights *'] for sleeping_action in actions + ['\n'.join(actions)]: for awake_action in actions: with self.subTest(sleeping=sleeping_action, awake=awake_action): raw = sleeping_action + '\nMochi: zzz [dream:moon]\n' + awake_action + '\n' raw += 'Mochi: hello Sam\nMochi: [emote:wave]' woke_action = (awake_action + '\n* Mochi woke up *' if 'lights' in awake_action else '* Mochi woke up *\n' + awake_action) expected = sleeping_action + '\nMochi: zzz [dream:moon]\n' + woke_action + '\n' expected += 'Mochi: hello Sam\nMochi: [emote:wave]' text, spans = G.check_chat(self.st, expected) old = dict(kind='chat', text=W.header('chat', self.st) + raw, spans=[]) migrated = M.migrate(old) self.assertEqual(migrated['text'], text) self.assertEqual(migrated['spans'], spans) self.assertEqual(migrated['text'].count('* Mochi woke up *'), 1) self.assertLess(migrated['text'].index('[dream:moon]'), migrated['text'].index('* Mochi woke up *')) G.recheck(migrated)
def test_migration_sleep_yes(self): self.st['stats']['tummy'] = 10 offer = '* Sam offered Mochi a cookie *\nMochi: cookie? [yes]\n' for prefix in ['', 'Sam: hello\nMochi: zzz [dream:moon]\n']: with self.subTest(prefix=prefix): raw = prefix + offer + 'Mochi: yummy [emote:happy]' expected = prefix + offer + '* Mochi woke up *\nMochi: yummy [emote:happy]' text, spans = G.check_chat(self.st, expected) old = dict(kind='chat', text=W.header('chat', self.st) + raw, spans=[]) migrated = M.migrate(old) self.assertEqual(migrated['text'], text) self.assertEqual(migrated['spans'], spans) self.assertIn('[yes]\n* Mochi woke up *\n', migrated['text']) G.recheck(migrated) # Already-current rows must not acquire a second wake either. self.assertEqual(M.migrate({**migrated, 'spans': []}), migrated)
def test_migration_standin(self): hdr = W.header('chat', self.st).replace('---\n', 'today: Sam said: hello; ate a cookie\n---\n') row = M.migrate(dict(kind='chat', text=hdr + 'Sam: hello\nMochi: [recall:cat]{nothing} hello\nMochi: hi\n', spans=[])) self.assertNotIn('Sam said:', row['text']) self.assertIn('* Mochi woke up *\nSam: hello', row['text']) start = row['text'].index('{nothing}') self.assertTrue(all(b <= start or a >= start + 9 for a, b in row['spans']))
def test_migration_cli(self): self.st['stage'] = 'kid' old = dict(kind='chat', stage='kid', text=W.header('chat', self.st) + 'Sam: hello\nMochi: hello Sam\nMochi: good morning\n', spans=[]) migrated = M.migrate(old) teen = {**migrated, 'stage': 'teen', 'text': migrated['text'].replace(' · kid · ', ' · teen · ')} # The stage is one character longer, so rebuild the spans for the stand-in. teen = M.migrate(teen) with tempfile.TemporaryDirectory() as d: def write(name, content): path = os.path.join(d, name) with open(path, 'w') as f: f.write(content) return path src = write('old.jsonl', json.dumps(old)+'\n'+json.dumps(teen)+'\n') new = write('new.jsonl', json.dumps(teen)+'\n') excluded = write('exclude.json', json.dumps({hashlib.sha256(migrated['text'].encode()).hexdigest(): 'stand-in'})) out = os.path.join(d, 'out.jsonl') subprocess.run([sys.executable, M.__file__, src, out, new, '--exclude', excluded], check=True, capture_output=True) with open(out) as f: rows = [json.loads(l) for l in f] self.assertEqual(rows, [teen])
if __name__ == '__main__': unittest.main()