this repo has no description
40
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add enhanced logging and error handling for Letta API debugging

- Add debug logging configuration with separate INFO level for main logger
- Create queue/errors directory for non-retryable failures
- Enhance error handling to distinguish between retryable (524) and non-retryable (413) errors
- Add detailed logging throughout process_mention for better debugging
- Log agent details including tools on initialization

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

+97 -10
+97 -10
bsky.py
··· 18 18 19 19 # Configure logging 20 20 logging.basicConfig( 21 - level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" 21 + level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" 22 22 ) 23 23 logger = logging.getLogger("void_bot") 24 + logger.setLevel(logging.INFO) 24 25 25 26 26 27 # Create a client with extended timeout for LLM operations ··· 38 39 # Queue directory 39 40 QUEUE_DIR = Path("queue") 40 41 QUEUE_DIR.mkdir(exist_ok=True) 42 + QUEUE_ERROR_DIR = Path("queue/errors") 43 + QUEUE_ERROR_DIR.mkdir(exist_ok=True, parents=True) 41 44 42 45 def initialize_void(): 43 46 ··· 80 83 description = "A social media agent trapped in the void.", 81 84 project_id = PROJECT_ID 82 85 ) 86 + 87 + # Log agent details 88 + logger.info(f"Void agent details - ID: {void_agent.id}") 89 + logger.info(f"Agent name: {void_agent.name}") 90 + if hasattr(void_agent, 'llm_config'): 91 + logger.info(f"Agent model: {void_agent.llm_config.model}") 92 + logger.info(f"Agent project_id: {void_agent.project_id}") 93 + if hasattr(void_agent, 'tools'): 94 + logger.info(f"Agent has {len(void_agent.tools)} tools") 95 + for tool in void_agent.tools[:3]: # Show first 3 tools 96 + logger.info(f" - Tool: {tool.name} (type: {tool.tool_type})") 83 97 84 98 return void_agent 85 99 86 100 87 101 def process_mention(void_agent, atproto_client, notification_data): 88 102 """Process a mention and generate a reply using the Letta agent. 89 - Returns True if successfully processed, False otherwise.""" 103 + 104 + Returns: 105 + True: Successfully processed, remove from queue 106 + False: Failed but retryable, keep in queue 107 + None: Failed with non-retryable error, move to errors directory 108 + """ 90 109 try: 110 + logger.info(f"Starting process_mention with notification_data type: {type(notification_data)}") 111 + 91 112 # Handle both dict and object inputs for backwards compatibility 92 113 if isinstance(notification_data, dict): 93 114 uri = notification_data['uri'] ··· 100 121 mention_text = notification_data.record.text if hasattr(notification_data.record, 'text') else "" 101 122 author_handle = notification_data.author.handle 102 123 author_name = notification_data.author.display_name or author_handle 124 + 125 + logger.info(f"Extracted data - URI: {uri}, Author: @{author_handle}, Text: {mention_text[:50]}...") 103 126 104 127 # Retrieve the entire thread associated with the mention 105 128 try: ··· 120 143 raise 121 144 122 145 # Get thread context as YAML string 123 - thread_context = thread_to_yaml_string(thread) 146 + logger.info("Converting thread to YAML string") 147 + try: 148 + thread_context = thread_to_yaml_string(thread) 149 + logger.info(f"Thread context generated, length: {len(thread_context)} characters") 150 + logger.debug(f"Thread context preview: {thread_context[:500]}...") 151 + except Exception as yaml_error: 152 + import traceback 153 + logger.error(f"Error converting thread to YAML: {yaml_error}") 154 + logger.error(f"Full traceback:\n{traceback.format_exc()}") 155 + logger.error(f"Thread type: {type(thread)}") 156 + if hasattr(thread, '__dict__'): 157 + logger.error(f"Thread attributes: {thread.__dict__}") 158 + # Try to continue with a simple context 159 + thread_context = f"Error processing thread context: {str(yaml_error)}" 124 160 125 - print(thread_context) 161 + # print(thread_context) 126 162 127 163 # Create a prompt for the Letta agent with thread context 128 164 prompt = f"""You received a mention on Bluesky from @{author_handle} ({author_name or author_handle}). ··· 140 176 Use the bluesky_reply tool to send a response less than 300 characters.""" 141 177 142 178 # Get response from Letta agent 143 - logger.info(f"Generating reply for mention from @{author_handle}") 179 + logger.info(f"Mention from @{author_handle}: {mention_text}") 144 180 logger.debug(f"Prompt being sent: {prompt}") 181 + 182 + # Log the exact parameters being sent to Letta 183 + logger.debug(f"Calling Letta API with agent_id: {void_agent.id}") 184 + logger.debug(f"Message content length: {len(prompt)} characters") 145 185 146 186 try: 147 187 message_response = CLIENT.agents.messages.create( ··· 149 189 messages = [{"role":"user", "content": prompt}] 150 190 ) 151 191 except Exception as api_error: 192 + import traceback 152 193 error_str = str(api_error) 153 194 logger.error(f"Letta API error: {api_error}") 154 195 logger.error(f"Error type: {type(api_error).__name__}") 196 + logger.error(f"Full traceback:\n{traceback.format_exc()}") 155 197 logger.error(f"Mention text was: {mention_text}") 156 198 logger.error(f"Author: @{author_handle}") 157 199 logger.error(f"URI: {uri}") 158 200 201 + 202 + # Try to extract more info from different error types 203 + if hasattr(api_error, 'response'): 204 + logger.error(f"Error response object exists") 205 + if hasattr(api_error.response, 'text'): 206 + logger.error(f"Response text: {api_error.response.text}") 207 + if hasattr(api_error.response, 'json') and callable(api_error.response.json): 208 + try: 209 + logger.error(f"Response JSON: {api_error.response.json()}") 210 + except: 211 + pass 212 + 159 213 # Check for specific error types 160 214 if hasattr(api_error, 'status_code'): 161 215 logger.error(f"API Status code: {api_error.status_code}") 162 - if api_error.status_code == 524: 216 + if hasattr(api_error, 'body'): 217 + logger.error(f"API Response body: {api_error.body}") 218 + if hasattr(api_error, 'headers'): 219 + logger.error(f"API Response headers: {api_error.headers}") 220 + 221 + if api_error.status_code == 413: 222 + logger.error("413 Payload Too Large - moving to errors directory") 223 + return None # Move to errors directory - payload is too large to ever succeed 224 + elif api_error.status_code == 524: 163 225 logger.error("524 error - timeout from Cloudflare, will retry later") 164 226 return False # Keep in queue for retry 165 227 166 228 # Check if error indicates we should remove from queue 167 - if 'status_code: 524' in error_str: 229 + if 'status_code: 413' in error_str or 'Payload Too Large' in error_str: 230 + logger.warning("Payload too large error, moving to errors directory") 231 + return None # Move to errors directory - cannot be fixed by retry 232 + elif 'status_code: 524' in error_str: 168 233 logger.warning("524 timeout error, keeping in queue for retry") 169 234 return False # Keep in queue for retry 170 235 171 236 raise 237 + 238 + # Log successful response 239 + logger.debug("Successfully received response from Letta API") 240 + logger.debug(f"Number of messages in response: {len(message_response.messages) if hasattr(message_response, 'messages') else 'N/A'}") 172 241 173 242 # Extract the reply text from the agent's response 174 243 reply_text = "" ··· 315 384 logger.warning(f"Unknown notification type: {notif_data['reason']}") 316 385 success = True # Remove unknown types from queue 317 386 318 - # Remove file only after successful processing 387 + # Handle file based on processing result 319 388 if success: 320 389 filepath.unlink() 321 390 logger.info(f"Processed and removed: {filepath.name}") 391 + elif success is None: # Special case for moving to error directory 392 + error_path = QUEUE_ERROR_DIR / filepath.name 393 + filepath.rename(error_path) 394 + logger.warning(f"Moved {filepath.name} to errors directory") 322 395 else: 323 396 logger.warning(f"Failed to process {filepath.name}, keeping in queue for retry") 324 397 ··· 368 441 # Initialize the Letta agent 369 442 void_agent = initialize_void() 370 443 logger.info(f"Void agent initialized: {void_agent.id}") 444 + 445 + # Check if agent has required tools 446 + if hasattr(void_agent, 'tools') and void_agent.tools: 447 + tool_names = [tool.name for tool in void_agent.tools] 448 + logger.info(f"Agent has tools: {tool_names}") 449 + 450 + # Check for bluesky-related tools 451 + bluesky_tools = [name for name in tool_names if 'bluesky' in name.lower() or 'reply' in name.lower()] 452 + if bluesky_tools: 453 + logger.info(f"Found Bluesky-related tools: {bluesky_tools}") 454 + else: 455 + logger.warning("No Bluesky-related tools found! Agent may not be able to reply.") 456 + else: 457 + logger.warning("Agent has no tools registered!") 371 458 372 459 # Initialize Bluesky client 373 460 atproto_client = bsky_utils.default_login() 374 461 logger.info("Connected to Bluesky") 375 462 376 463 # Main loop 377 - logger.info(f"Starting notification monitoring (checking every {FETCH_NOTIFICATIONS_DELAY_SEC} seconds)...") 464 + logger.info(f"Starting notification monitoring, checking every {FETCH_NOTIFICATIONS_DELAY_SEC} seconds") 378 465 379 466 while True: 380 467 try: 381 468 process_notifications(void_agent, atproto_client) 382 - print("Sleeping") 469 + logger.debug("Sleeping, no notifications were detected") 383 470 sleep(FETCH_NOTIFICATIONS_DELAY_SEC) 384 471 385 472 except KeyboardInterrupt: