neuro-simulator 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/PKG-INFO +3 -4
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/README.md +2 -3
- neuro_simulator-0.3.2/neuro_simulator/agent/core.py +234 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/agent/memory/manager.py +26 -26
- neuro_simulator-0.3.2/neuro_simulator/api/stream.py +1 -0
- neuro_simulator-0.3.2/neuro_simulator/api/system.py +63 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/cli.py +3 -2
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/core/agent_interface.py +1 -1
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/core/application.py +197 -5
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/services/builtin.py +34 -4
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/utils/process.py +8 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/utils/websocket.py +11 -14
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator.egg-info/PKG-INFO +3 -4
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator.egg-info/SOURCES.txt +0 -1
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/pyproject.toml +1 -1
- neuro_simulator-0.3.0/neuro_simulator/agent/core.py +0 -201
- neuro_simulator-0.3.0/neuro_simulator/api/agent.py +0 -163
- neuro_simulator-0.3.0/neuro_simulator/api/stream.py +0 -55
- neuro_simulator-0.3.0/neuro_simulator/api/system.py +0 -90
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/__init__.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/agent/__init__.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/agent/base.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/agent/factory.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/agent/llm.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/agent/memory/__init__.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/agent/tools/__init__.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/agent/tools/core.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/api/__init__.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/core/__init__.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/core/agent_factory.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/core/config.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/services/__init__.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/services/audience.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/services/audio.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/services/letta.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/services/stream.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/utils/__init__.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/utils/logging.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/utils/queue.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator/utils/state.py +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator.egg-info/dependency_links.txt +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator.egg-info/entry_points.txt +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator.egg-info/requires.txt +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/neuro_simulator.egg-info/top_level.txt +0 -0
- {neuro_simulator-0.3.0 → neuro_simulator-0.3.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: neuro_simulator
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.2
|
4
4
|
Summary: Neuro Simulator Server
|
5
5
|
Author-email: Moha-Master <hongkongreporter@outlook.com>
|
6
6
|
License-Expression: MIT
|
@@ -69,7 +69,7 @@ neuro_simulator/
|
|
69
69
|
│ ├── memory/ # 记忆管理模块
|
70
70
|
│ │ ├── __init__.py
|
71
71
|
│ │ ├── manager.py # 记忆管理器
|
72
|
-
│ │ ├──
|
72
|
+
│ │ ├── chat_history.json # 上下文记忆文件
|
73
73
|
│ │ ├── core_memory.json # 核心记忆文件
|
74
74
|
│ │ ├── init_memory.json # 初始化记忆文件
|
75
75
|
│ │ └── temp_memory.json # 临时记忆文件
|
@@ -109,7 +109,7 @@ working_dir_example/ # 工作目录结构,请将这个目录重命名和
|
|
109
109
|
├── config.yaml.example # 自动生成的配置文件模板,必须手动重命名和填写
|
110
110
|
└── agent/ # Agent相关文件夹
|
111
111
|
└── memory/ # Agent记忆文件夹
|
112
|
-
├──
|
112
|
+
├── chat_history.json # 上下文记忆文件
|
113
113
|
├── core_memory.json # 核心记忆文件
|
114
114
|
├── init_memory.json # 初始化记忆文件
|
115
115
|
└── temp_memory.json # 临时记忆文件
|
@@ -191,7 +191,6 @@ neuro -D /path/to/your/config -H 0.0.0.0 -P 8080
|
|
191
191
|
- `/api/configs/*` - 配置管理接口(获取/更新/重载配置)
|
192
192
|
- `api_keys` `server` 等敏感配置项无法从接口获取和修改
|
193
193
|
- `/api/logs` - 日志获取接口
|
194
|
-
- `/api/tts/synthesize` - TTS 合成接口
|
195
194
|
- `/api/system/health` - 健康检查接口
|
196
195
|
- `/ws/stream` - 客户端使用的直播接口
|
197
196
|
- `/ws/admin` - 日志和内建 Agent的 Context 流接口
|
@@ -32,7 +32,7 @@ neuro_simulator/
|
|
32
32
|
│ ├── memory/ # 记忆管理模块
|
33
33
|
│ │ ├── __init__.py
|
34
34
|
│ │ ├── manager.py # 记忆管理器
|
35
|
-
│ │ ├──
|
35
|
+
│ │ ├── chat_history.json # 上下文记忆文件
|
36
36
|
│ │ ├── core_memory.json # 核心记忆文件
|
37
37
|
│ │ ├── init_memory.json # 初始化记忆文件
|
38
38
|
│ │ └── temp_memory.json # 临时记忆文件
|
@@ -72,7 +72,7 @@ working_dir_example/ # 工作目录结构,请将这个目录重命名和
|
|
72
72
|
├── config.yaml.example # 自动生成的配置文件模板,必须手动重命名和填写
|
73
73
|
└── agent/ # Agent相关文件夹
|
74
74
|
└── memory/ # Agent记忆文件夹
|
75
|
-
├──
|
75
|
+
├── chat_history.json # 上下文记忆文件
|
76
76
|
├── core_memory.json # 核心记忆文件
|
77
77
|
├── init_memory.json # 初始化记忆文件
|
78
78
|
└── temp_memory.json # 临时记忆文件
|
@@ -154,7 +154,6 @@ neuro -D /path/to/your/config -H 0.0.0.0 -P 8080
|
|
154
154
|
- `/api/configs/*` - 配置管理接口(获取/更新/重载配置)
|
155
155
|
- `api_keys` `server` 等敏感配置项无法从接口获取和修改
|
156
156
|
- `/api/logs` - 日志获取接口
|
157
|
-
- `/api/tts/synthesize` - TTS 合成接口
|
158
157
|
- `/api/system/health` - 健康检查接口
|
159
158
|
- `/ws/stream` - 客户端使用的直播接口
|
160
159
|
- `/ws/admin` - 日志和内建 Agent的 Context 流接口
|
@@ -0,0 +1,234 @@
|
|
1
|
+
# neuro_simulator/agent/core.py
|
2
|
+
"""
|
3
|
+
Core module for the Neuro Simulator's built-in agent.
|
4
|
+
Implements a dual-LLM "Actor/Thinker" architecture for responsive interaction
|
5
|
+
and asynchronous memory consolidation.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import asyncio
|
9
|
+
import json
|
10
|
+
import logging
|
11
|
+
import re
|
12
|
+
from pathlib import Path
|
13
|
+
from typing import Any, Dict, List
|
14
|
+
|
15
|
+
from ..utils.logging import QueueLogHandler, agent_log_queue
|
16
|
+
from ..utils.websocket import connection_manager
|
17
|
+
from .llm import LLMClient
|
18
|
+
from .memory.manager import MemoryManager
|
19
|
+
from .tools.core import ToolManager
|
20
|
+
|
21
|
+
# Create a logger for the agent
|
22
|
+
agent_logger = logging.getLogger("neuro_agent")
|
23
|
+
agent_logger.setLevel(logging.DEBUG)
|
24
|
+
|
25
|
+
# Configure agent logging to use the shared queue
|
26
|
+
def configure_agent_logging():
|
27
|
+
"""Configure agent logging to use the shared agent_log_queue."""
|
28
|
+
if agent_logger.hasHandlers():
|
29
|
+
agent_logger.handlers.clear()
|
30
|
+
|
31
|
+
agent_queue_handler = QueueLogHandler(agent_log_queue)
|
32
|
+
formatter = logging.Formatter('%(asctime)s - [%(name)-32s] - %(levelname)-8s - %(message)s', datefmt='%H:%M:%S')
|
33
|
+
agent_queue_handler.setFormatter(formatter)
|
34
|
+
agent_logger.addHandler(agent_queue_handler)
|
35
|
+
agent_logger.propagate = False
|
36
|
+
agent_logger.info("Agent logging configured to use agent_log_queue.")
|
37
|
+
|
38
|
+
configure_agent_logging()
|
39
|
+
|
40
|
+
class Agent:
|
41
|
+
"""
|
42
|
+
Main Agent class implementing the Actor/Thinker model.
|
43
|
+
- The "Neuro" part (Actor) handles real-time interaction.
|
44
|
+
- The "Memory" part (Thinker) handles background memory consolidation.
|
45
|
+
"""
|
46
|
+
|
47
|
+
def __init__(self, working_dir: str = None):
|
48
|
+
self.memory_manager = MemoryManager(working_dir)
|
49
|
+
self.tool_manager = ToolManager(self.memory_manager)
|
50
|
+
|
51
|
+
# Dual LLM clients
|
52
|
+
self.neuro_llm = LLMClient()
|
53
|
+
self.memory_llm = LLMClient()
|
54
|
+
|
55
|
+
self._initialized = False
|
56
|
+
self.turn_counter = 0
|
57
|
+
self.reflection_threshold = 3 # Trigger reflection every 3 turns
|
58
|
+
|
59
|
+
agent_logger.info("Agent instance created with dual-LLM architecture.")
|
60
|
+
agent_logger.debug(f"Agent working directory: {working_dir}")
|
61
|
+
|
62
|
+
async def initialize(self):
|
63
|
+
"""Initialize the agent, loading any persistent memory."""
|
64
|
+
if not self._initialized:
|
65
|
+
agent_logger.info("Initializing agent memory manager...")
|
66
|
+
await self.memory_manager.initialize()
|
67
|
+
self._initialized = True
|
68
|
+
agent_logger.info("Agent initialized successfully.")
|
69
|
+
|
70
|
+
async def reset_all_memory(self):
|
71
|
+
"""Reset all agent memory types."""
|
72
|
+
await self.memory_manager.reset_temp_memory()
|
73
|
+
await self.memory_manager.reset_chat_history()
|
74
|
+
agent_logger.info("All agent memory has been reset.")
|
75
|
+
|
76
|
+
async def _build_neuro_prompt(self, messages: List[Dict[str, str]]) -> str:
|
77
|
+
"""Builds the prompt for the Neuro (Actor) LLM."""
|
78
|
+
template_path = Path(self.memory_manager.memory_dir).parent / "neuro_prompt.txt"
|
79
|
+
with open(template_path, 'r', encoding='utf-8') as f:
|
80
|
+
prompt_template = f.read()
|
81
|
+
|
82
|
+
# Gather context
|
83
|
+
tool_descriptions = self.tool_manager.get_tool_descriptions()
|
84
|
+
|
85
|
+
# Format Core Memory from blocks
|
86
|
+
core_memory_blocks = await self.memory_manager.get_core_memory_blocks()
|
87
|
+
core_memory_parts = []
|
88
|
+
if core_memory_blocks:
|
89
|
+
for block_id, block in core_memory_blocks.items():
|
90
|
+
core_memory_parts.append(f"\nBlock: {block.get('title', '')} ({block_id})")
|
91
|
+
core_memory_parts.append(f"Description: {block.get('description', '')}")
|
92
|
+
content_items = block.get("content", [])
|
93
|
+
if content_items:
|
94
|
+
core_memory_parts.append("Content:")
|
95
|
+
for item in content_items:
|
96
|
+
core_memory_parts.append(f" - {item}")
|
97
|
+
core_memory_text = "\n".join(core_memory_parts) if core_memory_parts else "Not set."
|
98
|
+
|
99
|
+
# Format Temp Memory
|
100
|
+
temp_memory_items = self.memory_manager.temp_memory
|
101
|
+
temp_memory_text = "\n".join(
|
102
|
+
[f"[{item.get('role', 'system')}] {item.get('content', '')}" for item in temp_memory_items]
|
103
|
+
) if temp_memory_items else "Empty."
|
104
|
+
|
105
|
+
recent_history = await self.memory_manager.get_recent_chat(entries=10)
|
106
|
+
|
107
|
+
user_messages_text = "\n".join([f"{msg['username']}: {msg['text']}" for msg in messages])
|
108
|
+
recent_history_text = "\n".join([f"{msg['role']}: {msg['content']}" for msg in recent_history])
|
109
|
+
|
110
|
+
return prompt_template.format(
|
111
|
+
tool_descriptions=tool_descriptions,
|
112
|
+
core_memory=core_memory_text,
|
113
|
+
temp_memory=temp_memory_text,
|
114
|
+
recent_history=recent_history_text,
|
115
|
+
user_messages=user_messages_text
|
116
|
+
)
|
117
|
+
|
118
|
+
async def _build_memory_prompt(self, conversation_history: List[Dict[str, str]]) -> str:
|
119
|
+
"""Builds the prompt for the Memory (Thinker) LLM."""
|
120
|
+
template_path = Path(self.memory_manager.memory_dir).parent / "memory_prompt.txt"
|
121
|
+
with open(template_path, 'r', encoding='utf-8') as f:
|
122
|
+
prompt_template = f.read()
|
123
|
+
|
124
|
+
history_text = "\n".join([f"{msg['role']}: {msg['content']}" for msg in conversation_history])
|
125
|
+
|
126
|
+
return prompt_template.format(conversation_history=history_text)
|
127
|
+
|
128
|
+
def _parse_tool_calls(self, response_text: str) -> List[Dict[str, Any]]:
|
129
|
+
"""Parses LLM response for JSON tool calls."""
|
130
|
+
try:
|
131
|
+
# The LLM is prompted to return a JSON array of tool calls.
|
132
|
+
# Find the JSON block, which might be wrapped in markdown.
|
133
|
+
match = re.search(r'''```json\s*([\s\S]*?)\s*```|(\[[\s\S]*\])''', response_text)
|
134
|
+
if not match:
|
135
|
+
agent_logger.warning(f"No valid JSON tool call block found in response: {response_text}")
|
136
|
+
return []
|
137
|
+
|
138
|
+
json_str = match.group(1) or match.group(2)
|
139
|
+
tool_calls = json.loads(json_str)
|
140
|
+
|
141
|
+
if isinstance(tool_calls, list):
|
142
|
+
return tool_calls
|
143
|
+
return []
|
144
|
+
except json.JSONDecodeError as e:
|
145
|
+
agent_logger.error(f"Failed to decode JSON from LLM response: {e}\nResponse text: {response_text}")
|
146
|
+
return []
|
147
|
+
except Exception as e:
|
148
|
+
agent_logger.error(f"An unexpected error occurred while parsing tool calls: {e}")
|
149
|
+
return []
|
150
|
+
|
151
|
+
async def _execute_tool_calls(self, tool_calls: List[Dict[str, Any]]) -> Dict[str, Any]:
|
152
|
+
"""Executes a list of parsed tool calls."""
|
153
|
+
execution_results = []
|
154
|
+
final_response = ""
|
155
|
+
for tool_call in tool_calls:
|
156
|
+
tool_name = tool_call.get("name")
|
157
|
+
params = tool_call.get("params", {})
|
158
|
+
if not tool_name:
|
159
|
+
continue
|
160
|
+
|
161
|
+
agent_logger.info(f"Executing tool: {tool_name} with params: {params}")
|
162
|
+
try:
|
163
|
+
result = await self.tool_manager.execute_tool(tool_name, params)
|
164
|
+
execution_results.append({"name": tool_name, "params": params, "result": result})
|
165
|
+
if tool_name == "speak":
|
166
|
+
final_response = params.get("text", "")
|
167
|
+
except Exception as e:
|
168
|
+
agent_logger.error(f"Error executing tool {tool_name}: {e}")
|
169
|
+
execution_results.append({"name": tool_name, "params": params, "error": str(e)})
|
170
|
+
|
171
|
+
return {"tool_executions": execution_results, "final_response": final_response}
|
172
|
+
|
173
|
+
async def process_and_respond(self, messages: List[Dict[str, str]]) -> Dict[str, Any]:
|
174
|
+
"""
|
175
|
+
The main entry point for the "Neuro" (Actor) flow.
|
176
|
+
Handles real-time interaction and triggers background reflection.
|
177
|
+
"""
|
178
|
+
await self.initialize()
|
179
|
+
agent_logger.info(f"Processing {len(messages)} messages in Actor flow.")
|
180
|
+
|
181
|
+
# Add user messages to context
|
182
|
+
for msg in messages:
|
183
|
+
await self.memory_manager.add_chat_entry("user", f"{msg['username']}: {msg['text']}")
|
184
|
+
|
185
|
+
# Build prompt and get response from Neuro LLM
|
186
|
+
prompt = await self._build_neuro_prompt(messages)
|
187
|
+
response_text = await self.neuro_llm.generate(prompt)
|
188
|
+
agent_logger.debug(f"Neuro LLM raw response: {response_text[:150] if response_text else 'None'}...")
|
189
|
+
|
190
|
+
# Parse and execute tools
|
191
|
+
tool_calls = self._parse_tool_calls(response_text)
|
192
|
+
processing_result = await self._execute_tool_calls(tool_calls)
|
193
|
+
|
194
|
+
# Add agent's response to context
|
195
|
+
if processing_result["final_response"]:
|
196
|
+
await self.memory_manager.add_chat_entry("assistant", processing_result["final_response"])
|
197
|
+
|
198
|
+
# Update dashboard/UI
|
199
|
+
final_context = await self.memory_manager.get_recent_chat()
|
200
|
+
# Broadcast to stream clients
|
201
|
+
await connection_manager.broadcast({"type": "agent_context", "action": "update", "messages": final_context})
|
202
|
+
# Broadcast to admin clients (Dashboard)
|
203
|
+
await connection_manager.broadcast_to_admins({"type": "agent_context", "action": "update", "messages": final_context})
|
204
|
+
|
205
|
+
# Handle reflection trigger
|
206
|
+
self.turn_counter += 1
|
207
|
+
if self.turn_counter >= self.reflection_threshold:
|
208
|
+
agent_logger.info(f"Reflection threshold reached ({self.turn_counter}/{self.reflection_threshold}). Scheduling background reflection.")
|
209
|
+
history_for_reflection = await self.memory_manager.get_recent_chat(entries=self.reflection_threshold * 2) # Get a bit more context
|
210
|
+
asyncio.create_task(self.reflect_on_context(history_for_reflection))
|
211
|
+
self.turn_counter = 0
|
212
|
+
|
213
|
+
agent_logger.info("Actor flow completed.")
|
214
|
+
return processing_result
|
215
|
+
|
216
|
+
async def reflect_on_context(self, conversation_history: List[Dict[str, str]]):
|
217
|
+
"""
|
218
|
+
The main entry point for the "Memory" (Thinker) flow.
|
219
|
+
Runs in the background to consolidate memories.
|
220
|
+
"""
|
221
|
+
agent_logger.info("Thinker flow started: Reflecting on recent context.")
|
222
|
+
|
223
|
+
prompt = await self._build_memory_prompt(conversation_history)
|
224
|
+
response_text = await self.memory_llm.generate(prompt)
|
225
|
+
agent_logger.debug(f"Memory LLM raw response: {response_text[:150] if response_text else 'None'}...")
|
226
|
+
|
227
|
+
tool_calls = self._parse_tool_calls(response_text)
|
228
|
+
if not tool_calls:
|
229
|
+
agent_logger.info("Thinker flow: No memory operations were suggested by the LLM.")
|
230
|
+
return
|
231
|
+
|
232
|
+
agent_logger.info(f"Thinker flow: Executing {len(tool_calls)} memory operations.")
|
233
|
+
await self._execute_tool_calls(tool_calls)
|
234
|
+
agent_logger.info("Thinker flow completed, memory has been updated.")
|
@@ -31,12 +31,12 @@ class MemoryManager:
|
|
31
31
|
|
32
32
|
self.init_memory_file = os.path.join(self.memory_dir, "init_memory.json")
|
33
33
|
self.core_memory_file = os.path.join(self.memory_dir, "core_memory.json")
|
34
|
-
self.
|
34
|
+
self.chat_history_file = os.path.join(self.memory_dir, "chat_history.json")
|
35
35
|
self.temp_memory_file = os.path.join(self.memory_dir, "temp_memory.json")
|
36
36
|
|
37
37
|
self.init_memory: Dict[str, Any] = {}
|
38
38
|
self.core_memory: Dict[str, Any] = {}
|
39
|
-
self.
|
39
|
+
self.chat_history: List[Dict[str, Any]] = []
|
40
40
|
self.temp_memory: List[Dict[str, Any]] = []
|
41
41
|
|
42
42
|
async def initialize(self):
|
@@ -61,12 +61,12 @@ class MemoryManager:
|
|
61
61
|
self.core_memory = {"blocks": {}}
|
62
62
|
await self._save_core_memory()
|
63
63
|
|
64
|
-
# Load
|
65
|
-
if os.path.exists(self.
|
66
|
-
with open(self.
|
67
|
-
self.
|
64
|
+
# Load chat history
|
65
|
+
if os.path.exists(self.chat_history_file):
|
66
|
+
with open(self.chat_history_file, 'r', encoding='utf-8') as f:
|
67
|
+
self.chat_history = json.load(f)
|
68
68
|
else:
|
69
|
-
self.
|
69
|
+
self.chat_history = []
|
70
70
|
|
71
71
|
# Load temp memory
|
72
72
|
if os.path.exists(self.temp_memory_file):
|
@@ -89,20 +89,20 @@ class MemoryManager:
|
|
89
89
|
with open(self.core_memory_file, 'w', encoding='utf-8') as f:
|
90
90
|
json.dump(self.core_memory, f, ensure_ascii=False, indent=2)
|
91
91
|
|
92
|
-
async def
|
93
|
-
with open(self.
|
94
|
-
json.dump(self.
|
92
|
+
async def _save_chat_history(self):
|
93
|
+
with open(self.chat_history_file, 'w', encoding='utf-8') as f:
|
94
|
+
json.dump(self.chat_history, f, ensure_ascii=False, indent=2)
|
95
95
|
|
96
96
|
async def _save_temp_memory(self):
|
97
97
|
with open(self.temp_memory_file, 'w', encoding='utf-8') as f:
|
98
98
|
json.dump(self.temp_memory, f, ensure_ascii=False, indent=2)
|
99
99
|
|
100
|
-
async def
|
100
|
+
async def add_chat_entry(self, role: str, content: str):
|
101
101
|
entry = {"id": generate_id(), "role": role, "content": content, "timestamp": datetime.now().isoformat()}
|
102
|
-
self.
|
103
|
-
await self.
|
102
|
+
self.chat_history.append(entry)
|
103
|
+
await self._save_chat_history()
|
104
104
|
|
105
|
-
async def
|
105
|
+
async def add_detailed_chat_entry(self, input_messages: List[Dict[str, str]],
|
106
106
|
prompt: str, llm_response: str,
|
107
107
|
tool_executions: List[Dict[str, Any]],
|
108
108
|
final_response: str, entry_id: str = None):
|
@@ -112,25 +112,25 @@ class MemoryManager:
|
|
112
112
|
"timestamp": datetime.now().isoformat()
|
113
113
|
}
|
114
114
|
if entry_id:
|
115
|
-
for entry in self.
|
115
|
+
for entry in self.chat_history:
|
116
116
|
if entry.get("id") == entry_id:
|
117
117
|
entry.update(update_data)
|
118
|
-
await self.
|
118
|
+
await self._save_chat_history()
|
119
119
|
return entry_id
|
120
120
|
|
121
121
|
new_entry = {"id": entry_id or generate_id(), "type": "llm_interaction", "role": "assistant", **update_data}
|
122
|
-
self.
|
123
|
-
await self.
|
122
|
+
self.chat_history.append(new_entry)
|
123
|
+
await self._save_chat_history()
|
124
124
|
return new_entry["id"]
|
125
125
|
|
126
|
-
async def
|
127
|
-
return self.
|
126
|
+
async def get_recent_chat(self, entries: int = 10) -> List[Dict[str, Any]]:
|
127
|
+
return self.chat_history[-entries:]
|
128
128
|
|
129
|
-
async def
|
130
|
-
return self.
|
129
|
+
async def get_detailed_chat_history(self) -> List[Dict[str, Any]]:
|
130
|
+
return self.chat_history
|
131
131
|
|
132
132
|
async def get_last_agent_response(self) -> Optional[str]:
|
133
|
-
for entry in reversed(self.
|
133
|
+
for entry in reversed(self.chat_history):
|
134
134
|
if entry.get("type") == "llm_interaction":
|
135
135
|
final_response = entry.get("final_response", "")
|
136
136
|
if final_response and final_response not in ["Processing started", "Prompt sent to LLM", "LLM response received"]:
|
@@ -141,9 +141,9 @@ class MemoryManager:
|
|
141
141
|
return content
|
142
142
|
return None
|
143
143
|
|
144
|
-
async def
|
145
|
-
self.
|
146
|
-
await self.
|
144
|
+
async def reset_chat_history(self):
|
145
|
+
self.chat_history = []
|
146
|
+
await self._save_chat_history()
|
147
147
|
|
148
148
|
async def reset_temp_memory(self):
|
149
149
|
"""Reset temp memory to a default empty state."""
|
@@ -0,0 +1 @@
|
|
1
|
+
# This file is now empty as all stream control API endpoints have been migrated to WebSockets.
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# neuro_simulator/api/system.py
|
2
|
+
"""API endpoints for system, config, and utility functions."""
|
3
|
+
|
4
|
+
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
5
|
+
import time
|
6
|
+
|
7
|
+
from ..core.config import config_manager
|
8
|
+
|
9
|
+
|
10
|
+
router = APIRouter(tags=["System & Utilities"])
|
11
|
+
|
12
|
+
|
13
|
+
# --- Utility function to filter config for frontend ---
|
14
|
+
def filter_config_for_frontend(settings):
|
15
|
+
"""Filters the full settings object to remove sensitive fields before sending to the frontend."""
|
16
|
+
# Create a dictionary representation of the settings
|
17
|
+
config_dict = settings.model_dump()
|
18
|
+
|
19
|
+
# Remove sensitive fields
|
20
|
+
config_dict.pop('api_keys', None)
|
21
|
+
|
22
|
+
return config_dict
|
23
|
+
|
24
|
+
|
25
|
+
# --- Auth Dependency ---
|
26
|
+
|
27
|
+
async def get_api_token(request: Request):
|
28
|
+
"""FastAPI dependency to check for the API token in headers."""
|
29
|
+
password = config_manager.settings.server.panel_password
|
30
|
+
if not password:
|
31
|
+
return True
|
32
|
+
header_token = request.headers.get("X-API-Token")
|
33
|
+
if header_token and header_token == password:
|
34
|
+
return True
|
35
|
+
raise HTTPException(
|
36
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
37
|
+
detail="Invalid API token",
|
38
|
+
headers={"WWW-Authenticate": "Bearer"},
|
39
|
+
)
|
40
|
+
|
41
|
+
# --- System Endpoints ---
|
42
|
+
|
43
|
+
@router.get("/api/system/health")
|
44
|
+
async def health_check():
|
45
|
+
"""Provides a simple health check of the server."""
|
46
|
+
from ..utils.process import process_manager
|
47
|
+
return {
|
48
|
+
"status": "healthy",
|
49
|
+
"backend_running": True,
|
50
|
+
"process_manager_running": process_manager.is_running,
|
51
|
+
"timestamp": time.time()
|
52
|
+
}
|
53
|
+
|
54
|
+
@router.get("/")
|
55
|
+
async def root():
|
56
|
+
"""Returns basic information about the API."""
|
57
|
+
return {
|
58
|
+
"message": "Neuro-Sama Simulator Backend",
|
59
|
+
"version": "2.0",
|
60
|
+
"api_docs": "/docs",
|
61
|
+
}
|
62
|
+
|
63
|
+
|
@@ -60,12 +60,13 @@ def main():
|
|
60
60
|
# Ensure agent directory and its contents exist
|
61
61
|
agent_dir = work_dir / "agent"
|
62
62
|
agent_dir.mkdir(parents=True, exist_ok=True)
|
63
|
-
copy_resource('neuro_simulator', 'agent/
|
63
|
+
copy_resource('neuro_simulator', 'agent/neuro_prompt.txt', agent_dir / 'neuro_prompt.txt')
|
64
|
+
copy_resource('neuro_simulator', 'agent/memory_prompt.txt', agent_dir / 'memory_prompt.txt')
|
64
65
|
|
65
66
|
# Ensure agent memory directory and its contents exist
|
66
67
|
agent_memory_dir = agent_dir / "memory"
|
67
68
|
agent_memory_dir.mkdir(parents=True, exist_ok=True)
|
68
|
-
for filename in ["
|
69
|
+
for filename in ["chat_history.json", "core_memory.json", "init_memory.json"]:
|
69
70
|
copy_resource('neuro_simulator', f'agent/memory/{filename}', agent_memory_dir / filename)
|
70
71
|
|
71
72
|
# 3. Validate essential files
|
@@ -16,7 +16,7 @@ class BaseAgent(ABC):
|
|
16
16
|
pass
|
17
17
|
|
18
18
|
@abstractmethod
|
19
|
-
async def
|
19
|
+
async def process_and_respond(self, messages: List[Dict[str, str]]) -> Dict[str, Any]:
|
20
20
|
"""Process messages and generate a response."""
|
21
21
|
pass
|
22
22
|
|