| """DoppelGround / ReviewGround Evidence Normalizer. | |
| Transforms raw route events into normalized evidence for the Route Plane. | |
| Implements Phase 2 from the routing redesign document. | |
| """ | |
| from typing import Dict, List, Optional, Any | |
| from dataclasses import dataclass, field | |
| from datetime import datetime, timezone | |
| import re | |
| class RawRouteEvent: | |
| """Raw event from provider/relay logs.""" | |
| timestamp: str | |
| provider_id: str | |
| route_class: str | |
| event_type: str # attempt, failover, crash, timeout, stream_disconnect | |
| latency_ms: Optional[float] = None | |
| failure_code: Optional[str] = None | |
| status_code: Optional[int] = None | |
| error_message: Optional[str] = None | |
| metadata: Dict[str, Any] = field(default_factory=dict) | |
| class NormalizedEvidence: | |
| """Normalized evidence ready for Route Plane consumption.""" | |
| provider_id: str | |
| route_class: str | |
| evidence_type: str # success, transient_failure, persistent_degradation, circuit_break | |
| quality_score: float # 0.0 to 1.0 | |
| confidence: float # 0.0 to 1.0 | |
| supporting_data: Dict[str, Any] = field(default_factory=dict) | |
| timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat()) | |
| class EvidenceNormalizer: | |
| """Normalizes raw routing events into evidence. | |
| DoppelGround: Classifies and normalizes provider incidents. | |
| ReviewGround: De-noises benchmark results and separates noise from persistent issues. | |
| """ | |
| # Patterns for classifying failures | |
| TRANSIENT_PATTERNS = [ | |
| r"429", # Rate limit | |
| r"timeout", | |
| r"UND_ERR_SOCKET", | |
| r"stream.*disconnect", | |
| r"temporary", | |
| ] | |
| PERSISTENT_PATTERNS = [ | |
| r"401", # Auth failure | |
| r"403", | |
| r"model.*not found", | |
| r"invalid.*api.*key", | |
| ] | |
| def __init__(self): | |
| self._transient_regex = re.compile("|".join(self.TRANSIENT_PATTERNS), re.IGNORECASE) | |
| self._persistent_regex = re.compile("|".join(self.PERSISTENT_PATTERNS), re.IGNORECASE) | |
| def normalize_event(self, event: RawRouteEvent) -> NormalizedEvidence: | |
| """Normalize a single raw event into evidence.""" | |
| # Classify the event | |
| if event.event_type == "attempt" and event.status_code and 200 <= event.status_code < 300: | |
| return self._create_success_evidence(event) | |
| elif event.failure_code or (event.status_code and event.status_code >= 400): | |
| return self._classify_failure(event) | |
| else: | |
| return self._classify_neutral(event) | |
| def _create_success_evidence(self, event: RawRouteEvent) -> NormalizedEvidence: | |
| """Create evidence for successful route.""" | |
| # Higher quality for lower latency | |
| latency = event.latency_ms or 1000.0 | |
| quality = max(0.1, 1.0 - (latency / 5000.0)) # Normalize: 0ms=1.0, 5000ms+=0.1 | |
| return NormalizedEvidence( | |
| provider_id=event.provider_id, | |
| route_class=event.route_class, | |
| evidence_type="success", | |
| quality_score=quality, | |
| confidence=0.9, # High confidence for explicit success | |
| supporting_data={ | |
| "latency_ms": latency, | |
| "status_code": event.status_code, | |
| }, | |
| ) | |
| def _classify_failure(self, event: RawRouteEvent) -> NormalizedEvidence: | |
| """Classify a failure event.""" | |
| error_text = " ".join(filter(None, [ | |
| event.failure_code or "", | |
| str(event.status_code or ""), | |
| event.error_message or "", | |
| ])) | |
| is_transient = bool(self._transient_regex.search(error_text)) | |
| is_persistent = bool(self._persistent_regex.search(error_text)) | |
| if is_transient: | |
| return NormalizedEvidence( | |
| provider_id=event.provider_id, | |
| route_class=event.route_class, | |
| evidence_type="transient_failure", | |
| quality_score=0.3, # Partial penalty | |
| confidence=0.7, | |
| supporting_data={ | |
| "failure_code": event.failure_code, | |
| "status_code": event.status_code, | |
| "error": event.error_message, | |
| }, | |
| ) | |
| elif is_persistent: | |
| return NormalizedEvidence( | |
| provider_id=event.provider_id, | |
| route_class=event.route_class, | |
| evidence_type="persistent_degradation", | |
| quality_score=0.1, # Strong penalty | |
| confidence=0.9, | |
| supporting_data={ | |
| "failure_code": event.failure_code, | |
| "status_code": event.status_code, | |
| "error": event.error_message, | |
| }, | |
| ) | |
| else: | |
| # Unknown failure - treat as moderate transient | |
| return NormalizedEvidence( | |
| provider_id=event.provider_id, | |
| route_class=event.route_class, | |
| evidence_type="transient_failure", | |
| quality_score=0.2, | |
| confidence=0.5, # Low confidence for unknown | |
| supporting_data={ | |
| "failure_code": event.failure_code, | |
| "status_code": event.status_code, | |
| "error": event.error_message, | |
| }, | |
| ) | |
| def _classify_neutral(self, event: RawRouteEvent) -> NormalizedEvidence: | |
| """Classify neutral/incomplete events.""" | |
| return NormalizedEvidence( | |
| provider_id=event.provider_id, | |
| route_class=event.route_class, | |
| evidence_type="transient_failure", | |
| quality_score=0.5, | |
| confidence=0.3, # Low confidence | |
| supporting_data={"event_type": event.event_type}, | |
| ) | |
| def batch_normalize( | |
| self, events: List[RawRouteEvent] | |
| ) -> List[NormalizedEvidence]: | |
| """Normalize a batch of events.""" | |
| return [self.normalize_event(e) for e in events] | |
| def generate_evidence_summary( | |
| self, evidence_list: List[NormalizedEvidence] | |
| ) -> Dict[str, Any]: | |
| """Generate a summary of evidence for dashboards/debugging.""" | |
| if not evidence_list: | |
| return {"total": 0} | |
| by_type = {} | |
| for ev in evidence_list: | |
| by_type.setdefault(ev.evidence_type, []).append(ev) | |
| summary = {"total": len(evidence_list), "by_type": {}} | |
| for ev_type, evs in by_type.items(): | |
| summary["by_type"][ev_type] = { | |
| "count": len(evs), | |
| "avg_quality": sum(e.quality_score for e in evs) / len(evs), | |
| "avg_confidence": sum(e.confidence for e in evs) / len(evs), | |
| } | |
| return summary | |
Xet Storage Details
- Size:
- 6.73 kB
- Xet hash:
- f2d7932f43458357d0580b35061181e657979b7a812d7d1e4824f2d1cb5f8cd5
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.