neuro-simulator 0.1.3__py3-none-any.whl → 0.2.1__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 (49) hide show
  1. neuro_simulator/__init__.py +1 -10
  2. neuro_simulator/agent/__init__.py +1 -8
  3. neuro_simulator/agent/base.py +43 -0
  4. neuro_simulator/agent/core.py +105 -398
  5. neuro_simulator/agent/factory.py +30 -0
  6. neuro_simulator/agent/llm.py +34 -31
  7. neuro_simulator/agent/memory/__init__.py +1 -4
  8. neuro_simulator/agent/memory/manager.py +61 -203
  9. neuro_simulator/agent/tools/__init__.py +1 -4
  10. neuro_simulator/agent/tools/core.py +8 -18
  11. neuro_simulator/api/__init__.py +1 -0
  12. neuro_simulator/api/agent.py +163 -0
  13. neuro_simulator/api/stream.py +55 -0
  14. neuro_simulator/api/system.py +90 -0
  15. neuro_simulator/cli.py +60 -143
  16. neuro_simulator/core/__init__.py +1 -0
  17. neuro_simulator/core/agent_factory.py +52 -0
  18. neuro_simulator/core/agent_interface.py +91 -0
  19. neuro_simulator/core/application.py +278 -0
  20. neuro_simulator/services/__init__.py +1 -0
  21. neuro_simulator/{chatbot.py → services/audience.py} +24 -24
  22. neuro_simulator/{audio_synthesis.py → services/audio.py} +18 -15
  23. neuro_simulator/services/builtin.py +87 -0
  24. neuro_simulator/services/letta.py +206 -0
  25. neuro_simulator/{stream_manager.py → services/stream.py} +39 -47
  26. neuro_simulator/utils/__init__.py +1 -0
  27. neuro_simulator/utils/logging.py +90 -0
  28. neuro_simulator/utils/process.py +67 -0
  29. neuro_simulator/{stream_chat.py → utils/queue.py} +17 -4
  30. neuro_simulator/utils/state.py +14 -0
  31. neuro_simulator/{websocket_manager.py → utils/websocket.py} +18 -14
  32. {neuro_simulator-0.1.3.dist-info → neuro_simulator-0.2.1.dist-info}/METADATA +83 -33
  33. neuro_simulator-0.2.1.dist-info/RECORD +37 -0
  34. neuro_simulator/agent/api.py +0 -737
  35. neuro_simulator/agent/memory.py +0 -137
  36. neuro_simulator/agent/tools.py +0 -69
  37. neuro_simulator/builtin_agent.py +0 -83
  38. neuro_simulator/config.yaml.example +0 -157
  39. neuro_simulator/letta.py +0 -164
  40. neuro_simulator/log_handler.py +0 -43
  41. neuro_simulator/main.py +0 -673
  42. neuro_simulator/media/neuro_start.mp4 +0 -0
  43. neuro_simulator/process_manager.py +0 -70
  44. neuro_simulator/shared_state.py +0 -11
  45. neuro_simulator-0.1.3.dist-info/RECORD +0 -31
  46. /neuro_simulator/{config.py → core/config.py} +0 -0
  47. {neuro_simulator-0.1.3.dist-info → neuro_simulator-0.2.1.dist-info}/WHEEL +0 -0
  48. {neuro_simulator-0.1.3.dist-info → neuro_simulator-0.2.1.dist-info}/entry_points.txt +0 -0
  49. {neuro_simulator-0.1.3.dist-info → neuro_simulator-0.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,91 @@
1
+ # neuro_simulator/core/agent_interface.py
2
+ from abc import ABC, abstractmethod
3
+ from typing import List, Dict, Any, Optional
4
+
5
+ class BaseAgent(ABC):
6
+ """Abstract base class for all agents, defining a common interface for the server."""
7
+
8
+ @abstractmethod
9
+ async def initialize(self):
10
+ """Initialize the agent."""
11
+ pass
12
+
13
+ @abstractmethod
14
+ async def reset_memory(self):
15
+ """Reset all types of agent memory."""
16
+ pass
17
+
18
+ @abstractmethod
19
+ async def process_messages(self, messages: List[Dict[str, str]]) -> Dict[str, Any]:
20
+ """Process messages and generate a response."""
21
+ pass
22
+
23
+ # Memory Block Management
24
+ @abstractmethod
25
+ async def get_memory_blocks(self) -> List[Dict[str, Any]]:
26
+ """Get all memory blocks."""
27
+ pass
28
+
29
+ @abstractmethod
30
+ async def get_memory_block(self, block_id: str) -> Optional[Dict[str, Any]]:
31
+ """Get a specific memory block by its ID."""
32
+ pass
33
+
34
+ @abstractmethod
35
+ async def create_memory_block(self, title: str, description: str, content: List[str]) -> Dict[str, str]:
36
+ """Create a new memory block."""
37
+ pass
38
+
39
+ @abstractmethod
40
+ async def update_memory_block(self, block_id: str, title: Optional[str], description: Optional[str], content: Optional[List[str]]):
41
+ """Update an existing memory block."""
42
+ pass
43
+
44
+ @abstractmethod
45
+ async def delete_memory_block(self, block_id: str):
46
+ """Delete a memory block."""
47
+ pass
48
+
49
+ # Init Memory Management
50
+ @abstractmethod
51
+ async def get_init_memory(self) -> Dict[str, Any]:
52
+ """Get the agent's initialization memory."""
53
+ pass
54
+
55
+ @abstractmethod
56
+ async def update_init_memory(self, memory: Dict[str, Any]):
57
+ """Update the agent's initialization memory."""
58
+ pass
59
+
60
+ # Temp Memory Management
61
+ @abstractmethod
62
+ async def get_temp_memory(self) -> List[Dict[str, Any]]:
63
+ """Get the agent's temporary memory."""
64
+ pass
65
+
66
+ @abstractmethod
67
+ async def add_temp_memory(self, content: str, role: str):
68
+ """Add an item to the agent's temporary memory."""
69
+ pass
70
+
71
+ @abstractmethod
72
+ async def clear_temp_memory(self):
73
+ """Clear the agent's temporary memory."""
74
+ pass
75
+
76
+ # Tool Management
77
+ @abstractmethod
78
+ async def get_available_tools(self) -> List[Dict[str, Any]]:
79
+ """Get a list of available tools."""
80
+ pass
81
+
82
+ @abstractmethod
83
+ async def execute_tool(self, tool_name: str, params: Dict[str, Any]) -> Any:
84
+ """Execute a tool with given parameters."""
85
+ pass
86
+
87
+ # Context/Message History
88
+ @abstractmethod
89
+ async def get_message_history(self, limit: int = 20) -> List[Dict[str, Any]]:
90
+ """Get the recent message history."""
91
+ pass
@@ -0,0 +1,278 @@
1
+ # neuro_simulator/core/application.py
2
+ """Main application file: FastAPI app instance, events, and websockets."""
3
+
4
+ import asyncio
5
+ import json
6
+ import logging
7
+ import random
8
+ import re
9
+ import time
10
+ from pathlib import Path
11
+ from typing import List
12
+
13
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect
14
+ from fastapi.middleware.cors import CORSMiddleware
15
+ from starlette.websockets import WebSocketState
16
+
17
+ # --- Core Imports ---
18
+ from .config import config_manager, AppSettings
19
+ from ..core.agent_factory import create_agent
20
+
21
+ # --- API Routers ---
22
+ from ..api.agent import router as agent_router
23
+ from ..api.stream import router as stream_router
24
+ from ..api.system import router as system_router
25
+
26
+ # --- Services and Utilities ---
27
+ from ..services.audience import AudienceChatbotManager, get_dynamic_audience_prompt
28
+ from ..services.audio import synthesize_audio_segment
29
+ from ..services.stream import live_stream_manager
30
+ from ..utils.logging import configure_server_logging, server_log_queue, agent_log_queue
31
+ from ..utils.process import process_manager
32
+ from ..utils.queue import (
33
+ add_to_audience_buffer,
34
+ add_to_neuro_input_queue,
35
+ get_recent_audience_chats,
36
+ is_neuro_input_queue_empty,
37
+ get_all_neuro_input_chats
38
+ )
39
+ from ..utils.state import app_state
40
+ from ..utils.websocket import connection_manager
41
+
42
+ # --- Logger Setup ---
43
+ logger = logging.getLogger(__name__.replace("neuro_simulator", "server", 1))
44
+
45
+ # --- FastAPI App Initialization ---
46
+ app = FastAPI(
47
+ title="Neuro-Sama Simulator API",
48
+ version="2.0.0",
49
+ description="Backend for the Neuro-Sama digital being simulator."
50
+ )
51
+
52
+ app.add_middleware(
53
+ CORSMiddleware,
54
+ allow_origins=config_manager.settings.server.client_origins + ["http://localhost:8080", "https://dashboard.live.jiahui.cafe"],
55
+ allow_credentials=True,
56
+ allow_methods=["*"],
57
+ allow_headers=["*"],
58
+ expose_headers=["X-API-Token"],
59
+ )
60
+
61
+ app.include_router(agent_router)
62
+ app.include_router(stream_router)
63
+ app.include_router(system_router)
64
+
65
+ # --- Background Task Definitions ---
66
+
67
+ chatbot_manager: AudienceChatbotManager = None
68
+
69
+ async def broadcast_events_task():
70
+ """Broadcasts events from the live_stream_manager's queue to all clients."""
71
+ while True:
72
+ try:
73
+ event = await live_stream_manager.event_queue.get()
74
+ await connection_manager.broadcast(event)
75
+ live_stream_manager.event_queue.task_done()
76
+ except asyncio.CancelledError:
77
+ break
78
+ except Exception as e:
79
+ logger.error(f"Error in broadcast_events_task: {e}", exc_info=True)
80
+
81
+ async def fetch_and_process_audience_chats():
82
+ """Generates a batch of audience chat messages."""
83
+ if not chatbot_manager or not chatbot_manager.client:
84
+ return
85
+ try:
86
+ dynamic_prompt = await get_dynamic_audience_prompt()
87
+ raw_chat_text = await chatbot_manager.client.generate_chat_messages(
88
+ prompt=dynamic_prompt,
89
+ max_tokens=config_manager.settings.audience_simulation.max_output_tokens
90
+ )
91
+
92
+ parsed_chats = []
93
+ for line in raw_chat_text.split('\n'):
94
+ line = line.strip()
95
+ if ':' in line:
96
+ username_raw, text = line.split(':', 1)
97
+ username = username_raw.strip()
98
+ if username in config_manager.settings.audience_simulation.username_blocklist:
99
+ username = random.choice(config_manager.settings.audience_simulation.username_pool)
100
+ if username and text.strip():
101
+ parsed_chats.append({"username": username, "text": text.strip()})
102
+ elif line:
103
+ parsed_chats.append({"username": random.choice(config_manager.settings.audience_simulation.username_pool), "text": line})
104
+
105
+ chats_to_broadcast = parsed_chats[:config_manager.settings.audience_simulation.chats_per_batch]
106
+
107
+ for chat in chats_to_broadcast:
108
+ add_to_audience_buffer(chat)
109
+ add_to_neuro_input_queue(chat)
110
+ broadcast_message = {"type": "chat_message", **chat, "is_user_message": False}
111
+ await connection_manager.broadcast(broadcast_message)
112
+ await asyncio.sleep(random.uniform(0.1, 0.4))
113
+ except Exception as e:
114
+ logger.error(f"Error in fetch_and_process_audience_chats: {e}", exc_info=True)
115
+
116
+ async def generate_audience_chat_task():
117
+ """Periodically triggers the audience chat generation task."""
118
+ while True:
119
+ try:
120
+ asyncio.create_task(fetch_and_process_audience_chats())
121
+ await asyncio.sleep(config_manager.settings.audience_simulation.chat_generation_interval_sec)
122
+ except asyncio.CancelledError:
123
+ break
124
+
125
+ async def neuro_response_cycle():
126
+ """The core response loop for the agent."""
127
+ await app_state.live_phase_started_event.wait()
128
+ agent = await create_agent()
129
+ is_first_response = True
130
+
131
+ while True:
132
+ try:
133
+ if is_first_response:
134
+ add_to_neuro_input_queue({"username": "System", "text": config_manager.settings.neuro_behavior.initial_greeting})
135
+ is_first_response = False
136
+ elif is_neuro_input_queue_empty():
137
+ await asyncio.sleep(1)
138
+ continue
139
+
140
+ current_queue_snapshot = get_all_neuro_input_chats()
141
+ sample_size = min(config_manager.settings.neuro_behavior.input_chat_sample_size, len(current_queue_snapshot))
142
+ selected_chats = random.sample(current_queue_snapshot, sample_size)
143
+
144
+ response_result = await asyncio.wait_for(agent.process_messages(selected_chats), timeout=20.0)
145
+
146
+ response_text = response_result.get("final_response", "").strip()
147
+ if not response_text:
148
+ continue
149
+
150
+ async with app_state.neuro_last_speech_lock:
151
+ app_state.neuro_last_speech = response_text
152
+
153
+ sentences = [s.strip() for s in re.split(r'(?<=[.!?])\s+', response_text.replace('\n', ' ')) if s.strip()]
154
+ if not sentences: continue
155
+
156
+ synthesis_tasks = [synthesize_audio_segment(s) for s in sentences]
157
+ synthesis_results = await asyncio.gather(*synthesis_tasks, return_exceptions=True)
158
+
159
+ speech_packages = [
160
+ {"segment_id": i, "text": sentences[i], "audio_base64": res[0], "duration": res[1]}
161
+ for i, res in enumerate(synthesis_results) if not isinstance(res, Exception)
162
+ ]
163
+
164
+ if not speech_packages: continue
165
+
166
+ live_stream_manager.set_neuro_speaking_status(True)
167
+ for package in speech_packages:
168
+ await connection_manager.broadcast({"type": "neuro_speech_segment", **package, "is_end": False})
169
+ await asyncio.sleep(package['duration'])
170
+
171
+ await connection_manager.broadcast({"type": "neuro_speech_segment", "is_end": True})
172
+ live_stream_manager.set_neuro_speaking_status(False)
173
+ await asyncio.sleep(config_manager.settings.neuro_behavior.post_speech_cooldown_sec)
174
+
175
+ except asyncio.TimeoutError:
176
+ logger.warning("Agent response timed out, skipping this cycle.")
177
+ await asyncio.sleep(5)
178
+ except asyncio.CancelledError:
179
+ live_stream_manager.set_neuro_speaking_status(False)
180
+ break
181
+ except Exception as e:
182
+ logger.error(f"Critical error in neuro_response_cycle: {e}", exc_info=True)
183
+ live_stream_manager.set_neuro_speaking_status(False)
184
+ await asyncio.sleep(10)
185
+
186
+ # --- Application Lifecycle Events ---
187
+
188
+ @app.on_event("startup")
189
+ async def startup_event():
190
+ """Actions to perform on application startup."""
191
+ global chatbot_manager
192
+ configure_server_logging()
193
+
194
+ chatbot_manager = AudienceChatbotManager()
195
+
196
+ async def metadata_callback(settings: AppSettings):
197
+ await live_stream_manager.broadcast_stream_metadata()
198
+
199
+ config_manager.register_update_callback(metadata_callback)
200
+ config_manager.register_update_callback(chatbot_manager.handle_config_update)
201
+
202
+ try:
203
+ await create_agent()
204
+ logger.info(f"Successfully initialized agent type: {config_manager.settings.agent_type}")
205
+ except Exception as e:
206
+ logger.critical(f"Agent initialization failed on startup: {e}", exc_info=True)
207
+
208
+ logger.info("FastAPI application has started.")
209
+
210
+ @app.on_event("shutdown")
211
+ async def shutdown_event():
212
+ """Actions to perform on application shutdown."""
213
+ if process_manager.is_running:
214
+ process_manager.stop_live_processes()
215
+ logger.info("FastAPI application has shut down.")
216
+
217
+ # --- WebSocket Endpoints ---
218
+
219
+ @app.websocket("/ws/stream")
220
+ async def websocket_stream_endpoint(websocket: WebSocket):
221
+ await connection_manager.connect(websocket)
222
+ try:
223
+ await connection_manager.send_personal_message(live_stream_manager.get_initial_state_for_client(), websocket)
224
+ await connection_manager.send_personal_message({"type": "update_stream_metadata", **config_manager.settings.stream_metadata.model_dump()}, websocket)
225
+
226
+ initial_chats = get_recent_audience_chats(config_manager.settings.performance.initial_chat_backlog_limit)
227
+ for chat in initial_chats:
228
+ await connection_manager.send_personal_message({"type": "chat_message", **chat, "is_user_message": False}, websocket)
229
+ await asyncio.sleep(0.01)
230
+
231
+ while True:
232
+ raw_data = await websocket.receive_text()
233
+ data = json.loads(raw_data)
234
+ if data.get("type") == "user_message":
235
+ user_message = {"username": data.get("username", "User"), "text": data.get("message", "").strip()}
236
+ if user_message["text"]:
237
+ add_to_audience_buffer(user_message)
238
+ add_to_neuro_input_queue(user_message)
239
+ await connection_manager.broadcast({"type": "chat_message", **user_message, "is_user_message": True})
240
+ except WebSocketDisconnect:
241
+ pass
242
+ finally:
243
+ connection_manager.disconnect(websocket)
244
+
245
+ @app.websocket("/ws/admin")
246
+ async def websocket_admin_endpoint(websocket: WebSocket):
247
+ await websocket.accept()
248
+ try:
249
+ for log_entry in list(server_log_queue): await websocket.send_json({"type": "server_log", "data": log_entry})
250
+ for log_entry in list(agent_log_queue): await websocket.send_json({"type": "agent_log", "data": log_entry})
251
+
252
+ agent = await create_agent()
253
+ initial_context = await agent.get_message_history()
254
+ await websocket.send_json({"type": "agent_context", "action": "update", "messages": initial_context})
255
+
256
+ while websocket.client_state == WebSocketState.CONNECTED:
257
+ if server_log_queue: await websocket.send_json({"type": "server_log", "data": server_log_queue.popleft()})
258
+ if agent_log_queue: await websocket.send_json({"type": "agent_log", "data": agent_log_queue.popleft()})
259
+ await asyncio.sleep(0.1)
260
+ except WebSocketDisconnect:
261
+ pass
262
+ finally:
263
+ logger.info("Admin WebSocket client disconnected.")
264
+
265
+ # --- Server Entrypoint ---
266
+
267
+ def run_server(host: str = None, port: int = None):
268
+ """Runs the FastAPI server with Uvicorn."""
269
+ import uvicorn
270
+ server_host = host or config_manager.settings.server.host
271
+ server_port = port or config_manager.settings.server.port
272
+
273
+ uvicorn.run(
274
+ "neuro_simulator.core.application:app",
275
+ host=server_host,
276
+ port=server_port,
277
+ reload=False
278
+ )
@@ -0,0 +1 @@
1
+ # This file makes the 'services' directory a Python package.
@@ -1,11 +1,16 @@
1
- # backend/chatbot.py
1
+ # neuro_simulator/services/audience.py
2
+ import asyncio
3
+ import logging
4
+ import random
5
+
2
6
  from google import genai
3
7
  from google.genai import types
4
8
  from openai import AsyncOpenAI
5
- import random
6
- import asyncio
7
- from .config import config_manager, AppSettings
8
- import neuro_simulator.shared_state as shared_state
9
+
10
+ from ..core.config import config_manager, AppSettings
11
+ from ..utils.state import app_state
12
+
13
+ logger = logging.getLogger(__name__.replace("neuro_simulator", "server", 1))
9
14
 
10
15
  class AudienceLLMClient:
11
16
  async def generate_chat_messages(self, prompt: str, max_tokens: int) -> str:
@@ -15,13 +20,11 @@ class GeminiAudienceLLM(AudienceLLMClient):
15
20
  def __init__(self, api_key: str, model_name: str):
16
21
  if not api_key:
17
22
  raise ValueError("Gemini API Key is not provided for GeminiAudienceLLM.")
18
- # 根据新文档,正确初始化客户端
19
23
  self.client = genai.Client(api_key=api_key)
20
24
  self.model_name = model_name
21
- print(f"已初始化 GeminiAudienceLLM (new SDK),模型: {self.model_name}")
25
+ logger.info(f"Initialized GeminiAudienceLLM (new SDK), model: {self.model_name}")
22
26
 
23
27
  async def generate_chat_messages(self, prompt: str, max_tokens: int) -> str:
24
- # 根据新文档,使用正确的异步方法和参数
25
28
  response = await self.client.aio.models.generate_content(
26
29
  model=self.model_name,
27
30
  contents=prompt,
@@ -45,7 +48,7 @@ class OpenAIAudienceLLM(AudienceLLMClient):
45
48
  raise ValueError("OpenAI API Key is not provided for OpenAIAudienceLLM.")
46
49
  self.client = AsyncOpenAI(api_key=api_key, base_url=base_url)
47
50
  self.model_name = model_name
48
- print(f"已初始化 OpenAIAudienceLLM,模型: {self.model_name}API Base: {base_url}")
51
+ logger.info(f"Initialized OpenAIAudienceLLM, model: {self.model_name}, API Base: {base_url}")
49
52
 
50
53
  async def generate_chat_messages(self, prompt: str, max_tokens: int) -> str:
51
54
  response = await self.client.chat.completions.create(
@@ -60,45 +63,42 @@ class OpenAIAudienceLLM(AudienceLLMClient):
60
63
 
61
64
  async def get_dynamic_audience_prompt() -> str:
62
65
  current_neuro_speech = ""
63
- async with shared_state.neuro_last_speech_lock:
64
- current_neuro_speech = shared_state.neuro_last_speech
65
-
66
- # 使用 settings 对象中的模板和变量
66
+ async with app_state.neuro_last_speech_lock:
67
+ current_neuro_speech = app_state.neuro_last_speech
68
+
67
69
  prompt = config_manager.settings.audience_simulation.prompt_template.format(
68
70
  neuro_speech=current_neuro_speech,
69
71
  num_chats_to_generate=config_manager.settings.audience_simulation.chats_per_batch
70
72
  )
71
73
  return prompt
72
74
 
73
- class ChatbotManager:
75
+ class AudienceChatbotManager:
74
76
  def __init__(self):
75
77
  self.client: AudienceLLMClient = self._create_client(config_manager.settings)
76
78
  self._last_checked_settings: dict = config_manager.settings.audience_simulation.model_dump()
77
- print("ChatbotManager initialized.")
79
+ logger.info("AudienceChatbotManager initialized.")
78
80
 
79
81
  def _create_client(self, settings: AppSettings) -> AudienceLLMClient:
80
82
  provider = settings.audience_simulation.llm_provider
81
- print(f"正在为 provider 创建新的 audience LLM client: {provider}")
83
+ logger.info(f"Creating new audience LLM client for provider: {provider}")
82
84
  if provider.lower() == "gemini":
83
85
  if not settings.api_keys.gemini_api_key:
84
- raise ValueError("GEMINI_API_KEY 未在配置中设置")
86
+ raise ValueError("GEMINI_API_KEY not set in config")
85
87
  return GeminiAudienceLLM(api_key=settings.api_keys.gemini_api_key, model_name=settings.audience_simulation.gemini_model)
86
88
  elif provider.lower() == "openai":
87
89
  if not settings.api_keys.openai_api_key:
88
- raise ValueError("OPENAI_API_KEY 未在配置中设置")
90
+ raise ValueError("OPENAI_API_KEY not set in config")
89
91
  return OpenAIAudienceLLM(api_key=settings.api_keys.openai_api_key, model_name=settings.audience_simulation.openai_model, base_url=settings.api_keys.openai_api_base_url)
90
92
  else:
91
- raise ValueError(f"不支持的 AUDIENCE_LLM_PROVIDER: {provider}")
93
+ raise ValueError(f"Unsupported AUDIENCE_LLM_PROVIDER: {provider}")
92
94
 
93
95
  def handle_config_update(self, new_settings: AppSettings):
94
96
  new_audience_settings = new_settings.audience_simulation.model_dump()
95
97
  if new_audience_settings != self._last_checked_settings:
96
- print("检测到观众模拟设置已更改,正在重新初始化 LLM client...")
98
+ logger.info("Audience simulation settings changed, re-initializing LLM client...")
97
99
  try:
98
100
  self.client = self._create_client(new_settings)
99
101
  self._last_checked_settings = new_audience_settings
100
- print("LLM client 已成功热重载。")
102
+ logger.info("LLM client hot-reloaded successfully.")
101
103
  except Exception as e:
102
- print(f"错误:热重载 LLM client 失败: {e}")
103
- else:
104
- print("观众模拟设置未更改,跳过 LLM client 重载。")
104
+ logger.error(f"Error hot-reloading LLM client: {e}", exc_info=True)
@@ -1,24 +1,27 @@
1
- # backend/audio_synthesis.py
2
- import html
1
+ # neuro_simulator/services/audio.py
2
+ import asyncio
3
3
  import base64
4
+ import html
5
+ import logging
6
+ from pathlib import Path
7
+
4
8
  import azure.cognitiveservices.speech as speechsdk
5
- import asyncio
6
- from .config import config_manager
9
+
10
+ from ..core.config import config_manager
11
+
12
+ logger = logging.getLogger(__name__.replace("neuro_simulator", "server", 1))
7
13
 
8
14
  async def synthesize_audio_segment(text: str, voice_name: str = None, pitch: float = None) -> tuple[str, float]:
9
15
  """
10
- 使用 Azure TTS 合成音频。
11
- 如果 voice_name pitch 未提供,则使用配置中的默认值。
12
- 返回 Base64 编码的音频字符串和音频时长(秒)。
16
+ Synthesizes audio using Azure TTS.
17
+ Returns a Base64 encoded audio string and the audio duration in seconds.
13
18
  """
14
- # 使用 config_manager.settings 中的值
15
19
  azure_key = config_manager.settings.api_keys.azure_speech_key
16
20
  azure_region = config_manager.settings.api_keys.azure_speech_region
17
21
 
18
22
  if not azure_key or not azure_region:
19
- raise ValueError("Azure Speech Key Region 未在配置中设置。")
23
+ raise ValueError("Azure Speech Key or Region is not set in the configuration.")
20
24
 
21
- # 如果未传入参数,则使用配置的默认值
22
25
  final_voice_name = voice_name if voice_name is not None else config_manager.settings.tts.voice_name
23
26
  final_pitch = pitch if pitch is not None else config_manager.settings.tts.voice_pitch
24
27
 
@@ -52,15 +55,15 @@ async def synthesize_audio_segment(text: str, voice_name: str = None, pitch: flo
52
55
  audio_data = result.audio_data
53
56
  encoded_audio = base64.b64encode(audio_data).decode('utf-8')
54
57
  audio_duration_sec = result.audio_duration.total_seconds()
55
- print(f"TTS 合成完成: '{text[:30]}...' (时长: {audio_duration_sec:.2f}s)")
58
+ logger.info(f"TTS synthesis completed: '{text[:30]}...' (Duration: {audio_duration_sec:.2f}s)")
56
59
  return encoded_audio, audio_duration_sec
57
60
  else:
58
61
  cancellation_details = result.cancellation_details
59
- error_message = f"TTS 合成失败/取消 (原因: {cancellation_details.reason})。文本: '{text}'"
62
+ error_message = f"TTS synthesis failed (Reason: {cancellation_details.reason}). Text: '{text}'"
60
63
  if cancellation_details.error_details:
61
- error_message += f" | 详情: {cancellation_details.error_details}"
62
- print(f"错误: {error_message}")
64
+ error_message += f" | Details: {cancellation_details.error_details}"
65
+ logger.error(error_message)
63
66
  raise Exception(error_message)
64
67
  except Exception as e:
65
- print(f"错误: 在调用 Azure TTS SDK 时发生异常: {e}")
68
+ logger.error(f"An exception occurred during the Azure TTS SDK call: {e}", exc_info=True)
66
69
  raise
@@ -0,0 +1,87 @@
1
+ # neuro_simulator/services/builtin.py
2
+ """Builtin agent module for Neuro Simulator"""
3
+
4
+ import asyncio
5
+ import re
6
+ import logging
7
+ from typing import List, Dict, Any, Optional
8
+
9
+ from ..core.agent_interface import BaseAgent
10
+ from ..agent.core import Agent as LocalAgent
11
+ from ..services.stream import live_stream_manager
12
+
13
+ logger = logging.getLogger(__name__.replace("neuro_simulator", "server", 1))
14
+
15
+ async def initialize_builtin_agent() -> Optional[LocalAgent]:
16
+ """Initializes the builtin agent instance and returns it."""
17
+ try:
18
+ working_dir = live_stream_manager._working_dir
19
+ agent_instance = LocalAgent(working_dir=working_dir)
20
+ await agent_instance.initialize()
21
+ logger.info("Builtin agent implementation initialized successfully.")
22
+ return agent_instance
23
+ except Exception as e:
24
+ logger.error(f"Failed to initialize local agent implementation: {e}", exc_info=True)
25
+ return None
26
+
27
+ class BuiltinAgentWrapper(BaseAgent):
28
+ """Wrapper for the builtin agent to implement the BaseAgent interface."""
29
+ def __init__(self, agent_instance: LocalAgent):
30
+ self.agent_instance = agent_instance
31
+
32
+ async def initialize(self):
33
+ if self.agent_instance is None:
34
+ raise RuntimeError("Builtin agent not initialized")
35
+ await self.agent_instance.initialize()
36
+
37
+ async def reset_memory(self):
38
+ await self.agent_instance.reset_all_memory()
39
+
40
+ async def process_messages(self, messages: List[Dict[str, str]]) -> Dict[str, Any]:
41
+ return await self.agent_instance.process_messages(messages)
42
+
43
+ # Memory Block Management
44
+ async def get_memory_blocks(self) -> List[Dict[str, Any]]:
45
+ blocks_dict = await self.agent_instance.memory_manager.get_core_memory_blocks()
46
+ return list(blocks_dict.values())
47
+
48
+ async def get_memory_block(self, block_id: str) -> Optional[Dict[str, Any]]:
49
+ return await self.agent_instance.memory_manager.get_core_memory_block(block_id)
50
+
51
+ async def create_memory_block(self, title: str, description: str, content: List[str]) -> Dict[str, str]:
52
+ block_id = await self.agent_instance.memory_manager.create_core_memory_block(title, description, content)
53
+ return {"block_id": block_id}
54
+
55
+ async def update_memory_block(self, block_id: str, title: Optional[str], description: Optional[str], content: Optional[List[str]]):
56
+ await self.agent_instance.memory_manager.update_core_memory_block(block_id, title, description, content)
57
+
58
+ async def delete_memory_block(self, block_id: str):
59
+ await self.agent_instance.memory_manager.delete_core_memory_block(block_id)
60
+
61
+ # Init Memory Management
62
+ async def get_init_memory(self) -> Dict[str, Any]:
63
+ return self.agent_instance.memory_manager.init_memory
64
+
65
+ async def update_init_memory(self, memory: Dict[str, Any]):
66
+ await self.agent_instance.memory_manager.update_init_memory(memory)
67
+
68
+ # Temp Memory Management
69
+ async def get_temp_memory(self) -> List[Dict[str, Any]]:
70
+ return self.agent_instance.memory_manager.temp_memory
71
+
72
+ async def add_temp_memory(self, content: str, role: str):
73
+ await self.agent_instance.memory_manager.add_temp_memory(content, role)
74
+
75
+ async def clear_temp_memory(self):
76
+ await self.agent_instance.memory_manager.reset_temp_memory()
77
+
78
+ # Tool Management
79
+ async def get_available_tools(self) -> str:
80
+ return self.agent_instance.tool_manager.get_tool_descriptions()
81
+
82
+ async def execute_tool(self, tool_name: str, params: Dict[str, Any]) -> Any:
83
+ return await self.agent_instance.execute_tool(tool_name, params)
84
+
85
+ # Context/Message History
86
+ async def get_message_history(self, limit: int = 20) -> List[Dict[str, Any]]:
87
+ return await self.agent_instance.memory_manager.get_recent_context(limit)