Spaces:
Sleeping
Sleeping
File size: 9,323 Bytes
27cdb3e | 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 | """
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
]
@dataclass
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"])
|