neuro-simulator 0.3.3__py3-none-any.whl → 0.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- neuro_simulator/agent/core.py +73 -138
- neuro_simulator/agent/llm.py +1 -1
- neuro_simulator/agent/memory/manager.py +46 -106
- neuro_simulator/agent/tools/add_temp_memory.py +3 -3
- neuro_simulator/agent/tools/add_to_core_memory_block.py +2 -2
- neuro_simulator/agent/tools/create_core_memory_block.py +2 -2
- neuro_simulator/agent/tools/delete_core_memory_block.py +2 -2
- neuro_simulator/agent/tools/get_core_memory_block.py +2 -2
- neuro_simulator/agent/tools/get_core_memory_blocks.py +2 -2
- neuro_simulator/agent/tools/manager.py +86 -63
- neuro_simulator/agent/tools/remove_from_core_memory_block.py +2 -2
- neuro_simulator/agent/tools/speak.py +2 -2
- neuro_simulator/agent/tools/update_core_memory_block.py +2 -2
- neuro_simulator/api/system.py +5 -2
- neuro_simulator/cli.py +83 -53
- neuro_simulator/core/agent_factory.py +0 -1
- neuro_simulator/core/application.py +68 -32
- neuro_simulator/core/config.py +66 -63
- neuro_simulator/core/path_manager.py +69 -0
- neuro_simulator/services/audience.py +0 -2
- neuro_simulator/services/audio.py +0 -1
- neuro_simulator/services/builtin.py +26 -26
- neuro_simulator/services/letta.py +19 -1
- neuro_simulator/services/stream.py +24 -21
- neuro_simulator/utils/logging.py +9 -0
- neuro_simulator/utils/queue.py +27 -4
- neuro_simulator/utils/websocket.py +1 -3
- {neuro_simulator-0.3.3.dist-info → neuro_simulator-0.4.1.dist-info}/METADATA +1 -1
- neuro_simulator-0.4.1.dist-info/RECORD +46 -0
- neuro_simulator/agent/base.py +0 -43
- neuro_simulator/agent/factory.py +0 -30
- neuro_simulator/api/stream.py +0 -1
- neuro_simulator-0.3.3.dist-info/RECORD +0 -48
- {neuro_simulator-0.3.3.dist-info → neuro_simulator-0.4.1.dist-info}/WHEEL +0 -0
- {neuro_simulator-0.3.3.dist-info → neuro_simulator-0.4.1.dist-info}/entry_points.txt +0 -0
- {neuro_simulator-0.3.3.dist-info → neuro_simulator-0.4.1.dist-info}/top_level.txt +0 -0
neuro_simulator/agent/core.py
CHANGED
@@ -9,34 +9,16 @@ import asyncio
|
|
9
9
|
import json
|
10
10
|
import logging
|
11
11
|
import re
|
12
|
+
from datetime import datetime
|
12
13
|
from pathlib import Path
|
13
14
|
from typing import Any, Dict, List
|
14
15
|
|
15
|
-
from ..
|
16
|
-
from ..utils.websocket import connection_manager
|
16
|
+
from ..core.path_manager import path_manager
|
17
17
|
from .llm import LLMClient
|
18
18
|
from .memory.manager import MemoryManager
|
19
19
|
from .tools.manager import ToolManager
|
20
20
|
|
21
|
-
|
22
|
-
# Create a logger for the agent
|
23
|
-
agent_logger = logging.getLogger("neuro_agent")
|
24
|
-
agent_logger.setLevel(logging.DEBUG)
|
25
|
-
|
26
|
-
# Configure agent logging to use the shared queue
|
27
|
-
def configure_agent_logging():
|
28
|
-
"""Configure agent logging to use the shared agent_log_queue."""
|
29
|
-
if agent_logger.hasHandlers():
|
30
|
-
agent_logger.handlers.clear()
|
31
|
-
|
32
|
-
agent_queue_handler = QueueLogHandler(agent_log_queue)
|
33
|
-
formatter = logging.Formatter('%(asctime)s - [%(name)-32s] - %(levelname)-8s - %(message)s', datefmt='%H:%M:%S')
|
34
|
-
agent_queue_handler.setFormatter(formatter)
|
35
|
-
agent_logger.addHandler(agent_queue_handler)
|
36
|
-
agent_logger.propagate = False
|
37
|
-
agent_logger.info("Agent logging configured to use agent_log_queue.")
|
38
|
-
|
39
|
-
configure_agent_logging()
|
21
|
+
logger = logging.getLogger("neuro_agent")
|
40
22
|
|
41
23
|
class Agent:
|
42
24
|
"""
|
@@ -45,40 +27,65 @@ class Agent:
|
|
45
27
|
- The "Memory" part (Thinker) handles background memory consolidation.
|
46
28
|
"""
|
47
29
|
|
48
|
-
def __init__(self
|
49
|
-
|
30
|
+
def __init__(self):
|
31
|
+
if not path_manager:
|
32
|
+
raise RuntimeError("PathManager must be initialized before the Agent.")
|
33
|
+
|
34
|
+
self.memory_manager = MemoryManager()
|
50
35
|
self.tool_manager = ToolManager(self.memory_manager)
|
51
36
|
|
52
|
-
# Dual LLM clients
|
53
37
|
self.neuro_llm = LLMClient()
|
54
38
|
self.memory_llm = LLMClient()
|
55
39
|
|
56
40
|
self._initialized = False
|
57
41
|
self.turn_counter = 0
|
58
|
-
self.reflection_threshold = 3
|
59
|
-
|
60
|
-
agent_logger.info("Agent instance created with dual-LLM architecture.")
|
61
|
-
agent_logger.debug(f"Agent working directory: {working_dir}")
|
42
|
+
self.reflection_threshold = 3
|
62
43
|
|
44
|
+
logger.info("Agent instance created with dual-LLM architecture.")
|
45
|
+
|
63
46
|
async def initialize(self):
|
64
47
|
"""Initialize the agent, loading any persistent memory."""
|
65
48
|
if not self._initialized:
|
66
|
-
|
49
|
+
logger.info("Initializing agent memory manager...")
|
67
50
|
await self.memory_manager.initialize()
|
68
51
|
self._initialized = True
|
69
|
-
|
52
|
+
logger.info("Agent initialized successfully.")
|
70
53
|
|
71
54
|
async def reset_all_memory(self):
|
72
|
-
"""Reset all agent memory types."""
|
55
|
+
"""Reset all agent memory types and clear history logs."""
|
73
56
|
await self.memory_manager.reset_temp_memory()
|
74
|
-
|
75
|
-
|
57
|
+
# Clear history files by overwriting them
|
58
|
+
open(path_manager.neuro_history_path, 'w').close()
|
59
|
+
open(path_manager.memory_agent_history_path, 'w').close()
|
60
|
+
logger.info("All agent memory and history logs have been reset.")
|
61
|
+
|
62
|
+
async def get_neuro_history(self, limit: int = 20) -> List[Dict[str, Any]]:
|
63
|
+
"""Reads the last N lines from the Neuro agent's history log."""
|
64
|
+
return await self._read_history_log(path_manager.neuro_history_path, limit)
|
65
|
+
|
66
|
+
async def _append_to_history_log(self, file_path: Path, data: Dict[str, Any]):
|
67
|
+
"""Appends a new entry to a JSON Lines history file."""
|
68
|
+
data['timestamp'] = datetime.now().isoformat()
|
69
|
+
with open(file_path, 'a', encoding='utf-8') as f:
|
70
|
+
f.write(json.dumps(data, ensure_ascii=False) + '\n')
|
71
|
+
|
72
|
+
async def _read_history_log(self, file_path: Path, limit: int) -> List[Dict[str, Any]]:
|
73
|
+
"""Reads the last N lines from a JSON Lines history file."""
|
74
|
+
if not file_path.exists():
|
75
|
+
return []
|
76
|
+
try:
|
77
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
78
|
+
lines = f.readlines()
|
79
|
+
# Get the last N lines and parse them
|
80
|
+
return [json.loads(line) for line in lines[-limit:]]
|
81
|
+
except (json.JSONDecodeError, IndexError) as e:
|
82
|
+
logger.error(f"Could not read or parse history from {file_path}: {e}")
|
83
|
+
return []
|
76
84
|
|
77
85
|
def _format_tool_schemas_for_prompt(self, schemas: List[Dict[str, Any]]) -> str:
|
78
86
|
"""Formats a list of tool schemas into a string for the LLM prompt."""
|
79
87
|
if not schemas:
|
80
88
|
return "No tools available."
|
81
|
-
|
82
89
|
lines = ["Available tools:"]
|
83
90
|
for i, schema in enumerate(schemas):
|
84
91
|
params_str_parts = []
|
@@ -89,47 +96,31 @@ class Agent:
|
|
89
96
|
params_str_parts.append(f"{p_name}: {p_type} ({p_req})")
|
90
97
|
params_str = ", ".join(params_str_parts)
|
91
98
|
lines.append(f"{i+1}. {schema.get('name')}({params_str}) - {schema.get('description')}")
|
92
|
-
|
93
99
|
return "\n".join(lines)
|
94
100
|
|
95
101
|
async def _build_neuro_prompt(self, messages: List[Dict[str, str]]) -> str:
|
96
102
|
"""Builds the prompt for the Neuro (Actor) LLM."""
|
97
|
-
|
98
|
-
|
99
|
-
|
103
|
+
prompt_template = "" # Define a default empty prompt
|
104
|
+
if path_manager.neuro_prompt_path.exists():
|
105
|
+
with open(path_manager.neuro_prompt_path, 'r', encoding='utf-8') as f:
|
106
|
+
prompt_template = f.read()
|
107
|
+
else:
|
108
|
+
logger.warning(f"Neuro prompt template not found at {path_manager.neuro_prompt_path}")
|
100
109
|
|
101
|
-
# Gather context for Neuro Agent
|
102
110
|
tool_schemas = self.tool_manager.get_tool_schemas_for_agent('neuro_agent')
|
103
111
|
tool_descriptions = self._format_tool_schemas_for_prompt(tool_schemas)
|
104
112
|
|
105
|
-
|
106
|
-
|
107
|
-
init_memory_text = "\n".join(f"{key}: {value}" for key, value in init_memory_items.items())
|
108
|
-
|
109
|
-
# Format Core Memory from blocks
|
113
|
+
init_memory_text = "\n".join(f"{key}: {value}" for key, value in self.memory_manager.init_memory.items())
|
114
|
+
|
110
115
|
core_memory_blocks = await self.memory_manager.get_core_memory_blocks()
|
111
|
-
core_memory_parts = []
|
112
|
-
if core_memory_blocks:
|
113
|
-
for block_id, block in core_memory_blocks.items():
|
114
|
-
core_memory_parts.append(f"\nBlock: {block.get('title', '')} ({block_id})")
|
115
|
-
core_memory_parts.append(f"Description: {block.get('description', '')}")
|
116
|
-
content_items = block.get("content", [])
|
117
|
-
if content_items:
|
118
|
-
core_memory_parts.append("Content:")
|
119
|
-
for item in content_items:
|
120
|
-
core_memory_parts.append(f" - {item}")
|
116
|
+
core_memory_parts = [f"\nBlock: {b.get('title', '')} ({b_id})\nDescription: {b.get('description', '')}\nContent:\n" + "\n".join([f" - {item}" for item in b.get("content", [])]) for b_id, b in core_memory_blocks.items()]
|
121
117
|
core_memory_text = "\n".join(core_memory_parts) if core_memory_parts else "Not set."
|
122
118
|
|
123
|
-
|
124
|
-
temp_memory_items = self.memory_manager.temp_memory
|
125
|
-
temp_memory_text = "\n".join(
|
126
|
-
[f"[{item.get('role', 'system')}] {item.get('content', '')}" for item in temp_memory_items]
|
127
|
-
) if temp_memory_items else "Empty."
|
119
|
+
temp_memory_text = "\n".join([f"[{item.get('role', 'system')}] {item.get('content', '')}" for item in self.memory_manager.temp_memory]) if self.memory_manager.temp_memory else "Empty."
|
128
120
|
|
129
|
-
recent_history = await self.
|
130
|
-
|
121
|
+
recent_history = await self._read_history_log(path_manager.neuro_history_path, limit=10)
|
122
|
+
recent_history_text = "\n".join([f"{msg.get('role', 'unknown')}: {msg.get('content', '')}" for msg in recent_history])
|
131
123
|
user_messages_text = "\n".join([f"{msg['username']}: {msg['text']}" for msg in messages])
|
132
|
-
recent_history_text = "\n".join([f"{msg['role']}: {msg['content']}" for msg in recent_history])
|
133
124
|
|
134
125
|
return prompt_template.format(
|
135
126
|
tool_descriptions=tool_descriptions,
|
@@ -142,15 +133,16 @@ class Agent:
|
|
142
133
|
|
143
134
|
async def _build_memory_prompt(self, conversation_history: List[Dict[str, str]]) -> str:
|
144
135
|
"""Builds the prompt for the Memory (Thinker) LLM."""
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
136
|
+
prompt_template = "" # Define a default empty prompt
|
137
|
+
if path_manager.memory_agent_prompt_path.exists():
|
138
|
+
with open(path_manager.memory_agent_prompt_path, 'r', encoding='utf-8') as f:
|
139
|
+
prompt_template = f.read()
|
140
|
+
else:
|
141
|
+
logger.warning(f"Memory prompt template not found at {path_manager.memory_agent_prompt_path}")
|
142
|
+
|
150
143
|
tool_schemas = self.tool_manager.get_tool_schemas_for_agent('memory_agent')
|
151
144
|
tool_descriptions = self._format_tool_schemas_for_prompt(tool_schemas)
|
152
|
-
|
153
|
-
history_text = "\n".join([f"{msg['role']}: {msg['content']}" for msg in conversation_history])
|
145
|
+
history_text = "\n".join([f"{msg.get('role', 'unknown')}: {msg.get('content', '')}" for msg in conversation_history])
|
154
146
|
|
155
147
|
return prompt_template.format(
|
156
148
|
tool_descriptions=tool_descriptions,
|
@@ -158,30 +150,18 @@ class Agent:
|
|
158
150
|
)
|
159
151
|
|
160
152
|
def _parse_tool_calls(self, response_text: str) -> List[Dict[str, Any]]:
|
161
|
-
"""Parses LLM response for JSON tool calls."""
|
162
153
|
try:
|
163
|
-
|
164
|
-
# Find the JSON block, which might be wrapped in markdown.
|
165
|
-
match = re.search(r'''```json\s*([\s\S]*?)\s*```|([[\][\s\S]*]])''', response_text)
|
154
|
+
match = re.search(r'''```json\s*([\s\S]*?)\s*```|(\[[\s\S]*\])''', response_text)
|
166
155
|
if not match:
|
167
|
-
|
156
|
+
logger.warning(f"No valid JSON tool call block found in response: {response_text}")
|
168
157
|
return []
|
169
|
-
|
170
158
|
json_str = match.group(1) or match.group(2)
|
171
|
-
|
172
|
-
|
173
|
-
if isinstance(tool_calls, list):
|
174
|
-
return tool_calls
|
175
|
-
return []
|
176
|
-
except json.JSONDecodeError as e:
|
177
|
-
agent_logger.error(f"Failed to decode JSON from LLM response: {e}\nResponse text: {response_text}")
|
178
|
-
return []
|
159
|
+
return json.loads(json_str)
|
179
160
|
except Exception as e:
|
180
|
-
|
161
|
+
logger.error(f"Failed to parse tool calls from LLM response: {e}")
|
181
162
|
return []
|
182
163
|
|
183
164
|
async def _execute_tool_calls(self, tool_calls: List[Dict[str, Any]]) -> Dict[str, Any]:
|
184
|
-
"""Executes a list of parsed tool calls."""
|
185
165
|
execution_results = []
|
186
166
|
final_response = ""
|
187
167
|
for tool_call in tool_calls:
|
@@ -189,78 +169,33 @@ class Agent:
|
|
189
169
|
params = tool_call.get("params", {})
|
190
170
|
if not tool_name:
|
191
171
|
continue
|
192
|
-
|
193
|
-
agent_logger.info(f"Executing tool: {tool_name} with params: {params}")
|
172
|
+
logger.info(f"Executing tool: {tool_name} with params: {params}")
|
194
173
|
try:
|
195
174
|
result = await self.tool_manager.execute_tool(tool_name, **params)
|
196
175
|
execution_results.append({"name": tool_name, "params": params, "result": result})
|
197
176
|
if tool_name == "speak" and result.get("status") == "success":
|
198
177
|
final_response = result.get("spoken_text", "")
|
199
178
|
except Exception as e:
|
200
|
-
|
179
|
+
logger.error(f"Error executing tool {tool_name}: {e}")
|
201
180
|
execution_results.append({"name": tool_name, "params": params, "error": str(e)})
|
202
|
-
|
203
181
|
return {"tool_executions": execution_results, "final_response": final_response}
|
204
182
|
|
205
183
|
async def process_and_respond(self, messages: List[Dict[str, str]]) -> Dict[str, Any]:
|
206
|
-
"""
|
207
|
-
The main entry point for the "Neuro" (Actor) flow.
|
208
|
-
Handles real-time interaction and triggers background reflection.
|
209
|
-
"""
|
210
184
|
await self.initialize()
|
211
|
-
|
185
|
+
logger.info(f"Processing {len(messages)} messages in Actor flow.")
|
212
186
|
|
213
|
-
# Add user messages to context
|
214
187
|
for msg in messages:
|
215
|
-
await self.
|
188
|
+
await self._append_to_history_log(path_manager.neuro_history_path, {'role': 'user', 'content': f"{msg['username']}: {msg['text']}"})
|
216
189
|
|
217
|
-
# Build prompt and get response from Neuro LLM
|
218
190
|
prompt = await self._build_neuro_prompt(messages)
|
219
191
|
response_text = await self.neuro_llm.generate(prompt)
|
220
|
-
|
221
|
-
|
222
|
-
# Parse and execute tools
|
192
|
+
|
223
193
|
tool_calls = self._parse_tool_calls(response_text)
|
224
194
|
processing_result = await self._execute_tool_calls(tool_calls)
|
225
195
|
|
226
|
-
|
227
|
-
|
228
|
-
await self.memory_manager.add_chat_entry("assistant", processing_result["final_response"])
|
229
|
-
|
230
|
-
# Update dashboard/UI
|
231
|
-
final_context = await self.memory_manager.get_recent_chat()
|
232
|
-
# Broadcast to stream clients
|
233
|
-
await connection_manager.broadcast({"type": "agent_context", "action": "update", "messages": final_context})
|
234
|
-
# Broadcast to admin clients (Dashboard)
|
235
|
-
await connection_manager.broadcast_to_admins({"type": "agent_context", "action": "update", "messages": final_context})
|
196
|
+
if final_response := processing_result.get("final_response", ""):
|
197
|
+
await self._append_to_history_log(path_manager.neuro_history_path, {'role': 'assistant', 'content': final_response})
|
236
198
|
|
237
|
-
# Handle reflection trigger
|
238
|
-
self.turn_counter += 1
|
239
|
-
if self.turn_counter >= self.reflection_threshold:
|
240
|
-
agent_logger.info(f"Reflection threshold reached ({self.turn_counter}/{self.reflection_threshold}). Scheduling background reflection.")
|
241
|
-
history_for_reflection = await self.memory_manager.get_recent_chat(entries=self.reflection_threshold * 2) # Get a bit more context
|
242
|
-
asyncio.create_task(self.reflect_on_context(history_for_reflection))
|
243
|
-
self.turn_counter = 0
|
244
|
-
|
245
|
-
agent_logger.info("Actor flow completed.")
|
246
199
|
return processing_result
|
247
200
|
|
248
|
-
|
249
|
-
"""
|
250
|
-
The main entry point for the "Memory" (Thinker) flow.
|
251
|
-
Runs in the background to consolidate memories.
|
252
|
-
"""
|
253
|
-
agent_logger.info("Thinker flow started: Reflecting on recent context.")
|
254
|
-
|
255
|
-
prompt = await self._build_memory_prompt(conversation_history)
|
256
|
-
response_text = await self.memory_llm.generate(prompt)
|
257
|
-
agent_logger.debug(f"Memory LLM raw response: {response_text[:150] if response_text else 'None'}...")
|
258
|
-
|
259
|
-
tool_calls = self._parse_tool_calls(response_text)
|
260
|
-
if not tool_calls:
|
261
|
-
agent_logger.info("Thinker flow: No memory operations were suggested by the LLM.")
|
262
|
-
return
|
263
|
-
|
264
|
-
agent_logger.info(f"Thinker flow: Executing {len(tool_calls)} memory operations.")
|
265
|
-
await self._execute_tool_calls(tool_calls)
|
266
|
-
agent_logger.info("Thinker flow completed, memory has been updated.")
|
201
|
+
|
neuro_simulator/agent/llm.py
CHANGED
@@ -1,48 +1,43 @@
|
|
1
1
|
# neuro_simulator/agent/memory/manager.py
|
2
2
|
"""
|
3
|
-
|
3
|
+
Manages the agent's shared memory state (init, core, temp).
|
4
4
|
"""
|
5
5
|
|
6
|
-
import asyncio
|
7
6
|
import json
|
8
7
|
import logging
|
9
|
-
import os
|
10
8
|
import random
|
11
9
|
import string
|
12
|
-
from datetime import datetime
|
13
10
|
from typing import Any, Dict, List, Optional
|
11
|
+
from datetime import datetime
|
14
12
|
|
15
|
-
|
16
|
-
|
13
|
+
from ...core.path_manager import path_manager
|
14
|
+
|
15
|
+
logger = logging.getLogger(__name__.replace("neuro_simulator", "agent", 1))
|
17
16
|
|
18
17
|
def generate_id(length=6) -> str:
|
19
18
|
"""Generate a random ID string."""
|
20
19
|
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
|
21
20
|
|
22
21
|
class MemoryManager:
|
23
|
-
"""Manages
|
22
|
+
"""Manages the three types of shared memory for the agent."""
|
24
23
|
|
25
|
-
def __init__(self
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
self.
|
33
|
-
self.core_memory_file = os.path.join(self.memory_dir, "core_memory.json")
|
34
|
-
self.chat_history_file = os.path.join(self.memory_dir, "chat_history.json")
|
35
|
-
self.temp_memory_file = os.path.join(self.memory_dir, "temp_memory.json")
|
24
|
+
def __init__(self):
|
25
|
+
"""Initializes the MemoryManager using paths from the global path_manager."""
|
26
|
+
if not path_manager:
|
27
|
+
raise RuntimeError("PathManager not initialized before MemoryManager.")
|
28
|
+
|
29
|
+
self.init_memory_file = path_manager.init_memory_path
|
30
|
+
self.core_memory_file = path_manager.core_memory_path
|
31
|
+
self.temp_memory_file = path_manager.temp_memory_path
|
36
32
|
|
37
33
|
self.init_memory: Dict[str, Any] = {}
|
38
34
|
self.core_memory: Dict[str, Any] = {}
|
39
|
-
self.chat_history: List[Dict[str, Any]] = []
|
40
35
|
self.temp_memory: List[Dict[str, Any]] = []
|
41
36
|
|
42
37
|
async def initialize(self):
|
43
|
-
"""Load all memory types from files."""
|
44
|
-
# Load init memory
|
45
|
-
if
|
38
|
+
"""Load all memory types from files, creating defaults if they don't exist."""
|
39
|
+
# Load or create init memory
|
40
|
+
if self.init_memory_file.exists():
|
46
41
|
with open(self.init_memory_file, 'r', encoding='utf-8') as f:
|
47
42
|
self.init_memory = json.load(f)
|
48
43
|
else:
|
@@ -53,125 +48,70 @@ class MemoryManager:
|
|
53
48
|
}
|
54
49
|
await self._save_init_memory()
|
55
50
|
|
56
|
-
# Load core memory
|
57
|
-
if
|
51
|
+
# Load or create core memory
|
52
|
+
if self.core_memory_file.exists():
|
58
53
|
with open(self.core_memory_file, 'r', encoding='utf-8') as f:
|
59
54
|
self.core_memory = json.load(f)
|
60
55
|
else:
|
61
56
|
self.core_memory = {"blocks": {}}
|
62
57
|
await self._save_core_memory()
|
63
58
|
|
64
|
-
# Load
|
65
|
-
if
|
66
|
-
with open(self.chat_history_file, 'r', encoding='utf-8') as f:
|
67
|
-
self.chat_history = json.load(f)
|
68
|
-
else:
|
69
|
-
self.chat_history = []
|
70
|
-
|
71
|
-
# Load temp memory
|
72
|
-
if os.path.exists(self.temp_memory_file):
|
59
|
+
# Load or create temp memory
|
60
|
+
if self.temp_memory_file.exists():
|
73
61
|
with open(self.temp_memory_file, 'r', encoding='utf-8') as f:
|
74
62
|
self.temp_memory = json.load(f)
|
75
63
|
else:
|
76
64
|
self.temp_memory = []
|
65
|
+
await self._save_temp_memory()
|
77
66
|
|
78
|
-
logger.info("
|
67
|
+
logger.info("MemoryManager initialized and memory files loaded/created.")
|
79
68
|
|
80
69
|
async def _save_init_memory(self):
|
81
70
|
with open(self.init_memory_file, 'w', encoding='utf-8') as f:
|
82
71
|
json.dump(self.init_memory, f, ensure_ascii=False, indent=2)
|
83
|
-
|
84
|
-
async def
|
85
|
-
|
72
|
+
|
73
|
+
async def replace_init_memory(self, new_memory: Dict[str, Any]):
|
74
|
+
"""Replaces the entire init memory with a new object."""
|
75
|
+
self.init_memory = new_memory
|
86
76
|
await self._save_init_memory()
|
77
|
+
|
78
|
+
async def update_init_memory_item(self, key: str, value: Any):
|
79
|
+
"""Updates or adds a single key-value pair in init memory."""
|
80
|
+
self.init_memory[key] = value
|
81
|
+
await self._save_init_memory()
|
82
|
+
|
83
|
+
async def delete_init_memory_key(self, key: str):
|
84
|
+
"""Deletes a key from init memory."""
|
85
|
+
if key in self.init_memory:
|
86
|
+
del self.init_memory[key]
|
87
|
+
await self._save_init_memory()
|
87
88
|
|
88
89
|
async def _save_core_memory(self):
|
89
90
|
with open(self.core_memory_file, 'w', encoding='utf-8') as f:
|
90
91
|
json.dump(self.core_memory, f, ensure_ascii=False, indent=2)
|
91
92
|
|
92
|
-
async def _save_chat_history(self):
|
93
|
-
with open(self.chat_history_file, 'w', encoding='utf-8') as f:
|
94
|
-
json.dump(self.chat_history, f, ensure_ascii=False, indent=2)
|
95
|
-
|
96
93
|
async def _save_temp_memory(self):
|
97
94
|
with open(self.temp_memory_file, 'w', encoding='utf-8') as f:
|
98
95
|
json.dump(self.temp_memory, f, ensure_ascii=False, indent=2)
|
99
96
|
|
100
|
-
async def add_chat_entry(self, role: str, content: str):
|
101
|
-
entry = {"id": generate_id(), "role": role, "content": content, "timestamp": datetime.now().isoformat()}
|
102
|
-
self.chat_history.append(entry)
|
103
|
-
await self._save_chat_history()
|
104
|
-
|
105
|
-
async def add_detailed_chat_entry(self, input_messages: List[Dict[str, str]],
|
106
|
-
prompt: str, llm_response: str,
|
107
|
-
tool_executions: List[Dict[str, Any]],
|
108
|
-
final_response: str, entry_id: str = None):
|
109
|
-
update_data = {
|
110
|
-
"input_messages": input_messages, "prompt": prompt, "llm_response": llm_response,
|
111
|
-
"tool_executions": tool_executions, "final_response": final_response,
|
112
|
-
"timestamp": datetime.now().isoformat()
|
113
|
-
}
|
114
|
-
if entry_id:
|
115
|
-
for entry in self.chat_history:
|
116
|
-
if entry.get("id") == entry_id:
|
117
|
-
entry.update(update_data)
|
118
|
-
await self._save_chat_history()
|
119
|
-
return entry_id
|
120
|
-
|
121
|
-
new_entry = {"id": entry_id or generate_id(), "type": "llm_interaction", "role": "assistant", **update_data}
|
122
|
-
self.chat_history.append(new_entry)
|
123
|
-
await self._save_chat_history()
|
124
|
-
return new_entry["id"]
|
125
|
-
|
126
|
-
async def get_recent_chat(self, entries: int = 10) -> List[Dict[str, Any]]:
|
127
|
-
return self.chat_history[-entries:]
|
128
|
-
|
129
|
-
async def get_detailed_chat_history(self) -> List[Dict[str, Any]]:
|
130
|
-
return self.chat_history
|
131
|
-
|
132
|
-
async def get_last_agent_response(self) -> Optional[str]:
|
133
|
-
for entry in reversed(self.chat_history):
|
134
|
-
if entry.get("type") == "llm_interaction":
|
135
|
-
final_response = entry.get("final_response", "")
|
136
|
-
if final_response and final_response not in ["Processing started", "Prompt sent to LLM", "LLM response received"]:
|
137
|
-
return final_response
|
138
|
-
elif entry.get("role") == "assistant":
|
139
|
-
content = entry.get("content", "")
|
140
|
-
if content and content != "Processing started":
|
141
|
-
return content
|
142
|
-
return None
|
143
|
-
|
144
|
-
async def reset_chat_history(self):
|
145
|
-
self.chat_history = []
|
146
|
-
await self._save_chat_history()
|
147
|
-
|
148
97
|
async def reset_temp_memory(self):
|
149
98
|
"""Reset temp memory to a default empty state."""
|
150
99
|
self.temp_memory = []
|
151
100
|
await self._save_temp_memory()
|
152
101
|
logger.info("Agent temp memory has been reset.")
|
153
102
|
|
154
|
-
async def get_full_context(self) -> str:
|
155
|
-
context_parts = ["=== INIT MEMORY (Immutable) ===", json.dumps(self.init_memory, indent=2)]
|
156
|
-
context_parts.append("\n=== CORE MEMORY (Long-term, Mutable) ===")
|
157
|
-
if "blocks" in self.core_memory:
|
158
|
-
for block_id, block in self.core_memory["blocks"].items():
|
159
|
-
context_parts.append(f"\nBlock: {block.get('title', '')} ({block_id})")
|
160
|
-
context_parts.append(f"Description: {block.get('description', '')}")
|
161
|
-
context_parts.append("Content:")
|
162
|
-
for item in block.get("content", []):
|
163
|
-
context_parts.append(f" - {item}")
|
164
|
-
if self.temp_memory:
|
165
|
-
context_parts.append("\n=== TEMP MEMORY (Processing State) ===")
|
166
|
-
for item in self.temp_memory:
|
167
|
-
context_parts.append(f"[{item.get('role', 'system')}] {item.get('content', '')}")
|
168
|
-
return "\n".join(context_parts)
|
169
|
-
|
170
103
|
async def add_temp_memory(self, content: str, role: str = "system"):
|
171
104
|
self.temp_memory.append({"id": generate_id(), "content": content, "role": role, "timestamp": datetime.now().isoformat()})
|
172
105
|
if len(self.temp_memory) > 20:
|
173
106
|
self.temp_memory = self.temp_memory[-20:]
|
174
107
|
await self._save_temp_memory()
|
108
|
+
|
109
|
+
async def delete_temp_memory_item(self, item_id: str):
|
110
|
+
"""Deletes an item from temp memory by its ID."""
|
111
|
+
initial_len = len(self.temp_memory)
|
112
|
+
self.temp_memory = [item for item in self.temp_memory if item.get("id") != item_id]
|
113
|
+
if len(self.temp_memory) < initial_len:
|
114
|
+
await self._save_temp_memory()
|
175
115
|
|
176
116
|
async def get_core_memory_blocks(self) -> Dict[str, Any]:
|
177
117
|
return self.core_memory.get("blocks", {})
|
@@ -201,4 +141,4 @@ class MemoryManager:
|
|
201
141
|
async def delete_core_memory_block(self, block_id: str):
|
202
142
|
if "blocks" in self.core_memory and block_id in self.core_memory["blocks"]:
|
203
143
|
del self.core_memory["blocks"][block_id]
|
204
|
-
await self._save_core_memory()
|
144
|
+
await self._save_core_memory()
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# neuro_simulator/agent/tools/add_temp_memory.py
|
2
2
|
"""The Add Temp Memory tool for the agent."""
|
3
3
|
|
4
|
-
from typing import
|
4
|
+
from typing import Any, Dict, List
|
5
5
|
|
6
|
-
from .base import BaseTool
|
7
|
-
from
|
6
|
+
from neuro_simulator.agent.tools.base import BaseTool
|
7
|
+
from neuro_simulator.agent.memory.manager import MemoryManager
|
8
8
|
|
9
9
|
class AddTempMemoryTool(BaseTool):
|
10
10
|
"""Tool to add an entry to the agent's temporary memory."""
|
@@ -3,8 +3,8 @@
|
|
3
3
|
|
4
4
|
from typing import Dict, Any, List
|
5
5
|
|
6
|
-
from .base import BaseTool
|
7
|
-
from
|
6
|
+
from neuro_simulator.agent.tools.base import BaseTool
|
7
|
+
from neuro_simulator.agent.memory.manager import MemoryManager
|
8
8
|
|
9
9
|
class AddToCoreMemoryBlockTool(BaseTool):
|
10
10
|
"""Tool to add an item to an existing core memory block's content list."""
|
@@ -3,8 +3,8 @@
|
|
3
3
|
|
4
4
|
from typing import Dict, Any, List
|
5
5
|
|
6
|
-
from .base import BaseTool
|
7
|
-
from
|
6
|
+
from neuro_simulator.agent.tools.base import BaseTool
|
7
|
+
from neuro_simulator.agent.memory.manager import MemoryManager
|
8
8
|
|
9
9
|
class CreateCoreMemoryBlockTool(BaseTool):
|
10
10
|
"""Tool to create a new core memory block."""
|
@@ -3,8 +3,8 @@
|
|
3
3
|
|
4
4
|
from typing import Dict, Any, List
|
5
5
|
|
6
|
-
from .base import BaseTool
|
7
|
-
from
|
6
|
+
from neuro_simulator.agent.tools.base import BaseTool
|
7
|
+
from neuro_simulator.agent.memory.manager import MemoryManager
|
8
8
|
|
9
9
|
class DeleteCoreMemoryBlockTool(BaseTool):
|
10
10
|
"""Tool to delete an existing core memory block."""
|
@@ -3,8 +3,8 @@
|
|
3
3
|
|
4
4
|
from typing import Dict, Any, List
|
5
5
|
|
6
|
-
from .base import BaseTool
|
7
|
-
from
|
6
|
+
from neuro_simulator.agent.tools.base import BaseTool
|
7
|
+
from neuro_simulator.agent.memory.manager import MemoryManager
|
8
8
|
|
9
9
|
class GetCoreMemoryBlockTool(BaseTool):
|
10
10
|
"""Tool to retrieve a single core memory block by its ID."""
|
@@ -3,8 +3,8 @@
|
|
3
3
|
|
4
4
|
from typing import Dict, Any, List
|
5
5
|
|
6
|
-
from .base import BaseTool
|
7
|
-
from
|
6
|
+
from neuro_simulator.agent.tools.base import BaseTool
|
7
|
+
from neuro_simulator.agent.memory.manager import MemoryManager
|
8
8
|
|
9
9
|
class GetCoreMemoryBlocksTool(BaseTool):
|
10
10
|
"""Tool to retrieve all core memory blocks."""
|