neuro-simulator 0.3.3__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. neuro_simulator/agent/core.py +73 -138
  2. neuro_simulator/agent/llm.py +1 -1
  3. neuro_simulator/agent/memory/manager.py +24 -103
  4. neuro_simulator/agent/tools/add_temp_memory.py +3 -3
  5. neuro_simulator/agent/tools/add_to_core_memory_block.py +2 -2
  6. neuro_simulator/agent/tools/create_core_memory_block.py +2 -2
  7. neuro_simulator/agent/tools/delete_core_memory_block.py +2 -2
  8. neuro_simulator/agent/tools/get_core_memory_block.py +2 -2
  9. neuro_simulator/agent/tools/get_core_memory_blocks.py +2 -2
  10. neuro_simulator/agent/tools/manager.py +86 -63
  11. neuro_simulator/agent/tools/remove_from_core_memory_block.py +2 -2
  12. neuro_simulator/agent/tools/speak.py +2 -2
  13. neuro_simulator/agent/tools/update_core_memory_block.py +2 -2
  14. neuro_simulator/api/system.py +5 -2
  15. neuro_simulator/cli.py +83 -53
  16. neuro_simulator/core/agent_factory.py +0 -1
  17. neuro_simulator/core/application.py +46 -32
  18. neuro_simulator/core/config.py +66 -63
  19. neuro_simulator/core/path_manager.py +69 -0
  20. neuro_simulator/services/audience.py +0 -2
  21. neuro_simulator/services/audio.py +0 -1
  22. neuro_simulator/services/builtin.py +10 -25
  23. neuro_simulator/services/letta.py +19 -1
  24. neuro_simulator/services/stream.py +24 -21
  25. neuro_simulator/utils/logging.py +9 -0
  26. neuro_simulator/utils/queue.py +27 -4
  27. neuro_simulator/utils/websocket.py +1 -3
  28. {neuro_simulator-0.3.3.dist-info → neuro_simulator-0.4.0.dist-info}/METADATA +1 -1
  29. neuro_simulator-0.4.0.dist-info/RECORD +46 -0
  30. neuro_simulator/agent/base.py +0 -43
  31. neuro_simulator/agent/factory.py +0 -30
  32. neuro_simulator/api/stream.py +0 -1
  33. neuro_simulator-0.3.3.dist-info/RECORD +0 -48
  34. {neuro_simulator-0.3.3.dist-info → neuro_simulator-0.4.0.dist-info}/WHEEL +0 -0
  35. {neuro_simulator-0.3.3.dist-info → neuro_simulator-0.4.0.dist-info}/entry_points.txt +0 -0
  36. {neuro_simulator-0.3.3.dist-info → neuro_simulator-0.4.0.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,9 @@
1
1
  # backend/config.py
2
- import os
2
+ import shutil
3
+ from pathlib import Path
3
4
  import yaml
4
5
  from pydantic import BaseModel, Field
5
- from typing import List, Optional, Dict, Any
6
+ from typing import Dict, Optional, List
6
7
  import logging
7
8
  import asyncio
8
9
  from collections.abc import Mapping
@@ -106,76 +107,77 @@ class ConfigManager:
106
107
  self._update_callbacks = []
107
108
  self._initialized = True
108
109
 
109
- def _get_config_file_path(self) -> str:
110
- """获取配置文件路径"""
111
- import sys
112
- import argparse
113
-
114
- # 解析命令行参数以获取工作目录
115
- parser = argparse.ArgumentParser()
116
- parser.add_argument('--dir', '-D', type=str, help='Working directory')
117
- # 只解析已知参数,避免干扰其他模块的参数解析
118
- args, _ = parser.parse_known_args()
119
-
120
- if args.dir:
121
- # 如果指定了工作目录,使用该目录下的配置文件
122
- config_path = os.path.join(args.dir, "config.yaml")
123
- else:
124
- # 默认使用 ~/.config/neuro-simulator 目录
125
- config_path = os.path.join(os.path.expanduser("~"), ".config", "neuro-simulator", "config.yaml")
126
-
127
- return config_path
110
+ import sys
128
111
 
129
- def _load_config_from_yaml(self) -> dict:
130
- # 获取配置文件路径
131
- config_path = self._get_config_file_path()
132
-
133
- # 检查配置文件是否存在
134
- if not os.path.exists(config_path):
135
- raise FileNotFoundError(f"Configuration file '{config_path}' not found. "
136
- "Please create it from config.yaml.example.")
137
-
112
+ class ConfigManager:
113
+ _instance = None
114
+
115
+ def __new__(cls):
116
+ if cls._instance is None:
117
+ cls._instance = super(ConfigManager, cls).__new__(cls)
118
+ cls._instance._initialized = False
119
+ return cls._instance
120
+
121
+ def __init__(self):
122
+ if self._initialized:
123
+ return
124
+ self.settings: Optional[AppSettings] = None
125
+ self._update_callbacks = []
126
+ self._initialized = True
127
+
128
+ def load_and_validate(self, config_path_str: str, example_path_str: str):
129
+ """Loads the main config file, handling first-run scenarios, and validates it."""
130
+ config_path = Path(config_path_str)
131
+ example_path = Path(example_path_str)
132
+
133
+ # Scenario 1: Both config and example are missing in the working directory.
134
+ if not config_path.exists() and not example_path.exists():
135
+ try:
136
+ import pkg_resources
137
+ package_example_path_str = pkg_resources.resource_filename('neuro_simulator', 'core/config.yaml.example')
138
+ shutil.copy(package_example_path_str, example_path)
139
+ logging.info(f"Created '{example_path}' from package resource.")
140
+ logging.error(f"Configuration file '{config_path.name}' not found. A new '{example_path.name}' has been created. Please configure it and rename it to '{config_path.name}'.")
141
+ sys.exit(1)
142
+ except Exception as e:
143
+ logging.error(f"FATAL: Could not create config from package resources: {e}")
144
+ sys.exit(1)
145
+
146
+ # Scenario 2: Config is missing, but example exists.
147
+ elif not config_path.exists() and example_path.exists():
148
+ logging.error(f"Configuration file '{config_path.name}' not found, but '{example_path.name}' exists. Please rename it to '{config_path.name}' after configuration.")
149
+ sys.exit(1)
150
+
151
+ # Scenario 3: Config exists, but example is missing.
152
+ elif config_path.exists() and not example_path.exists():
153
+ try:
154
+ import pkg_resources
155
+ package_example_path_str = pkg_resources.resource_filename('neuro_simulator', 'core/config.yaml.example')
156
+ shutil.copy(package_example_path_str, example_path)
157
+ logging.info(f"Created missing '{example_path.name}' from package resource.")
158
+ except Exception as e:
159
+ logging.warning(f"Could not create missing '{example_path.name}': {e}")
160
+
161
+ # Proceed with loading the config if it exists.
138
162
  try:
139
163
  with open(config_path, 'r', encoding='utf-8') as f:
140
- content = yaml.safe_load(f)
141
- if content is None:
164
+ yaml_config = yaml.safe_load(f)
165
+ if yaml_config is None:
142
166
  raise ValueError(f"Configuration file '{config_path}' is empty.")
143
- return content
144
- except Exception as e:
145
- logging.error(f"Error loading or parsing {config_path}: {e}")
146
- raise
147
-
148
- def _load_settings(self) -> AppSettings:
149
- yaml_config = self._load_config_from_yaml()
150
-
151
- base_settings = AppSettings.model_validate(yaml_config)
152
-
153
- # 检查关键配置项
154
- if base_settings.agent_type == "letta":
155
- missing_keys = []
156
- if not base_settings.api_keys.letta_token:
157
- missing_keys.append("api_keys.letta_token")
158
- if not base_settings.api_keys.neuro_agent_id:
159
- missing_keys.append("api_keys.neuro_agent_id")
160
167
 
161
- if missing_keys:
162
- raise ValueError(f"Critical config missing in config.yaml for letta agent: {', '.join(missing_keys)}. "
163
- f"Please check your config.yaml file against config.yaml.example.")
168
+ self.settings = AppSettings.model_validate(yaml_config)
169
+ logging.info("Main configuration loaded successfully.")
164
170
 
165
- logging.info("Configuration loaded successfully.")
166
- return base_settings
171
+ except Exception as e:
172
+ logging.error(f"Error loading or parsing {config_path}: {e}")
173
+ sys.exit(1) # Exit if the main config is invalid
167
174
 
168
175
  def save_settings(self):
169
176
  """Saves the current configuration to config.yaml while preserving comments and formatting."""
177
+ from .path_manager import path_manager
178
+ config_file_path = str(path_manager.working_dir / "config.yaml")
179
+
170
180
  try:
171
- # 获取配置文件路径
172
- config_file_path = self._get_config_file_path()
173
-
174
- # 检查配置文件目录是否存在,如果不存在则创建
175
- config_dir = os.path.dirname(config_file_path)
176
- if config_dir and not os.path.exists(config_dir):
177
- os.makedirs(config_dir, exist_ok=True)
178
-
179
181
  # 1. Read the existing config file as text to preserve comments and formatting
180
182
  with open(config_file_path, 'r', encoding='utf-8') as f:
181
183
  config_lines = f.readlines()
@@ -184,7 +186,8 @@ class ConfigManager:
184
186
  config_to_save = self.settings.model_dump(mode='json', exclude={'api_keys'})
185
187
 
186
188
  # 3. Read the existing config on disk to get the api_keys that should be preserved.
187
- existing_config = self._load_config_from_yaml()
189
+ with open(config_file_path, 'r', encoding='utf-8') as f:
190
+ existing_config = yaml.safe_load(f)
188
191
  if 'api_keys' in existing_config:
189
192
  # 4. Add the preserved api_keys block back to the data to be saved.
190
193
  config_to_save['api_keys'] = existing_config['api_keys']
@@ -0,0 +1,69 @@
1
+ # neuro_simulator/core/path_manager.py
2
+ """Manages all file and directory paths for the application's working directory."""
3
+
4
+ import os
5
+ from pathlib import Path
6
+
7
+ class PathManager:
8
+ """A centralized manager for all dynamic paths within the working directory."""
9
+
10
+ def __init__(self, working_dir: str):
11
+ """Initializes the PathManager and defines the directory structure."""
12
+ self.working_dir = Path(working_dir).resolve()
13
+
14
+ # Top-level directories
15
+ self.agents_dir = self.working_dir / "agents"
16
+ self.assets_dir = self.working_dir / "assets"
17
+
18
+ # Agents subdirectories
19
+ self.neuro_agent_dir = self.agents_dir / "neuro"
20
+ self.memory_agent_dir = self.agents_dir / "memory_manager"
21
+ self.shared_memories_dir = self.agents_dir / "memories"
22
+ self.user_tools_dir = self.agents_dir / "tools"
23
+ self.builtin_tools_dir = self.user_tools_dir / "builtin_tools"
24
+
25
+ # Agent-specific config files
26
+ self.neuro_config_path = self.neuro_agent_dir / "config.yaml"
27
+ self.neuro_tools_path = self.neuro_agent_dir / "tools.json"
28
+ self.neuro_history_path = self.neuro_agent_dir / "history.jsonl"
29
+ self.neuro_prompt_path = self.neuro_agent_dir / "neuro_prompt.txt"
30
+
31
+ self.memory_agent_config_path = self.memory_agent_dir / "config.yaml"
32
+ self.memory_agent_tools_path = self.memory_agent_dir / "tools.json"
33
+ self.memory_agent_history_path = self.memory_agent_dir / "history.jsonl"
34
+ self.memory_agent_prompt_path = self.memory_agent_dir / "memory_prompt.txt"
35
+
36
+ # Shared memory files
37
+ self.init_memory_path = self.shared_memories_dir / "init_memory.json"
38
+ self.core_memory_path = self.shared_memories_dir / "core_memory.json"
39
+ self.temp_memory_path = self.shared_memories_dir / "temp_memory.json"
40
+
41
+ def initialize_directories(self):
42
+ """Creates all necessary directories if they don't exist."""
43
+ dirs_to_create = [
44
+ self.agents_dir,
45
+ self.assets_dir,
46
+ self.neuro_agent_dir,
47
+ self.memory_agent_dir,
48
+ self.shared_memories_dir,
49
+ self.user_tools_dir,
50
+ self.builtin_tools_dir
51
+ ]
52
+ for dir_path in dirs_to_create:
53
+ os.makedirs(dir_path, exist_ok=True)
54
+
55
+ # Create the warning file in the builtin_tools directory
56
+ warning_file_path = self.builtin_tools_dir / "!!!NO-CHANGE-WILL-BE-SAVED-AFTER-RESTART!!!"
57
+ if not warning_file_path.exists():
58
+ warning_file_path.touch()
59
+
60
+ # A global instance that can be imported and used by other modules.
61
+ # It will be initialized on application startup.
62
+ path_manager: PathManager = None
63
+
64
+ def initialize_path_manager(working_dir: str):
65
+ """Initializes the global path_manager instance."""
66
+ global path_manager
67
+ if path_manager is None:
68
+ path_manager = PathManager(working_dir)
69
+ path_manager.initialize_directories()
@@ -1,7 +1,5 @@
1
1
  # neuro_simulator/services/audience.py
2
- import asyncio
3
2
  import logging
4
- import random
5
3
 
6
4
  from google import genai
7
5
  from google.genai import types
@@ -3,7 +3,6 @@ import asyncio
3
3
  import base64
4
4
  import html
5
5
  import logging
6
- from pathlib import Path
7
6
 
8
7
  import azure.cognitiveservices.speech as speechsdk
9
8
 
@@ -1,27 +1,24 @@
1
1
  # neuro_simulator/services/builtin.py
2
2
  """Builtin agent module for Neuro Simulator"""
3
3
 
4
- import asyncio
5
- import re
6
4
  import logging
7
5
  from typing import List, Dict, Any, Optional
8
6
 
9
7
  from ..core.agent_interface import BaseAgent
10
8
  from ..agent.core import Agent as LocalAgent
11
- from ..services.stream import live_stream_manager
9
+ from ..utils.websocket import connection_manager
12
10
 
13
11
  logger = logging.getLogger(__name__.replace("neuro_simulator", "server", 1))
14
12
 
15
13
  async def initialize_builtin_agent() -> Optional[LocalAgent]:
16
- """Initializes the builtin agent instance and returns it."""
14
+ """Initializes a new builtin agent instance and returns it."""
17
15
  try:
18
- working_dir = live_stream_manager._working_dir
19
- agent_instance = LocalAgent(working_dir=working_dir)
16
+ agent_instance = LocalAgent()
20
17
  await agent_instance.initialize()
21
- logger.info("Builtin agent implementation initialized successfully.")
18
+ logger.info("New builtin agent instance initialized successfully.")
22
19
  return agent_instance
23
20
  except Exception as e:
24
- logger.error(f"Failed to initialize local agent implementation: {e}", exc_info=True)
21
+ logger.error(f"Failed to initialize local agent instance: {e}", exc_info=True)
25
22
  return None
26
23
 
27
24
  class BuiltinAgentWrapper(BaseAgent):
@@ -50,24 +47,18 @@ class BuiltinAgentWrapper(BaseAgent):
50
47
 
51
48
  async def create_memory_block(self, title: str, description: str, content: List[str]) -> Dict[str, str]:
52
49
  block_id = await self.agent_instance.memory_manager.create_core_memory_block(title, description, content)
53
- # Broadcast core_memory_updated event
54
50
  updated_blocks = await self.get_memory_blocks()
55
- from ..utils.websocket import connection_manager
56
51
  await connection_manager.broadcast_to_admins({"type": "core_memory_updated", "payload": updated_blocks})
57
52
  return {"block_id": block_id}
58
53
 
59
54
  async def update_memory_block(self, block_id: str, title: Optional[str], description: Optional[str], content: Optional[List[str]]):
60
55
  await self.agent_instance.memory_manager.update_core_memory_block(block_id, title, description, content)
61
- # Broadcast core_memory_updated event
62
56
  updated_blocks = await self.get_memory_blocks()
63
- from ..utils.websocket import connection_manager
64
57
  await connection_manager.broadcast_to_admins({"type": "core_memory_updated", "payload": updated_blocks})
65
58
 
66
59
  async def delete_memory_block(self, block_id: str):
67
60
  await self.agent_instance.memory_manager.delete_core_memory_block(block_id)
68
- # Broadcast core_memory_updated event
69
61
  updated_blocks = await self.get_memory_blocks()
70
- from ..utils.websocket import connection_manager
71
62
  await connection_manager.broadcast_to_admins({"type": "core_memory_updated", "payload": updated_blocks})
72
63
 
73
64
  # Init Memory Management
@@ -76,9 +67,7 @@ class BuiltinAgentWrapper(BaseAgent):
76
67
 
77
68
  async def update_init_memory(self, memory: Dict[str, Any]):
78
69
  await self.agent_instance.memory_manager.update_init_memory(memory)
79
- # Broadcast init_memory_updated event
80
70
  updated_init_mem = await self.get_init_memory()
81
- from ..utils.websocket import connection_manager
82
71
  await connection_manager.broadcast_to_admins({"type": "init_memory_updated", "payload": updated_init_mem})
83
72
 
84
73
  # Temp Memory Management
@@ -87,31 +76,27 @@ class BuiltinAgentWrapper(BaseAgent):
87
76
 
88
77
  async def add_temp_memory(self, content: str, role: str):
89
78
  await self.agent_instance.memory_manager.add_temp_memory(content, role)
90
- # Broadcast temp_memory_updated event
91
79
  updated_temp_mem = await self.get_temp_memory()
92
- from ..utils.websocket import connection_manager
93
80
  await connection_manager.broadcast_to_admins({"type": "temp_memory_updated", "payload": updated_temp_mem})
94
81
 
95
82
  async def clear_temp_memory(self):
96
83
  await self.agent_instance.memory_manager.reset_temp_memory()
97
- # Broadcast temp_memory_updated event
98
84
  updated_temp_mem = await self.get_temp_memory()
99
- from ..utils.websocket import connection_manager
100
85
  await connection_manager.broadcast_to_admins({"type": "temp_memory_updated", "payload": updated_temp_mem})
101
86
 
102
87
  # Tool Management
103
88
  async def get_available_tools(self) -> str:
104
- return self.agent_instance.tool_manager.get_tool_descriptions()
89
+ # This method is now for internal use, the dashboard uses the new API
90
+ schemas = self.agent_instance.tool_manager.get_tool_schemas_for_agent('neuro_agent')
91
+ return self.agent_instance._format_tool_schemas_for_prompt(schemas)
105
92
 
106
93
  async def execute_tool(self, tool_name: str, params: Dict[str, Any]) -> Any:
107
- result = await self.agent_instance.execute_tool(tool_name, params)
108
- # If the tool was add_temp_memory, broadcast temp_memory_updated event
94
+ result = await self.agent_instance.tool_manager.execute_tool(tool_name, **params)
109
95
  if tool_name == "add_temp_memory":
110
96
  updated_temp_mem = await self.get_temp_memory()
111
- from ..utils.websocket import connection_manager
112
97
  await connection_manager.broadcast_to_admins({"type": "temp_memory_updated", "payload": updated_temp_mem})
113
98
  return result
114
99
 
115
100
  # Context/Message History
116
101
  async def get_message_history(self, limit: int = 20) -> List[Dict[str, Any]]:
117
- return await self.agent_instance.memory_manager.get_recent_chat(limit)
102
+ return await self.agent_instance.get_neuro_history(limit)
@@ -67,11 +67,29 @@ class LettaAgent(BaseAgent):
67
67
  raise ValueError("Letta agent ID (neuro_agent_id) is not configured.")
68
68
 
69
69
  async def reset_memory(self):
70
+ """Resets message history and clears the conversation_summary block."""
70
71
  try:
72
+ # Reset message history
71
73
  await asyncio.to_thread(self.client.agents.messages.reset, agent_id=self.agent_id)
72
74
  logger.info(f"Letta Agent (ID: {self.agent_id}) message history has been reset.")
75
+
76
+ # Find and clear the conversation_summary block
77
+ blocks = await asyncio.to_thread(self.client.agents.blocks.list, agent_id=self.agent_id)
78
+ summary_block = next((block for block in blocks if block.name == "conversation_summary"), None)
79
+
80
+ if summary_block:
81
+ await asyncio.to_thread(
82
+ self.client.agents.blocks.modify,
83
+ agent_id=self.agent_id,
84
+ block_id=summary_block.id,
85
+ content=""
86
+ )
87
+ logger.info(f"Cleared content of 'conversation_summary' block (ID: {summary_block.id}) for Letta Agent.")
88
+ else:
89
+ logger.warning("'conversation_summary' block not found for Letta Agent, skipping clearing.")
90
+
73
91
  except Exception as e:
74
- logger.warning(f"Failed to reset Letta Agent message history: {e}")
92
+ logger.warning(f"Failed during Letta Agent memory reset: {e}")
75
93
 
76
94
  async def process_messages(self, messages: List[Dict[str, str]]) -> Dict[str, Any]:
77
95
  # Check if this is a superchat message based on the specific structure
@@ -11,6 +11,28 @@ from ..utils.state import app_state
11
11
 
12
12
  logger = logging.getLogger(__name__.replace("neuro_simulator", "server", 1))
13
13
 
14
+ _WORKING_DIR = os.getcwd()
15
+ _WELCOME_VIDEO_PATH_BACKEND = os.path.join(_WORKING_DIR, "assets", "neuro_start.mp4")
16
+ _WELCOME_VIDEO_DURATION_SEC_DEFAULT = 10.0
17
+
18
+ @staticmethod
19
+ def _get_video_duration(video_path: str) -> float:
20
+ """Gets the duration of an MP4 video file using mutagen."""
21
+ if not os.path.exists(video_path):
22
+ logger.warning(f"Video file '{video_path}' not found. Using default duration.")
23
+ return _WELCOME_VIDEO_DURATION_SEC_DEFAULT
24
+ try:
25
+ video = MP4(video_path)
26
+ duration = video.info.length
27
+ logger.info(f"Successfully read video duration for '{video_path}': {duration:.2f}s.")
28
+ return duration
29
+ except MP4StreamInfoError:
30
+ logger.warning(f"Could not parse stream info for '{video_path}'. Using default duration.")
31
+ return _WELCOME_VIDEO_DURATION_SEC_DEFAULT
32
+ except Exception as e:
33
+ logger.error(f"Error getting video duration: {e}. Using default duration.")
34
+ return _WELCOME_VIDEO_DURATION_SEC_DEFAULT
35
+
14
36
  class LiveStreamManager:
15
37
  class NeuroAvatarStage:
16
38
  HIDDEN = "hidden"
@@ -25,28 +47,9 @@ class LiveStreamManager:
25
47
 
26
48
  event_queue: asyncio.Queue = asyncio.Queue()
27
49
 
28
- _working_dir = os.getcwd()
29
- _WELCOME_VIDEO_PATH_BACKEND = os.path.join(_working_dir, "assets", "neuro_start.mp4")
50
+ _WORKING_DIR = os.getcwd()
51
+ _WELCOME_VIDEO_PATH_BACKEND = os.path.join(_WORKING_DIR, "assets", "neuro_start.mp4")
30
52
  _WELCOME_VIDEO_DURATION_SEC_DEFAULT = 10.0
31
-
32
- @staticmethod
33
- def _get_video_duration(video_path: str) -> float:
34
- """Gets the duration of an MP4 video file using mutagen."""
35
- if not os.path.exists(video_path):
36
- logger.warning(f"Video file '{video_path}' not found. Using default duration.")
37
- return LiveStreamManager._WELCOME_VIDEO_DURATION_SEC_DEFAULT
38
- try:
39
- video = MP4(video_path)
40
- duration = video.info.length
41
- logger.info(f"Successfully read video duration for '{video_path}': {duration:.2f}s.")
42
- return duration
43
- except MP4StreamInfoError:
44
- logger.warning(f"Could not parse stream info for '{video_path}'. Using default duration.")
45
- return LiveStreamManager._WELCOME_VIDEO_DURATION_SEC_DEFAULT
46
- except Exception as e:
47
- logger.error(f"Error getting video duration: {e}. Using default duration.")
48
- return LiveStreamManager._WELCOME_VIDEO_DURATION_SEC_DEFAULT
49
-
50
53
  _WELCOME_VIDEO_DURATION_SEC = _get_video_duration(_WELCOME_VIDEO_PATH_BACKEND)
51
54
  AVATAR_INTRO_TOTAL_DURATION_SEC = 3.0
52
55
 
@@ -87,4 +87,13 @@ def configure_server_logging():
87
87
  uvicorn_logger.handlers = [server_queue_handler, console_handler]
88
88
  uvicorn_logger.propagate = False # Prevent double-logging
89
89
 
90
+ # Configure the neuro_agent logger
91
+ neuro_agent_logger = logging.getLogger("neuro_agent")
92
+ neuro_agent_queue_handler = QueueLogHandler(agent_log_queue)
93
+ neuro_agent_queue_handler.setFormatter(queue_formatter)
94
+ neuro_agent_logger.addHandler(neuro_agent_queue_handler)
95
+ neuro_agent_logger.addHandler(console_handler) # Also send agent logs to console
96
+ neuro_agent_logger.setLevel(logging.INFO)
97
+ neuro_agent_logger.propagate = False # Prevent double-logging
98
+
90
99
  root_logger.info("Server logging configured with unified formatting for queue and console.")
@@ -3,16 +3,39 @@
3
3
 
4
4
  import logging
5
5
  from collections import deque
6
- from pathlib import Path
7
6
 
8
7
  from ..core.config import config_manager
9
8
  from ..utils.state import app_state
10
9
 
11
10
  logger = logging.getLogger(__name__.replace("neuro_simulator", "server", 1))
12
11
 
13
- # Use settings from the config manager to initialize deque maxlen
14
- audience_chat_buffer: deque[dict] = deque(maxlen=config_manager.settings.performance.audience_chat_buffer_max_size)
15
- neuro_input_queue: deque[dict] = deque(maxlen=config_manager.settings.performance.neuro_input_queue_max_size)
12
+ # Deques for chat messages
13
+ audience_chat_buffer: deque[dict] = deque()
14
+ neuro_input_queue: deque[dict] = deque()
15
+
16
+ def initialize_queues():
17
+ """
18
+ Initializes the chat queues with sizes from the loaded configuration.
19
+ This must be called after the config is loaded.
20
+ """
21
+ global audience_chat_buffer, neuro_input_queue
22
+
23
+ settings = config_manager.settings
24
+ if not settings:
25
+ logger.error("Queue initialization failed: Config not loaded.")
26
+ return
27
+
28
+ logger.info("Initializing queues with configured sizes.")
29
+
30
+ # Re-initialize the deques with the correct maxlen
31
+ audience_chat_buffer = deque(
32
+ audience_chat_buffer,
33
+ maxlen=settings.performance.audience_chat_buffer_max_size
34
+ )
35
+ neuro_input_queue = deque(
36
+ neuro_input_queue,
37
+ maxlen=settings.performance.neuro_input_queue_max_size
38
+ )
16
39
 
17
40
  def clear_all_queues():
18
41
  """Clears all chat queues."""
@@ -1,8 +1,6 @@
1
1
  # neuro_simulator/utils/websocket.py
2
- import asyncio
3
2
  import json
4
3
  import logging
5
- from collections import deque
6
4
 
7
5
  from fastapi import WebSocket
8
6
  from starlette.websockets import WebSocketState
@@ -39,7 +37,7 @@ class WebSocketManager:
39
37
 
40
38
  async def broadcast(self, message: dict):
41
39
  for connection in self.active_connections:
42
- await connection.send_json(message)
40
+ await self.send_personal_message(message, connection)
43
41
 
44
42
  async def broadcast_to_admins(self, message: dict):
45
43
  for connection in self.admin_connections:
@@ -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,46 @@
1
+ neuro_simulator/__init__.py,sha256=-tposzyvg6UckPcfSvtc03UjxBa9oCe_zRvlKf8splk,31
2
+ neuro_simulator/cli.py,sha256=ma7kxzQqPaCbnUrZ516avIAsVk43PEc7-wH7BtfFe4E,5464
3
+ neuro_simulator/agent/__init__.py,sha256=t52CZlyTGWqcGjMs90qvpFpRckY2WSSlO7r_H3K_mSY,32
4
+ neuro_simulator/agent/core.py,sha256=_UkxMV1S33VHjD1JFlKz17GpaDhHQGNA9s9dsRgd3j0,9561
5
+ neuro_simulator/agent/llm.py,sha256=xPBEXpZ19WOt7YkERNaY9rscNI-ePASTjIt-_sZV7UI,4262
6
+ neuro_simulator/agent/memory_prompt.txt,sha256=wdpdnbOYhMKgPJnnGlcSejGdt2uItrXzDgz9_8cKnqw,824
7
+ neuro_simulator/agent/neuro_prompt.txt,sha256=WSN5Fa6AwVnJaSFhz98fQTb2EtQ35U6w2qga_C-s1Go,1438
8
+ neuro_simulator/agent/memory/__init__.py,sha256=YJ7cynQJI6kD7vjyv3rKc-CZqmoYSuGQtRZl_XdGEps,39
9
+ neuro_simulator/agent/memory/manager.py,sha256=tnwxxAlpS0wEd0jfX2MG9hnyAcuhUiZOIP7mz9LCPh8,5388
10
+ neuro_simulator/agent/tools/__init__.py,sha256=1WZy6PADfi6o1avyy1y-ThWBFAPJ_bBqtkobyYpf5ao,38
11
+ neuro_simulator/agent/tools/add_temp_memory.py,sha256=R6K-iVMD6ykd7pS3uI811dZIkuuAf7nPn9c6xCzqiHc,2127
12
+ neuro_simulator/agent/tools/add_to_core_memory_block.py,sha256=dt7y3w-qr379gS79mdata9w3hHG45wxdVIDXPnROO1Y,2306
13
+ neuro_simulator/agent/tools/base.py,sha256=Xj9ABTAtSFazhQf-qCMMWTCQVLsq48MZtKFVuBsYLQg,1799
14
+ neuro_simulator/agent/tools/create_core_memory_block.py,sha256=uM2vF71Ai3NH2-Qbr7u8nRmo4EoRzWYsTurfUH2X6s8,2766
15
+ neuro_simulator/agent/tools/delete_core_memory_block.py,sha256=_t2NZWZaxuWJsm9Uof3Zscvucyz-xJfyC0KrssSXPGI,1379
16
+ neuro_simulator/agent/tools/get_core_memory_block.py,sha256=vFK6lrbOqdxWVpYa7yF96wkXKMcpQAn4E68GCZlu0Ow,1432
17
+ neuro_simulator/agent/tools/get_core_memory_blocks.py,sha256=UbK-GkPgha35n703bCKzdnoIKgXR4U4YxqsSYWimW6c,1059
18
+ neuro_simulator/agent/tools/manager.py,sha256=-GFeGR1wO2GteIXOwWcFgZ-iPTj1syzGfL7coGSWf4I,7124
19
+ neuro_simulator/agent/tools/remove_from_core_memory_block.py,sha256=uiY69y2iIJYp3Zh8EdtP2_G5Zqfs4YKSmTBd9zcIp_c,2372
20
+ neuro_simulator/agent/tools/speak.py,sha256=7vPbfWjllxSz39HmOUbKRPwPx64q2vsYrqDu-JsfZJk,1870
21
+ neuro_simulator/agent/tools/update_core_memory_block.py,sha256=CQgimyPNLmOuK_pDAiAxV9qEf3Tx6u7OKludlIR08BA,2272
22
+ neuro_simulator/api/__init__.py,sha256=5LWyDSayPGdQS8Rv13nmAKLyhPnMVPyTYDdvoMPB4xw,56
23
+ neuro_simulator/api/system.py,sha256=OJT6m6HIYATMCAKrgeBRhficaiUjIDl9f-WyUT-RoBw,1874
24
+ neuro_simulator/core/__init__.py,sha256=-ojq25c8XA0CU25b0OxcGjH4IWFEDHR-HXSRSZIuKe8,57
25
+ neuro_simulator/core/agent_factory.py,sha256=p3IKT6sNzSpUojQjvbZJu0pbmyyb258sGn_YNSJlqwI,1717
26
+ neuro_simulator/core/agent_interface.py,sha256=ZXUCtkQUvsBQ5iCb0gTILJaShn5KmSrEgKhd7PK18HE,2794
27
+ neuro_simulator/core/application.py,sha256=oJUuKW3U8Uadr1IlzZThef8CgNoW1zncW_U94OFOUPQ,25518
28
+ neuro_simulator/core/config.py,sha256=QRbkm0h_FBTtYd-z5RRauSa1B0cp0LkNHVLqGBOBTYY,15324
29
+ neuro_simulator/core/path_manager.py,sha256=hfjI4s8-WXlgwvPWDSLYMQ2d0OaXPOMYWWlP5xe5NLg,2954
30
+ neuro_simulator/services/__init__.py,sha256=s3ZrAHg5TpJakadAAGY1h0wDw_xqN4Je4aJwJyRBmk4,61
31
+ neuro_simulator/services/audience.py,sha256=sAmvkz1ip1MNqaw7t-9abqfnmp0yh8EdG5bS5KYR-Us,4999
32
+ neuro_simulator/services/audio.py,sha256=EElBue80njZY8_CKQhVtZEcGchJUdmS4EDOvJEEu_LY,2909
33
+ neuro_simulator/services/builtin.py,sha256=NrdQhf4BuB8_evxnz8m6wiY9DgLwhY3l7Yp5TnmHGYo,5156
34
+ neuro_simulator/services/letta.py,sha256=OEM4qYeRRPoj8qY645YZwCHmMfg1McLp0eTbSAu3KL0,11976
35
+ neuro_simulator/services/stream.py,sha256=eueAaxhAwX_n0mT1W0W4YOVCe7fH5uPoMdRf2o0wQMI,5937
36
+ neuro_simulator/utils/__init__.py,sha256=xSEFzjT827W81mNyQ_DLtr00TgFlttqfFgpz9pSxFXQ,58
37
+ neuro_simulator/utils/logging.py,sha256=fAM8mW4czr0RLgAUS6oN-YTWKj5daqwc5g7yjyAgaPw,3892
38
+ neuro_simulator/utils/process.py,sha256=9OYWx8fzaJZqmFUcjQX37AnBhl7YWvrLxDWBa30vqwU,3192
39
+ neuro_simulator/utils/queue.py,sha256=bg-mIFF8ycClwmmfcKSPjAXtvjImzTKf6z7xZc2VX20,2196
40
+ neuro_simulator/utils/state.py,sha256=DdBqSAYfjOFtJfB1hEGhYPh32r1ZvFuVlN_-29_-luA,664
41
+ neuro_simulator/utils/websocket.py,sha256=fjC-qipzjgM_e7XImP12DFmLhvxkMOSr2GnUWws7esE,2058
42
+ neuro_simulator-0.4.0.dist-info/METADATA,sha256=n3zf_b6P5veJQ-VPxlqlCsxCfZXTd2-loTHn6XB4N3w,8643
43
+ neuro_simulator-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
44
+ neuro_simulator-0.4.0.dist-info/entry_points.txt,sha256=qVd5ypnRRgU8Cw7rWfZ7o0OXyS9P9hgY-cRoN_mgz9g,51
45
+ neuro_simulator-0.4.0.dist-info/top_level.txt,sha256=V8awSKpcrFnjJDiJxSfy7jtOrnuE2BgAR9hLmfMDWK8,16
46
+ neuro_simulator-0.4.0.dist-info/RECORD,,
@@ -1,43 +0,0 @@
1
- # agent/base.py
2
- """Base classes for Neuro Simulator Agent"""
3
-
4
- from abc import ABC, abstractmethod
5
- from typing import List, Dict, Any, Optional
6
-
7
-
8
- class BaseAgent(ABC):
9
- """Abstract base class for all agents"""
10
-
11
- @abstractmethod
12
- async def initialize(self):
13
- """Initialize the agent"""
14
- pass
15
-
16
- @abstractmethod
17
- async def reset_memory(self):
18
- """Reset agent memory"""
19
- pass
20
-
21
- @abstractmethod
22
- async def get_response(self, chat_messages: List[Dict[str, str]]) -> Dict[str, Any]:
23
- """Get response from the agent
24
-
25
- Args:
26
- chat_messages: List of message dictionaries with 'username' and 'text' keys
27
-
28
- Returns:
29
- Dictionary containing processing details including tool executions and final response
30
- """
31
- pass
32
-
33
- @abstractmethod
34
- async def process_messages(self, messages: List[Dict[str, str]]) -> Dict[str, Any]:
35
- """Process messages and generate a response
36
-
37
- Args:
38
- messages: List of message dictionaries with 'username' and 'text' keys
39
-
40
- Returns:
41
- Dictionary containing processing details including tool executions and final response
42
- """
43
- pass
@@ -1,30 +0,0 @@
1
- # agent/factory.py
2
- """Factory for creating agent instances"""
3
-
4
- from .base import BaseAgent
5
- from ..config import config_manager
6
-
7
-
8
- async def create_agent() -> BaseAgent:
9
- """Create an agent instance based on the configuration"""
10
- agent_type = config_manager.settings.agent_type
11
-
12
- if agent_type == "builtin":
13
- from ..builtin_agent import local_agent, BuiltinAgentWrapper, initialize_builtin_agent
14
- if local_agent is None:
15
- # Try to initialize the builtin agent
16
- await initialize_builtin_agent()
17
- # Re-import local_agent after initialization
18
- from ..builtin_agent import local_agent
19
- if local_agent is None:
20
- raise RuntimeError("Failed to initialize Builtin agent")
21
- return BuiltinAgentWrapper(local_agent)
22
- elif agent_type == "letta":
23
- from ..letta import get_letta_agent, initialize_letta_client
24
- # Try to initialize the letta client
25
- initialize_letta_client()
26
- agent = get_letta_agent()
27
- await agent.initialize()
28
- return agent
29
- else:
30
- raise ValueError(f"Unknown agent type: {agent_type}")
@@ -1 +0,0 @@
1
- # This file is now empty as all stream control API endpoints have been migrated to WebSockets.