File size: 14,286 Bytes
330b6e4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
"""

Centralized error handling utilities for the chat agent.



This module provides comprehensive error handling, logging, and fallback

response mechanisms for all chat agent components.

"""

import logging
import traceback
import functools
from typing import Dict, Any, Optional, Callable, Union
from datetime import datetime
from enum import Enum

from flask import jsonify, request
from flask_socketio import emit


class ErrorSeverity(Enum):
    """Error severity levels for categorization and handling."""
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"


class ErrorCategory(Enum):
    """Error categories for better classification and handling."""
    API_ERROR = "api_error"
    DATABASE_ERROR = "database_error"
    VALIDATION_ERROR = "validation_error"
    AUTHENTICATION_ERROR = "authentication_error"
    RATE_LIMIT_ERROR = "rate_limit_error"
    NETWORK_ERROR = "network_error"
    SYSTEM_ERROR = "system_error"
    USER_ERROR = "user_error"


class ChatAgentError(Exception):
    """Base exception class for all chat agent errors."""
    
    def __init__(self, message: str, category: ErrorCategory = ErrorCategory.SYSTEM_ERROR,

                 severity: ErrorSeverity = ErrorSeverity.MEDIUM, 

                 user_message: Optional[str] = None, 

                 error_code: Optional[str] = None,

                 context: Optional[Dict[str, Any]] = None):
        """

        Initialize chat agent error.

        

        Args:

            message: Technical error message for logging

            category: Error category for classification

            severity: Error severity level

            user_message: User-friendly error message

            error_code: Unique error code for tracking

            context: Additional context information

        """
        super().__init__(message)
        self.category = category
        self.severity = severity
        self.user_message = user_message or self._get_default_user_message()
        self.error_code = error_code or self._generate_error_code()
        self.context = context or {}
        self.timestamp = datetime.utcnow()
    
    def _get_default_user_message(self) -> str:
        """Get default user-friendly message based on category."""
        default_messages = {
            ErrorCategory.API_ERROR: "I'm having trouble connecting to my services. Please try again in a moment.",
            ErrorCategory.DATABASE_ERROR: "I'm experiencing some technical difficulties. Please try again later.",
            ErrorCategory.VALIDATION_ERROR: "There seems to be an issue with your request. Please check your input and try again.",
            ErrorCategory.AUTHENTICATION_ERROR: "Authentication failed. Please check your credentials.",
            ErrorCategory.RATE_LIMIT_ERROR: "I'm currently experiencing high demand. Please try again in a moment.",
            ErrorCategory.NETWORK_ERROR: "I'm having trouble connecting right now. Please check your connection and try again.",
            ErrorCategory.SYSTEM_ERROR: "I encountered an unexpected error. Please try again.",
            ErrorCategory.USER_ERROR: "There's an issue with your request. Please check your input and try again."
        }
        return default_messages.get(self.category, "An unexpected error occurred. Please try again.")
    
    def _generate_error_code(self) -> str:
        """Generate unique error code for tracking."""
        import uuid
        return f"{self.category.value.upper()}_{str(uuid.uuid4())[:8]}"
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert error to dictionary for JSON serialization."""
        return {
            'error_code': self.error_code,
            'category': self.category.value,
            'severity': self.severity.value,
            'message': self.user_message,
            'timestamp': self.timestamp.isoformat(),
            'context': self.context
        }


class ErrorHandler:
    """Centralized error handler for the chat agent."""
    
    def __init__(self, logger: Optional[logging.Logger] = None):
        """

        Initialize error handler.

        

        Args:

            logger: Logger instance to use for error logging

        """
        self.logger = logger or logging.getLogger(__name__)
        self.fallback_responses = self._initialize_fallback_responses()
    
    def _initialize_fallback_responses(self) -> Dict[ErrorCategory, str]:
        """Initialize fallback responses for different error categories."""
        return {
            ErrorCategory.API_ERROR: "I'm having trouble with my AI services right now. Here are some general programming tips that might help:\n\n• Break down complex problems into smaller steps\n• Use descriptive variable names\n• Add comments to explain your logic\n• Test your code frequently\n\nPlease try your question again in a moment!",
            
            ErrorCategory.DATABASE_ERROR: "I'm experiencing some technical difficulties accessing my knowledge base. In the meantime, I recommend:\n\n• Checking official documentation for your programming language\n• Looking for similar examples online\n• Breaking your problem into smaller parts\n\nPlease try again shortly!",
            
            ErrorCategory.RATE_LIMIT_ERROR: "I'm currently helping many students and need a moment to catch up. While you wait:\n\n• Review your code for any obvious syntax errors\n• Try running your code to see what happens\n• Think about what you're trying to accomplish\n\nPlease try your question again in a few seconds!",
            
            ErrorCategory.NETWORK_ERROR: "I'm having connection issues right now. Here's what you can try:\n\n• Check your internet connection\n• Refresh the page and try again\n• Make sure your code follows proper syntax\n\nI'll be back online shortly!",
            
            ErrorCategory.SYSTEM_ERROR: "I encountered an unexpected issue. Don't worry - this happens sometimes! Try:\n\n• Rephrasing your question\n• Being more specific about what you need help with\n• Checking if your code has any obvious errors\n\nPlease try again!",
            
            ErrorCategory.USER_ERROR: "I need a bit more information to help you effectively. Please:\n\n• Be specific about what you're trying to do\n• Include any error messages you're seeing\n• Share the relevant code if possible\n\nThen I can give you much better assistance!"
        }
    
    def handle_error(self, error: Exception, context: Optional[Dict[str, Any]] = None) -> ChatAgentError:
        """

        Handle and classify an error, returning a standardized ChatAgentError.

        

        Args:

            error: The original exception

            context: Additional context information

            

        Returns:

            ChatAgentError: Standardized error object

        """
        # Convert to ChatAgentError if not already
        if isinstance(error, ChatAgentError):
            chat_error = error
        else:
            chat_error = self._classify_error(error, context)
        
        # Log the error
        self._log_error(chat_error, error)
        
        return chat_error
    
    def _classify_error(self, error: Exception, context: Optional[Dict[str, Any]] = None) -> ChatAgentError:
        """Classify an error and convert to ChatAgentError."""
        error_str = str(error).lower()
        error_type = type(error).__name__
        
        # API-related errors
        if any(keyword in error_str for keyword in ['groq', 'api', 'langchain']):
            if 'rate limit' in error_str or '429' in error_str:
                return ChatAgentError(
                    message=str(error),
                    category=ErrorCategory.RATE_LIMIT_ERROR,
                    severity=ErrorSeverity.MEDIUM,
                    context=context
                )
            elif 'authentication' in error_str or '401' in error_str:
                return ChatAgentError(
                    message=str(error),
                    category=ErrorCategory.AUTHENTICATION_ERROR,
                    severity=ErrorSeverity.HIGH,
                    context=context
                )
            else:
                return ChatAgentError(
                    message=str(error),
                    category=ErrorCategory.API_ERROR,
                    severity=ErrorSeverity.MEDIUM,
                    context=context
                )
        
        # Database-related errors
        elif any(keyword in error_str for keyword in ['database', 'sql', 'connection', 'postgresql', 'redis']):
            return ChatAgentError(
                message=str(error),
                category=ErrorCategory.DATABASE_ERROR,
                severity=ErrorSeverity.HIGH,
                context=context
            )
        
        # Network-related errors
        elif any(keyword in error_str for keyword in ['network', 'connection', 'timeout', 'unreachable']):
            return ChatAgentError(
                message=str(error),
                category=ErrorCategory.NETWORK_ERROR,
                severity=ErrorSeverity.MEDIUM,
                context=context
            )
        
        # Validation errors
        elif any(keyword in error_str for keyword in ['validation', 'invalid', 'malformed']):
            return ChatAgentError(
                message=str(error),
                category=ErrorCategory.VALIDATION_ERROR,
                severity=ErrorSeverity.LOW,
                context=context
            )
        
        # Default to system error
        else:
            return ChatAgentError(
                message=str(error),
                category=ErrorCategory.SYSTEM_ERROR,
                severity=ErrorSeverity.MEDIUM,
                context=context
            )
    
    def _log_error(self, chat_error: ChatAgentError, original_error: Exception):
        """Log error with appropriate level and context."""
        log_data = {
            'error_code': chat_error.error_code,
            'category': chat_error.category.value,
            'severity': chat_error.severity.value,
            'original_error': str(original_error),
            'error_type': type(original_error).__name__,
            'context': chat_error.context,
            'timestamp': chat_error.timestamp.isoformat()
        }
        
        # Add traceback for higher severity errors
        if chat_error.severity in [ErrorSeverity.HIGH, ErrorSeverity.CRITICAL]:
            log_data['traceback'] = traceback.format_exc()
        
        # Log with appropriate level
        if chat_error.severity == ErrorSeverity.CRITICAL:
            self.logger.critical(f"Critical error: {chat_error.message}", extra=log_data)
        elif chat_error.severity == ErrorSeverity.HIGH:
            self.logger.error(f"High severity error: {chat_error.message}", extra=log_data)
        elif chat_error.severity == ErrorSeverity.MEDIUM:
            self.logger.warning(f"Medium severity error: {chat_error.message}", extra=log_data)
        else:
            self.logger.info(f"Low severity error: {chat_error.message}", extra=log_data)
    
    def get_fallback_response(self, error: ChatAgentError) -> str:
        """Get fallback response for an error category."""
        return self.fallback_responses.get(error.category, self.fallback_responses[ErrorCategory.SYSTEM_ERROR])
    
    def handle_api_response_error(self, error: Exception, context: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        """Handle error for API responses."""
        chat_error = self.handle_error(error, context)
        
        return {
            'success': False,
            'error': chat_error.to_dict(),
            'fallback_response': self.get_fallback_response(chat_error)
        }
    
    def handle_websocket_error(self, error: Exception, context: Optional[Dict[str, Any]] = None):
        """Handle error for WebSocket responses."""
        chat_error = self.handle_error(error, context)
        
        emit('error', {
            'error': chat_error.to_dict(),
            'fallback_response': self.get_fallback_response(chat_error)
        })


def error_handler_decorator(error_handler: ErrorHandler, 

                          return_fallback: bool = False,

                          emit_websocket_error: bool = False):
    """

    Decorator for automatic error handling in functions.

    

    Args:

        error_handler: ErrorHandler instance to use

        return_fallback: Whether to return fallback response on error

        emit_websocket_error: Whether to emit WebSocket error on exception

    """
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except Exception as e:
                context = {
                    'function': func.__name__,
                    'args': str(args)[:200],  # Limit context size
                    'kwargs': str(kwargs)[:200]
                }
                
                chat_error = error_handler.handle_error(e, context)
                
                if emit_websocket_error:
                    error_handler.handle_websocket_error(e, context)
                    return None
                elif return_fallback:
                    return error_handler.get_fallback_response(chat_error)
                else:
                    raise chat_error
        
        return wrapper
    return decorator


# Global error handler instance
_global_error_handler = None


def get_error_handler() -> ErrorHandler:
    """Get global error handler instance."""
    global _global_error_handler
    if _global_error_handler is None:
        _global_error_handler = ErrorHandler()
    return _global_error_handler


def set_error_handler(error_handler: ErrorHandler):
    """Set global error handler instance."""
    global _global_error_handler
    _global_error_handler = error_handler