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.
- neuro_simulator/agent/core.py +73 -138
- neuro_simulator/agent/llm.py +1 -1
- neuro_simulator/agent/memory/manager.py +24 -103
- neuro_simulator/agent/tools/add_temp_memory.py +3 -3
- neuro_simulator/agent/tools/add_to_core_memory_block.py +2 -2
- neuro_simulator/agent/tools/create_core_memory_block.py +2 -2
- neuro_simulator/agent/tools/delete_core_memory_block.py +2 -2
- neuro_simulator/agent/tools/get_core_memory_block.py +2 -2
- neuro_simulator/agent/tools/get_core_memory_blocks.py +2 -2
- neuro_simulator/agent/tools/manager.py +86 -63
- neuro_simulator/agent/tools/remove_from_core_memory_block.py +2 -2
- neuro_simulator/agent/tools/speak.py +2 -2
- neuro_simulator/agent/tools/update_core_memory_block.py +2 -2
- neuro_simulator/api/system.py +5 -2
- neuro_simulator/cli.py +83 -53
- neuro_simulator/core/agent_factory.py +0 -1
- neuro_simulator/core/application.py +46 -32
- neuro_simulator/core/config.py +66 -63
- neuro_simulator/core/path_manager.py +69 -0
- neuro_simulator/services/audience.py +0 -2
- neuro_simulator/services/audio.py +0 -1
- neuro_simulator/services/builtin.py +10 -25
- neuro_simulator/services/letta.py +19 -1
- neuro_simulator/services/stream.py +24 -21
- neuro_simulator/utils/logging.py +9 -0
- neuro_simulator/utils/queue.py +27 -4
- neuro_simulator/utils/websocket.py +1 -3
- {neuro_simulator-0.3.3.dist-info → neuro_simulator-0.4.0.dist-info}/METADATA +1 -1
- neuro_simulator-0.4.0.dist-info/RECORD +46 -0
- neuro_simulator/agent/base.py +0 -43
- neuro_simulator/agent/factory.py +0 -30
- neuro_simulator/api/stream.py +0 -1
- neuro_simulator-0.3.3.dist-info/RECORD +0 -48
- {neuro_simulator-0.3.3.dist-info → neuro_simulator-0.4.0.dist-info}/WHEEL +0 -0
- {neuro_simulator-0.3.3.dist-info → neuro_simulator-0.4.0.dist-info}/entry_points.txt +0 -0
- {neuro_simulator-0.3.3.dist-info → neuro_simulator-0.4.0.dist-info}/top_level.txt +0 -0
@@ -2,119 +2,142 @@
|
|
2
2
|
"""The central tool manager for the agent, responsible for loading, managing, and executing tools."""
|
3
3
|
|
4
4
|
import os
|
5
|
+
import json
|
5
6
|
import importlib
|
6
7
|
import inspect
|
7
8
|
import logging
|
9
|
+
import shutil
|
8
10
|
from pathlib import Path
|
9
|
-
from typing import
|
11
|
+
from typing import Any, Dict, List
|
10
12
|
|
11
13
|
from .base import BaseTool
|
12
14
|
from ..memory.manager import MemoryManager
|
15
|
+
from ...core.path_manager import path_manager
|
13
16
|
|
14
17
|
logger = logging.getLogger(__name__.replace("neuro_simulator", "agent", 1))
|
15
18
|
|
16
19
|
class ToolManager:
|
17
20
|
"""
|
18
21
|
Acts as a central registry and executor for all available tools.
|
19
|
-
This manager dynamically loads tools from the '
|
22
|
+
This manager dynamically loads tools from the user's working directory.
|
20
23
|
"""
|
21
24
|
|
22
25
|
def __init__(self, memory_manager: MemoryManager):
|
23
|
-
|
24
|
-
|
25
|
-
Args:
|
26
|
-
memory_manager: An instance of MemoryManager, passed to tools that need it.
|
27
|
-
"""
|
26
|
+
if not path_manager:
|
27
|
+
raise RuntimeError("PathManager not initialized before ToolManager.")
|
28
28
|
self.memory_manager = memory_manager
|
29
29
|
self.tools: Dict[str, BaseTool] = {}
|
30
|
-
self.
|
30
|
+
self.agent_tool_allocations: Dict[str, List[str]] = {}
|
31
31
|
|
32
|
-
|
33
|
-
self.
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
32
|
+
self._copy_builtin_tools()
|
33
|
+
self.reload_tools() # Initial load
|
34
|
+
self._load_allocations()
|
35
|
+
|
36
|
+
def _copy_builtin_tools(self):
|
37
|
+
"""Copies the packaged built-in tools to the working directory, overwriting existing ones."""
|
38
|
+
try:
|
39
|
+
import pkg_resources
|
40
|
+
source_dir_str = pkg_resources.resource_filename('neuro_simulator', 'agent/tools')
|
41
|
+
source_dir = Path(source_dir_str)
|
42
|
+
except (ModuleNotFoundError, KeyError):
|
43
|
+
source_dir = Path(__file__).parent
|
44
|
+
|
45
|
+
dest_dir = path_manager.builtin_tools_dir
|
46
|
+
if not dest_dir.exists():
|
47
|
+
os.makedirs(dest_dir)
|
48
|
+
|
49
|
+
for item in os.listdir(source_dir):
|
50
|
+
source_item = source_dir / item
|
51
|
+
if source_item.is_file() and source_item.name.endswith('.py') and not source_item.name.startswith(('__', 'manager')):
|
52
|
+
shutil.copy(source_item, dest_dir / item)
|
53
|
+
logger.info(f"Built-in tools copied to {dest_dir}")
|
38
54
|
|
39
55
|
def _load_and_register_tools(self):
|
40
|
-
"""Dynamically scans the
|
56
|
+
"""Dynamically scans the user tools directory, imports modules, and registers tool instances."""
|
41
57
|
self.tools = {}
|
42
|
-
tools_dir =
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
58
|
+
tools_dir = path_manager.user_tools_dir
|
59
|
+
|
60
|
+
for root, _, files in os.walk(tools_dir):
|
61
|
+
for filename in files:
|
62
|
+
if filename.endswith('.py') and not filename.startswith('__'):
|
63
|
+
module_path = Path(root) / filename
|
64
|
+
# Create a module spec from the file path
|
65
|
+
spec = importlib.util.spec_from_file_location(module_path.stem, module_path)
|
66
|
+
if spec:
|
67
|
+
try:
|
68
|
+
module = importlib.util.module_from_spec(spec)
|
69
|
+
spec.loader.exec_module(module)
|
70
|
+
for _, cls in inspect.getmembers(module, inspect.isclass):
|
71
|
+
if issubclass(cls, BaseTool) and cls is not BaseTool:
|
72
|
+
tool_instance = cls(memory_manager=self.memory_manager)
|
73
|
+
if tool_instance.name in self.tools:
|
74
|
+
logger.warning(f"Duplicate tool name '{tool_instance.name}' found. Overwriting.")
|
75
|
+
self.tools[tool_instance.name] = tool_instance
|
76
|
+
logger.info(f"Successfully loaded and registered tool: {tool_instance.name}")
|
77
|
+
except Exception as e:
|
78
|
+
logger.error(f"Failed to load tool from {module_path}: {e}", exc_info=True)
|
79
|
+
|
80
|
+
def _load_allocations(self):
|
81
|
+
"""Loads tool allocations from JSON files, creating defaults if they don't exist."""
|
82
|
+
default_allocations = {
|
83
|
+
"neuro_agent": ["speak", "get_core_memory_blocks", "get_core_memory_block"],
|
84
|
+
"memory_agent": ["add_temp_memory", "create_core_memory_block", "update_core_memory_block", "delete_core_memory_block", "add_to_core_memory_block", "remove_from_core_memory_block", "get_core_memory_blocks", "get_core_memory_block"]
|
85
|
+
}
|
86
|
+
|
87
|
+
# Load neuro agent allocations
|
88
|
+
if path_manager.neuro_tools_path.exists():
|
89
|
+
with open(path_manager.neuro_tools_path, 'r', encoding='utf-8') as f:
|
90
|
+
self.agent_tool_allocations['neuro_agent'] = json.load(f)
|
91
|
+
else:
|
92
|
+
self.agent_tool_allocations['neuro_agent'] = default_allocations['neuro_agent']
|
93
|
+
with open(path_manager.neuro_tools_path, 'w', encoding='utf-8') as f:
|
94
|
+
json.dump(default_allocations['neuro_agent'], f, indent=2)
|
95
|
+
|
96
|
+
# Load memory agent allocations
|
97
|
+
if path_manager.memory_agent_tools_path.exists():
|
98
|
+
with open(path_manager.memory_agent_tools_path, 'r', encoding='utf-8') as f:
|
99
|
+
self.agent_tool_allocations['memory_agent'] = json.load(f)
|
100
|
+
else:
|
101
|
+
self.agent_tool_allocations['memory_agent'] = default_allocations['memory_agent']
|
102
|
+
with open(path_manager.memory_agent_tools_path, 'w', encoding='utf-8') as f:
|
103
|
+
json.dump(default_allocations['memory_agent'], f, indent=2)
|
104
|
+
|
105
|
+
logger.info(f"Tool allocations loaded: {self.agent_tool_allocations}")
|
59
106
|
|
60
107
|
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
108
|
return [tool.get_schema() for tool in self.tools.values()]
|
66
109
|
|
67
110
|
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
111
|
allowed_names = set(self.agent_tool_allocations.get(agent_name, []))
|
73
112
|
if not allowed_names:
|
74
113
|
return []
|
75
|
-
|
76
114
|
return [tool.get_schema() for tool in self.tools.values() if tool.name in allowed_names]
|
77
115
|
|
78
116
|
def reload_tools(self):
|
79
|
-
"""Forces a re-scan and registration of tools from the tools directory."""
|
80
117
|
logger.info("Reloading tools...")
|
81
118
|
self._load_and_register_tools()
|
82
119
|
logger.info(f"Tools reloaded. {len(self.tools)} tools available.")
|
83
120
|
|
84
121
|
def get_allocations(self) -> Dict[str, List[str]]:
|
85
|
-
"""Returns the current agent-to-tool-tag allocation mapping."""
|
86
122
|
return self.agent_tool_allocations
|
87
123
|
|
88
124
|
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
125
|
self.agent_tool_allocations = allocations
|
97
|
-
|
126
|
+
# Persist the changes to the JSON files
|
127
|
+
with open(path_manager.neuro_tools_path, 'w', encoding='utf-8') as f:
|
128
|
+
json.dump(allocations.get('neuro_agent', []), f, indent=2)
|
129
|
+
with open(path_manager.memory_agent_tools_path, 'w', encoding='utf-8') as f:
|
130
|
+
json.dump(allocations.get('memory_agent', []), f, indent=2)
|
131
|
+
logger.info(f"Tool allocations updated and saved: {self.agent_tool_allocations}")
|
98
132
|
|
99
133
|
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
134
|
if tool_name not in self.tools:
|
111
135
|
logger.error(f"Attempted to execute non-existent tool: {tool_name}")
|
112
136
|
return {"error": f"Tool '{tool_name}' not found."}
|
113
|
-
|
114
137
|
tool = self.tools[tool_name]
|
115
138
|
try:
|
116
139
|
result = await tool.execute(**kwargs)
|
117
140
|
return result
|
118
141
|
except Exception as e:
|
119
142
|
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)}"}
|
143
|
+
return {"error": f"An unexpected error occurred while executing the tool: {str(e)}"}
|
@@ -3,8 +3,8 @@
|
|
3
3
|
|
4
4
|
from typing import Dict, Any, List
|
5
5
|
|
6
|
-
from .base import BaseTool
|
7
|
-
from
|
6
|
+
from neuro_simulator.agent.tools.base import BaseTool
|
7
|
+
from neuro_simulator.agent.memory.manager import MemoryManager
|
8
8
|
|
9
9
|
class RemoveFromCoreMemoryBlockTool(BaseTool):
|
10
10
|
"""Tool to remove an item from an existing core memory block's content list by its index."""
|
@@ -4,8 +4,8 @@
|
|
4
4
|
import logging
|
5
5
|
from typing import Dict, Any, List
|
6
6
|
|
7
|
-
from .base import BaseTool
|
8
|
-
from
|
7
|
+
from neuro_simulator.agent.tools.base import BaseTool
|
8
|
+
from neuro_simulator.agent.memory.manager import MemoryManager
|
9
9
|
|
10
10
|
logger = logging.getLogger(__name__.replace("neuro_simulator", "agent", 1))
|
11
11
|
|
@@ -3,8 +3,8 @@
|
|
3
3
|
|
4
4
|
from typing import Dict, Any, List, Optional
|
5
5
|
|
6
|
-
from .base import BaseTool
|
7
|
-
from
|
6
|
+
from neuro_simulator.agent.tools.base import BaseTool
|
7
|
+
from neuro_simulator.agent.memory.manager import MemoryManager
|
8
8
|
|
9
9
|
class UpdateCoreMemoryBlockTool(BaseTool):
|
10
10
|
"""Tool to update an existing core memory block."""
|
neuro_simulator/api/system.py
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
# neuro_simulator/api/system.py
|
2
2
|
"""API endpoints for system, config, and utility functions."""
|
3
3
|
|
4
|
-
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
5
4
|
import time
|
5
|
+
from typing import Any, Dict
|
6
|
+
|
7
|
+
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
6
8
|
|
7
|
-
from ..core.config import config_manager
|
9
|
+
from ..core.config import AppSettings, config_manager
|
10
|
+
from ..utils.process import process_manager
|
8
11
|
|
9
12
|
|
10
13
|
router = APIRouter(tags=["System & Utilities"])
|
neuro_simulator/cli.py
CHANGED
@@ -8,29 +8,7 @@ import shutil
|
|
8
8
|
import sys
|
9
9
|
from pathlib import Path
|
10
10
|
|
11
|
-
def copy_resource(package_name: str, resource_path: str, destination_path: Path, is_dir: bool = False):
|
12
|
-
"""A helper function to copy a resource from the package to the working directory."""
|
13
|
-
if destination_path.exists():
|
14
|
-
return
|
15
11
|
|
16
|
-
try:
|
17
|
-
import pkg_resources
|
18
|
-
source_path_str = pkg_resources.resource_filename(package_name, resource_path)
|
19
|
-
source_path = Path(source_path_str)
|
20
|
-
except (ModuleNotFoundError, KeyError):
|
21
|
-
source_path = Path(__file__).parent / resource_path
|
22
|
-
|
23
|
-
if source_path.exists():
|
24
|
-
try:
|
25
|
-
if is_dir:
|
26
|
-
shutil.copytree(source_path, destination_path)
|
27
|
-
else:
|
28
|
-
shutil.copy(source_path, destination_path)
|
29
|
-
logging.info(f"Created '{destination_path}' from package resource.")
|
30
|
-
except Exception as e:
|
31
|
-
logging.warning(f"Could not copy resource '{resource_path}'. Error: {e}")
|
32
|
-
else:
|
33
|
-
logging.warning(f"Resource '{resource_path}' not found in package or development folder.")
|
34
12
|
|
35
13
|
def main():
|
36
14
|
"""Main entry point for the CLI."""
|
@@ -54,39 +32,91 @@ def main():
|
|
54
32
|
os.chdir(work_dir)
|
55
33
|
logging.info(f"Using working directory: {work_dir}")
|
56
34
|
|
57
|
-
# 2.
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
35
|
+
# 2. Initialize paths and load configuration
|
36
|
+
from neuro_simulator.core import path_manager
|
37
|
+
from neuro_simulator.core.config import config_manager
|
38
|
+
import uvicorn
|
39
|
+
|
40
|
+
path_manager.initialize_path_manager(os.getcwd())
|
41
|
+
|
42
|
+
# Define example_path early for config loading
|
43
|
+
example_path = Path(__file__).parent / "core" / "config.yaml.example"
|
44
|
+
|
45
|
+
# 2.2. Copy default config.yaml.example if it doesn't exist
|
46
|
+
try:
|
47
|
+
source_config_example = example_path
|
48
|
+
destination_config_example = path_manager.path_manager.working_dir / "config.yaml.example"
|
49
|
+
if not destination_config_example.exists():
|
50
|
+
shutil.copy(source_config_example, destination_config_example)
|
51
|
+
logging.info(f"Copied default config.yaml.example to {destination_config_example}")
|
52
|
+
except Exception as e:
|
53
|
+
logging.warning(f"Could not copy default config.yaml.example: {e}")
|
54
|
+
|
55
|
+
main_config_path = path_manager.path_manager.working_dir / "config.yaml"
|
56
|
+
config_manager.load_and_validate(str(main_config_path), str(example_path))
|
57
|
+
|
58
|
+
# 2.5. Copy default prompt templates if they don't exist
|
59
|
+
try:
|
60
|
+
# Use Path(__file__).parent for robust path resolution
|
61
|
+
base_path = Path(__file__).parent
|
62
|
+
neuro_prompt_example = base_path / "agent" / "neuro_prompt.txt"
|
63
|
+
memory_prompt_example = base_path / "agent" / "memory_prompt.txt"
|
64
|
+
|
65
|
+
if not path_manager.path_manager.neuro_prompt_path.exists():
|
66
|
+
shutil.copy(neuro_prompt_example, path_manager.path_manager.neuro_prompt_path)
|
67
|
+
logging.info(f"Copied default neuro prompt to {path_manager.path_manager.neuro_prompt_path}")
|
68
|
+
if not path_manager.path_manager.memory_agent_prompt_path.exists():
|
69
|
+
shutil.copy(memory_prompt_example, path_manager.path_manager.memory_agent_prompt_path)
|
70
|
+
logging.info(f"Copied default memory prompt to {path_manager.path_manager.memory_agent_prompt_path}")
|
71
|
+
|
72
|
+
# Copy default memory JSON files if they don't exist
|
73
|
+
memory_files = {
|
74
|
+
"core_memory.json": path_manager.path_manager.core_memory_path,
|
75
|
+
"init_memory.json": path_manager.path_manager.init_memory_path,
|
76
|
+
"temp_memory.json": path_manager.path_manager.temp_memory_path,
|
77
|
+
}
|
78
|
+
for filename, dest_path in memory_files.items():
|
79
|
+
src_path = base_path / "agent" / "memory" / filename
|
80
|
+
if not dest_path.exists():
|
81
|
+
shutil.copy(src_path, dest_path)
|
82
|
+
logging.info(f"Copied default {filename} to {dest_path}")
|
83
|
+
|
84
|
+
# Copy default assets directory if it doesn't exist
|
85
|
+
source_assets_dir = base_path / "assets"
|
86
|
+
destination_assets_dir = path_manager.path_manager.assets_dir
|
87
|
+
|
88
|
+
# Ensure the destination assets directory exists
|
89
|
+
destination_assets_dir.mkdir(parents=True, exist_ok=True)
|
90
|
+
|
91
|
+
# Copy individual files from source assets to destination assets
|
92
|
+
for item in source_assets_dir.iterdir():
|
93
|
+
if item.is_file():
|
94
|
+
dest_file = destination_assets_dir / item.name
|
95
|
+
if not dest_file.exists():
|
96
|
+
shutil.copy(item, dest_file)
|
97
|
+
logging.info(f"Copied asset {item.name} to {dest_file}")
|
98
|
+
elif item.is_dir():
|
99
|
+
# Recursively copy subdirectories if they don't exist
|
100
|
+
dest_subdir = destination_assets_dir / item.name
|
101
|
+
if not dest_subdir.exists():
|
102
|
+
shutil.copytree(item, dest_subdir)
|
103
|
+
logging.info(f"Copied asset directory {item.name} to {dest_subdir}")
|
104
|
+
except Exception as e:
|
105
|
+
logging.warning(f"Could not copy default prompt templates, memory files, or assets: {e}")
|
106
|
+
|
107
|
+
# 3. Determine server host and port
|
108
|
+
server_host = args.host or config_manager.settings.server.host
|
109
|
+
server_port = args.port or config_manager.settings.server.port
|
83
110
|
|
84
|
-
# 4.
|
111
|
+
# 4. Run the server
|
112
|
+
logging.info(f"Starting Neuro-Simulator server on {server_host}:{server_port}...")
|
85
113
|
try:
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
114
|
+
uvicorn.run(
|
115
|
+
"neuro_simulator.core.application:app",
|
116
|
+
host=server_host,
|
117
|
+
port=server_port,
|
118
|
+
reload=False
|
119
|
+
)
|
90
120
|
except ImportError as e:
|
91
121
|
logging.error(f"Could not import the application. Make sure the package is installed correctly. Details: {e}", exc_info=True)
|
92
122
|
sys.exit(1)
|
@@ -7,7 +7,7 @@ import logging
|
|
7
7
|
import random
|
8
8
|
import re
|
9
9
|
import time
|
10
|
-
|
10
|
+
import os
|
11
11
|
from typing import List
|
12
12
|
|
13
13
|
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
@@ -17,6 +17,7 @@ from starlette.websockets import WebSocketState
|
|
17
17
|
# --- Core Imports ---
|
18
18
|
from .config import config_manager, AppSettings
|
19
19
|
from ..core.agent_factory import create_agent
|
20
|
+
from ..agent.core import Agent as LocalAgent
|
20
21
|
from ..services.letta import LettaAgent
|
21
22
|
from ..services.builtin import BuiltinAgentWrapper
|
22
23
|
|
@@ -34,7 +35,8 @@ from ..utils.queue import (
|
|
34
35
|
add_to_neuro_input_queue,
|
35
36
|
get_recent_audience_chats,
|
36
37
|
is_neuro_input_queue_empty,
|
37
|
-
get_all_neuro_input_chats
|
38
|
+
get_all_neuro_input_chats,
|
39
|
+
initialize_queues,
|
38
40
|
)
|
39
41
|
from ..utils.state import app_state
|
40
42
|
from ..utils.websocket import connection_manager
|
@@ -169,6 +171,10 @@ async def neuro_response_cycle():
|
|
169
171
|
if not response_text:
|
170
172
|
continue
|
171
173
|
|
174
|
+
# Push updated agent context to admin clients immediately after processing
|
175
|
+
updated_context = await agent.get_message_history()
|
176
|
+
await connection_manager.broadcast_to_admins({"type": "agent_context", "action": "update", "messages": updated_context})
|
177
|
+
|
172
178
|
async with app_state.neuro_last_speech_lock:
|
173
179
|
app_state.neuro_last_speech = response_text
|
174
180
|
|
@@ -192,6 +198,7 @@ async def neuro_response_cycle():
|
|
192
198
|
|
193
199
|
await connection_manager.broadcast({"type": "neuro_speech_segment", "is_end": True})
|
194
200
|
live_stream_manager.set_neuro_speaking_status(False)
|
201
|
+
|
195
202
|
await asyncio.sleep(config_manager.settings.neuro_behavior.post_speech_cooldown_sec)
|
196
203
|
|
197
204
|
except asyncio.TimeoutError:
|
@@ -210,17 +217,24 @@ async def neuro_response_cycle():
|
|
210
217
|
@app.on_event("startup")
|
211
218
|
async def startup_event():
|
212
219
|
"""Actions to perform on application startup."""
|
213
|
-
|
220
|
+
# 1. Configure logging first
|
214
221
|
configure_server_logging()
|
215
|
-
|
222
|
+
|
223
|
+
# 2. Initialize queues now that config is loaded
|
224
|
+
initialize_queues()
|
225
|
+
|
226
|
+
# 4. Initialize other managers/services that depend on config
|
227
|
+
global chatbot_manager
|
216
228
|
chatbot_manager = AudienceChatbotManager()
|
217
229
|
|
230
|
+
# 5. Register callbacks
|
218
231
|
async def metadata_callback(settings: AppSettings):
|
219
232
|
await live_stream_manager.broadcast_stream_metadata()
|
220
233
|
|
221
234
|
config_manager.register_update_callback(metadata_callback)
|
222
235
|
config_manager.register_update_callback(chatbot_manager.handle_config_update)
|
223
236
|
|
237
|
+
# 6. Initialize agent (which will load its own configs)
|
224
238
|
try:
|
225
239
|
await create_agent()
|
226
240
|
logger.info(f"Successfully initialized agent type: {config_manager.settings.agent_type}")
|
@@ -410,6 +424,8 @@ async def handle_admin_ws_message(websocket: WebSocket, data: dict):
|
|
410
424
|
|
411
425
|
# Stream Control Actions
|
412
426
|
elif action == "start_stream":
|
427
|
+
logger.info("Start stream action received. Resetting agent memory before starting processes...")
|
428
|
+
await agent.reset_memory()
|
413
429
|
if not process_manager.is_running:
|
414
430
|
process_manager.start_live_processes()
|
415
431
|
response["payload"] = {"status": "success", "message": "Stream started"}
|
@@ -464,22 +480,33 @@ async def handle_admin_ws_message(websocket: WebSocket, data: dict):
|
|
464
480
|
response["payload"] = context
|
465
481
|
|
466
482
|
elif action == "get_last_prompt":
|
467
|
-
#
|
468
|
-
agent_instance = getattr(agent, 'agent_instance',
|
469
|
-
if not
|
470
|
-
|
483
|
+
# This is specific to the builtin agent, as Letta doesn't expose its prompt.
|
484
|
+
agent_instance = getattr(agent, 'agent_instance', agent)
|
485
|
+
if not isinstance(agent_instance, LocalAgent):
|
486
|
+
response["payload"] = {"prompt": "The active agent does not support prompt generation introspection."}
|
471
487
|
else:
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
488
|
+
try:
|
489
|
+
# 1. Get the recent history from the agent itself
|
490
|
+
history = await agent_instance.get_neuro_history(limit=10)
|
491
|
+
|
492
|
+
# 2. Reconstruct the 'messages' list that _build_neuro_prompt expects
|
493
|
+
messages_for_prompt = []
|
494
|
+
for entry in history:
|
495
|
+
if entry.get('role') == 'user':
|
496
|
+
# Content is in the format "username: text"
|
497
|
+
content = entry.get('content', '')
|
498
|
+
parts = content.split(':', 1)
|
499
|
+
if len(parts) == 2:
|
500
|
+
messages_for_prompt.append({'username': parts[0].strip(), 'text': parts[1].strip()})
|
501
|
+
elif content: # Handle cases where there's no colon
|
502
|
+
messages_for_prompt.append({'username': 'user', 'text': content})
|
503
|
+
|
504
|
+
# 3. Build the prompt using the agent's own internal logic
|
505
|
+
prompt = await agent_instance._build_neuro_prompt(messages_for_prompt)
|
506
|
+
response["payload"] = {"prompt": prompt}
|
507
|
+
except Exception as e:
|
508
|
+
logger.error(f"Error generating last prompt: {e}", exc_info=True)
|
509
|
+
response["payload"] = {"prompt": f"Failed to generate prompt: {e}"}
|
483
510
|
|
484
511
|
elif action == "reset_agent_memory":
|
485
512
|
await agent.reset_memory()
|
@@ -504,17 +531,4 @@ async def handle_admin_ws_message(websocket: WebSocket, data: dict):
|
|
504
531
|
await websocket.send_json(response)
|
505
532
|
|
506
533
|
|
507
|
-
# --- Server Entrypoint ---
|
508
534
|
|
509
|
-
def run_server(host: str = None, port: int = None):
|
510
|
-
"""Runs the FastAPI server with Uvicorn."""
|
511
|
-
import uvicorn
|
512
|
-
server_host = host or config_manager.settings.server.host
|
513
|
-
server_port = port or config_manager.settings.server.port
|
514
|
-
|
515
|
-
uvicorn.run(
|
516
|
-
"neuro_simulator.core.application:app",
|
517
|
-
host=server_host,
|
518
|
-
port=server_port,
|
519
|
-
reload=False
|
520
|
-
)
|