Memory recall speedup

frdel committed Dec 8, 2024 at 11:28 UTC 4e028a3ce428cec581273a2261b6bdd8c474853e
7 files changed +212 -178
python/api/nudge.py
-2
@@ -1,8 +1,6 @@
1 from python.helpers.api import ApiHandler
2 from flask import Request, Response
3
4 -from python.helpers import persist_chat
5 -
4 class Nudge(ApiHandler):
5 async def process(self, input: dict, request: Request) -> dict | Response:
6 ctxid = input.get("ctxid", "")
python/extensions/message_loop_prompts/_50_recall_memories.py
+18 -9
@@ -1,29 +1,36 @@
1 +import asyncio
2 from python.helpers.extension import Extension
3 from python.helpers.memory import Memory
4 from agent import LoopData
5
6 +DATA_NAME_TASK = "_recall_memories_task"
7
8 class RecallMemories(Extension):
9
10 INTERVAL = 3
9 - HISTORY = 5 # TODO cleanup
11 + HISTORY = 5 # TODO cleanup
12 RESULTS = 3
13 THRESHOLD = 0.6
14
15 async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
16
15 - if (
16 - loop_data.iteration % RecallMemories.INTERVAL == 0
17 - ): # every 3 iterations (or the first one) recall memories
18 - await self.search_memories(loop_data=loop_data, **kwargs)
17 + # every 3 iterations (or the first one) recall memories
18 + if loop_data.iteration % RecallMemories.INTERVAL == 0:
19 + task = asyncio.create_task(self.search_memories(loop_data=loop_data, **kwargs))
20 + else:
21 + task = None
22 +
23 + # set to agent to be able to wait for it
24 + self.agent.set_data(DATA_NAME_TASK, task)
25 +
26
27 async def search_memories(self, loop_data: LoopData, **kwargs):
28
22 - #cleanup
29 + # cleanup
30 extras = loop_data.extras_temporary
31 if "memories" in extras:
32 del extras["memories"]
26 -
33 +
34 # try:
35 # show temp info message
36 self.agent.context.log.log(
@@ -51,7 +58,9 @@ class RecallMemories(Extension):
58
59 # call util llm to summarize conversation
60 query = await self.agent.call_utility_llm(
54 - system=system, msg=loop_data.user_message.output_text() if loop_data.user_message else "", callback=log_callback
61 + system=system,
62 + msg=loop_data.user_message.output_text() if loop_data.user_message else "",
63 + callback=log_callback,
64 )
65
66 # get solutions database
@@ -91,7 +100,7 @@ class RecallMemories(Extension):
100
101 # append to prompt
102 extras["memories"] = memories_prompt
94 -
103 +
104 # except Exception as e:
105 # err = errors.format_error(e)
106 # self.agent.context.log.log(
python/extensions/message_loop_prompts/_51_recall_solutions.py
+10 -4
@@ -1,7 +1,9 @@
1 +import asyncio
2 from python.helpers.extension import Extension
3 from python.helpers.memory import Memory
4 from agent import LoopData
5
6 +DATA_NAME_TASK = "_recall_solutions_task"
7
8 class RecallSolutions(Extension):
9
@@ -13,10 +15,14 @@ class RecallSolutions(Extension):
15
16 async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
17
16 - if (
17 - loop_data.iteration % RecallSolutions.INTERVAL == 0
18 - ): # every 3 iterations (or the first one) recall solution memories
19 - await self.search_solutions(loop_data=loop_data, **kwargs)
18 + # every 3 iterations (or the first one) recall memories
19 + if loop_data.iteration % RecallSolutions.INTERVAL == 0:
20 + task = asyncio.create_task(self.search_solutions(loop_data=loop_data, **kwargs))
21 + else:
22 + task = None
23 +
24 + # set to agent to be able to wait for it
25 + self.agent.set_data(DATA_NAME_TASK, task)
26
27 async def search_solutions(self, loop_data: LoopData, **kwargs):
28
python/extensions/message_loop_prompts/_90_organize_history_wait.py
+2 -7
@@ -15,7 +15,7 @@ class OrganizeHistoryWait(Extension):
15 # Check if the task is already done
16 if task:
17 if not task.done():
18 - self.log()
18 + self.agent.context.log.set_progress("Compressing history...")
19
20 # Wait for the task to complete
21 await task
@@ -24,11 +24,6 @@ class OrganizeHistoryWait(Extension):
24 self.agent.set_data(DATA_NAME_TASK, None)
25 else:
26 # no task running, start and wait
27 - self.log()
27 + self.agent.context.log.set_progress("Compressing history...")
28 await self.agent.history.compress()
29
30 - def log(self):
31 - if not hasattr(self, 'log_item') or not self.log_item:
32 - self.log_item = self.agent.context.log.log(
33 - type="util", heading="Waiting for history to be compressed..."
34 - )
python/extensions/message_loop_prompts/_91_recall_wait.py new
+19
@@ -0,0 +1,19 @@
1 +from python.helpers.extension import Extension
2 +from agent import LoopData
3 +from python.extensions.message_loop_prompts._50_recall_memories import DATA_NAME_TASK as DATA_NAME_TASK_MEMORIES
4 +from python.extensions.message_loop_prompts._51_recall_solutions import DATA_NAME_TASK as DATA_NAME_TASK_SOLUTIONS
5 +
6 +
7 +class RecallWait(Extension):
8 + async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
9 +
10 + task = self.agent.get_data(DATA_NAME_TASK_MEMORIES)
11 + if task and not task.done():
12 + self.agent.context.log.set_progress("Recalling memories...")
13 + await task
14 +
15 + task = self.agent.get_data(DATA_NAME_TASK_SOLUTIONS)
16 + if task and not task.done():
17 + self.agent.context.log.set_progress("Recalling solutions...")
18 + await task
19 +
python/helpers/log.py
+162 -155
@@ -5,165 +5,172 @@ import uuid
5 from collections import OrderedDict # Import OrderedDict
6
7 Type = Literal[
8 - "agent",
9 - "code_exe",
10 - "error",
11 - "hint",
12 - "info",
13 - "progress",
14 - "response",
15 - "tool",
16 - "user",
17 - "util",
18 - "warning",
8 + "agent",
9 + "code_exe",
10 + "error",
11 + "hint",
12 + "info",
13 + "progress",
14 + "response",
15 + "tool",
16 + "input",
17 + "user",
18 + "util",
19 + "warning",
20 ]
21
22 +
23 @dataclass
24 class LogItem:
23 - log: "Log"
24 - no: int
25 - type: str
26 - heading: str
27 - content: str
28 - temp: bool
29 - kvps: Optional[OrderedDict] = None # Use OrderedDict for kvps
30 - id: Optional[str] = None # Add id field
31 - guid: str = ""
32 -
33 - def __post_init__(self):
34 - self.guid = self.log.guid
35 -
36 - def update(
37 - self,
38 - type: Type | None = None,
39 - heading: str | None = None,
40 - content: str | None = None,
41 - kvps: dict | None = None,
42 - temp: bool | None = None,
43 - **kwargs,
44 - ):
45 - if self.guid == self.log.guid:
46 - self.log.update_item(
47 - self.no,
48 - type=type,
49 - heading=heading,
50 - content=content,
51 - kvps=kvps,
52 - temp=temp,
53 - **kwargs,
54 - )
55 -
56 - def stream(self, heading: str | None = None, content: str | None = None, **kwargs):
57 - if heading is not None:
58 - self.update(heading=self.heading + heading)
59 - if content is not None:
60 - self.update(content=self.content + content)
61 -
62 - for k, v in kwargs.items():
63 - prev = self.kvps.get(k, "") if self.kvps else ""
64 - self.update(**{k: prev + v})
65 -
66 - def output(self):
67 - return {
68 - "no": self.no,
69 - "id": self.id, # Include id in output
70 - "type": self.type,
71 - "heading": self.heading,
72 - "content": self.content,
73 - "temp": self.temp,
74 - "kvps": self.kvps,
75 - }
25 + log: "Log"
26 + no: int
27 + type: str
28 + heading: str
29 + content: str
30 + temp: bool
31 + kvps: Optional[OrderedDict] = None # Use OrderedDict for kvps
32 + id: Optional[str] = None # Add id field
33 + guid: str = ""
34 +
35 + def __post_init__(self):
36 + self.guid = self.log.guid
37 +
38 + def update(
39 + self,
40 + type: Type | None = None,
41 + heading: str | None = None,
42 + content: str | None = None,
43 + kvps: dict | None = None,
44 + temp: bool | None = None,
45 + **kwargs,
46 + ):
47 + if self.guid == self.log.guid:
48 + self.log.update_item(
49 + self.no,
50 + type=type,
51 + heading=heading,
52 + content=content,
53 + kvps=kvps,
54 + temp=temp,
55 + **kwargs,
56 + )
57 +
58 + def stream(self, heading: str | None = None, content: str | None = None, **kwargs):
59 + if heading is not None:
60 + self.update(heading=self.heading + heading)
61 + if content is not None:
62 + self.update(content=self.content + content)
63 +
64 + for k, v in kwargs.items():
65 + prev = self.kvps.get(k, "") if self.kvps else ""
66 + self.update(**{k: prev + v})
67 +
68 + def output(self):
69 + return {
70 + "no": self.no,
71 + "id": self.id, # Include id in output
72 + "type": self.type,
73 + "heading": self.heading,
74 + "content": self.content,
75 + "temp": self.temp,
76 + "kvps": self.kvps,
77 + }
78 +
79
80 class Log:
81
79 - def __init__(self):
80 - self.guid: str = str(uuid.uuid4())
81 - self.updates: list[int] = []
82 - self.logs: list[LogItem] = []
83 - self.progress = ""
84 - self.progress_no = 0
85 -
86 - def log(
87 - self,
88 - type: Type,
89 - heading: str | None = None,
90 - content: str | None = None,
91 - kvps: dict | None = None,
92 - temp: bool | None = None,
93 - id: Optional[str] = None, # Add id parameter
94 - ) -> LogItem:
95 - # Use OrderedDict if kvps is provided
96 - if kvps is not None:
97 - kvps = OrderedDict(kvps)
98 - item = LogItem(
99 - log=self,
100 - no=len(self.logs),
101 - type=type,
102 - heading=heading or "",
103 - content=content or "",
104 - kvps=kvps,
105 - temp=temp or False,
106 - id=id, # Pass id to LogItem
107 - )
108 - self.logs.append(item)
109 - self.updates += [item.no]
110 - if heading and item.no >= self.progress_no:
111 - self.progress = heading
112 - self.progress_no = item.no
113 - return item
114 -
115 - def update_item(
116 - self,
117 - no: int,
118 - type: str | None = None,
119 - heading: str | None = None,
120 - content: str | None = None,
121 - kvps: dict | None = None,
122 - temp: bool | None = None,
123 - **kwargs,
124 - ):
125 - item = self.logs[no]
126 - if type is not None:
127 - item.type = type
128 - if heading is not None:
129 - item.heading = heading
130 - if no >= self.progress_no:
131 - self.progress = heading
132 - self.progress_no = no
133 - if content is not None:
134 - item.content = content
135 - if kvps is not None:
136 - item.kvps = OrderedDict(kvps) # Use OrderedDict to keep the order
137 -
138 - if temp is not None:
139 - item.temp = temp
140 -
141 - if kwargs:
142 - if item.kvps is None:
143 - item.kvps = OrderedDict() # Ensure kvps is an OrderedDict
144 - for k, v in kwargs.items():
145 - item.kvps[k] = v
146 -
147 - self.updates += [item.no]
148 -
149 - def output(self, start=None, end=None):
150 - if start is None:
151 - start = 0
152 - if end is None:
153 - end = len(self.updates)
154 -
155 - out = []
156 - seen = set()
157 - for update in self.updates[start:end]:
158 - if update not in seen:
159 - out.append(self.logs[update].output())
160 - seen.add(update)
161 -
162 - return out
163 -
164 - def reset(self):
165 - self.guid = str(uuid.uuid4())
166 - self.updates = []
167 - self.logs = []
168 - self.progress = ""
169 - self.progress_no = 0
\ No newline at end of file
82 + def __init__(self):
83 + self.guid: str = str(uuid.uuid4())
84 + self.updates: list[int] = []
85 + self.logs: list[LogItem] = []
86 + self.progress = ""
87 + self.progress_no = 0
88 +
89 + def log(
90 + self,
91 + type: Type,
92 + heading: str | None = None,
93 + content: str | None = None,
94 + kvps: dict | None = None,
95 + temp: bool | None = None,
96 + id: Optional[str] = None, # Add id parameter
97 + ) -> LogItem:
98 + # Use OrderedDict if kvps is provided
99 + if kvps is not None:
100 + kvps = OrderedDict(kvps)
101 + item = LogItem(
102 + log=self,
103 + no=len(self.logs),
104 + type=type,
105 + heading=heading or "",
106 + content=content or "",
107 + kvps=kvps,
108 + temp=temp or False,
109 + id=id, # Pass id to LogItem
110 + )
111 + self.logs.append(item)
112 + self.updates += [item.no]
113 + if heading and item.no >= self.progress_no:
114 + self.progress = heading
115 + self.progress_no = item.no
116 + return item
117 +
118 + def update_item(
119 + self,
120 + no: int,
121 + type: str | None = None,
122 + heading: str | None = None,
123 + content: str | None = None,
124 + kvps: dict | None = None,
125 + temp: bool | None = None,
126 + **kwargs,
127 + ):
128 + item = self.logs[no]
129 + if type is not None:
130 + item.type = type
131 + if heading is not None:
132 + item.heading = heading
133 + if no >= self.progress_no:
134 + self.progress = heading
135 + self.progress_no = no
136 + if content is not None:
137 + item.content = content
138 + if kvps is not None:
139 + item.kvps = OrderedDict(kvps) # Use OrderedDict to keep the order
140 +
141 + if temp is not None:
142 + item.temp = temp
143 +
144 + if kwargs:
145 + if item.kvps is None:
146 + item.kvps = OrderedDict() # Ensure kvps is an OrderedDict
147 + for k, v in kwargs.items():
148 + item.kvps[k] = v
149 +
150 + self.updates += [item.no]
151 +
152 + def set_progress(self, progress: str):
153 + self.progress = progress
154 + self.progress_no = len(self.logs)
155 +
156 + def output(self, start=None, end=None):
157 + if start is None:
158 + start = 0
159 + if end is None:
160 + end = len(self.updates)
161 +
162 + out = []
163 + seen = set()
164 + for update in self.updates[start:end]:
165 + if update not in seen:
166 + out.append(self.logs[update].output())
167 + seen.add(update)
168 +
169 + return out
170 +
171 + def reset(self):
172 + self.guid = str(uuid.uuid4())
173 + self.updates = []
174 + self.logs = []
175 + self.progress = ""
176 + self.progress_no = 0
python/helpers/settings.py
+1 -1
@@ -703,7 +703,7 @@ def get_default_settings() -> Settings:
703 chat_model_temperature=0,
704 chat_model_kwargs={},
705 chat_model_ctx_length=8192,
706 - chat_model_ctx_history=0.65,
706 + chat_model_ctx_history=0.7,
707 util_model_provider=ModelProvider.OPENAI.name,
708 util_model_name="gpt-4o-mini",
709 util_model_temperature=0,