neuro-simulator 0.5.4__py3-none-any.whl → 0.6.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/llm.py +23 -19
- neuro_simulator/chatbot/core.py +10 -10
- neuro_simulator/chatbot/llm.py +22 -19
- neuro_simulator/chatbot/nickname_gen/generator.py +3 -3
- neuro_simulator/chatbot/tools/manager.py +10 -8
- neuro_simulator/cli.py +7 -12
- neuro_simulator/core/agent_factory.py +9 -18
- neuro_simulator/core/application.py +59 -56
- neuro_simulator/core/config.py +88 -301
- neuro_simulator/core/path_manager.py +7 -7
- neuro_simulator/dashboard/assets/{AgentView-C6qW7TIe.js → AgentView-DBq2msN_.js} +2 -2
- neuro_simulator/dashboard/assets/{ChatBotView-BRYIM_8s.js → ChatBotView-BqQsuJUv.js} +2 -2
- neuro_simulator/dashboard/assets/ConfigView-CPYMgl_d.js +2 -0
- neuro_simulator/dashboard/assets/ConfigView-aFribfyR.css +1 -0
- neuro_simulator/dashboard/assets/{ContextTab-GRHICOS3.js → ContextTab-BSROkcd2.js} +1 -1
- neuro_simulator/dashboard/assets/{ControlView-D5vPB_OE.js → ControlView-BvflkxO-.js} +1 -1
- neuro_simulator/dashboard/assets/FieldRenderer-DyPAEyOT.js +1 -0
- neuro_simulator/dashboard/assets/LogsTab-C-SZhHdN.js +1 -0
- neuro_simulator/dashboard/assets/LogsView-82wOs2Pp.js +1 -0
- neuro_simulator/dashboard/assets/{MemoryTab-BSUWFbcV.js → MemoryTab-p3Q-Wa4e.js} +3 -3
- neuro_simulator/dashboard/assets/{ToolsTab-Bjcm3fFL.js → ToolsTab-BxbFZhXs.js} +1 -1
- neuro_simulator/dashboard/assets/index-Ba5ZG3QB.js +52 -0
- neuro_simulator/dashboard/assets/{index-C7dox9UB.css → index-CcYt9OR6.css} +1 -1
- neuro_simulator/dashboard/index.html +2 -2
- neuro_simulator/services/audio.py +55 -47
- neuro_simulator/services/builtin.py +3 -0
- neuro_simulator/services/stream.py +1 -1
- neuro_simulator/utils/queue.py +2 -2
- {neuro_simulator-0.5.4.dist-info → neuro_simulator-0.6.0.dist-info}/METADATA +1 -2
- {neuro_simulator-0.5.4.dist-info → neuro_simulator-0.6.0.dist-info}/RECORD +34 -35
- requirements.txt +1 -1
- neuro_simulator/config.yaml.example +0 -117
- neuro_simulator/dashboard/assets/ConfigView-Cw-VPFzt.js +0 -2
- neuro_simulator/dashboard/assets/FieldRenderer-DaTYxmtO.js +0 -1
- neuro_simulator/dashboard/assets/LogsTab-CATao-mZ.js +0 -1
- neuro_simulator/dashboard/assets/LogsView-BM419A5R.js +0 -1
- neuro_simulator/dashboard/assets/index-BiAhe8fO.js +0 -34
- neuro_simulator/services/letta.py +0 -254
- {neuro_simulator-0.5.4.dist-info → neuro_simulator-0.6.0.dist-info}/WHEEL +0 -0
- {neuro_simulator-0.5.4.dist-info → neuro_simulator-0.6.0.dist-info}/entry_points.txt +0 -0
- {neuro_simulator-0.5.4.dist-info → neuro_simulator-0.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,254 +0,0 @@
|
|
1
|
-
# neuro_simulator/services/letta.py
|
2
|
-
import asyncio
|
3
|
-
import logging
|
4
|
-
from typing import Union, List, Dict, Any, Optional
|
5
|
-
|
6
|
-
from fastapi import HTTPException, status
|
7
|
-
from letta_client import Letta, MessageCreate, TextContent, LlmConfig, AssistantMessage
|
8
|
-
|
9
|
-
from ..core.agent_interface import BaseAgent
|
10
|
-
from ..core.config import config_manager
|
11
|
-
|
12
|
-
# Standard logger for this module
|
13
|
-
logger = logging.getLogger(__name__.replace("neuro_simulator", "server", 1))
|
14
|
-
|
15
|
-
# Global client instance, initialized once
|
16
|
-
letta_client: Union[Letta, None] = None
|
17
|
-
|
18
|
-
def initialize_letta_client():
|
19
|
-
"""Initializes the global Letta client if not already initialized."""
|
20
|
-
global letta_client
|
21
|
-
if letta_client:
|
22
|
-
return
|
23
|
-
|
24
|
-
try:
|
25
|
-
if not config_manager.settings.api_keys.letta_token:
|
26
|
-
raise ValueError("LETTA_API_TOKEN is not set. Cannot initialize Letta client.")
|
27
|
-
|
28
|
-
client_args = {'token': config_manager.settings.api_keys.letta_token}
|
29
|
-
if config_manager.settings.api_keys.letta_base_url:
|
30
|
-
client_args['base_url'] = config_manager.settings.api_keys.letta_base_url
|
31
|
-
logger.info(f"Letta client is being initialized for self-hosted URL: {config_manager.settings.api_keys.letta_base_url}")
|
32
|
-
else:
|
33
|
-
logger.info("Letta client is being initialized for Letta Cloud.")
|
34
|
-
|
35
|
-
letta_client = Letta(**client_args)
|
36
|
-
|
37
|
-
agent_id = config_manager.settings.api_keys.neuro_agent_id
|
38
|
-
if agent_id:
|
39
|
-
try:
|
40
|
-
agent_data = letta_client.agents.retrieve(agent_id=agent_id)
|
41
|
-
logger.info(f"Successfully verified Letta Agent, ID: {agent_data.id}, Name: {agent_data.name}")
|
42
|
-
except Exception as e:
|
43
|
-
error_msg = f"Error: Cannot retrieve Letta Agent (ID: {agent_id}). Details: {e}"
|
44
|
-
logger.error(error_msg)
|
45
|
-
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=error_msg)
|
46
|
-
except Exception as e:
|
47
|
-
logger.error(f"Failed to initialize Letta client: {e}")
|
48
|
-
letta_client = None
|
49
|
-
|
50
|
-
def get_letta_client() -> Letta:
|
51
|
-
if letta_client is None:
|
52
|
-
raise ValueError("Letta client is not initialized.")
|
53
|
-
return letta_client
|
54
|
-
|
55
|
-
class LettaAgent(BaseAgent):
|
56
|
-
"""Letta Agent implementation that adheres to the BaseAgent interface."""
|
57
|
-
|
58
|
-
def __init__(self):
|
59
|
-
self.client: Letta = None
|
60
|
-
self.agent_id: str = None
|
61
|
-
|
62
|
-
async def initialize(self):
|
63
|
-
initialize_letta_client()
|
64
|
-
self.client = get_letta_client()
|
65
|
-
self.agent_id = config_manager.settings.api_keys.neuro_agent_id
|
66
|
-
if not self.agent_id:
|
67
|
-
raise ValueError("Letta agent ID (neuro_agent_id) is not configured.")
|
68
|
-
|
69
|
-
async def reset_memory(self):
|
70
|
-
"""Resets message history and clears the conversation_summary block."""
|
71
|
-
try:
|
72
|
-
# Reset message history
|
73
|
-
await asyncio.to_thread(self.client.agents.messages.reset, agent_id=self.agent_id)
|
74
|
-
logger.info(f"Letta Agent (ID: {self.agent_id}) message history has been reset.")
|
75
|
-
|
76
|
-
# Find and clear the conversation_summary block
|
77
|
-
blocks = await asyncio.to_thread(self.client.agents.blocks.list, agent_id=self.agent_id)
|
78
|
-
summary_block = next((block for block in blocks if block.name == "conversation_summary"), None)
|
79
|
-
|
80
|
-
if summary_block:
|
81
|
-
await asyncio.to_thread(
|
82
|
-
self.client.agents.blocks.modify,
|
83
|
-
agent_id=self.agent_id,
|
84
|
-
block_id=summary_block.id,
|
85
|
-
content=""
|
86
|
-
)
|
87
|
-
logger.info(f"Cleared content of 'conversation_summary' block (ID: {summary_block.id}) for Letta Agent.")
|
88
|
-
else:
|
89
|
-
logger.warning("'conversation_summary' block not found for Letta Agent, skipping clearing.")
|
90
|
-
|
91
|
-
except Exception as e:
|
92
|
-
logger.warning(f"Failed during Letta Agent memory reset: {e}")
|
93
|
-
|
94
|
-
async def process_messages(self, messages: List[Dict[str, str]]) -> Dict[str, Any]:
|
95
|
-
# Check if this is a superchat message based on the specific structure
|
96
|
-
# from neuro_response_cycle
|
97
|
-
is_superchat = (
|
98
|
-
len(messages) == 2 and
|
99
|
-
messages[0].get("role") == "system" and
|
100
|
-
messages[1].get("role") == "system" and
|
101
|
-
"HIGHLIGHTED MESSAGE" in messages[1].get("content", "")
|
102
|
-
)
|
103
|
-
|
104
|
-
if is_superchat:
|
105
|
-
try:
|
106
|
-
# Extract username and text from the superchat message
|
107
|
-
# Format is "=== HIGHLIGHTED MESSAGE ===\n{username}: {text}"
|
108
|
-
content_lines = messages[1]["content"].split('\n', 1)
|
109
|
-
user_and_text = content_lines[1]
|
110
|
-
parts = user_and_text.split(':', 1)
|
111
|
-
sc_username = parts[0].strip()
|
112
|
-
sc_text = parts[1].strip()
|
113
|
-
injected_chat_lines = [f"{sc_username}: {sc_text}"]
|
114
|
-
injected_chat_text = (
|
115
|
-
"Here is a highlighted message from my Twitch chat:\n---\n" +
|
116
|
-
"\n".join(injected_chat_lines) +
|
117
|
-
"\n---\nNow, as the streamer Neuro-Sama, please continue the conversation naturally."
|
118
|
-
)
|
119
|
-
logger.info(f"Processing highlighted message for Letta: {injected_chat_lines[0]}")
|
120
|
-
except (IndexError, AttributeError) as e:
|
121
|
-
logger.error(f"Failed to parse superchat for Letta, falling back. Error: {e}")
|
122
|
-
# Fallback to default empty prompt if parsing fails
|
123
|
-
injected_chat_text = "My chat is quiet right now. As Neuro-Sama, what should I say to engage them?"
|
124
|
-
|
125
|
-
elif messages:
|
126
|
-
injected_chat_lines = [f"{chat['username']}: {chat['text']}" for chat in messages if 'username' in chat and 'text' in chat]
|
127
|
-
injected_chat_text = (
|
128
|
-
"Here are some recent messages from my Twitch chat:\n---\n" +
|
129
|
-
"\n".join(injected_chat_lines) +
|
130
|
-
"\n---\nNow, as the streamer Neuro-Sama, please continue the conversation naturally."
|
131
|
-
)
|
132
|
-
else:
|
133
|
-
injected_chat_text = "My chat is quiet right now. As Neuro-Sama, what should I say to engage them?"
|
134
|
-
|
135
|
-
logger.info(f"Sending input to Letta Agent ({len(messages)} messages)...")
|
136
|
-
|
137
|
-
response_text = ""
|
138
|
-
error_str = None
|
139
|
-
|
140
|
-
try:
|
141
|
-
response = await asyncio.to_thread(
|
142
|
-
self.client.agents.messages.create,
|
143
|
-
agent_id=self.agent_id,
|
144
|
-
messages=[MessageCreate(role="user", content=injected_chat_text)]
|
145
|
-
)
|
146
|
-
|
147
|
-
if not response or not response.messages:
|
148
|
-
raise ValueError("Letta response is empty or contains no messages.")
|
149
|
-
|
150
|
-
for message in reversed(response.messages):
|
151
|
-
if isinstance(message, AssistantMessage) and hasattr(message, 'content'):
|
152
|
-
content = message.content
|
153
|
-
if isinstance(content, str) and content.strip():
|
154
|
-
response_text = content.strip()
|
155
|
-
break
|
156
|
-
elif isinstance(content, list) and content:
|
157
|
-
first_part = content[0]
|
158
|
-
if isinstance(first_part, TextContent) and hasattr(first_part, 'text') and first_part.text.strip():
|
159
|
-
response_text = first_part.text.strip()
|
160
|
-
break
|
161
|
-
|
162
|
-
if not response_text:
|
163
|
-
logger.warning(f"No valid AssistantMessage content found in Letta response.")
|
164
|
-
response_text = "I'm not sure what to say to that."
|
165
|
-
|
166
|
-
except Exception as e:
|
167
|
-
logger.error(f"Error calling Letta Agent ({self.agent_id}): {e}")
|
168
|
-
error_str = str(e)
|
169
|
-
response_text = "Someone tell Vedal there is a problem with my AI."
|
170
|
-
|
171
|
-
return {
|
172
|
-
"input_messages": messages,
|
173
|
-
"final_response": response_text,
|
174
|
-
"llm_response": response_text,
|
175
|
-
"tool_executions": [],
|
176
|
-
"error": error_str
|
177
|
-
}
|
178
|
-
|
179
|
-
# Memory Block Management
|
180
|
-
async def get_memory_blocks(self) -> List[Dict[str, Any]]:
|
181
|
-
try:
|
182
|
-
blocks = await asyncio.to_thread(self.client.agents.blocks.list, agent_id=self.agent_id)
|
183
|
-
return [block.model_dump() for block in blocks]
|
184
|
-
except Exception as e:
|
185
|
-
raise HTTPException(status_code=500, detail=f"Error getting memory blocks from Letta: {e}")
|
186
|
-
|
187
|
-
async def get_memory_block(self, block_id: str) -> Optional[Dict[str, Any]]:
|
188
|
-
try:
|
189
|
-
block = await asyncio.to_thread(self.client.agents.blocks.retrieve, agent_id=self.agent_id, block_id=block_id)
|
190
|
-
return block.model_dump()
|
191
|
-
except Exception as e:
|
192
|
-
raise HTTPException(status_code=500, detail=f"Error getting memory block from Letta: {e}")
|
193
|
-
|
194
|
-
async def create_memory_block(self, title: str, description: str, content: List[str]) -> Dict[str, str]:
|
195
|
-
try:
|
196
|
-
block = await asyncio.to_thread(
|
197
|
-
self.client.agents.blocks.create,
|
198
|
-
agent_id=self.agent_id,
|
199
|
-
name=title,
|
200
|
-
content="\n".join(content),
|
201
|
-
description=description
|
202
|
-
)
|
203
|
-
return {"block_id": block.id}
|
204
|
-
except Exception as e:
|
205
|
-
raise HTTPException(status_code=500, detail=f"Error creating memory block in Letta: {e}")
|
206
|
-
|
207
|
-
async def update_memory_block(self, block_id: str, title: Optional[str], description: Optional[str], content: Optional[List[str]]):
|
208
|
-
try:
|
209
|
-
update_params = {}
|
210
|
-
if title is not None: update_params["name"] = title
|
211
|
-
if description is not None: update_params["description"] = description
|
212
|
-
if content is not None: update_params["content"] = "\n".join(content)
|
213
|
-
|
214
|
-
await asyncio.to_thread(
|
215
|
-
self.client.agents.blocks.modify,
|
216
|
-
agent_id=self.agent_id,
|
217
|
-
block_id=block_id,
|
218
|
-
**update_params
|
219
|
-
)
|
220
|
-
except Exception as e:
|
221
|
-
raise HTTPException(status_code=500, detail=f"Error updating memory block in Letta: {e}")
|
222
|
-
|
223
|
-
async def delete_memory_block(self, block_id: str):
|
224
|
-
try:
|
225
|
-
await asyncio.to_thread(self.client.agents.blocks.delete, agent_id=self.agent_id, block_id=block_id)
|
226
|
-
except Exception as e:
|
227
|
-
raise HTTPException(status_code=500, detail=f"Error deleting memory block in Letta: {e}")
|
228
|
-
|
229
|
-
# Unsupported Features for Letta Agent
|
230
|
-
async def get_init_memory(self) -> Dict[str, Any]:
|
231
|
-
raise HTTPException(status_code=400, detail="Getting init memory is not supported for Letta agent")
|
232
|
-
|
233
|
-
async def update_init_memory(self, memory: Dict[str, Any]):
|
234
|
-
raise HTTPException(status_code=400, detail="Updating init memory is not supported for Letta agent")
|
235
|
-
|
236
|
-
async def get_temp_memory(self) -> List[Dict[str, Any]]:
|
237
|
-
raise HTTPException(status_code=400, detail="Getting temp memory is not supported for Letta agent")
|
238
|
-
|
239
|
-
async def add_temp_memory(self, content: str, role: str):
|
240
|
-
raise HTTPException(status_code=400, detail="Adding to temp memory is not supported for Letta agent")
|
241
|
-
|
242
|
-
async def clear_temp_memory(self):
|
243
|
-
raise HTTPException(status_code=400, detail="Clearing temp memory is not supported for Letta agent")
|
244
|
-
|
245
|
-
async def get_available_tools(self) -> str:
|
246
|
-
return "Tool management is not supported for Letta agent via this API"
|
247
|
-
|
248
|
-
async def execute_tool(self, tool_name: str, params: Dict[str, Any]) -> Any:
|
249
|
-
raise HTTPException(status_code=400, detail="Tool execution is not supported for Letta agent via this API")
|
250
|
-
|
251
|
-
async def get_message_history(self, limit: int = 20) -> List[Dict[str, Any]]:
|
252
|
-
# Letta's history is managed on their server and not directly exposed.
|
253
|
-
# Return an empty list to prevent breaking internal consumers like the admin panel.
|
254
|
-
return []
|
File without changes
|
File without changes
|
File without changes
|