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.
Files changed (41) hide show
  1. neuro_simulator/agent/llm.py +23 -19
  2. neuro_simulator/chatbot/core.py +10 -10
  3. neuro_simulator/chatbot/llm.py +22 -19
  4. neuro_simulator/chatbot/nickname_gen/generator.py +3 -3
  5. neuro_simulator/chatbot/tools/manager.py +10 -8
  6. neuro_simulator/cli.py +7 -12
  7. neuro_simulator/core/agent_factory.py +9 -18
  8. neuro_simulator/core/application.py +59 -56
  9. neuro_simulator/core/config.py +88 -301
  10. neuro_simulator/core/path_manager.py +7 -7
  11. neuro_simulator/dashboard/assets/{AgentView-C6qW7TIe.js → AgentView-DBq2msN_.js} +2 -2
  12. neuro_simulator/dashboard/assets/{ChatBotView-BRYIM_8s.js → ChatBotView-BqQsuJUv.js} +2 -2
  13. neuro_simulator/dashboard/assets/ConfigView-CPYMgl_d.js +2 -0
  14. neuro_simulator/dashboard/assets/ConfigView-aFribfyR.css +1 -0
  15. neuro_simulator/dashboard/assets/{ContextTab-GRHICOS3.js → ContextTab-BSROkcd2.js} +1 -1
  16. neuro_simulator/dashboard/assets/{ControlView-D5vPB_OE.js → ControlView-BvflkxO-.js} +1 -1
  17. neuro_simulator/dashboard/assets/FieldRenderer-DyPAEyOT.js +1 -0
  18. neuro_simulator/dashboard/assets/LogsTab-C-SZhHdN.js +1 -0
  19. neuro_simulator/dashboard/assets/LogsView-82wOs2Pp.js +1 -0
  20. neuro_simulator/dashboard/assets/{MemoryTab-BSUWFbcV.js → MemoryTab-p3Q-Wa4e.js} +3 -3
  21. neuro_simulator/dashboard/assets/{ToolsTab-Bjcm3fFL.js → ToolsTab-BxbFZhXs.js} +1 -1
  22. neuro_simulator/dashboard/assets/index-Ba5ZG3QB.js +52 -0
  23. neuro_simulator/dashboard/assets/{index-C7dox9UB.css → index-CcYt9OR6.css} +1 -1
  24. neuro_simulator/dashboard/index.html +2 -2
  25. neuro_simulator/services/audio.py +55 -47
  26. neuro_simulator/services/builtin.py +3 -0
  27. neuro_simulator/services/stream.py +1 -1
  28. neuro_simulator/utils/queue.py +2 -2
  29. {neuro_simulator-0.5.4.dist-info → neuro_simulator-0.6.0.dist-info}/METADATA +1 -2
  30. {neuro_simulator-0.5.4.dist-info → neuro_simulator-0.6.0.dist-info}/RECORD +34 -35
  31. requirements.txt +1 -1
  32. neuro_simulator/config.yaml.example +0 -117
  33. neuro_simulator/dashboard/assets/ConfigView-Cw-VPFzt.js +0 -2
  34. neuro_simulator/dashboard/assets/FieldRenderer-DaTYxmtO.js +0 -1
  35. neuro_simulator/dashboard/assets/LogsTab-CATao-mZ.js +0 -1
  36. neuro_simulator/dashboard/assets/LogsView-BM419A5R.js +0 -1
  37. neuro_simulator/dashboard/assets/index-BiAhe8fO.js +0 -34
  38. neuro_simulator/services/letta.py +0 -254
  39. {neuro_simulator-0.5.4.dist-info → neuro_simulator-0.6.0.dist-info}/WHEEL +0 -0
  40. {neuro_simulator-0.5.4.dist-info → neuro_simulator-0.6.0.dist-info}/entry_points.txt +0 -0
  41. {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 []