neuro-simulator 0.3.3__tar.gz → 0.4.0__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.
Files changed (58) hide show
  1. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/PKG-INFO +1 -1
  2. neuro_simulator-0.4.0/neuro_simulator/agent/core.py +201 -0
  3. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/agent/llm.py +1 -1
  4. neuro_simulator-0.4.0/neuro_simulator/agent/memory/manager.py +125 -0
  5. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/agent/tools/add_temp_memory.py +3 -3
  6. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/agent/tools/add_to_core_memory_block.py +2 -2
  7. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/agent/tools/create_core_memory_block.py +2 -2
  8. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/agent/tools/delete_core_memory_block.py +2 -2
  9. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/agent/tools/get_core_memory_block.py +2 -2
  10. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/agent/tools/get_core_memory_blocks.py +2 -2
  11. neuro_simulator-0.4.0/neuro_simulator/agent/tools/manager.py +143 -0
  12. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/agent/tools/remove_from_core_memory_block.py +2 -2
  13. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/agent/tools/speak.py +2 -2
  14. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/agent/tools/update_core_memory_block.py +2 -2
  15. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/api/system.py +5 -2
  16. neuro_simulator-0.4.0/neuro_simulator/cli.py +125 -0
  17. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/core/agent_factory.py +0 -1
  18. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/core/application.py +46 -32
  19. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/core/config.py +66 -63
  20. neuro_simulator-0.4.0/neuro_simulator/core/path_manager.py +69 -0
  21. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/services/audience.py +0 -2
  22. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/services/audio.py +0 -1
  23. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/services/builtin.py +10 -25
  24. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/services/letta.py +19 -1
  25. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/services/stream.py +24 -21
  26. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/utils/logging.py +9 -0
  27. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/utils/queue.py +27 -4
  28. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/utils/websocket.py +1 -3
  29. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator.egg-info/PKG-INFO +1 -1
  30. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator.egg-info/SOURCES.txt +1 -3
  31. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/pyproject.toml +1 -1
  32. neuro_simulator-0.3.3/neuro_simulator/agent/base.py +0 -43
  33. neuro_simulator-0.3.3/neuro_simulator/agent/core.py +0 -266
  34. neuro_simulator-0.3.3/neuro_simulator/agent/factory.py +0 -30
  35. neuro_simulator-0.3.3/neuro_simulator/agent/memory/manager.py +0 -204
  36. neuro_simulator-0.3.3/neuro_simulator/agent/tools/manager.py +0 -120
  37. neuro_simulator-0.3.3/neuro_simulator/api/stream.py +0 -1
  38. neuro_simulator-0.3.3/neuro_simulator/cli.py +0 -95
  39. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/README.md +0 -0
  40. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/__init__.py +0 -0
  41. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/agent/__init__.py +0 -0
  42. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/agent/memory/__init__.py +0 -0
  43. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/agent/memory_prompt.txt +0 -0
  44. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/agent/neuro_prompt.txt +0 -0
  45. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/agent/tools/__init__.py +0 -0
  46. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/agent/tools/base.py +0 -0
  47. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/api/__init__.py +0 -0
  48. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/core/__init__.py +0 -0
  49. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/core/agent_interface.py +0 -0
  50. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/services/__init__.py +0 -0
  51. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/utils/__init__.py +0 -0
  52. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/utils/process.py +0 -0
  53. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator/utils/state.py +0 -0
  54. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator.egg-info/dependency_links.txt +0 -0
  55. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator.egg-info/entry_points.txt +0 -0
  56. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator.egg-info/requires.txt +0 -0
  57. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/neuro_simulator.egg-info/top_level.txt +0 -0
  58. {neuro_simulator-0.3.3 → neuro_simulator-0.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: neuro_simulator
3
- Version: 0.3.3
3
+ Version: 0.4.0
4
4
  Summary: Neuro Simulator Server
5
5
  Author-email: Moha-Master <hongkongreporter@outlook.com>
6
6
  License-Expression: MIT
@@ -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
+
@@ -6,7 +6,7 @@ LLM client for the Neuro Simulator's built-in agent.
6
6
  import asyncio
7
7
  import logging
8
8
  from pathlib import Path
9
- from typing import Optional
9
+ from typing import Any, Dict
10
10
 
11
11
  from google import genai
12
12
  from google.genai import types
@@ -0,0 +1,125 @@
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 update_init_memory(self, new_memory: Dict[str, Any]):
74
+ self.init_memory.update(new_memory)
75
+ await self._save_init_memory()
76
+
77
+ async def _save_core_memory(self):
78
+ with open(self.core_memory_file, 'w', encoding='utf-8') as f:
79
+ json.dump(self.core_memory, f, ensure_ascii=False, indent=2)
80
+
81
+ async def _save_temp_memory(self):
82
+ with open(self.temp_memory_file, 'w', encoding='utf-8') as f:
83
+ json.dump(self.temp_memory, f, ensure_ascii=False, indent=2)
84
+
85
+ async def reset_temp_memory(self):
86
+ """Reset temp memory to a default empty state."""
87
+ self.temp_memory = []
88
+ await self._save_temp_memory()
89
+ logger.info("Agent temp memory has been reset.")
90
+
91
+ async def add_temp_memory(self, content: str, role: str = "system"):
92
+ self.temp_memory.append({"id": generate_id(), "content": content, "role": role, "timestamp": datetime.now().isoformat()})
93
+ if len(self.temp_memory) > 20:
94
+ self.temp_memory = self.temp_memory[-20:]
95
+ await self._save_temp_memory()
96
+
97
+ async def get_core_memory_blocks(self) -> Dict[str, Any]:
98
+ return self.core_memory.get("blocks", {})
99
+
100
+ async def get_core_memory_block(self, block_id: str) -> Optional[Dict[str, Any]]:
101
+ return self.core_memory.get("blocks", {}).get(block_id)
102
+
103
+ async def create_core_memory_block(self, title: str, description: str, content: List[str]) -> str:
104
+ block_id = generate_id()
105
+ if "blocks" not in self.core_memory:
106
+ self.core_memory["blocks"] = {}
107
+ self.core_memory["blocks"][block_id] = {
108
+ "id": block_id, "title": title, "description": description, "content": content or []
109
+ }
110
+ await self._save_core_memory()
111
+ return block_id
112
+
113
+ async def update_core_memory_block(self, block_id: str, title: Optional[str] = None, description: Optional[str] = None, content: Optional[List[str]] = None):
114
+ block = self.core_memory.get("blocks", {}).get(block_id)
115
+ if not block:
116
+ raise ValueError(f"Block '{block_id}' not found")
117
+ if title is not None: block["title"] = title
118
+ if description is not None: block["description"] = description
119
+ if content is not None: block["content"] = content
120
+ await self._save_core_memory()
121
+
122
+ async def delete_core_memory_block(self, block_id: str):
123
+ if "blocks" in self.core_memory and block_id in self.core_memory["blocks"]:
124
+ del self.core_memory["blocks"][block_id]
125
+ 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 Dict, Any, List
4
+ from typing import Any, Dict, List
5
5
 
6
- from .base import BaseTool
7
- from ..memory.manager import MemoryManager
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 ..memory.manager import MemoryManager
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 ..memory.manager import MemoryManager
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 ..memory.manager import MemoryManager
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 ..memory.manager import MemoryManager
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 ..memory.manager import MemoryManager
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 ..memory.manager import MemoryManager
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 ..memory.manager import MemoryManager
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 ..memory.manager import MemoryManager
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"])