Emergent generative agents
Revisión | 3c803c8de0d04c3fb45e9e0455f8ab2111e59206 (tree) |
---|---|
Tiempo | 2023-04-15 00:16:39 |
Autor | Corbin <cds@corb...> |
Commiter | Corbin |
Fix some newlines, and experiment with thoughts.
Trying to avoid picking exponentially-long chains of thought by
preferring shorter thoughts. First, select relevant thoughts; then,
select shortest thoughts.
@@ -3,6 +3,7 @@ | ||
3 | 3 | |
4 | 4 | from concurrent.futures import ThreadPoolExecutor |
5 | 5 | from datetime import datetime |
6 | +from heapq import nsmallest | |
6 | 7 | import json |
7 | 8 | import os.path |
8 | 9 | import random |
@@ -37,10 +38,10 @@ def build_prompt(persona, shadow, flavorText="", selfAware=False, | ||
37 | 38 | return f"""Character Persona: {titledName} ({personaTraits}) |
38 | 39 | |
39 | 40 | Character Shadow Persona: {shadow[0]} ({shadowTraits}) |
40 | - | |
41 | -Flavor text: {flavorText} | |
42 | 41 | """, titledName |
43 | 42 | |
43 | +# Flavor text: {flavorText} | |
44 | + | |
44 | 45 | def load_character(path): |
45 | 46 | with open(os.path.join(path, "character.json"), "r") as handle: |
46 | 47 | return json.load(handle) |
@@ -60,7 +61,7 @@ class SentenceIndex: | ||
60 | 61 | with open(self.path, "w") as f: json.dump(dict(self.db), f) |
61 | 62 | |
62 | 63 | def search(self, embedding, k): |
63 | - with Timer("k nearest neighbors"): | |
64 | + with Timer("%d nearest neighbors" % k): | |
64 | 65 | D, I = self.index.search(np.array([embedding], dtype="float32"), k) |
65 | 66 | return [self.db[i][0] for i in I[0] if i >= 0] |
66 | 67 |
@@ -140,7 +141,12 @@ class Agent(SingleServerIRCBot): | ||
140 | 141 | |
141 | 142 | def thoughtPrompt(self): |
142 | 143 | key = NO_THOUGHTS_EMBED if self.recent_thought is None else self.recent_thought[1] |
143 | - new_thoughts = thought_index.search(key, 4) | |
144 | + # Fetch more thoughts than necessary, and always prefer shorter | |
145 | + # thoughts. This is an attempt to prevent exponential rumination. | |
146 | + new_thoughts = thought_index.search(key, 10) | |
147 | + # .search() returns most relevant thoughts first; reversing the list | |
148 | + # creates more focused chains of thought. | |
149 | + new_thoughts = nsmallest(5, new_thoughts.reverse(), key=len) | |
144 | 150 | if self.recent_thought is not None: |
145 | 151 | new_thoughts.append(self.recent_thought[0]) |
146 | 152 | print("~ Thoughts:", *new_thoughts) |
@@ -148,8 +154,9 @@ class Agent(SingleServerIRCBot): | ||
148 | 154 | |
149 | 155 | def newThoughtPrompt(self, channel): |
150 | 156 | lines = self.logs[channel].l[-10:] |
151 | - return (self.thoughtPrompt() + self.chatPrompt(channel) + | |
152 | - "\n".join(lines) + "\nEND OF LOG\n") | |
157 | + return (self.chatPrompt(channel) + "\n" + | |
158 | + "\n".join(lines) + "\nEND OF LOG\n" + | |
159 | + self.thoughtPrompt()) | |
153 | 160 | |
154 | 161 | def chatPrompt(self, channel): |
155 | 162 | c = self.channels[channel] |
@@ -178,7 +185,7 @@ Users: {users}""" | ||
178 | 185 | prefix = f"{datetime.now():%H:%M:%S} <{nick}>" |
179 | 186 | examples = self.examplesFromOtherChannels(channel) |
180 | 187 | # NB: "full" prompt needs log lines from current channel... |
181 | - fullPrompt = prompt + self.thoughtPrompt() + examples + self.chatPrompt(channel) | |
188 | + fullPrompt = prompt + self.thoughtPrompt() + examples + self.chatPrompt(channel) + "\n" | |
182 | 189 | # ...but we need to adjust the log offset first... |
183 | 190 | log.bumpCutoff(max_context_length, gen.countTokens, fullPrompt, prefix) |
184 | 191 | # ...and current channel's log lines are added here. |
@@ -196,9 +203,7 @@ Users: {users}""" | ||
196 | 203 | |
197 | 204 | def thinkAbout(self, channel): |
198 | 205 | print("~ Will ponder channel:", channel) |
199 | - thoughtPrompt = prompt + self.newThoughtPrompt(channel) | |
200 | - prefix = "New thought:" | |
201 | - s = thoughtPrompt + prefix | |
206 | + s = prompt + self.newThoughtPrompt(channel) | |
202 | 207 | def cb(completion): |
203 | 208 | self.thinking = False |
204 | 209 | thought = breakIRCLine(completion.result()) |
@@ -9,6 +9,7 @@ path = sys.argv[-1] | ||
9 | 9 | gen = CamelidGen() |
10 | 10 | |
11 | 11 | with open(path, "r") as handle: db = json.load(handle) |
12 | +print("Thought database:", len(db), "entries") | |
12 | 13 | |
13 | 14 | while True: |
14 | 15 | try: thought = input("> ").strip() |
@@ -16,4 +17,5 @@ while True: | ||
16 | 17 | if not thought: break |
17 | 18 | db[thought] = gen.embed(thought) |
18 | 19 | |
20 | +print("Saving thought database:", len(db), "entries") | |
19 | 21 | with open(path, "w") as handle: json.dump(db, handle) |
@@ -29,7 +29,7 @@ class Log: | ||
29 | 29 | return self.finishPromptAtCutoff(self.cutoff, s, prefix) |
30 | 30 | |
31 | 31 | def finishPromptAtCutoff(self, cutoff, s, prefix): |
32 | - return s + "\n".join(self.l[cutoff:]) + prefix | |
32 | + return s + "\n".join(self.l[cutoff:]) + "\n" + prefix | |
33 | 33 | |
34 | 34 | def undo(self): self.l.pop() |
35 | 35 |