neuro-simulator 0.3.2__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.
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/PKG-INFO +1 -1
- neuro_simulator-0.4.0/neuro_simulator/agent/core.py +201 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/agent/llm.py +1 -1
- neuro_simulator-0.4.0/neuro_simulator/agent/memory/manager.py +125 -0
- neuro_simulator-0.4.0/neuro_simulator/agent/memory_prompt.txt +14 -0
- neuro_simulator-0.4.0/neuro_simulator/agent/neuro_prompt.txt +32 -0
- neuro_simulator-0.4.0/neuro_simulator/agent/tools/add_temp_memory.py +61 -0
- neuro_simulator-0.4.0/neuro_simulator/agent/tools/add_to_core_memory_block.py +64 -0
- neuro_simulator-0.4.0/neuro_simulator/agent/tools/base.py +56 -0
- neuro_simulator-0.4.0/neuro_simulator/agent/tools/create_core_memory_block.py +78 -0
- neuro_simulator-0.4.0/neuro_simulator/agent/tools/delete_core_memory_block.py +44 -0
- neuro_simulator-0.4.0/neuro_simulator/agent/tools/get_core_memory_block.py +44 -0
- neuro_simulator-0.4.0/neuro_simulator/agent/tools/get_core_memory_blocks.py +30 -0
- neuro_simulator-0.4.0/neuro_simulator/agent/tools/manager.py +143 -0
- neuro_simulator-0.4.0/neuro_simulator/agent/tools/remove_from_core_memory_block.py +65 -0
- neuro_simulator-0.4.0/neuro_simulator/agent/tools/speak.py +56 -0
- neuro_simulator-0.4.0/neuro_simulator/agent/tools/update_core_memory_block.py +65 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/api/system.py +5 -2
- neuro_simulator-0.4.0/neuro_simulator/cli.py +125 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/core/agent_factory.py +0 -1
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/core/application.py +72 -43
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/core/config.py +66 -63
- neuro_simulator-0.4.0/neuro_simulator/core/path_manager.py +69 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/services/audience.py +0 -2
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/services/audio.py +0 -1
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/services/builtin.py +10 -25
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/services/letta.py +19 -1
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/services/stream.py +24 -21
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/utils/logging.py +9 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/utils/queue.py +27 -4
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/utils/websocket.py +1 -3
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator.egg-info/PKG-INFO +1 -1
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator.egg-info/SOURCES.txt +14 -4
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/pyproject.toml +2 -2
- neuro_simulator-0.3.2/neuro_simulator/agent/base.py +0 -43
- neuro_simulator-0.3.2/neuro_simulator/agent/core.py +0 -234
- neuro_simulator-0.3.2/neuro_simulator/agent/factory.py +0 -30
- neuro_simulator-0.3.2/neuro_simulator/agent/memory/manager.py +0 -204
- neuro_simulator-0.3.2/neuro_simulator/agent/tools/core.py +0 -102
- neuro_simulator-0.3.2/neuro_simulator/api/stream.py +0 -1
- neuro_simulator-0.3.2/neuro_simulator/cli.py +0 -95
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/README.md +0 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/__init__.py +0 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/agent/__init__.py +0 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/agent/memory/__init__.py +0 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/agent/tools/__init__.py +0 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/api/__init__.py +0 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/core/__init__.py +0 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/core/agent_interface.py +0 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/services/__init__.py +0 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/utils/__init__.py +0 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/utils/process.py +0 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator/utils/state.py +0 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator.egg-info/dependency_links.txt +0 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator.egg-info/entry_points.txt +0 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator.egg-info/requires.txt +0 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/neuro_simulator.egg-info/top_level.txt +0 -0
- {neuro_simulator-0.3.2 → neuro_simulator-0.4.0}/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,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()
|
@@ -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 Any, Dict, List
|
5
|
+
|
6
|
+
from neuro_simulator.agent.tools.base import BaseTool
|
7
|
+
from neuro_simulator.agent.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 neuro_simulator.agent.tools.base import BaseTool
|
7
|
+
from neuro_simulator.agent.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 neuro_simulator.agent.tools.base import BaseTool
|
7
|
+
from neuro_simulator.agent.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
|
+
}
|