File size: 8,006 Bytes
79d4fd5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# Error handling for integration layer with fallback mechanisms
import logging
import traceback
from dataclasses import dataclass
from enum import Enum
from typing import Any, Callable, Dict, Optional, TypeVar

logger = logging.getLogger(__name__)

T = TypeVar("T")


class ErrorType(Enum):
    """Types of errors that can occur during query processing."""
    DATABASE_CONNECTION = "database_connection"
    DATABASE_QUERY = "database_query"
    SQL_VALIDATION = "sql_validation"
    SQL_INJECTION = "sql_injection"
    DOCUMENT_RETRIEVAL = "document_retrieval"
    LLM_GENERATION = "llm_generation"
    INTENT_CLASSIFICATION = "intent_classification"
    AGENT_EXECUTION = "agent_execution"
    TIMEOUT = "timeout"
    UNKNOWN = "unknown"


@dataclass
class ErrorContext:
    """Context information for error handling."""
    error_type: ErrorType
    message: str
    original_error: Optional[Exception] = None
    query: Optional[str] = None
    additional_info: Optional[Dict[str, Any]] = None
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert error context to dictionary."""
        return {
            "error_type": self.error_type.value,
            "message": self.message,
            "query": self.query,
            "additional_info": self.additional_info,
            "original_error": str(self.original_error) if self.original_error else None,
        }


class ErrorHandler:
    """Handles errors with fallback mechanisms and user-friendly messages."""
    
    # Turkish user-friendly error messages
    ERROR_MESSAGES = {
        ErrorType.DATABASE_CONNECTION: "Veritabanı bağlantısı kurulamadı. Lütfen daha sonra tekrar deneyin.",
        ErrorType.DATABASE_QUERY: "Veritabanı sorgusu başarısız oldu. Lütfen sorunuzu farklı şekilde sormayı deneyin.",
        ErrorType.SQL_VALIDATION: "Oluşturulan sorgu geçersiz. Lütfen sorunuzu daha açık ifade edin.",
        ErrorType.SQL_INJECTION: "Güvenlik nedeniyle bu sorgu işlenemedi.",
        ErrorType.DOCUMENT_RETRIEVAL: "Dokümanlar aranamadı. Lütfen daha sonra tekrar deneyin.",
        ErrorType.LLM_GENERATION: "Yanıt oluşturulamadı. Lütfen daha sonra tekrar deneyin.",
        ErrorType.INTENT_CLASSIFICATION: "Sorgunuz anlaşılamadı. Lütfen farklı kelimeler kullanarak tekrar deneyin.",
        ErrorType.AGENT_EXECUTION: "İşlem tamamlanamadı. Lütfen daha sonra tekrar deneyin.",
        ErrorType.TIMEOUT: "İşlem zaman aşımına uğradı. Lütfen daha kısa bir sorgu deneyin.",
        ErrorType.UNKNOWN: "Beklenmeyen bir hata oluştu. Lütfen daha sonra tekrar deneyin.",
    }
    
    def __init__(self, enable_fallback: bool = True):
        """Initialize error handler with fallback configuration."""
        self._enable_fallback = enable_fallback
        self._error_count: Dict[ErrorType, int] = {et: 0 for et in ErrorType}
        self._fallback_handlers: Dict[ErrorType, Callable[..., Any]] = {}
    
    @property
    def enable_fallback(self) -> bool:
        """Check if fallback is enabled."""
        return self._enable_fallback
    
    @enable_fallback.setter
    def enable_fallback(self, value: bool) -> None:
        """Set fallback enabled state."""
        self._enable_fallback = value
    
    @property
    def error_counts(self) -> Dict[ErrorType, int]:
        """Get error counts by type."""
        return self._error_count.copy()
    
    def reset_counts(self) -> None:
        """Reset all error counts."""
        self._error_count = {et: 0 for et in ErrorType}
    
    def register_fallback(
        self,
        error_type: ErrorType,
        handler: Callable[..., Any],
    ) -> None:
        """Register a fallback handler for specific error type."""
        self._fallback_handlers[error_type] = handler
    
    def handle(
        self,
        error: Exception,
        error_type: Optional[ErrorType] = None,
        query: Optional[str] = None,
        fallback_value: Optional[T] = None,
    ) -> ErrorContext:
        """Handle an error and return context with user-friendly message."""
        detected_type = error_type or self._detect_error_type(error)
        self._error_count[detected_type] += 1
        
        context = ErrorContext(
            error_type=detected_type,
            message=self.ERROR_MESSAGES.get(detected_type, self.ERROR_MESSAGES[ErrorType.UNKNOWN]),
            original_error=error,
            query=query,
            additional_info={"traceback": traceback.format_exc()},
        )
        
        logger.error(
            f"Error handled: type={detected_type.value}, query={query}, error={str(error)}",
            exc_info=True,
        )
        
        return context
    
    def execute_with_fallback(
        self,
        func: Callable[..., T],
        error_type: ErrorType,
        fallback_value: T,
        query: Optional[str] = None,
        *args: Any,
        **kwargs: Any,
    ) -> T:
        """Execute function with fallback on error."""
        try:
            return func(*args, **kwargs)
        except Exception as e:
            self.handle(e, error_type, query)
            
            if self._enable_fallback and error_type in self._fallback_handlers:
                try:
                    return self._fallback_handlers[error_type](query, e, *args, **kwargs)
                except Exception:
                    pass
            
            return fallback_value
    
    def _detect_error_type(self, error: Exception) -> ErrorType:
        """Detect error type from exception."""
        error_str = str(error).lower()
        error_type_name = type(error).__name__.lower()
        
        if "connection" in error_str or "connect" in error_type_name:
            return ErrorType.DATABASE_CONNECTION
        elif "timeout" in error_str or "timed out" in error_str:
            return ErrorType.TIMEOUT
        elif "injection" in error_str or "forbidden" in error_str:
            return ErrorType.SQL_INJECTION
        elif "sql" in error_str or "query" in error_str:
            return ErrorType.DATABASE_QUERY
        elif "validation" in error_str or "invalid" in error_str:
            return ErrorType.SQL_VALIDATION
        elif "retriev" in error_str or "document" in error_str:
            return ErrorType.DOCUMENT_RETRIEVAL
        elif "llm" in error_str or "generat" in error_str:
            return ErrorType.LLM_GENERATION
        elif "intent" in error_str or "classif" in error_str:
            return ErrorType.INTENT_CLASSIFICATION
        elif "agent" in error_str or "execution" in error_str:
            return ErrorType.AGENT_EXECUTION
        
        return ErrorType.UNKNOWN
    
    def get_user_message(self, error_type: ErrorType) -> str:
        """Get user-friendly message for error type."""
        return self.ERROR_MESSAGES.get(error_type, self.ERROR_MESSAGES[ErrorType.UNKNOWN])
    
    def get_most_frequent_error(self) -> Optional[ErrorType]:
        """Get the most frequently occurring error type."""
        if all(count == 0 for count in self._error_count.values()):
            return None
        return max(self._error_count, key=lambda k: self._error_count[k])


_error_handler_instance: Optional[ErrorHandler] = None


def get_error_handler(enable_fallback: bool = True) -> ErrorHandler:
    """Get or create singleton error handler instance."""
    global _error_handler_instance
    
    if _error_handler_instance is None:
        _error_handler_instance = ErrorHandler(enable_fallback=enable_fallback)
    
    return _error_handler_instance


def reset_error_handler() -> None:
    """Reset the singleton error handler instance."""
    global _error_handler_instance
    _error_handler_instance = None


def handle_error(
    error: Exception,
    error_type: Optional[ErrorType] = None,
    query: Optional[str] = None,
) -> ErrorContext:
    """Convenience function to handle errors using singleton handler."""
    handler = get_error_handler()
    return handler.handle(error, error_type, query)