Spaces:
Sleeping
Sleeping
| """ | |
| Error Fingerprinting System — Classifies terminal errors into actionable categories. | |
| Uses rule-based regex patterns to identify error types before the LLM agent | |
| generates a fix command. This gives the agent better context and enables | |
| analysis of which error categories the agent struggles with. | |
| """ | |
| from __future__ import annotations | |
| import re | |
| from dataclasses import dataclass | |
| from typing import Dict, List, Optional, Tuple | |
| # Canonical error type taxonomy | |
| ERROR_TYPES: List[str] = [ | |
| "missing_package", # ModuleNotFoundError, ImportError | |
| "port_conflict", # Address already in use | |
| "missing_env", # KeyError on env var, undefined variable | |
| "permission_denied", # PermissionError | |
| "version_conflict", # incompatible package versions | |
| "syntax_error", # Python SyntaxError | |
| "config_error", # misconfiguration in app config | |
| "service_not_running", # failed to connect, connection refused | |
| "file_not_found", # FileNotFoundError, No such file | |
| "unknown", # unclassified | |
| ] | |
| class FingerprintResult: | |
| """Result of error classification. | |
| Attributes: | |
| error_type: The classified error type from ERROR_TYPES. | |
| confidence: Confidence score (0.0 to 1.0). | |
| matched_pattern: The regex pattern that matched. | |
| matched_text: The text fragment that matched. | |
| suggested_category: Alternative error type if confidence is low. | |
| """ | |
| error_type: str | |
| confidence: float | |
| matched_pattern: str = "" | |
| matched_text: str = "" | |
| suggested_category: str = "" | |
| # Regex patterns for each error type, ordered by specificity (most specific first) | |
| _FINGERPRINT_RULES: List[Tuple[str, str, float]] = [ | |
| # (error_type, regex_pattern, base_confidence) | |
| # Missing package — most common | |
| ("missing_package", r"ModuleNotFoundError:\s*No module named\s+['\"]?(\w+)", 0.95), | |
| ("missing_package", r"ImportError:\s*No module named\s+['\"]?(\w+)", 0.95), | |
| ("missing_package", r"ModuleNotFoundError", 0.85), | |
| ("missing_package", r"ImportError.*cannot import", 0.80), | |
| ("missing_package", r"No module named", 0.90), | |
| # Port conflict | |
| ("port_conflict", r"Address already in use", 0.95), | |
| ("port_conflict", r"EADDRINUSE", 0.95), | |
| ("port_conflict", r"port\s+\d+\s+(is\s+)?(already\s+)?in\s+use", 0.90), | |
| ("port_conflict", r"bind\(\).*failed", 0.75), | |
| ("port_conflict", r"Errno\s+98", 0.90), | |
| # Missing environment variable | |
| ("missing_env", r"KeyError:\s*['\"]([A-Z_]+)['\"]", 0.95), | |
| ("missing_env", r"undefined.*variable", 0.85), | |
| ("missing_env", r"environment variable.*not set", 0.90), | |
| ("missing_env", r"os\.environ\[", 0.80), | |
| ("missing_env", r"env.*not found", 0.75), | |
| # Permission denied | |
| ("permission_denied", r"PermissionError", 0.95), | |
| ("permission_denied", r"Permission denied", 0.95), | |
| ("permission_denied", r"EACCES", 0.90), | |
| ("permission_denied", r"Operation not permitted", 0.90), | |
| ("permission_denied", r"Access denied", 0.85), | |
| # Version conflict | |
| ("version_conflict", r"ResolutionImpossible", 0.95), | |
| ("version_conflict", r"version.*conflict", 0.90), | |
| ("version_conflict", r"incompatible.*version", 0.85), | |
| ("version_conflict", r"conflicting\s+dependencies", 0.90), | |
| ("version_conflict", r"requires.*but.*installed", 0.85), | |
| ("version_conflict", r"package versions have conflicting", 0.95), | |
| # Syntax error | |
| ("syntax_error", r"SyntaxError:\s*invalid syntax", 0.95), | |
| ("syntax_error", r"SyntaxError", 0.90), | |
| ("syntax_error", r"IndentationError", 0.90), | |
| ("syntax_error", r"TabError", 0.90), | |
| ("syntax_error", r"unexpected EOF", 0.85), | |
| # Config error | |
| ("config_error", r"config.*error", 0.80), | |
| ("config_error", r"misconfigur", 0.85), | |
| ("config_error", r"invalid.*config", 0.80), | |
| ("config_error", r"configuration.*failed", 0.80), | |
| ("config_error", r"binding.*error", 0.70), | |
| # Service not running | |
| ("service_not_running", r"Connection refused", 0.90), | |
| ("service_not_running", r"ECONNREFUSED", 0.90), | |
| ("service_not_running", r"failed to connect", 0.85), | |
| ("service_not_running", r"service.*not.*running", 0.90), | |
| ("service_not_running", r"connection.*timed?\s*out", 0.75), | |
| ("service_not_running", r"could not connect", 0.85), | |
| # File not found | |
| ("file_not_found", r"FileNotFoundError", 0.95), | |
| ("file_not_found", r"No such file or directory", 0.95), | |
| ("file_not_found", r"ENOENT", 0.90), | |
| ("file_not_found", r"(file|directory|command|script)\s+(not found|does not exist)", 0.65), | |
| ("file_not_found", r"bad interpreter", 0.80), | |
| # NameError (often related to config/code errors) | |
| ("config_error", r"NameError:\s*name\s+['\"](\w+)['\"]", 0.80), | |
| ] | |
| class ErrorFingerprinter: | |
| """Rule-based error classifier for terminal output. | |
| Classifies error logs into one of the canonical ERROR_TYPES categories | |
| using regex pattern matching. Provides confidence scores and matched | |
| text for debugging. | |
| Usage: | |
| fp = ErrorFingerprinter() | |
| result = fp.classify("ModuleNotFoundError: No module named 'flask'") | |
| print(result.error_type) # "missing_package" | |
| print(result.confidence) # 0.95 | |
| """ | |
| def __init__(self, custom_rules: List[Tuple[str, str, float]] | None = None) -> None: | |
| """Initialize the fingerprinter. | |
| Args: | |
| custom_rules: Optional additional (error_type, regex, confidence) tuples. | |
| """ | |
| self.rules = list(_FINGERPRINT_RULES) | |
| if custom_rules: | |
| self.rules.extend(custom_rules) | |
| def classify(self, error_log: str) -> FingerprintResult: | |
| """Classify an error log into an error type. | |
| Scans the error log against all rules and returns the highest | |
| confidence match. | |
| Args: | |
| error_log: The terminal error output to classify. | |
| Returns: | |
| FingerprintResult with the classified error type and metadata. | |
| """ | |
| if not error_log or not error_log.strip(): | |
| return FingerprintResult( | |
| error_type="unknown", | |
| confidence=0.0, | |
| ) | |
| best_match: Optional[FingerprintResult] = None | |
| second_best_type: str = "" | |
| for error_type, pattern, base_confidence in self.rules: | |
| match = re.search(pattern, error_log, re.IGNORECASE | re.MULTILINE) | |
| if match: | |
| # Boost confidence if we match multiple patterns for same type | |
| confidence = base_confidence | |
| if best_match is None or confidence > best_match.confidence: | |
| if best_match: | |
| second_best_type = best_match.error_type | |
| best_match = FingerprintResult( | |
| error_type=error_type, | |
| confidence=confidence, | |
| matched_pattern=pattern, | |
| matched_text=match.group(0)[:200], | |
| suggested_category=second_best_type, | |
| ) | |
| elif confidence > 0.7 and error_type != best_match.error_type: | |
| second_best_type = error_type | |
| if best_match: | |
| best_match.suggested_category = second_best_type | |
| return best_match | |
| return FingerprintResult( | |
| error_type="unknown", | |
| confidence=0.0, | |
| ) | |
| def classify_with_all_matches(self, error_log: str) -> Dict[str, float]: | |
| """Return confidence scores for all error types found in the log. | |
| Args: | |
| error_log: The terminal error output to classify. | |
| Returns: | |
| Dict mapping error_type to highest confidence found. | |
| """ | |
| scores: Dict[str, float] = {} | |
| for error_type, pattern, base_confidence in self.rules: | |
| match = re.search(pattern, error_log, re.IGNORECASE | re.MULTILINE) | |
| if match: | |
| if error_type not in scores or base_confidence > scores[error_type]: | |
| scores[error_type] = base_confidence | |
| return scores | |
| def get_error_summary(self, error_log: str) -> str: | |
| """Generate a one-line summary of the error for the agent prompt. | |
| Args: | |
| error_log: The terminal error output. | |
| Returns: | |
| Human-readable one-line error summary. | |
| """ | |
| result = self.classify(error_log) | |
| summaries = { | |
| "missing_package": "A required Python package is not installed.", | |
| "port_conflict": "A network port is already in use by another process.", | |
| "missing_env": "A required environment variable is not set.", | |
| "permission_denied": "The operation lacks required permissions.", | |
| "version_conflict": "Package versions are incompatible.", | |
| "syntax_error": "The Python code has a syntax error.", | |
| "config_error": "The application configuration is incorrect.", | |
| "service_not_running": "A required service is not running or unreachable.", | |
| "file_not_found": "A required file or directory does not exist.", | |
| "unknown": "The error type could not be determined.", | |
| } | |
| return summaries.get(result.error_type, summaries["unknown"]) | |