neuro-simulator 0.1.3__py3-none-any.whl → 0.2.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/__init__.py +1 -10
- neuro_simulator/agent/__init__.py +1 -8
- neuro_simulator/agent/base.py +43 -0
- neuro_simulator/agent/core.py +111 -397
- neuro_simulator/agent/factory.py +30 -0
- neuro_simulator/agent/llm.py +34 -31
- neuro_simulator/agent/memory/__init__.py +1 -4
- neuro_simulator/agent/memory/manager.py +61 -203
- neuro_simulator/agent/tools/__init__.py +1 -4
- neuro_simulator/agent/tools/core.py +8 -18
- neuro_simulator/api/__init__.py +1 -0
- neuro_simulator/api/agent.py +163 -0
- neuro_simulator/api/stream.py +55 -0
- neuro_simulator/api/system.py +90 -0
- neuro_simulator/cli.py +53 -142
- neuro_simulator/core/__init__.py +1 -0
- neuro_simulator/core/agent_factory.py +52 -0
- neuro_simulator/core/agent_interface.py +91 -0
- neuro_simulator/core/application.py +278 -0
- neuro_simulator/services/__init__.py +1 -0
- neuro_simulator/{chatbot.py → services/audience.py} +24 -24
- neuro_simulator/{audio_synthesis.py → services/audio.py} +18 -15
- neuro_simulator/services/builtin.py +87 -0
- neuro_simulator/services/letta.py +206 -0
- neuro_simulator/{stream_manager.py → services/stream.py} +39 -47
- neuro_simulator/utils/__init__.py +1 -0
- neuro_simulator/utils/logging.py +90 -0
- neuro_simulator/utils/process.py +67 -0
- neuro_simulator/{stream_chat.py → utils/queue.py} +17 -4
- neuro_simulator/utils/state.py +14 -0
- neuro_simulator/{websocket_manager.py → utils/websocket.py} +18 -14
- {neuro_simulator-0.1.3.dist-info → neuro_simulator-0.2.0.dist-info}/METADATA +176 -176
- neuro_simulator-0.2.0.dist-info/RECORD +37 -0
- neuro_simulator/agent/api.py +0 -737
- neuro_simulator/agent/memory.py +0 -137
- neuro_simulator/agent/tools.py +0 -69
- neuro_simulator/builtin_agent.py +0 -83
- neuro_simulator/config.yaml.example +0 -157
- neuro_simulator/letta.py +0 -164
- neuro_simulator/log_handler.py +0 -43
- neuro_simulator/main.py +0 -673
- neuro_simulator/media/neuro_start.mp4 +0 -0
- neuro_simulator/process_manager.py +0 -70
- neuro_simulator/shared_state.py +0 -11
- neuro_simulator-0.1.3.dist-info/RECORD +0 -31
- /neuro_simulator/{config.py → core/config.py} +0 -0
- {neuro_simulator-0.1.3.dist-info → neuro_simulator-0.2.0.dist-info}/WHEEL +0 -0
- {neuro_simulator-0.1.3.dist-info → neuro_simulator-0.2.0.dist-info}/entry_points.txt +0 -0
- {neuro_simulator-0.1.3.dist-info → neuro_simulator-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,163 @@
|
|
1
|
+
# neuro_simulator/api/agent.py
|
2
|
+
"""Unified API endpoints for agent management, decoupled from implementation."""
|
3
|
+
|
4
|
+
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
5
|
+
from typing import Dict, Any, List, Optional
|
6
|
+
from pydantic import BaseModel
|
7
|
+
|
8
|
+
# Imports for the new structure
|
9
|
+
from ..core.config import config_manager
|
10
|
+
from ..core.agent_factory import create_agent
|
11
|
+
from ..core.agent_interface import BaseAgent
|
12
|
+
|
13
|
+
router = APIRouter(prefix="/api/agent", tags=["Agent Management"])
|
14
|
+
|
15
|
+
# Security dependency (remains the same)
|
16
|
+
async def get_api_token(request: Request):
|
17
|
+
password = config_manager.settings.server.panel_password
|
18
|
+
if not password:
|
19
|
+
return True
|
20
|
+
header_token = request.headers.get("X-API-Token")
|
21
|
+
if header_token and header_token == password:
|
22
|
+
return True
|
23
|
+
raise HTTPException(
|
24
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
25
|
+
detail="Invalid API token",
|
26
|
+
headers={"WWW-Authenticate": "Bearer"},
|
27
|
+
)
|
28
|
+
|
29
|
+
# Pydantic models (remains the same)
|
30
|
+
class MessageItem(BaseModel):
|
31
|
+
username: str
|
32
|
+
text: str
|
33
|
+
role: str = "user"
|
34
|
+
|
35
|
+
class ToolExecutionRequest(BaseModel):
|
36
|
+
tool_name: str
|
37
|
+
params: Dict[str, Any]
|
38
|
+
|
39
|
+
class MemoryUpdateRequest(BaseModel):
|
40
|
+
title: Optional[str] = None
|
41
|
+
description: Optional[str] = None
|
42
|
+
content: Optional[List[str]] = None
|
43
|
+
|
44
|
+
class MemoryCreateRequest(BaseModel):
|
45
|
+
title: str
|
46
|
+
description: str
|
47
|
+
content: List[str]
|
48
|
+
|
49
|
+
class InitMemoryUpdateRequest(BaseModel):
|
50
|
+
memory: Dict[str, Any]
|
51
|
+
|
52
|
+
class TempMemoryItem(BaseModel):
|
53
|
+
content: str
|
54
|
+
role: str = "system"
|
55
|
+
|
56
|
+
# Dependency to get the agent instance, making endpoints cleaner
|
57
|
+
async def get_agent() -> BaseAgent:
|
58
|
+
return await create_agent()
|
59
|
+
|
60
|
+
# A single dependency for both auth and agent instance
|
61
|
+
class AgentDeps:
|
62
|
+
def __init__(self, token: bool = Depends(get_api_token), agent: BaseAgent = Depends(get_agent)):
|
63
|
+
self.agent = agent
|
64
|
+
|
65
|
+
# --- Refactored Agent Endpoints ---
|
66
|
+
|
67
|
+
@router.get("/messages", response_model=List[Dict[str, Any]])
|
68
|
+
async def get_agent_messages(deps: AgentDeps = Depends()):
|
69
|
+
"""Get agent's detailed message processing history."""
|
70
|
+
return await deps.agent.get_message_history()
|
71
|
+
|
72
|
+
@router.get("/context", response_model=List[Dict[str, Any]])
|
73
|
+
async def get_agent_context(deps: AgentDeps = Depends()):
|
74
|
+
"""Get agent's recent conversation context (last 20 entries)."""
|
75
|
+
return await deps.agent.get_message_history(limit=20)
|
76
|
+
|
77
|
+
@router.delete("/messages")
|
78
|
+
async def clear_agent_messages(deps: AgentDeps = Depends()):
|
79
|
+
"""Clear agent's message history."""
|
80
|
+
await deps.agent.reset_memory()
|
81
|
+
return {"status": "success", "message": "Agent memory reset successfully"}
|
82
|
+
|
83
|
+
@router.post("/messages")
|
84
|
+
async def send_message_to_agent(message: MessageItem, deps: AgentDeps = Depends()):
|
85
|
+
"""Send a message to the agent."""
|
86
|
+
response = await deps.agent.process_messages([message.dict()])
|
87
|
+
return {"response": response}
|
88
|
+
|
89
|
+
@router.get("/memory/init", response_model=Dict[str, Any])
|
90
|
+
async def get_init_memory(deps: AgentDeps = Depends()):
|
91
|
+
"""Get initialization memory content."""
|
92
|
+
return await deps.agent.get_init_memory()
|
93
|
+
|
94
|
+
@router.put("/memory/init")
|
95
|
+
async def update_init_memory(request: InitMemoryUpdateRequest, deps: AgentDeps = Depends()):
|
96
|
+
"""Update initialization memory content."""
|
97
|
+
await deps.agent.update_init_memory(request.memory)
|
98
|
+
return {"status": "success", "message": "Initialization memory updated"}
|
99
|
+
|
100
|
+
@router.get("/memory/temp", response_model=List[Dict[str, Any]])
|
101
|
+
async def get_temp_memory(deps: AgentDeps = Depends()):
|
102
|
+
"""Get all temporary memory content."""
|
103
|
+
return await deps.agent.get_temp_memory()
|
104
|
+
|
105
|
+
@router.post("/memory/temp")
|
106
|
+
async def add_temp_memory_item(request: TempMemoryItem, deps: AgentDeps = Depends()):
|
107
|
+
"""Add an item to temporary memory."""
|
108
|
+
await deps.agent.add_temp_memory(request.content, request.role)
|
109
|
+
return {"status": "success", "message": "Item added to temporary memory"}
|
110
|
+
|
111
|
+
@router.delete("/memory/temp")
|
112
|
+
async def clear_temp_memory(deps: AgentDeps = Depends()):
|
113
|
+
"""Clear temporary memory."""
|
114
|
+
await deps.agent.clear_temp_memory()
|
115
|
+
return {"status": "success", "message": "Temporary memory cleared"}
|
116
|
+
|
117
|
+
@router.get("/memory/blocks", response_model=List[Dict[str, Any]])
|
118
|
+
async def get_memory_blocks(deps: AgentDeps = Depends()):
|
119
|
+
"""Get all memory blocks."""
|
120
|
+
return await deps.agent.get_memory_blocks()
|
121
|
+
|
122
|
+
@router.get("/memory/blocks/{block_id}", response_model=Dict[str, Any])
|
123
|
+
async def get_memory_block(block_id: str, deps: AgentDeps = Depends()):
|
124
|
+
"""Get a specific memory block."""
|
125
|
+
block = await deps.agent.get_memory_block(block_id)
|
126
|
+
if block is None:
|
127
|
+
raise HTTPException(status_code=404, detail="Memory block not found")
|
128
|
+
return block
|
129
|
+
|
130
|
+
@router.post("/memory/blocks", response_model=Dict[str, str])
|
131
|
+
async def create_memory_block(request: MemoryCreateRequest, deps: AgentDeps = Depends()):
|
132
|
+
"""Create a new memory block."""
|
133
|
+
return await deps.agent.create_memory_block(request.title, request.description, request.content)
|
134
|
+
|
135
|
+
@router.put("/memory/blocks/{block_id}")
|
136
|
+
async def update_memory_block(block_id: str, request: MemoryUpdateRequest, deps: AgentDeps = Depends()):
|
137
|
+
"""Update a memory block."""
|
138
|
+
await deps.agent.update_memory_block(block_id, request.title, request.description, request.content)
|
139
|
+
return {"status": "success"}
|
140
|
+
|
141
|
+
@router.delete("/memory/blocks/{block_id}")
|
142
|
+
async def delete_memory_block(block_id: str, deps: AgentDeps = Depends()):
|
143
|
+
"""Delete a memory block."""
|
144
|
+
await deps.agent.delete_memory_block(block_id)
|
145
|
+
return {"status": "success"}
|
146
|
+
|
147
|
+
@router.post("/reset_memory")
|
148
|
+
async def reset_agent_memory(deps: AgentDeps = Depends()):
|
149
|
+
"""Reset all agent memory types."""
|
150
|
+
await deps.agent.reset_memory()
|
151
|
+
return {"status": "success", "message": "Agent memory reset successfully"}
|
152
|
+
|
153
|
+
@router.get("/tools")
|
154
|
+
async def get_available_tools(deps: AgentDeps = Depends()):
|
155
|
+
"""Get list of available tools."""
|
156
|
+
# Return in the format expected by the frontend
|
157
|
+
return {"tools": await deps.agent.get_available_tools()}
|
158
|
+
|
159
|
+
@router.post("/tools/execute")
|
160
|
+
async def execute_tool(request: ToolExecutionRequest, deps: AgentDeps = Depends()):
|
161
|
+
"""Execute a tool with given parameters."""
|
162
|
+
result = await deps.agent.execute_tool(request.tool_name, request.params)
|
163
|
+
return {"result": result}
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# neuro_simulator/api/stream.py
|
2
|
+
"""API endpoints for controlling the live stream lifecycle."""
|
3
|
+
|
4
|
+
import asyncio
|
5
|
+
import logging
|
6
|
+
from pathlib import Path
|
7
|
+
|
8
|
+
from fastapi import APIRouter, Depends
|
9
|
+
|
10
|
+
from ..core.agent_factory import create_agent
|
11
|
+
from ..utils.process import process_manager
|
12
|
+
from .agent import get_api_token # Re-using the auth dependency from agent API
|
13
|
+
|
14
|
+
logger = logging.getLogger(__name__.replace("neuro_simulator", "server", 1))
|
15
|
+
router = APIRouter(prefix="/api/stream", tags=["Stream Control"])
|
16
|
+
|
17
|
+
@router.post("/start", dependencies=[Depends(get_api_token)])
|
18
|
+
async def start_stream():
|
19
|
+
"""Starts the live stream processes."""
|
20
|
+
try:
|
21
|
+
agent = await create_agent()
|
22
|
+
await agent.reset_memory()
|
23
|
+
except Exception as e:
|
24
|
+
logger.error(f"Could not reset agent memory on stream start: {e}", exc_info=True)
|
25
|
+
|
26
|
+
if not process_manager.is_running:
|
27
|
+
process_manager.start_live_processes()
|
28
|
+
return {"status": "success", "message": "Stream started"}
|
29
|
+
else:
|
30
|
+
return {"status": "info", "message": "Stream is already running"}
|
31
|
+
|
32
|
+
@router.post("/stop", dependencies=[Depends(get_api_token)])
|
33
|
+
async def stop_stream():
|
34
|
+
"""Stops the live stream processes."""
|
35
|
+
if process_manager.is_running:
|
36
|
+
process_manager.stop_live_processes()
|
37
|
+
return {"status": "success", "message": "Stream stopped"}
|
38
|
+
else:
|
39
|
+
return {"status": "info", "message": "Stream is not running"}
|
40
|
+
|
41
|
+
@router.post("/restart", dependencies=[Depends(get_api_token)])
|
42
|
+
async def restart_stream():
|
43
|
+
"""Restarts the live stream processes."""
|
44
|
+
process_manager.stop_live_processes()
|
45
|
+
await asyncio.sleep(1) # Give time for tasks to cancel
|
46
|
+
process_manager.start_live_processes()
|
47
|
+
return {"status": "success", "message": "Stream restarted"}
|
48
|
+
|
49
|
+
@router.get("/status", dependencies=[Depends(get_api_token)])
|
50
|
+
async def get_stream_status():
|
51
|
+
"""Gets the current status of the stream."""
|
52
|
+
return {
|
53
|
+
"is_running": process_manager.is_running,
|
54
|
+
"backend_status": "running" if process_manager.is_running else "stopped"
|
55
|
+
}
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# neuro_simulator/api/system.py
|
2
|
+
"""API endpoints for system, config, and utility functions."""
|
3
|
+
|
4
|
+
from fastapi import APIRouter, Depends, HTTPException
|
5
|
+
from pydantic import BaseModel
|
6
|
+
import time
|
7
|
+
|
8
|
+
from ..core.config import config_manager, AppSettings
|
9
|
+
from ..services.audio import synthesize_audio_segment
|
10
|
+
from .agent import get_api_token # Re-using the auth dependency
|
11
|
+
|
12
|
+
router = APIRouter(tags=["System & Utilities"])
|
13
|
+
|
14
|
+
# --- TTS Endpoint ---
|
15
|
+
|
16
|
+
class SpeechRequest(BaseModel):
|
17
|
+
text: str
|
18
|
+
voice_name: str | None = None
|
19
|
+
pitch: float | None = None
|
20
|
+
|
21
|
+
@router.post("/api/tts/synthesize", dependencies=[Depends(get_api_token)])
|
22
|
+
async def synthesize_speech_endpoint(request: SpeechRequest):
|
23
|
+
"""Synthesizes text to speech using the configured TTS service."""
|
24
|
+
try:
|
25
|
+
audio_base64, _ = await synthesize_audio_segment(
|
26
|
+
text=request.text, voice_name=request.voice_name, pitch=request.pitch
|
27
|
+
)
|
28
|
+
return {"audio_base64": audio_base64}
|
29
|
+
except Exception as e:
|
30
|
+
raise HTTPException(status_code=500, detail=str(e))
|
31
|
+
|
32
|
+
# --- Config Management Endpoints ---
|
33
|
+
|
34
|
+
# This helper can be moved to a more central location if needed
|
35
|
+
def filter_config_for_frontend(settings: AppSettings):
|
36
|
+
"""Filters the settings to return only the fields safe for the frontend."""
|
37
|
+
# Using .model_dump() with include is a more robust Pydantic approach
|
38
|
+
return settings.model_dump(include={
|
39
|
+
'stream_metadata': {'stream_title', 'stream_category', 'stream_tags'},
|
40
|
+
'agent': {'agent_provider', 'agent_model'},
|
41
|
+
'neuro_behavior': {'input_chat_sample_size', 'post_speech_cooldown_sec', 'initial_greeting'},
|
42
|
+
'audience_simulation': {'llm_provider', 'gemini_model', 'openai_model', 'llm_temperature', 'chat_generation_interval_sec', 'chats_per_batch', 'max_output_tokens', 'username_blocklist', 'username_pool'},
|
43
|
+
'performance': {'neuro_input_queue_max_size', 'audience_chat_buffer_max_size', 'initial_chat_backlog_limit'}
|
44
|
+
})
|
45
|
+
|
46
|
+
@router.get("/api/configs", dependencies=[Depends(get_api_token)])
|
47
|
+
async def get_configs():
|
48
|
+
"""Gets the current, frontend-safe configuration."""
|
49
|
+
return filter_config_for_frontend(config_manager.settings)
|
50
|
+
|
51
|
+
@router.patch("/api/configs", dependencies=[Depends(get_api_token)])
|
52
|
+
async def update_configs(new_settings: dict):
|
53
|
+
"""Updates the configuration with new values from the frontend."""
|
54
|
+
try:
|
55
|
+
await config_manager.update_settings(new_settings)
|
56
|
+
return filter_config_for_frontend(config_manager.settings)
|
57
|
+
except Exception as e:
|
58
|
+
raise HTTPException(status_code=500, detail=f"Failed to update settings: {str(e)}")
|
59
|
+
|
60
|
+
@router.post("/api/configs/reload", dependencies=[Depends(get_api_token)])
|
61
|
+
async def reload_configs():
|
62
|
+
"""Triggers a reload of the configuration from the config.yaml file."""
|
63
|
+
try:
|
64
|
+
# Passing an empty dict forces a reload and triggers callbacks
|
65
|
+
await config_manager.update_settings({})
|
66
|
+
return {"status": "success", "message": "Configuration reloaded"}
|
67
|
+
except Exception as e:
|
68
|
+
raise HTTPException(status_code=500, detail=f"Failed to reload settings: {str(e)}")
|
69
|
+
|
70
|
+
# --- System Endpoints ---
|
71
|
+
|
72
|
+
@router.get("/api/system/health")
|
73
|
+
async def health_check():
|
74
|
+
"""Provides a simple health check of the server."""
|
75
|
+
from ..utils.process import process_manager
|
76
|
+
return {
|
77
|
+
"status": "healthy",
|
78
|
+
"backend_running": True,
|
79
|
+
"process_manager_running": process_manager.is_running,
|
80
|
+
"timestamp": time.time()
|
81
|
+
}
|
82
|
+
|
83
|
+
@router.get("/")
|
84
|
+
async def root():
|
85
|
+
"""Returns basic information about the API."""
|
86
|
+
return {
|
87
|
+
"message": "Neuro-Sama Simulator Backend",
|
88
|
+
"version": "2.0",
|
89
|
+
"api_docs": "/docs",
|
90
|
+
}
|
neuro_simulator/cli.py
CHANGED
@@ -1,177 +1,88 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
+
"""Command-line interface for the Neuro-Simulator Server."""
|
2
3
|
|
3
4
|
import argparse
|
5
|
+
import logging
|
4
6
|
import os
|
5
|
-
import sys
|
6
7
|
import shutil
|
8
|
+
import sys
|
7
9
|
from pathlib import Path
|
8
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
|
+
|
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.")
|
9
34
|
|
10
35
|
def main():
|
36
|
+
"""Main entry point for the CLI."""
|
11
37
|
parser = argparse.ArgumentParser(description="Neuro-Simulator Server")
|
12
|
-
parser.add_argument("-D", "--dir", help="Working directory
|
38
|
+
parser.add_argument("-D", "--dir", help="Working directory for config and data")
|
13
39
|
parser.add_argument("-H", "--host", help="Host to bind the server to")
|
14
40
|
parser.add_argument("-P", "--port", type=int, help="Port to bind the server to")
|
15
41
|
|
16
42
|
args = parser.parse_args()
|
17
43
|
|
18
|
-
# Set working directory
|
44
|
+
# 1. Set working directory
|
19
45
|
if args.dir:
|
20
46
|
work_dir = Path(args.dir).resolve()
|
21
|
-
# If the directory doesn't exist (and it's not the default), raise an error
|
22
47
|
if not work_dir.exists():
|
23
|
-
|
48
|
+
logging.error(f"Working directory '{work_dir}' does not exist. Please create it first.")
|
24
49
|
sys.exit(1)
|
25
50
|
else:
|
26
51
|
work_dir = Path.home() / ".config" / "neuro-simulator"
|
27
52
|
work_dir.mkdir(parents=True, exist_ok=True)
|
28
53
|
|
29
|
-
# Change to working directory
|
30
54
|
os.chdir(work_dir)
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
# Copy config.yaml.example from package if it doesn't exist
|
37
|
-
if not settings_example_path.exists():
|
38
|
-
try:
|
39
|
-
# Try pkg_resources first (for installed packages)
|
40
|
-
try:
|
41
|
-
import pkg_resources
|
42
|
-
example_path = pkg_resources.resource_filename('neuro_simulator', 'config.yaml.example')
|
43
|
-
if os.path.exists(example_path):
|
44
|
-
shutil.copy(example_path, settings_example_path)
|
45
|
-
print(f"Created {settings_example_path} from package example")
|
46
|
-
else:
|
47
|
-
# Fallback to relative path (for development mode)
|
48
|
-
dev_example_path = Path(__file__).parent / "config.yaml.example"
|
49
|
-
if dev_example_path.exists():
|
50
|
-
shutil.copy(dev_example_path, settings_example_path)
|
51
|
-
print(f"Created {settings_example_path} from development example")
|
52
|
-
else:
|
53
|
-
print("Warning: config.yaml.example not found in package or development folder")
|
54
|
-
except Exception:
|
55
|
-
# Fallback to relative path (for development mode)
|
56
|
-
dev_example_path = Path(__file__).parent / "config.yaml.example"
|
57
|
-
if dev_example_path.exists():
|
58
|
-
shutil.copy(dev_example_path, settings_example_path)
|
59
|
-
print(f"Created {settings_example_path} from development example")
|
60
|
-
else:
|
61
|
-
print("Warning: config.yaml.example not found in package or development folder")
|
62
|
-
except Exception as e:
|
63
|
-
print(f"Warning: Could not copy config.yaml.example from package: {e}")
|
64
|
-
|
65
|
-
# Handle media folder
|
66
|
-
media_dir = work_dir / "media"
|
67
|
-
video_path = media_dir / "neuro_start.mp4"
|
68
|
-
|
69
|
-
# Copy media folder from package if it doesn't exist or is invalid
|
70
|
-
if not media_dir.exists() or not video_path.exists():
|
71
|
-
# If media dir exists but video doesn't, remove the incomplete media dir
|
72
|
-
if media_dir.exists():
|
73
|
-
shutil.rmtree(media_dir)
|
74
|
-
|
75
|
-
try:
|
76
|
-
# Try pkg_resources first (for installed packages)
|
77
|
-
try:
|
78
|
-
import pkg_resources
|
79
|
-
package_media_path = pkg_resources.resource_filename('neuro_simulator', 'media')
|
80
|
-
if os.path.exists(package_media_path):
|
81
|
-
shutil.copytree(package_media_path, media_dir)
|
82
|
-
print(f"Created {media_dir} from package media")
|
83
|
-
else:
|
84
|
-
# Fallback to relative path (for development mode)
|
85
|
-
dev_media_path = Path(__file__).parent / "media"
|
86
|
-
if dev_media_path.exists():
|
87
|
-
shutil.copytree(dev_media_path, media_dir)
|
88
|
-
print(f"Created {media_dir} from development media")
|
89
|
-
else:
|
90
|
-
print("Warning: media folder not found in package or development folder")
|
91
|
-
except Exception:
|
92
|
-
# Fallback to relative path (for development mode)
|
93
|
-
dev_media_path = Path(__file__).parent / "media"
|
94
|
-
if dev_media_path.exists():
|
95
|
-
shutil.copytree(dev_media_path, media_dir)
|
96
|
-
print(f"Created {media_dir} from development media")
|
97
|
-
else:
|
98
|
-
print("Warning: media folder not found in package or development folder")
|
99
|
-
except Exception as e:
|
100
|
-
print(f"Warning: Could not copy media folder from package: {e}")
|
101
|
-
|
102
|
-
# Handle agent/memory directory and example JSON files
|
55
|
+
logging.info(f"Using working directory: {work_dir}")
|
56
|
+
|
57
|
+
# 2. Ensure required assets and configs exist
|
58
|
+
copy_resource('neuro_simulator', 'core/config.yaml.example', work_dir / 'config.yaml.example')
|
59
|
+
copy_resource('neuro_simulator', 'assets', work_dir / 'assets', is_dir=True)
|
103
60
|
agent_memory_dir = work_dir / "agent" / "memory"
|
104
61
|
agent_memory_dir.mkdir(parents=True, exist_ok=True)
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
"core_memory.json",
|
110
|
-
"dialog_history.json",
|
111
|
-
"init_memory.json"
|
112
|
-
]
|
113
|
-
|
114
|
-
# Copy each example memory file if it doesn't exist
|
115
|
-
for filename in example_memory_files:
|
116
|
-
target_path = agent_memory_dir / filename
|
117
|
-
if not target_path.exists():
|
118
|
-
try:
|
119
|
-
# Try pkg_resources first (for installed packages)
|
120
|
-
try:
|
121
|
-
import pkg_resources
|
122
|
-
package_example_path = pkg_resources.resource_filename('neuro_simulator', f'agent/memory/{filename}')
|
123
|
-
if os.path.exists(package_example_path):
|
124
|
-
shutil.copy(package_example_path, target_path)
|
125
|
-
print(f"Created {target_path} from package example")
|
126
|
-
else:
|
127
|
-
# Fallback to relative path (for development mode)
|
128
|
-
dev_example_path = Path(__file__).parent / "agent" / "memory" / filename
|
129
|
-
if dev_example_path.exists():
|
130
|
-
shutil.copy(dev_example_path, target_path)
|
131
|
-
print(f"Created {target_path} from development example")
|
132
|
-
else:
|
133
|
-
print(f"Warning: {filename} not found in package or development folder")
|
134
|
-
except Exception:
|
135
|
-
# Fallback to relative path (for development mode)
|
136
|
-
dev_example_path = Path(__file__).parent / "agent" / "memory" / filename
|
137
|
-
if dev_example_path.exists():
|
138
|
-
shutil.copy(dev_example_path, target_path)
|
139
|
-
print(f"Created {target_path} from development example")
|
140
|
-
else:
|
141
|
-
print(f"Warning: {filename} not found in package or development folder")
|
142
|
-
except Exception as e:
|
143
|
-
print(f"Warning: Could not copy {filename} from package: {e}")
|
144
|
-
|
145
|
-
# Now check for required files and handle errors appropriately
|
62
|
+
for filename in ["context.json", "core_memory.json", "dialog_history.json", "init_memory.json"]:
|
63
|
+
copy_resource('neuro_simulator', f'agent/memory/{filename}', agent_memory_dir / filename)
|
64
|
+
|
65
|
+
# 3. Validate essential files
|
146
66
|
errors = []
|
147
|
-
|
148
|
-
|
149
|
-
if not
|
150
|
-
|
151
|
-
|
152
|
-
else:
|
153
|
-
errors.append(f"Error: Neither {settings_path} nor {settings_example_path} found. Please ensure proper configuration.")
|
154
|
-
|
155
|
-
# Check for required media files (required for running)
|
156
|
-
if not media_dir.exists() or not video_path.exists():
|
157
|
-
errors.append(f"Error: Required media files not found in {media_dir}.")
|
158
|
-
|
159
|
-
# If there are any errors, print them and exit
|
67
|
+
if not (work_dir / "config.yaml").exists():
|
68
|
+
errors.append(f"'config.yaml' not found in '{work_dir}'. Please copy 'config.yaml.example' to 'config.yaml' and configure it.")
|
69
|
+
if not (work_dir / "assets" / "neuro_start.mp4").exists():
|
70
|
+
errors.append(f"Required file 'neuro_start.mp4' not found in '{work_dir / 'assets'}'.")
|
71
|
+
|
160
72
|
if errors:
|
161
73
|
for error in errors:
|
162
|
-
|
74
|
+
logging.error(error)
|
163
75
|
sys.exit(1)
|
164
|
-
|
165
|
-
# Import and run the
|
76
|
+
|
77
|
+
# 4. Import and run the server
|
166
78
|
try:
|
167
|
-
from neuro_simulator.
|
79
|
+
from neuro_simulator.core.application import run_server
|
80
|
+
logging.info("Starting Neuro-Simulator server...")
|
81
|
+
# The full application logger will take over from here
|
168
82
|
run_server(args.host, args.port)
|
169
|
-
except ImportError:
|
170
|
-
|
171
|
-
sys.
|
172
|
-
from neuro_simulator.main import run_server
|
173
|
-
run_server(args.host, args.port)
|
174
|
-
|
83
|
+
except ImportError as e:
|
84
|
+
logging.error(f"Could not import the application. Make sure the package is installed correctly. Details: {e}", exc_info=True)
|
85
|
+
sys.exit(1)
|
175
86
|
|
176
87
|
if __name__ == "__main__":
|
177
88
|
main()
|
@@ -0,0 +1 @@
|
|
1
|
+
# This file makes the 'core' directory a Python package.
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# neuro_simulator/core/agent_factory.py
|
2
|
+
import logging
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
from .agent_interface import BaseAgent
|
6
|
+
from .config import config_manager, AppSettings
|
7
|
+
|
8
|
+
logger = logging.getLogger(__name__.replace("neuro_simulator", "server", 1))
|
9
|
+
|
10
|
+
# A cache for the agent instance to avoid re-initialization
|
11
|
+
_agent_instance: BaseAgent = None
|
12
|
+
|
13
|
+
def _reset_agent_on_config_update(new_settings: AppSettings):
|
14
|
+
global _agent_instance
|
15
|
+
logger.info("Configuration has been updated. Resetting cached agent instance.")
|
16
|
+
_agent_instance = None
|
17
|
+
|
18
|
+
# Register the callback to the config manager
|
19
|
+
config_manager.register_update_callback(_reset_agent_on_config_update)
|
20
|
+
|
21
|
+
async def create_agent() -> BaseAgent:
|
22
|
+
"""
|
23
|
+
Factory function to create and initialize an agent instance based on the configuration.
|
24
|
+
Returns a cached instance unless the configuration has changed.
|
25
|
+
"""
|
26
|
+
global _agent_instance
|
27
|
+
if _agent_instance is not None:
|
28
|
+
return _agent_instance
|
29
|
+
|
30
|
+
agent_type = config_manager.settings.agent_type
|
31
|
+
logger.info(f"Creating new agent instance of type: {agent_type}")
|
32
|
+
|
33
|
+
if agent_type == "builtin":
|
34
|
+
from ..services.builtin import BuiltinAgentWrapper, initialize_builtin_agent
|
35
|
+
|
36
|
+
agent_impl = await initialize_builtin_agent()
|
37
|
+
|
38
|
+
if agent_impl is None:
|
39
|
+
raise RuntimeError("Failed to initialize the Builtin agent implementation.")
|
40
|
+
|
41
|
+
_agent_instance = BuiltinAgentWrapper(agent_impl)
|
42
|
+
|
43
|
+
elif agent_type == "letta":
|
44
|
+
from ..services.letta import LettaAgent
|
45
|
+
_agent_instance = LettaAgent()
|
46
|
+
|
47
|
+
else:
|
48
|
+
raise ValueError(f"Unknown agent type: {agent_type}")
|
49
|
+
|
50
|
+
await _agent_instance.initialize()
|
51
|
+
|
52
|
+
return _agent_instance
|