ailab_errors_total era un contador texto-plano sin labels, sin categoría, sin atribución. Los errores coexistían en tres sistemas (in-memory dict + JSON persist + prometheus_client) sin coordinación. Había 47 except con pass que silenciaban fallos sin dejar traza.

Nuevo módulo runtime/errors/ con:

  • Taxonomy (31 categorías)
  • Severity (INFO/WARNING/ERROR/CRITICAL)
  • Recoverability (4 niveles)
  • Attribution engine (exception → categoría)
  • Structured JSONL logging a /opt/ai-lab/logs/runtime_errors.jsonl
  • Prometheus counters (10 nuevos)
  • Origin stage tracking (11 stages)
ArchivoPropósito
taxonomy.pyRuntimeErrorCategory enum con 31 categorías
severity.pyErrorSeverity, severity_for_category()
recovery.pyRecoverability, recoverability_for_category()
correlation.pyCorrelationTags, new_error_id(), stack_hash(), dedup_key()
runtime_errors.pyRuntimeErrorEvent dataclass con 25 campos
attribution.pyclassify_exception(), classify_http_status(), classify_stream_failure(), classify_timeout(), build_error_event()
metrics.py10 Prometheus counters + JSONL RotatingFileHandler (100MB, 7 backups)
__init__.pyExport público

Modificado: runtime/gateway/openai_gateway.py

Section titled “Modificado: runtime/gateway/openai_gateway.py”

5 record_error() reemplazados por emit_error(build_error_event(...)):

ContextoCategoryOrigin Stage
do_GET /v1/models fallaUPSTREAM_CONNECTIONupstream
relay_stream() ExceptionSTREAM_INTERRUPTEDstreaming
Fake SSE response fallaUPSTREAM_INVALID_RESPONSEupstream
RequestExceptionUPSTREAM_TIMEOUT / LMSTUDIO_TIMEOUTconnect/read
Catch-all Exceptionclasificado vía classify_exception()gateway

6 nuevos puntos observacionales (emiten evento pero no cambian comportamiento):

PuntoOrigin StageAntesAhora
apply_profile() fallaroutingpassemit(ROUTING_FAILURE)
format_report_context() fallareportingpassemit(GATEWAY_INTERNAL)
Pipeline agentic fallaagenticsolo print()emit(WORKFLOW_INVALID_STATE)
Disabled model attemptroutingsolo métricaemit(MODEL_DISABLED)
Stream parse failstreamingno capturadoemit(STREAM_INTERRUPTED)

Modificado: runtime/gateway/stream_sanitizer.py

Section titled “Modificado: runtime/gateway/stream_sanitizer.py”
ExcepciónAntesAhora
BrokenPipeError, ConnectionResetError, OSError_interrupted_streams++ silenciosoemit(CLIENT_DISCONNECT)
Exception fallbacksolo [STREAM_ERROR]emit(STREAM_INTERRUPTED)
Stream slot exhaustionsolo retornoemit(STREAM_BACKPRESSURE)
First chunk timeout >20sno detectadoDRY RUN: emit(LMSTUDIO_TIMEOUT) observacional
upstream.close() fallapassmantiene pass (no crítico)
UPSTREAM_TIMEOUT | UPSTREAM_CONNECTION | UPSTREAM_INVALID_RESPONSE
LMSTUDIO_TIMEOUT | LMSTUDIO_STREAM_STALL | LMSTUDIO_MODEL_UNAVAILABLE
STREAM_INTERRUPTED | STREAM_BACKPRESSURE | CLIENT_DISCONNECT
REQUEST_VALIDATION | REQUEST_TOO_LARGE | PROMPT_POLICY | PROMPT_SANITIZATION
GOVERNANCE_BLOCK | TOOL_VALIDATION | TOOL_MALFORMED
WORKFLOW_INVALID_STATE | EXECUTOR_READONLY_BLOCK
SANDBOX_POLICY_BLOCK | SANDBOX_TRAVERSAL_ATTEMPT | ROLLBACK_FAILURE
MEMORY_RECALL_FAILURE | MEMORY_EMPTY | ROUTING_FAILURE | MODEL_DISABLED
CONCURRENCY_THROTTLE | GPU_PRESSURE | VRAM_PRESSURE
GATEWAY_INTERNAL | UNKNOWN
ExceptionCategory
requests.ConnectTimeoutUPSTREAM_TIMEOUT
requests.ReadTimeoutLMSTUDIO_TIMEOUT
requests.ConnectionErrorUPSTREAM_CONNECTION
requests.RequestException (timeout)UPSTREAM_TIMEOUT
BrokenPipeErrorCLIENT_DISCONNECT
ConnectionResetErrorCLIENT_DISCONNECT
ConnectionAbortedErrorCLIENT_DISCONNECT
json.JSONDecodeErrorUPSTREAM_INVALID_RESPONSE
OSErrorSTREAM_INTERRUPTED
PermissionErrorGOVERNANCE_BLOCK
TimeoutErrorLMSTUDIO_TIMEOUT
ValueError / TypeErrorREQUEST_VALIDATION
RuntimeError / SystemErrorGATEWAY_INTERNAL
Exception (fallback)UNKNOWN
MétricaLabelsTipo
ailab_runtime_errors_totalcategory, severity, componentCounter
ailab_runtime_error_recoverability_totalrecoverabilityCounter
ailab_runtime_timeout_totalstageCounter
ailab_runtime_stream_interruptions_totalreasonCounter
ailab_runtime_upstream_failures_totalreasonCounter
ailab_runtime_gateway_internal_totalexceptionCounter
ailab_runtime_client_disconnect_totalCounter
ailab_runtime_error_slo_impact_totalsloCounter
ailab_runtime_error_retryable_totalCounter
ailab_runtime_error_nonrecoverable_totalCounter

Path: /opt/ai-lab/logs/runtime_errors.jsonl

Formato por línea:

{
"timestamp": 1711234567.89,
"category": "LMSTUDIO_TIMEOUT",
"severity": "warning",
"recoverability": "retryable",
"origin_stage": "upstream",
"component": "gateway",
"exception_class": "ReadTimeout",
"model": "qwen2.5-coder-14b-instruct",
"retryable": true,
"root_cause": "upstream too slow",
"latency_ms": 30211
}

Rotación: 100MB máx, 7 archivos de backup.

AI_LAB_ERROR_ATTRIBUTION_ENABLED=true
AI_LAB_ERROR_STRUCTURED_LOGS=true
AI_LAB_ERROR_PROMETHEUS_ENABLED=true
AI_LAB_ERROR_TIMEOUT_ENFORCEMENT=false

Mantenido en transición (FASE A→D):

  • FASE A (ahora): Emisión dual (ailab_errors_total legacy + ailab_runtime_errors_total nueva)
  • FASE B: Migrar dashboards Grafana
  • FASE C: Validar equivalencia en burn-in
  • FASE D: Deprecar legacy

183 tests:

  • 19 categorías de test (exception mapping, severity, recoverability, timing, correlation, dedup, origin stages, secrets filtering)
  • 0 regresiones en tests existentes