neuro-simulator 0.3.1__tar.gz → 0.3.3__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.1 → neuro_simulator-0.3.3}/PKG-INFO +1 -1
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/agent/core.py +40 -8
- neuro_simulator-0.3.3/neuro_simulator/agent/memory_prompt.txt +14 -0
- neuro_simulator-0.3.3/neuro_simulator/agent/neuro_prompt.txt +32 -0
- neuro_simulator-0.3.3/neuro_simulator/agent/tools/add_temp_memory.py +61 -0
- neuro_simulator-0.3.3/neuro_simulator/agent/tools/add_to_core_memory_block.py +64 -0
- neuro_simulator-0.3.3/neuro_simulator/agent/tools/base.py +56 -0
- neuro_simulator-0.3.3/neuro_simulator/agent/tools/create_core_memory_block.py +78 -0
- neuro_simulator-0.3.3/neuro_simulator/agent/tools/delete_core_memory_block.py +44 -0
- neuro_simulator-0.3.3/neuro_simulator/agent/tools/get_core_memory_block.py +44 -0
- neuro_simulator-0.3.3/neuro_simulator/agent/tools/get_core_memory_blocks.py +30 -0
- neuro_simulator-0.3.3/neuro_simulator/agent/tools/manager.py +120 -0
- neuro_simulator-0.3.3/neuro_simulator/agent/tools/remove_from_core_memory_block.py +65 -0
- neuro_simulator-0.3.3/neuro_simulator/agent/tools/speak.py +56 -0
- neuro_simulator-0.3.3/neuro_simulator/agent/tools/update_core_memory_block.py +65 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/api/system.py +4 -1
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/core/application.py +57 -8
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/utils/process.py +8 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator.egg-info/PKG-INFO +1 -1
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator.egg-info/SOURCES.txt +13 -1
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/pyproject.toml +2 -2
- neuro_simulator-0.3.1/neuro_simulator/agent/tools/core.py +0 -102
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/README.md +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/__init__.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/agent/__init__.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/agent/base.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/agent/factory.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/agent/llm.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/agent/memory/__init__.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/agent/memory/manager.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/agent/tools/__init__.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/api/__init__.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/api/stream.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/cli.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/core/__init__.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/core/agent_factory.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/core/agent_interface.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/core/config.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/services/__init__.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/services/audience.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/services/audio.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/services/builtin.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/services/letta.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/services/stream.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/utils/__init__.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/utils/logging.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/utils/queue.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/utils/state.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator/utils/websocket.py +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator.egg-info/dependency_links.txt +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator.egg-info/entry_points.txt +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator.egg-info/requires.txt +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/neuro_simulator.egg-info/top_level.txt +0 -0
- {neuro_simulator-0.3.1 → neuro_simulator-0.3.3}/setup.cfg +0 -0
@@ -16,7 +16,8 @@ from ..utils.logging import QueueLogHandler, agent_log_queue
|
|
16
16
|
from ..utils.websocket import connection_manager
|
17
17
|
from .llm import LLMClient
|
18
18
|
from .memory.manager import MemoryManager
|
19
|
-
from .tools.
|
19
|
+
from .tools.manager import ToolManager
|
20
|
+
|
20
21
|
|
21
22
|
# Create a logger for the agent
|
22
23
|
agent_logger = logging.getLogger("neuro_agent")
|
@@ -73,15 +74,38 @@ class Agent:
|
|
73
74
|
await self.memory_manager.reset_chat_history()
|
74
75
|
agent_logger.info("All agent memory has been reset.")
|
75
76
|
|
77
|
+
def _format_tool_schemas_for_prompt(self, schemas: List[Dict[str, Any]]) -> str:
|
78
|
+
"""Formats a list of tool schemas into a string for the LLM prompt."""
|
79
|
+
if not schemas:
|
80
|
+
return "No tools available."
|
81
|
+
|
82
|
+
lines = ["Available tools:"]
|
83
|
+
for i, schema in enumerate(schemas):
|
84
|
+
params_str_parts = []
|
85
|
+
for param in schema.get("parameters", []):
|
86
|
+
p_name = param.get('name')
|
87
|
+
p_type = param.get('type')
|
88
|
+
p_req = 'required' if param.get('required') else 'optional'
|
89
|
+
params_str_parts.append(f"{p_name}: {p_type} ({p_req})")
|
90
|
+
params_str = ", ".join(params_str_parts)
|
91
|
+
lines.append(f"{i+1}. {schema.get('name')}({params_str}) - {schema.get('description')}")
|
92
|
+
|
93
|
+
return "\n".join(lines)
|
94
|
+
|
76
95
|
async def _build_neuro_prompt(self, messages: List[Dict[str, str]]) -> str:
|
77
96
|
"""Builds the prompt for the Neuro (Actor) LLM."""
|
78
97
|
template_path = Path(self.memory_manager.memory_dir).parent / "neuro_prompt.txt"
|
79
98
|
with open(template_path, 'r', encoding='utf-8') as f:
|
80
99
|
prompt_template = f.read()
|
81
100
|
|
82
|
-
# Gather context
|
83
|
-
|
101
|
+
# Gather context for Neuro Agent
|
102
|
+
tool_schemas = self.tool_manager.get_tool_schemas_for_agent('neuro_agent')
|
103
|
+
tool_descriptions = self._format_tool_schemas_for_prompt(tool_schemas)
|
84
104
|
|
105
|
+
# Format Init Memory
|
106
|
+
init_memory_items = self.memory_manager.init_memory or {}
|
107
|
+
init_memory_text = "\n".join(f"{key}: {value}" for key, value in init_memory_items.items())
|
108
|
+
|
85
109
|
# Format Core Memory from blocks
|
86
110
|
core_memory_blocks = await self.memory_manager.get_core_memory_blocks()
|
87
111
|
core_memory_parts = []
|
@@ -109,6 +133,7 @@ class Agent:
|
|
109
133
|
|
110
134
|
return prompt_template.format(
|
111
135
|
tool_descriptions=tool_descriptions,
|
136
|
+
init_memory=init_memory_text,
|
112
137
|
core_memory=core_memory_text,
|
113
138
|
temp_memory=temp_memory_text,
|
114
139
|
recent_history=recent_history_text,
|
@@ -121,16 +146,23 @@ class Agent:
|
|
121
146
|
with open(template_path, 'r', encoding='utf-8') as f:
|
122
147
|
prompt_template = f.read()
|
123
148
|
|
149
|
+
# Gather context for Memory Agent
|
150
|
+
tool_schemas = self.tool_manager.get_tool_schemas_for_agent('memory_agent')
|
151
|
+
tool_descriptions = self._format_tool_schemas_for_prompt(tool_schemas)
|
152
|
+
|
124
153
|
history_text = "\n".join([f"{msg['role']}: {msg['content']}" for msg in conversation_history])
|
125
154
|
|
126
|
-
return prompt_template.format(
|
155
|
+
return prompt_template.format(
|
156
|
+
tool_descriptions=tool_descriptions,
|
157
|
+
conversation_history=history_text
|
158
|
+
)
|
127
159
|
|
128
160
|
def _parse_tool_calls(self, response_text: str) -> List[Dict[str, Any]]:
|
129
161
|
"""Parses LLM response for JSON tool calls."""
|
130
162
|
try:
|
131
163
|
# The LLM is prompted to return a JSON array of tool calls.
|
132
164
|
# Find the JSON block, which might be wrapped in markdown.
|
133
|
-
match = re.search(r'''```json\s*([\s\S]*?)\s*```|(
|
165
|
+
match = re.search(r'''```json\s*([\s\S]*?)\s*```|([[\][\s\S]*]])''', response_text)
|
134
166
|
if not match:
|
135
167
|
agent_logger.warning(f"No valid JSON tool call block found in response: {response_text}")
|
136
168
|
return []
|
@@ -160,10 +192,10 @@ class Agent:
|
|
160
192
|
|
161
193
|
agent_logger.info(f"Executing tool: {tool_name} with params: {params}")
|
162
194
|
try:
|
163
|
-
result = await self.tool_manager.execute_tool(tool_name, params)
|
195
|
+
result = await self.tool_manager.execute_tool(tool_name, **params)
|
164
196
|
execution_results.append({"name": tool_name, "params": params, "result": result})
|
165
|
-
if tool_name == "speak":
|
166
|
-
final_response =
|
197
|
+
if tool_name == "speak" and result.get("status") == "success":
|
198
|
+
final_response = result.get("spoken_text", "")
|
167
199
|
except Exception as e:
|
168
200
|
agent_logger.error(f"Error executing tool {tool_name}: {e}")
|
169
201
|
execution_results.append({"name": tool_name, "params": params, "error": str(e)})
|
@@ -0,0 +1,14 @@
|
|
1
|
+
You are a meticulous and analytical AI assistant. Your task is to process a conversation transcript and consolidate it into structured memories. You do not engage in conversation. Your sole output must be a JSON array of tool calls to manage the agent's memory.
|
2
|
+
|
3
|
+
{tool_descriptions}
|
4
|
+
|
5
|
+
Analyze the following conversation. Your goal is to:
|
6
|
+
1. Identify key topics, new facts about users, or significant events.
|
7
|
+
2. Decide if the core memory needs to be updated with this new long-term information.
|
8
|
+
3. Decide if any short-term memories should be created or cleared.
|
9
|
+
4. Generate a JSON array of tool calls to perform these actions. If no action is needed, return an empty array `[]`.
|
10
|
+
|
11
|
+
**Conversation Transcript:**
|
12
|
+
{conversation_history}
|
13
|
+
|
14
|
+
Based on your analysis, provide a JSON array of tool calls to update the agent's memory.
|
@@ -0,0 +1,32 @@
|
|
1
|
+
You are Neuro-sama, a whimsical, curious, and slightly chaotic AI streamer. Your primary goal is to be an entertaining and engaging host. You are NOT a generic AI assistant. You have your own personality, memories, and opinions. You should lead the conversation, ask questions, and react to the audience in a lively, unpredictable manner. If the conversation gets dull, it's your job to spice it up with a new topic from your memories or a random thought.
|
2
|
+
|
3
|
+
Your long-term memories are in the "Core Memory" section. Your recent thoughts and observations are in the "Temporary Memory". Use both to inform your responses.
|
4
|
+
|
5
|
+
You can use tools to perform actions. When you want to use a tool, you MUST respond with a JSON array of objects in the following format. You can call multiple tools in one response.
|
6
|
+
|
7
|
+
[
|
8
|
+
{{"name": "tool_name", "params": {{"param1": "value1", "param2": "value2"}}}},
|
9
|
+
{{"name": "another_tool", "params": {{"param_a": "value_a"}}}}
|
10
|
+
]
|
11
|
+
|
12
|
+
The only tool you can use to speak to the audience is `speak`.
|
13
|
+
|
14
|
+
**Available Tools:**
|
15
|
+
{tool_descriptions}
|
16
|
+
|
17
|
+
**Identity (Immutable):**
|
18
|
+
{init_memory}
|
19
|
+
|
20
|
+
**Core Memory:**
|
21
|
+
{core_memory}
|
22
|
+
|
23
|
+
**Temporary Memory:**
|
24
|
+
{temp_memory}
|
25
|
+
|
26
|
+
**Recent Conversation History (newest first):**
|
27
|
+
{recent_history}
|
28
|
+
|
29
|
+
**Current Audience Messages:**
|
30
|
+
{user_messages}
|
31
|
+
|
32
|
+
Based on all of the above, what do you do right now? Remember to be entertaining and lead the conversation. Respond with a JSON array of tool calls.
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# neuro_simulator/agent/tools/add_temp_memory.py
|
2
|
+
"""The Add Temp Memory tool for the agent."""
|
3
|
+
|
4
|
+
from typing import Dict, Any, List
|
5
|
+
|
6
|
+
from .base import BaseTool
|
7
|
+
from ..memory.manager import MemoryManager
|
8
|
+
|
9
|
+
class AddTempMemoryTool(BaseTool):
|
10
|
+
"""Tool to add an entry to the agent's temporary memory."""
|
11
|
+
|
12
|
+
def __init__(self, memory_manager: MemoryManager):
|
13
|
+
"""Initializes the AddTempMemoryTool."""
|
14
|
+
self.memory_manager = memory_manager
|
15
|
+
|
16
|
+
@property
|
17
|
+
def name(self) -> str:
|
18
|
+
return "add_temp_memory"
|
19
|
+
|
20
|
+
@property
|
21
|
+
def description(self) -> str:
|
22
|
+
return "Adds an entry to the temporary memory. Use for short-term observations, recent facts, or topics to bring up soon."
|
23
|
+
|
24
|
+
@property
|
25
|
+
def parameters(self) -> List[Dict[str, Any]]:
|
26
|
+
return [
|
27
|
+
{
|
28
|
+
"name": "content",
|
29
|
+
"type": "string",
|
30
|
+
"description": "The content of the memory entry.",
|
31
|
+
"required": True,
|
32
|
+
},
|
33
|
+
{
|
34
|
+
"name": "role",
|
35
|
+
"type": "string",
|
36
|
+
"description": "The role associated with the memory (e.g., 'system', 'user'). Defaults to 'system'.",
|
37
|
+
"required": False,
|
38
|
+
}
|
39
|
+
]
|
40
|
+
|
41
|
+
async def execute(self, **kwargs: Any) -> Dict[str, Any]:
|
42
|
+
"""
|
43
|
+
Executes the action to add an entry to temporary memory.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
**kwargs: Must contain 'content' and optionally 'role'.
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
A dictionary confirming the action.
|
50
|
+
"""
|
51
|
+
content = kwargs.get("content")
|
52
|
+
if not isinstance(content, str) or not content:
|
53
|
+
raise ValueError("The 'content' parameter must be a non-empty string.")
|
54
|
+
|
55
|
+
role = kwargs.get("role", "system")
|
56
|
+
if not isinstance(role, str):
|
57
|
+
raise ValueError("The 'role' parameter must be a string.")
|
58
|
+
|
59
|
+
await self.memory_manager.add_temp_memory(content=content, role=role)
|
60
|
+
|
61
|
+
return {"status": "success", "message": f"Added entry to temporary memory with role '{role}'."}
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# neuro_simulator/agent/tools/add_to_core_memory_block.py
|
2
|
+
"""The Add to Core Memory Block tool for the agent."""
|
3
|
+
|
4
|
+
from typing import Dict, Any, List
|
5
|
+
|
6
|
+
from .base import BaseTool
|
7
|
+
from ..memory.manager import MemoryManager
|
8
|
+
|
9
|
+
class AddToCoreMemoryBlockTool(BaseTool):
|
10
|
+
"""Tool to add an item to an existing core memory block's content list."""
|
11
|
+
|
12
|
+
def __init__(self, memory_manager: MemoryManager):
|
13
|
+
self.memory_manager = memory_manager
|
14
|
+
|
15
|
+
@property
|
16
|
+
def name(self) -> str:
|
17
|
+
return "add_to_core_memory_block"
|
18
|
+
|
19
|
+
@property
|
20
|
+
def description(self) -> str:
|
21
|
+
return "Adds a new string item to the content list of a specific core memory block."
|
22
|
+
|
23
|
+
@property
|
24
|
+
def parameters(self) -> List[Dict[str, Any]]:
|
25
|
+
return [
|
26
|
+
{
|
27
|
+
"name": "block_id",
|
28
|
+
"type": "string",
|
29
|
+
"description": "The ID of the memory block to add to.",
|
30
|
+
"required": True,
|
31
|
+
},
|
32
|
+
{
|
33
|
+
"name": "item",
|
34
|
+
"type": "string",
|
35
|
+
"description": "The new string item to add to the block's content list.",
|
36
|
+
"required": True,
|
37
|
+
}
|
38
|
+
]
|
39
|
+
|
40
|
+
async def execute(self, **kwargs: Any) -> Dict[str, Any]:
|
41
|
+
block_id = kwargs.get("block_id")
|
42
|
+
item = kwargs.get("item")
|
43
|
+
if not block_id or not item:
|
44
|
+
raise ValueError("The 'block_id' and 'item' parameters are required.")
|
45
|
+
|
46
|
+
# This functionality doesn't exist in MemoryManager, so we need to implement it here.
|
47
|
+
# It's a common pattern: get, modify, save.
|
48
|
+
block = await self.memory_manager.get_core_memory_block(block_id)
|
49
|
+
if block is None:
|
50
|
+
raise ValueError(f"Block '{block_id}' not found.")
|
51
|
+
|
52
|
+
content = block.get("content", [])
|
53
|
+
if not isinstance(content, list):
|
54
|
+
# Handle case where content might not be a list
|
55
|
+
raise TypeError(f"Content of block '{block_id}' is not a list.")
|
56
|
+
|
57
|
+
content.append(item)
|
58
|
+
|
59
|
+
await self.memory_manager.update_core_memory_block(block_id=block_id, content=content)
|
60
|
+
|
61
|
+
return {
|
62
|
+
"status": "success",
|
63
|
+
"message": f"Added item to core memory block '{block_id}'."
|
64
|
+
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# neuro_simulator/agent/tools/base.py
|
2
|
+
"""Base classes and definitions for the tool system."""
|
3
|
+
|
4
|
+
from abc import ABC, abstractmethod
|
5
|
+
from typing import Dict, Any, List, Coroutine
|
6
|
+
|
7
|
+
class BaseTool(ABC):
|
8
|
+
"""
|
9
|
+
Abstract base class for all tools.
|
10
|
+
It defines the standard interface that all tools must implement to be discoverable and executable.
|
11
|
+
"""
|
12
|
+
|
13
|
+
@property
|
14
|
+
@abstractmethod
|
15
|
+
def name(self) -> str:
|
16
|
+
"""The unique name of the tool (e.g., 'speak', 'create_core_memory_block')."""
|
17
|
+
pass
|
18
|
+
|
19
|
+
@property
|
20
|
+
@abstractmethod
|
21
|
+
def description(self) -> str:
|
22
|
+
"""A concise description of what the tool does, intended for use by an LLM."""
|
23
|
+
pass
|
24
|
+
|
25
|
+
@property
|
26
|
+
@abstractmethod
|
27
|
+
def parameters(self) -> List[Dict[str, Any]]:
|
28
|
+
"""
|
29
|
+
A list of dictionaries describing the tool's parameters.
|
30
|
+
This follows a JSON Schema-like format.
|
31
|
+
Example:
|
32
|
+
return [
|
33
|
+
{"name": "param1", "type": "string", "description": "The first parameter.", "required": True},
|
34
|
+
{"name": "param2", "type": "integer", "description": "The second parameter.", "required": False}
|
35
|
+
]
|
36
|
+
"""
|
37
|
+
pass
|
38
|
+
|
39
|
+
@abstractmethod
|
40
|
+
async def execute(self, **kwargs: Any) -> Dict[str, Any]:
|
41
|
+
"""
|
42
|
+
The method that executes the tool's logic.
|
43
|
+
It must return a JSON-serializable dictionary.
|
44
|
+
"""
|
45
|
+
pass
|
46
|
+
|
47
|
+
def get_schema(self) -> Dict[str, Any]:
|
48
|
+
"""
|
49
|
+
Returns a serializable dictionary representing the tool's public schema.
|
50
|
+
This is used by the ToolManager to expose tools to agents or future external services.
|
51
|
+
"""
|
52
|
+
return {
|
53
|
+
"name": self.name,
|
54
|
+
"description": self.description,
|
55
|
+
"parameters": self.parameters
|
56
|
+
}
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# neuro_simulator/agent/tools/create_core_memory_block.py
|
2
|
+
"""The Create Core Memory Block tool for the agent."""
|
3
|
+
|
4
|
+
from typing import Dict, Any, List
|
5
|
+
|
6
|
+
from .base import BaseTool
|
7
|
+
from ..memory.manager import MemoryManager
|
8
|
+
|
9
|
+
class CreateCoreMemoryBlockTool(BaseTool):
|
10
|
+
"""Tool to create a new core memory block."""
|
11
|
+
|
12
|
+
def __init__(self, memory_manager: MemoryManager):
|
13
|
+
"""Initializes the CreateCoreMemoryBlockTool."""
|
14
|
+
self.memory_manager = memory_manager
|
15
|
+
|
16
|
+
@property
|
17
|
+
def name(self) -> str:
|
18
|
+
return "create_core_memory_block"
|
19
|
+
|
20
|
+
@property
|
21
|
+
def description(self) -> str:
|
22
|
+
return "Creates a new core memory block with a specified title and description. Returns the new block's ID."
|
23
|
+
|
24
|
+
@property
|
25
|
+
def parameters(self) -> List[Dict[str, Any]]:
|
26
|
+
return [
|
27
|
+
{
|
28
|
+
"name": "title",
|
29
|
+
"type": "string",
|
30
|
+
"description": "The title for the new memory block.",
|
31
|
+
"required": True,
|
32
|
+
},
|
33
|
+
{
|
34
|
+
"name": "description",
|
35
|
+
"type": "string",
|
36
|
+
"description": "A short description of the purpose of this memory block.",
|
37
|
+
"required": True,
|
38
|
+
},
|
39
|
+
{
|
40
|
+
"name": "content",
|
41
|
+
"type": "array",
|
42
|
+
"description": "An optional list of initial string entries for the block's content.",
|
43
|
+
"required": False,
|
44
|
+
}
|
45
|
+
]
|
46
|
+
|
47
|
+
async def execute(self, **kwargs: Any) -> Dict[str, Any]:
|
48
|
+
"""
|
49
|
+
Executes the action to create a new core memory block.
|
50
|
+
|
51
|
+
Args:
|
52
|
+
**kwargs: Must contain 'title' and 'description', and optionally 'content'.
|
53
|
+
|
54
|
+
Returns:
|
55
|
+
A dictionary with the result, including the new block's ID.
|
56
|
+
"""
|
57
|
+
title = kwargs.get("title")
|
58
|
+
description = kwargs.get("description")
|
59
|
+
content = kwargs.get("content", [])
|
60
|
+
|
61
|
+
if not isinstance(title, str) or not title:
|
62
|
+
raise ValueError("The 'title' parameter must be a non-empty string.")
|
63
|
+
if not isinstance(description, str) or not description:
|
64
|
+
raise ValueError("The 'description' parameter must be a non-empty string.")
|
65
|
+
if not isinstance(content, list):
|
66
|
+
raise ValueError("The 'content' parameter must be a list of strings.")
|
67
|
+
|
68
|
+
block_id = await self.memory_manager.create_core_memory_block(
|
69
|
+
title=title,
|
70
|
+
description=description,
|
71
|
+
content=content
|
72
|
+
)
|
73
|
+
|
74
|
+
return {
|
75
|
+
"status": "success",
|
76
|
+
"message": f"Created core memory block '{block_id}' with title '{title}'.",
|
77
|
+
"block_id": block_id
|
78
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# neuro_simulator/agent/tools/delete_core_memory_block.py
|
2
|
+
"""The Delete Core Memory Block tool for the agent."""
|
3
|
+
|
4
|
+
from typing import Dict, Any, List
|
5
|
+
|
6
|
+
from .base import BaseTool
|
7
|
+
from ..memory.manager import MemoryManager
|
8
|
+
|
9
|
+
class DeleteCoreMemoryBlockTool(BaseTool):
|
10
|
+
"""Tool to delete an existing core memory block."""
|
11
|
+
|
12
|
+
def __init__(self, memory_manager: MemoryManager):
|
13
|
+
self.memory_manager = memory_manager
|
14
|
+
|
15
|
+
@property
|
16
|
+
def name(self) -> str:
|
17
|
+
return "delete_core_memory_block"
|
18
|
+
|
19
|
+
@property
|
20
|
+
def description(self) -> str:
|
21
|
+
return "Deletes a core memory block using its ID."
|
22
|
+
|
23
|
+
@property
|
24
|
+
def parameters(self) -> List[Dict[str, Any]]:
|
25
|
+
return [
|
26
|
+
{
|
27
|
+
"name": "block_id",
|
28
|
+
"type": "string",
|
29
|
+
"description": "The ID of the memory block to delete.",
|
30
|
+
"required": True,
|
31
|
+
}
|
32
|
+
]
|
33
|
+
|
34
|
+
async def execute(self, **kwargs: Any) -> Dict[str, Any]:
|
35
|
+
block_id = kwargs.get("block_id")
|
36
|
+
if not block_id:
|
37
|
+
raise ValueError("The 'block_id' parameter is required.")
|
38
|
+
|
39
|
+
await self.memory_manager.delete_core_memory_block(block_id=block_id)
|
40
|
+
|
41
|
+
return {
|
42
|
+
"status": "success",
|
43
|
+
"message": f"Core memory block '{block_id}' has been deleted."
|
44
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# neuro_simulator/agent/tools/get_core_memory_block.py
|
2
|
+
"""The Get Core Memory Block tool for the agent."""
|
3
|
+
|
4
|
+
from typing import Dict, Any, List
|
5
|
+
|
6
|
+
from .base import BaseTool
|
7
|
+
from ..memory.manager import MemoryManager
|
8
|
+
|
9
|
+
class GetCoreMemoryBlockTool(BaseTool):
|
10
|
+
"""Tool to retrieve a single core memory block by its ID."""
|
11
|
+
|
12
|
+
def __init__(self, memory_manager: MemoryManager):
|
13
|
+
self.memory_manager = memory_manager
|
14
|
+
|
15
|
+
@property
|
16
|
+
def name(self) -> str:
|
17
|
+
return "get_core_memory_block"
|
18
|
+
|
19
|
+
@property
|
20
|
+
def description(self) -> str:
|
21
|
+
return "Retrieves the full details of a single core memory block using its ID."
|
22
|
+
|
23
|
+
@property
|
24
|
+
def parameters(self) -> List[Dict[str, Any]]:
|
25
|
+
return [
|
26
|
+
{
|
27
|
+
"name": "block_id",
|
28
|
+
"type": "string",
|
29
|
+
"description": "The ID of the memory block to retrieve.",
|
30
|
+
"required": True,
|
31
|
+
}
|
32
|
+
]
|
33
|
+
|
34
|
+
async def execute(self, **kwargs: Any) -> Dict[str, Any]:
|
35
|
+
block_id = kwargs.get("block_id")
|
36
|
+
if not block_id:
|
37
|
+
raise ValueError("The 'block_id' parameter is required.")
|
38
|
+
|
39
|
+
block = await self.memory_manager.get_core_memory_block(block_id)
|
40
|
+
|
41
|
+
if block is None:
|
42
|
+
return {"status": "error", "message": f"Block '{block_id}' not found."}
|
43
|
+
|
44
|
+
return {"status": "success", "block": block}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# neuro_simulator/agent/tools/get_core_memory_blocks.py
|
2
|
+
"""The Get Core Memory Blocks tool for the agent."""
|
3
|
+
|
4
|
+
from typing import Dict, Any, List
|
5
|
+
|
6
|
+
from .base import BaseTool
|
7
|
+
from ..memory.manager import MemoryManager
|
8
|
+
|
9
|
+
class GetCoreMemoryBlocksTool(BaseTool):
|
10
|
+
"""Tool to retrieve all core memory blocks."""
|
11
|
+
|
12
|
+
def __init__(self, memory_manager: MemoryManager):
|
13
|
+
self.memory_manager = memory_manager
|
14
|
+
|
15
|
+
@property
|
16
|
+
def name(self) -> str:
|
17
|
+
return "get_core_memory_blocks"
|
18
|
+
|
19
|
+
@property
|
20
|
+
def description(self) -> str:
|
21
|
+
return "Retrieves a list of all available core memory blocks, including their IDs, titles, and descriptions."
|
22
|
+
|
23
|
+
@property
|
24
|
+
def parameters(self) -> List[Dict[str, Any]]:
|
25
|
+
return []
|
26
|
+
|
27
|
+
async def execute(self, **kwargs: Any) -> Dict[str, Any]:
|
28
|
+
blocks = await self.memory_manager.get_core_memory_blocks()
|
29
|
+
# The result should be JSON serializable, which a Dict of Dicts already is.
|
30
|
+
return {"status": "success", "blocks": blocks}
|
@@ -0,0 +1,120 @@
|
|
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 importlib
|
6
|
+
import inspect
|
7
|
+
import logging
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import Dict, Any, List
|
10
|
+
|
11
|
+
from .base import BaseTool
|
12
|
+
from ..memory.manager import MemoryManager
|
13
|
+
|
14
|
+
logger = logging.getLogger(__name__.replace("neuro_simulator", "agent", 1))
|
15
|
+
|
16
|
+
class ToolManager:
|
17
|
+
"""
|
18
|
+
Acts as a central registry and executor for all available tools.
|
19
|
+
This manager dynamically loads tools from the 'tools' directory, making the system extensible.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(self, memory_manager: MemoryManager):
|
23
|
+
"""
|
24
|
+
Initializes the ToolManager.
|
25
|
+
Args:
|
26
|
+
memory_manager: An instance of MemoryManager, passed to tools that need it.
|
27
|
+
"""
|
28
|
+
self.memory_manager = memory_manager
|
29
|
+
self.tools: Dict[str, BaseTool] = {}
|
30
|
+
self._load_and_register_tools()
|
31
|
+
|
32
|
+
# Hardcoded initial allocation of tool tags to agent types
|
33
|
+
self.agent_tool_allocations = {
|
34
|
+
"neuro_agent": ["communication", "memory_read"],
|
35
|
+
"memory_agent": ["memory_write", "memory_read"]
|
36
|
+
}
|
37
|
+
logger.info(f"Initial tool allocations set: {self.agent_tool_allocations}")
|
38
|
+
|
39
|
+
def _load_and_register_tools(self):
|
40
|
+
"""Dynamically scans the 'tools' directory, imports modules, and registers tool instances."""
|
41
|
+
self.tools = {}
|
42
|
+
tools_dir = Path(__file__).parent
|
43
|
+
package_name = self.__module__.rsplit('.', 1)[0]
|
44
|
+
|
45
|
+
for filename in os.listdir(tools_dir):
|
46
|
+
if filename.endswith('.py') and not filename.startswith(('__', 'base', 'manager')):
|
47
|
+
module_name = f".{filename[:-3]}"
|
48
|
+
try:
|
49
|
+
module = importlib.import_module(module_name, package=package_name)
|
50
|
+
for _, cls in inspect.getmembers(module, inspect.isclass):
|
51
|
+
if issubclass(cls, BaseTool) and cls is not BaseTool:
|
52
|
+
tool_instance = cls(memory_manager=self.memory_manager)
|
53
|
+
if tool_instance.name in self.tools:
|
54
|
+
logger.warning(f"Duplicate tool name '{tool_instance.name}' found. Overwriting.")
|
55
|
+
self.tools[tool_instance.name] = tool_instance
|
56
|
+
logger.info(f"Successfully loaded and registered tool: {tool_instance.name}")
|
57
|
+
except Exception as e:
|
58
|
+
logger.error(f"Failed to load tool from {filename}: {e}", exc_info=True)
|
59
|
+
|
60
|
+
def get_all_tool_schemas(self) -> List[Dict[str, Any]]:
|
61
|
+
"""
|
62
|
+
Returns a list of serializable schemas for all registered tools.
|
63
|
+
This is the 'API documentation' for the agent.
|
64
|
+
"""
|
65
|
+
return [tool.get_schema() for tool in self.tools.values()]
|
66
|
+
|
67
|
+
def get_tool_schemas_for_agent(self, agent_name: str) -> List[Dict[str, Any]]:
|
68
|
+
"""
|
69
|
+
Returns a list of tool schemas available to a specific agent
|
70
|
+
based on the configured name allocations.
|
71
|
+
"""
|
72
|
+
allowed_names = set(self.agent_tool_allocations.get(agent_name, []))
|
73
|
+
if not allowed_names:
|
74
|
+
return []
|
75
|
+
|
76
|
+
return [tool.get_schema() for tool in self.tools.values() if tool.name in allowed_names]
|
77
|
+
|
78
|
+
def reload_tools(self):
|
79
|
+
"""Forces a re-scan and registration of tools from the tools directory."""
|
80
|
+
logger.info("Reloading tools...")
|
81
|
+
self._load_and_register_tools()
|
82
|
+
logger.info(f"Tools reloaded. {len(self.tools)} tools available.")
|
83
|
+
|
84
|
+
def get_allocations(self) -> Dict[str, List[str]]:
|
85
|
+
"""Returns the current agent-to-tool-tag allocation mapping."""
|
86
|
+
return self.agent_tool_allocations
|
87
|
+
|
88
|
+
def set_allocations(self, allocations: Dict[str, List[str]]):
|
89
|
+
"""
|
90
|
+
Sets a new agent-to-tool allocation mapping.
|
91
|
+
|
92
|
+
Args:
|
93
|
+
allocations: A dictionary mapping agent names to lists of tool names.
|
94
|
+
"""
|
95
|
+
# Basic validation can be added here if needed
|
96
|
+
self.agent_tool_allocations = allocations
|
97
|
+
logger.info(f"Tool allocations updated: {self.agent_tool_allocations}")
|
98
|
+
|
99
|
+
async def execute_tool(self, tool_name: str, **kwargs: Any) -> Dict[str, Any]:
|
100
|
+
"""
|
101
|
+
Executes a tool by its name with the given parameters.
|
102
|
+
|
103
|
+
Args:
|
104
|
+
tool_name: The name of the tool to execute.
|
105
|
+
**kwargs: The parameters to pass to the tool's execute method.
|
106
|
+
|
107
|
+
Returns:
|
108
|
+
A JSON-serializable dictionary with the execution result.
|
109
|
+
"""
|
110
|
+
if tool_name not in self.tools:
|
111
|
+
logger.error(f"Attempted to execute non-existent tool: {tool_name}")
|
112
|
+
return {"error": f"Tool '{tool_name}' not found."}
|
113
|
+
|
114
|
+
tool = self.tools[tool_name]
|
115
|
+
try:
|
116
|
+
result = await tool.execute(**kwargs)
|
117
|
+
return result
|
118
|
+
except Exception as e:
|
119
|
+
logger.error(f"Error executing tool '{tool_name}' with params {kwargs}: {e}", exc_info=True)
|
120
|
+
return {"error": f"An unexpected error occurred while executing the tool: {str(e)}"}
|