this repo has no description

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: