neuro-simulator 0.3.3__tar.gz → 0.4.1__tar.gz
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-0.3.3 → neuro_simulator-0.4.1}/PKG-INFO +1 -1
- neuro_simulator-0.4.1/neuro_simulator/agent/core.py +201 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/agent/llm.py +1 -1
- neuro_simulator-0.4.1/neuro_simulator/agent/memory/manager.py +144 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/agent/tools/add_temp_memory.py +3 -3
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/agent/tools/add_to_core_memory_block.py +2 -2
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/agent/tools/create_core_memory_block.py +2 -2
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/agent/tools/delete_core_memory_block.py +2 -2
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/agent/tools/get_core_memory_block.py +2 -2
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/agent/tools/get_core_memory_blocks.py +2 -2
- neuro_simulator-0.4.1/neuro_simulator/agent/tools/manager.py +143 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/agent/tools/remove_from_core_memory_block.py +2 -2
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/agent/tools/speak.py +2 -2
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/agent/tools/update_core_memory_block.py +2 -2
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/api/system.py +5 -2
- neuro_simulator-0.4.1/neuro_simulator/cli.py +125 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/core/agent_factory.py +0 -1
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/core/application.py +68 -32
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/core/config.py +66 -63
- neuro_simulator-0.4.1/neuro_simulator/core/path_manager.py +69 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/services/audience.py +0 -2
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/services/audio.py +0 -1
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/services/builtin.py +26 -26
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/services/letta.py +19 -1
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/services/stream.py +24 -21
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/utils/logging.py +9 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/utils/queue.py +27 -4
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/utils/websocket.py +1 -3
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator.egg-info/PKG-INFO +1 -1
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator.egg-info/SOURCES.txt +1 -3
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/pyproject.toml +1 -1
- neuro_simulator-0.3.3/neuro_simulator/agent/base.py +0 -43
- neuro_simulator-0.3.3/neuro_simulator/agent/core.py +0 -266
- neuro_simulator-0.3.3/neuro_simulator/agent/factory.py +0 -30
- neuro_simulator-0.3.3/neuro_simulator/agent/memory/manager.py +0 -204
- neuro_simulator-0.3.3/neuro_simulator/agent/tools/manager.py +0 -120
- neuro_simulator-0.3.3/neuro_simulator/api/stream.py +0 -1
- neuro_simulator-0.3.3/neuro_simulator/cli.py +0 -95
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/README.md +0 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/__init__.py +0 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/agent/__init__.py +0 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/agent/memory/__init__.py +0 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/agent/memory_prompt.txt +0 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/agent/neuro_prompt.txt +0 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/agent/tools/__init__.py +0 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/agent/tools/base.py +0 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/api/__init__.py +0 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/core/__init__.py +0 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/core/agent_interface.py +0 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/services/__init__.py +0 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/utils/__init__.py +0 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/utils/process.py +0 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/utils/state.py +0 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator.egg-info/dependency_links.txt +0 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator.egg-info/entry_points.txt +0 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator.egg-info/requires.txt +0 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator.egg-info/top_level.txt +0 -0
- {neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/setup.cfg +0 -0
@@ -0,0 +1,201 @@
|
|
1
|
+
# neuro_simulator/agent/core.py
|
2
|
+
"""
|
3
|
+
Core module for the Neuro Simulator's built-in agent.
|
4
|
+
Implements a dual-LLM "Actor/Thinker" architecture for responsive interaction
|
5
|
+
and asynchronous memory consolidation.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import asyncio
|
9
|
+
import json
|
10
|
+
import logging
|
11
|
+
import re
|
12
|
+
from datetime import datetime
|
13
|
+
from pathlib import Path
|
14
|
+
from typing import Any, Dict, List
|
15
|
+
|
16
|
+
from ..core.path_manager import path_manager
|
17
|
+
from .llm import LLMClient
|
18
|
+
from .memory.manager import MemoryManager
|
19
|
+
from .tools.manager import ToolManager
|
20
|
+
|
21
|
+
logger = logging.getLogger("neuro_agent")
|
22
|
+
|
23
|
+
class Agent:
|
24
|
+
"""
|
25
|
+
Main Agent class implementing the Actor/Thinker model.
|
26
|
+
- The "Neuro" part (Actor) handles real-time interaction.
|
27
|
+
- The "Memory" part (Thinker) handles background memory consolidation.
|
28
|
+
"""
|
29
|
+
|
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()
|
35
|
+
self.tool_manager = ToolManager(self.memory_manager)
|
36
|
+
|
37
|
+
self.neuro_llm = LLMClient()
|
38
|
+
self.memory_llm = LLMClient()
|
39
|
+
|
40
|
+
self._initialized = False
|
41
|
+
self.turn_counter = 0
|
42
|
+
self.reflection_threshold = 3
|
43
|
+
|
44
|
+
logger.info("Agent instance created with dual-LLM architecture.")
|
45
|
+
|
46
|
+
async def initialize(self):
|
47
|
+
"""Initialize the agent, loading any persistent memory."""
|
48
|
+
if not self._initialized:
|
49
|
+
logger.info("Initializing agent memory manager...")
|
50
|
+
await self.memory_manager.initialize()
|
51
|
+
self._initialized = True
|
52
|
+
logger.info("Agent initialized successfully.")
|
53
|
+
|
54
|
+
async def reset_all_memory(self):
|
55
|
+
"""Reset all agent memory types and clear history logs."""
|
56
|
+
await self.memory_manager.reset_temp_memory()
|
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 []
|
84
|
+
|
85
|
+
def _format_tool_schemas_for_prompt(self, schemas: List[Dict[str, Any]]) -> str:
|
86
|
+
"""Formats a list of tool schemas into a string for the LLM prompt."""
|
87
|
+
if not schemas:
|
88
|
+
return "No tools available."
|
89
|
+
lines = ["Available tools:"]
|
90
|
+
for i, schema in enumerate(schemas):
|
91
|
+
params_str_parts = []
|
92
|
+
for param in schema.get("parameters", []):
|
93
|
+
p_name = param.get('name')
|
94
|
+
p_type = param.get('type')
|
95
|
+
p_req = 'required' if param.get('required') else 'optional'
|
96
|
+
params_str_parts.append(f"{p_name}: {p_type} ({p_req})")
|
97
|
+
params_str = ", ".join(params_str_parts)
|
98
|
+
lines.append(f"{i+1}. {schema.get('name')}({params_str}) - {schema.get('description')}")
|
99
|
+
return "\n".join(lines)
|
100
|
+
|
101
|
+
async def _build_neuro_prompt(self, messages: List[Dict[str, str]]) -> str:
|
102
|
+
"""Builds the prompt for the Neuro (Actor) LLM."""
|
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}")
|
109
|
+
|
110
|
+
tool_schemas = self.tool_manager.get_tool_schemas_for_agent('neuro_agent')
|
111
|
+
tool_descriptions = self._format_tool_schemas_for_prompt(tool_schemas)
|
112
|
+
|
113
|
+
init_memory_text = "\n".join(f"{key}: {value}" for key, value in self.memory_manager.init_memory.items())
|
114
|
+
|
115
|
+
core_memory_blocks = await self.memory_manager.get_core_memory_blocks()
|
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()]
|
117
|
+
core_memory_text = "\n".join(core_memory_parts) if core_memory_parts else "Not set."
|
118
|
+
|
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."
|
120
|
+
|
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])
|
123
|
+
user_messages_text = "\n".join([f"{msg['username']}: {msg['text']}" for msg in messages])
|
124
|
+
|
125
|
+
return prompt_template.format(
|
126
|
+
tool_descriptions=tool_descriptions,
|
127
|
+
init_memory=init_memory_text,
|
128
|
+
core_memory=core_memory_text,
|
129
|
+
temp_memory=temp_memory_text,
|
130
|
+
recent_history=recent_history_text,
|
131
|
+
user_messages=user_messages_text
|
132
|
+
)
|
133
|
+
|
134
|
+
async def _build_memory_prompt(self, conversation_history: List[Dict[str, str]]) -> str:
|
135
|
+
"""Builds the prompt for the Memory (Thinker) LLM."""
|
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
|
+
|
143
|
+
tool_schemas = self.tool_manager.get_tool_schemas_for_agent('memory_agent')
|
144
|
+
tool_descriptions = self._format_tool_schemas_for_prompt(tool_schemas)
|
145
|
+
history_text = "\n".join([f"{msg.get('role', 'unknown')}: {msg.get('content', '')}" for msg in conversation_history])
|
146
|
+
|
147
|
+
return prompt_template.format(
|
148
|
+
tool_descriptions=tool_descriptions,
|
149
|
+
conversation_history=history_text
|
150
|
+
)
|
151
|
+
|
152
|
+
def _parse_tool_calls(self, response_text: str) -> List[Dict[str, Any]]:
|
153
|
+
try:
|
154
|
+
match = re.search(r'''```json\s*([\s\S]*?)\s*```|(\[[\s\S]*\])''', response_text)
|
155
|
+
if not match:
|
156
|
+
logger.warning(f"No valid JSON tool call block found in response: {response_text}")
|
157
|
+
return []
|
158
|
+
json_str = match.group(1) or match.group(2)
|
159
|
+
return json.loads(json_str)
|
160
|
+
except Exception as e:
|
161
|
+
logger.error(f"Failed to parse tool calls from LLM response: {e}")
|
162
|
+
return []
|
163
|
+
|
164
|
+
async def _execute_tool_calls(self, tool_calls: List[Dict[str, Any]]) -> Dict[str, Any]:
|
165
|
+
execution_results = []
|
166
|
+
final_response = ""
|
167
|
+
for tool_call in tool_calls:
|
168
|
+
tool_name = tool_call.get("name")
|
169
|
+
params = tool_call.get("params", {})
|
170
|
+
if not tool_name:
|
171
|
+
continue
|
172
|
+
logger.info(f"Executing tool: {tool_name} with params: {params}")
|
173
|
+
try:
|
174
|
+
result = await self.tool_manager.execute_tool(tool_name, **params)
|
175
|
+
execution_results.append({"name": tool_name, "params": params, "result": result})
|
176
|
+
if tool_name == "speak" and result.get("status") == "success":
|
177
|
+
final_response = result.get("spoken_text", "")
|
178
|
+
except Exception as e:
|
179
|
+
logger.error(f"Error executing tool {tool_name}: {e}")
|
180
|
+
execution_results.append({"name": tool_name, "params": params, "error": str(e)})
|
181
|
+
return {"tool_executions": execution_results, "final_response": final_response}
|
182
|
+
|
183
|
+
async def process_and_respond(self, messages: List[Dict[str, str]]) -> Dict[str, Any]:
|
184
|
+
await self.initialize()
|
185
|
+
logger.info(f"Processing {len(messages)} messages in Actor flow.")
|
186
|
+
|
187
|
+
for msg in messages:
|
188
|
+
await self._append_to_history_log(path_manager.neuro_history_path, {'role': 'user', 'content': f"{msg['username']}: {msg['text']}"})
|
189
|
+
|
190
|
+
prompt = await self._build_neuro_prompt(messages)
|
191
|
+
response_text = await self.neuro_llm.generate(prompt)
|
192
|
+
|
193
|
+
tool_calls = self._parse_tool_calls(response_text)
|
194
|
+
processing_result = await self._execute_tool_calls(tool_calls)
|
195
|
+
|
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})
|
198
|
+
|
199
|
+
return processing_result
|
200
|
+
|
201
|
+
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# neuro_simulator/agent/memory/manager.py
|
2
|
+
"""
|
3
|
+
Manages the agent's shared memory state (init, core, temp).
|
4
|
+
"""
|
5
|
+
|
6
|
+
import json
|
7
|
+
import logging
|
8
|
+
import random
|
9
|
+
import string
|
10
|
+
from typing import Any, Dict, List, Optional
|
11
|
+
from datetime import datetime
|
12
|
+
|
13
|
+
from ...core.path_manager import path_manager
|
14
|
+
|
15
|
+
logger = logging.getLogger(__name__.replace("neuro_simulator", "agent", 1))
|
16
|
+
|
17
|
+
def generate_id(length=6) -> str:
|
18
|
+
"""Generate a random ID string."""
|
19
|
+
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
|
20
|
+
|
21
|
+
class MemoryManager:
|
22
|
+
"""Manages the three types of shared memory for the agent."""
|
23
|
+
|
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
|
32
|
+
|
33
|
+
self.init_memory: Dict[str, Any] = {}
|
34
|
+
self.core_memory: Dict[str, Any] = {}
|
35
|
+
self.temp_memory: List[Dict[str, Any]] = []
|
36
|
+
|
37
|
+
async def initialize(self):
|
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():
|
41
|
+
with open(self.init_memory_file, 'r', encoding='utf-8') as f:
|
42
|
+
self.init_memory = json.load(f)
|
43
|
+
else:
|
44
|
+
self.init_memory = {
|
45
|
+
"name": "Neuro-Sama", "role": "AI VTuber",
|
46
|
+
"personality": "Friendly, curious, and entertaining",
|
47
|
+
"capabilities": ["Chat with viewers", "Answer questions"]
|
48
|
+
}
|
49
|
+
await self._save_init_memory()
|
50
|
+
|
51
|
+
# Load or create core memory
|
52
|
+
if self.core_memory_file.exists():
|
53
|
+
with open(self.core_memory_file, 'r', encoding='utf-8') as f:
|
54
|
+
self.core_memory = json.load(f)
|
55
|
+
else:
|
56
|
+
self.core_memory = {"blocks": {}}
|
57
|
+
await self._save_core_memory()
|
58
|
+
|
59
|
+
# Load or create temp memory
|
60
|
+
if self.temp_memory_file.exists():
|
61
|
+
with open(self.temp_memory_file, 'r', encoding='utf-8') as f:
|
62
|
+
self.temp_memory = json.load(f)
|
63
|
+
else:
|
64
|
+
self.temp_memory = []
|
65
|
+
await self._save_temp_memory()
|
66
|
+
|
67
|
+
logger.info("MemoryManager initialized and memory files loaded/created.")
|
68
|
+
|
69
|
+
async def _save_init_memory(self):
|
70
|
+
with open(self.init_memory_file, 'w', encoding='utf-8') as f:
|
71
|
+
json.dump(self.init_memory, f, ensure_ascii=False, indent=2)
|
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
|
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()
|
88
|
+
|
89
|
+
async def _save_core_memory(self):
|
90
|
+
with open(self.core_memory_file, 'w', encoding='utf-8') as f:
|
91
|
+
json.dump(self.core_memory, f, ensure_ascii=False, indent=2)
|
92
|
+
|
93
|
+
async def _save_temp_memory(self):
|
94
|
+
with open(self.temp_memory_file, 'w', encoding='utf-8') as f:
|
95
|
+
json.dump(self.temp_memory, f, ensure_ascii=False, indent=2)
|
96
|
+
|
97
|
+
async def reset_temp_memory(self):
|
98
|
+
"""Reset temp memory to a default empty state."""
|
99
|
+
self.temp_memory = []
|
100
|
+
await self._save_temp_memory()
|
101
|
+
logger.info("Agent temp memory has been reset.")
|
102
|
+
|
103
|
+
async def add_temp_memory(self, content: str, role: str = "system"):
|
104
|
+
self.temp_memory.append({"id": generate_id(), "content": content, "role": role, "timestamp": datetime.now().isoformat()})
|
105
|
+
if len(self.temp_memory) > 20:
|
106
|
+
self.temp_memory = self.temp_memory[-20:]
|
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()
|
115
|
+
|
116
|
+
async def get_core_memory_blocks(self) -> Dict[str, Any]:
|
117
|
+
return self.core_memory.get("blocks", {})
|
118
|
+
|
119
|
+
async def get_core_memory_block(self, block_id: str) -> Optional[Dict[str, Any]]:
|
120
|
+
return self.core_memory.get("blocks", {}).get(block_id)
|
121
|
+
|
122
|
+
async def create_core_memory_block(self, title: str, description: str, content: List[str]) -> str:
|
123
|
+
block_id = generate_id()
|
124
|
+
if "blocks" not in self.core_memory:
|
125
|
+
self.core_memory["blocks"] = {}
|
126
|
+
self.core_memory["blocks"][block_id] = {
|
127
|
+
"id": block_id, "title": title, "description": description, "content": content or []
|
128
|
+
}
|
129
|
+
await self._save_core_memory()
|
130
|
+
return block_id
|
131
|
+
|
132
|
+
async def update_core_memory_block(self, block_id: str, title: Optional[str] = None, description: Optional[str] = None, content: Optional[List[str]] = None):
|
133
|
+
block = self.core_memory.get("blocks", {}).get(block_id)
|
134
|
+
if not block:
|
135
|
+
raise ValueError(f"Block '{block_id}' not found")
|
136
|
+
if title is not None: block["title"] = title
|
137
|
+
if description is not None: block["description"] = description
|
138
|
+
if content is not None: block["content"] = content
|
139
|
+
await self._save_core_memory()
|
140
|
+
|
141
|
+
async def delete_core_memory_block(self, block_id: str):
|
142
|
+
if "blocks" in self.core_memory and block_id in self.core_memory["blocks"]:
|
143
|
+
del self.core_memory["blocks"][block_id]
|
144
|
+
await self._save_core_memory()
|
{neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/agent/tools/add_temp_memory.py
RENAMED
@@ -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."""
|
{neuro_simulator-0.3.3 → neuro_simulator-0.4.1}/neuro_simulator/agent/tools/get_core_memory_block.py
RENAMED
@@ -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."""
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# neuro_simulator/agent/tools/manager.py
|
2
|
+
"""The central tool manager for the agent, responsible for loading, managing, and executing tools."""
|
3
|
+
|
4
|
+
import os
|
5
|
+
import json
|
6
|
+
import importlib
|
7
|
+
import inspect
|
8
|
+
import logging
|
9
|
+
import shutil
|
10
|
+
from pathlib import Path
|
11
|
+
from typing import Any, Dict, List
|
12
|
+
|
13
|
+
from .base import BaseTool
|
14
|
+
from ..memory.manager import MemoryManager
|
15
|
+
from ...core.path_manager import path_manager
|
16
|
+
|
17
|
+
logger = logging.getLogger(__name__.replace("neuro_simulator", "agent", 1))
|
18
|
+
|
19
|
+
class ToolManager:
|
20
|
+
"""
|
21
|
+
Acts as a central registry and executor for all available tools.
|
22
|
+
This manager dynamically loads tools from the user's working directory.
|
23
|
+
"""
|
24
|
+
|
25
|
+
def __init__(self, memory_manager: MemoryManager):
|
26
|
+
if not path_manager:
|
27
|
+
raise RuntimeError("PathManager not initialized before ToolManager.")
|
28
|
+
self.memory_manager = memory_manager
|
29
|
+
self.tools: Dict[str, BaseTool] = {}
|
30
|
+
self.agent_tool_allocations: Dict[str, List[str]] = {}
|
31
|
+
|
32
|
+
self._copy_builtin_tools()
|
33
|
+
self.reload_tools() # Initial load
|
34
|
+
self._load_allocations()
|
35
|
+
|
36
|
+
def _copy_builtin_tools(self):
|
37
|
+
"""Copies the packaged built-in tools to the working directory, overwriting existing ones."""
|
38
|
+
try:
|
39
|
+
import pkg_resources
|
40
|
+
source_dir_str = pkg_resources.resource_filename('neuro_simulator', 'agent/tools')
|
41
|
+
source_dir = Path(source_dir_str)
|
42
|
+
except (ModuleNotFoundError, KeyError):
|
43
|
+
source_dir = Path(__file__).parent
|
44
|
+
|
45
|
+
dest_dir = path_manager.builtin_tools_dir
|
46
|
+
if not dest_dir.exists():
|
47
|
+
os.makedirs(dest_dir)
|
48
|
+
|
49
|
+
for item in os.listdir(source_dir):
|
50
|
+
source_item = source_dir / item
|
51
|
+
if source_item.is_file() and source_item.name.endswith('.py') and not source_item.name.startswith(('__', 'manager')):
|
52
|
+
shutil.copy(source_item, dest_dir / item)
|
53
|
+
logger.info(f"Built-in tools copied to {dest_dir}")
|
54
|
+
|
55
|
+
def _load_and_register_tools(self):
|
56
|
+
"""Dynamically scans the user tools directory, imports modules, and registers tool instances."""
|
57
|
+
self.tools = {}
|
58
|
+
tools_dir = path_manager.user_tools_dir
|
59
|
+
|
60
|
+
for root, _, files in os.walk(tools_dir):
|
61
|
+
for filename in files:
|
62
|
+
if filename.endswith('.py') and not filename.startswith('__'):
|
63
|
+
module_path = Path(root) / filename
|
64
|
+
# Create a module spec from the file path
|
65
|
+
spec = importlib.util.spec_from_file_location(module_path.stem, module_path)
|
66
|
+
if spec:
|
67
|
+
try:
|
68
|
+
module = importlib.util.module_from_spec(spec)
|
69
|
+
spec.loader.exec_module(module)
|
70
|
+
for _, cls in inspect.getmembers(module, inspect.isclass):
|
71
|
+
if issubclass(cls, BaseTool) and cls is not BaseTool:
|
72
|
+
tool_instance = cls(memory_manager=self.memory_manager)
|
73
|
+
if tool_instance.name in self.tools:
|
74
|
+
logger.warning(f"Duplicate tool name '{tool_instance.name}' found. Overwriting.")
|
75
|
+
self.tools[tool_instance.name] = tool_instance
|
76
|
+
logger.info(f"Successfully loaded and registered tool: {tool_instance.name}")
|
77
|
+
except Exception as e:
|
78
|
+
logger.error(f"Failed to load tool from {module_path}: {e}", exc_info=True)
|
79
|
+
|
80
|
+
def _load_allocations(self):
|
81
|
+
"""Loads tool allocations from JSON files, creating defaults if they don't exist."""
|
82
|
+
default_allocations = {
|
83
|
+
"neuro_agent": ["speak", "get_core_memory_blocks", "get_core_memory_block"],
|
84
|
+
"memory_agent": ["add_temp_memory", "create_core_memory_block", "update_core_memory_block", "delete_core_memory_block", "add_to_core_memory_block", "remove_from_core_memory_block", "get_core_memory_blocks", "get_core_memory_block"]
|
85
|
+
}
|
86
|
+
|
87
|
+
# Load neuro agent allocations
|
88
|
+
if path_manager.neuro_tools_path.exists():
|
89
|
+
with open(path_manager.neuro_tools_path, 'r', encoding='utf-8') as f:
|
90
|
+
self.agent_tool_allocations['neuro_agent'] = json.load(f)
|
91
|
+
else:
|
92
|
+
self.agent_tool_allocations['neuro_agent'] = default_allocations['neuro_agent']
|
93
|
+
with open(path_manager.neuro_tools_path, 'w', encoding='utf-8') as f:
|
94
|
+
json.dump(default_allocations['neuro_agent'], f, indent=2)
|
95
|
+
|
96
|
+
# Load memory agent allocations
|
97
|
+
if path_manager.memory_agent_tools_path.exists():
|
98
|
+
with open(path_manager.memory_agent_tools_path, 'r', encoding='utf-8') as f:
|
99
|
+
self.agent_tool_allocations['memory_agent'] = json.load(f)
|
100
|
+
else:
|
101
|
+
self.agent_tool_allocations['memory_agent'] = default_allocations['memory_agent']
|
102
|
+
with open(path_manager.memory_agent_tools_path, 'w', encoding='utf-8') as f:
|
103
|
+
json.dump(default_allocations['memory_agent'], f, indent=2)
|
104
|
+
|
105
|
+
logger.info(f"Tool allocations loaded: {self.agent_tool_allocations}")
|
106
|
+
|
107
|
+
def get_all_tool_schemas(self) -> List[Dict[str, Any]]:
|
108
|
+
return [tool.get_schema() for tool in self.tools.values()]
|
109
|
+
|
110
|
+
def get_tool_schemas_for_agent(self, agent_name: str) -> List[Dict[str, Any]]:
|
111
|
+
allowed_names = set(self.agent_tool_allocations.get(agent_name, []))
|
112
|
+
if not allowed_names:
|
113
|
+
return []
|
114
|
+
return [tool.get_schema() for tool in self.tools.values() if tool.name in allowed_names]
|
115
|
+
|
116
|
+
def reload_tools(self):
|
117
|
+
logger.info("Reloading tools...")
|
118
|
+
self._load_and_register_tools()
|
119
|
+
logger.info(f"Tools reloaded. {len(self.tools)} tools available.")
|
120
|
+
|
121
|
+
def get_allocations(self) -> Dict[str, List[str]]:
|
122
|
+
return self.agent_tool_allocations
|
123
|
+
|
124
|
+
def set_allocations(self, allocations: Dict[str, List[str]]):
|
125
|
+
self.agent_tool_allocations = allocations
|
126
|
+
# Persist the changes to the JSON files
|
127
|
+
with open(path_manager.neuro_tools_path, 'w', encoding='utf-8') as f:
|
128
|
+
json.dump(allocations.get('neuro_agent', []), f, indent=2)
|
129
|
+
with open(path_manager.memory_agent_tools_path, 'w', encoding='utf-8') as f:
|
130
|
+
json.dump(allocations.get('memory_agent', []), f, indent=2)
|
131
|
+
logger.info(f"Tool allocations updated and saved: {self.agent_tool_allocations}")
|
132
|
+
|
133
|
+
async def execute_tool(self, tool_name: str, **kwargs: Any) -> Dict[str, Any]:
|
134
|
+
if tool_name not in self.tools:
|
135
|
+
logger.error(f"Attempted to execute non-existent tool: {tool_name}")
|
136
|
+
return {"error": f"Tool '{tool_name}' not found."}
|
137
|
+
tool = self.tools[tool_name]
|
138
|
+
try:
|
139
|
+
result = await tool.execute(**kwargs)
|
140
|
+
return result
|
141
|
+
except Exception as e:
|
142
|
+
logger.error(f"Error executing tool '{tool_name}' with params {kwargs}: {e}", exc_info=True)
|
143
|
+
return {"error": f"An unexpected error occurred while executing the tool: {str(e)}"}
|
@@ -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 RemoveFromCoreMemoryBlockTool(BaseTool):
|
10
10
|
"""Tool to remove an item from an existing core memory block's content list by its index."""
|
@@ -4,8 +4,8 @@
|
|
4
4
|
import logging
|
5
5
|
from typing import Dict, Any, List
|
6
6
|
|
7
|
-
from .base import BaseTool
|
8
|
-
from
|
7
|
+
from neuro_simulator.agent.tools.base import BaseTool
|
8
|
+
from neuro_simulator.agent.memory.manager import MemoryManager
|
9
9
|
|
10
10
|
logger = logging.getLogger(__name__.replace("neuro_simulator", "agent", 1))
|
11
11
|
|
@@ -3,8 +3,8 @@
|
|
3
3
|
|
4
4
|
from typing import Dict, Any, List, Optional
|
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 UpdateCoreMemoryBlockTool(BaseTool):
|
10
10
|
"""Tool to update an existing core memory block."""
|
@@ -1,10 +1,13 @@
|
|
1
1
|
# neuro_simulator/api/system.py
|
2
2
|
"""API endpoints for system, config, and utility functions."""
|
3
3
|
|
4
|
-
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
5
4
|
import time
|
5
|
+
from typing import Any, Dict
|
6
|
+
|
7
|
+
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
6
8
|
|
7
|
-
from ..core.config import config_manager
|
9
|
+
from ..core.config import AppSettings, config_manager
|
10
|
+
from ..utils.process import process_manager
|
8
11
|
|
9
12
|
|
10
13
|
router = APIRouter(tags=["System & Utilities"])
|