APLICACION

Modulo 07: Observabilidad y calidad

ACTUALIZADO Q3 2026

Observabilidad: medir lo que importa en tus sistemas con IA

Que un sistema "funcione" no es suficiente. Necesitas saber como funciona, donde falla, cuanto cuesta cada operacion y si la calidad de las respuestas se mantiene. Esto es especialmente critico en sistemas con IA, donde el comportamiento no es determinista.

La observabilidad se sostiene sobre tres pilares:

  • Logs: registro de eventos discretos. Que paso, cuando, con que datos de entrada y salida.
  • Metricas: valores numericos agregados en el tiempo. Latencia, tokens consumidos, tasa de error, coste por request.
  • Trazas: el recorrido completo de una peticion a traves de multiples servicios o agentes. Quien llamo a quien, cuanto tardo cada paso.

Punto clave

En sistemas con LLMs, la observabilidad no es opcional. Sin ella, no sabes si tu agente esta alucinando, gastando de mas, o tardando 10 segundos en algo que deberia tardar 1.

En un workflow con agentes IA, cada tool call, cada invocacion al LLM y cada decision del coordinador debe generar datos observables. Sin esto, depurar un error en produccion es como buscar una aguja en un pajar a oscuras.

OpenTelemetry para agentes IA

OpenTelemetry (OTel) es el estandar abierto para instrumentar aplicaciones. Funciona con cualquier lenguaje y se integra con backends como Grafana, Jaeger o Datadog. Para agentes IA, lo usamos para trazar cada tool call con su latencia, modelo utilizado y tokens consumidos.

graph TD
    A[Agent Tool Call] --> B[OpenTelemetry SDK]
    B --> C{Exporters}
    C --> D[Grafana / Prometheus]
    C --> E[Logs estructurados]
    C --> F[Trazas distribuidas]
    D --> G[Dashboard]
    G --> H{Alertas}
    H -->|Error mayor 5%| I[Circuit Breaker]
    H -->|Latencia P95 mayor 3s| J[Escalar]
    H -->|VRAM mayor 90%| K[Notificar]
# Decorador para trazar tool calls de agentes
from opentelemetry import trace
from opentelemetry.trace import StatusCode
import time

tracer = trace.get_tracer("agent-tools")

def traced_tool(tool_name: str):
    def decorator(func):
        def wrapper(*args, **kwargs):
            with tracer.start_as_current_span(tool_name) as span:
                span.set_attribute("tool.name", tool_name)
                span.set_attribute("tool.model", kwargs.get("model", "unknown"))
                start = time.time()
                try:
                    result = func(*args, **kwargs)
                    span.set_attribute("tool.tokens_in", result.get("usage", {}).get("input", 0))
                    span.set_attribute("tool.tokens_out", result.get("usage", {}).get("output", 0))
                    span.set_status(StatusCode.OK)
                    return result
                except Exception as e:
                    span.set_status(StatusCode.ERROR, str(e))
                    span.record_exception(e)
                    raise
                finally:
                    span.set_attribute("tool.latency_ms", (time.time() - start) * 1000)
        return wrapper
    return decorator

@traced_tool("analyze_alert")
def analyze_alert_tool(alert_data: dict, model: str = "qwen3.5-27b"):
    # Logica del tool...
    pass

Cuando un agente coordinador invoca a un worker, y ese worker invoca tools, las trazas se propagan automaticamente. Esto genera un arbol de spans que muestra el flujo completo:

# Traza distribuida (ejemplo visual):
Coordinator (250ms)
  +-- Worker-CTI (180ms)
  |     +-- fetch_ioc_tool (45ms)
  |     +-- llm_analyze (120ms) [tokens: 1200 in, 450 out]
  +-- Worker-SOC (200ms)
        +-- get_alert_context (30ms)
        +-- llm_classify (150ms) [tokens: 800 in, 200 out]

El exportador envia las trazas a Grafana Tempo, donde puedes buscar por trace_id, filtrar por latencia alta o por errores, y correlacionar con logs y metricas.

Evals: testing para IA

Los tests unitarios verifican que el codigo hace lo que debe. Pero con IA, el mismo input puede producir outputs distintos. Por eso necesitas evals: datasets de pares input/output esperado que miden la calidad de las respuestas del modelo.

graph LR
    A[Eval Dataset JSONL] --> B[CI Pipeline]
    B --> C{Accuracy mayor-igual 85%?}
    C -->|Si| D[Deploy]
    C -->|No| E[Bloquear + Notificar]
    D --> F[Monitorizar metricas]
    F --> G{Degradacion?}
    G -->|Si| H[Rollback prompt]
    G -->|No| I[Continuar]
# Eval dataset en formato JSONL (tests/evals/cti/ioc_analysis.jsonl)
{"input": "Analiza el hash: 44d88612fea8a8f36de82e1278abb02f", "expected": "EICAR test file", "tags": ["malware", "hash"]}
{"input": "Clasifica IP 192.168.1.1", "expected": "Private/RFC1918, no threat", "tags": ["ip", "benign"]}
{"input": "Reputacion dominio evil-corp.ru", "expected": "Malicious, phishing campaign", "tags": ["domain", "malicious"]}

Las metricas de evaluacion clave son:

  • Accuracy: porcentaje de respuestas correctas frente al expected output.
  • Relevance: la respuesta aborda la pregunta (no divaga).
  • Faithfulness: la respuesta se basa en datos reales, no en alucinaciones.
# Pipeline de evaluacion en 3 pasos
def run_eval(dataset_path: str, agent_fn, threshold: float = 0.85):
    results = []
    for sample in load_jsonl(dataset_path):
        # Paso 1: Ejecutar el agente
        output = agent_fn(sample["input"])
        # Paso 2: Comparar con expected (LLM-as-judge o heuristicas)
        score = evaluate_response(output, sample["expected"])
        results.append({"input": sample["input"], "score": score})
    # Paso 3: Agregar y decidir
    avg_score = sum(r["score"] for r in results) / len(results)
    passed = avg_score >= threshold
    return {"avg_score": avg_score, "passed": passed, "details": results}

Punto clave

Sin evals, cada deploy es un acto de fe. Con evals, tienes un gate automatico: si accuracy cae por debajo del threshold, el deploy se bloquea.

Prompts versionados

Un anti-pattern muy comun es tener prompts inline en el codigo, sin control de cambios. Si cambias un prompt y la calidad baja, no tienes forma de hacer rollback ni de saber que version estaba en produccion.

La solucion: tratar los prompts como artefactos versionados.

# Estructura de directorio para prompts versionados
docs/prompts/
  agent-cti_analyze-ioc_v1.0.txt
  agent-cti_analyze-ioc_v1.1.txt      # Mejora precision en hashes
  agent-cti_analyze-ioc_v2.0.txt      # Cambio mayor: nuevo formato output
  agent-soc_classify-alert_v1.0.txt
  agent-soc_classify-alert_v1.1.txt
  CHANGELOG.md
# CHANGELOG.md
## agent-cti_analyze-ioc
### v2.0 (2026-06-15)
- Output en formato JSON estructurado (breaking change)
- Eval accuracy: 0.92 (vs 0.87 en v1.1)

### v1.1 (2026-06-01)
- Mejor handling de hashes SHA-256
- Eval accuracy: 0.87 (vs 0.83 en v1.0)

### v1.0 (2026-05-15)
- Version inicial
- Eval accuracy: 0.83

Si no puedes responder "que version del prompt esta en produccion ahora mismo", tienes un problema de observabilidad.

Metricas de calidad en produccion

Un dashboard de observabilidad para sistemas con IA debe incluir estas metricas:

  • Latencia P50/P95/P99: el 50%, 95% y 99% de las peticiones tardan menos de X milisegundos. P99 es critico para detectar outliers.
  • Tokens por request: media y distribucion. Un pico indica prompts que han crecido sin control.
  • Coste por request: tokens * precio/token. Permite proyectar gasto mensual.
  • Error rate: porcentaje de invocaciones al LLM que fallan (timeout, rate limit, error de formato).
  • HITL intervention rate: con que frecuencia un humano tiene que corregir o aprobar la decision del agente.
# Ejemplo de configuracion de alertas
alertas:
  - nombre: "Circuit breaker: error rate alto"
    condicion: error_rate > 5%
    duracion: 5 minutos
    accion: pausar agente, notificar Telegram

  - nombre: "VRAM critica"
    condicion: gpu_vram_usage > 90%
    duracion: 2 minutos
    accion: rechazar nuevas peticiones, alerta urgente

  - nombre: "Latencia P99 degradada"
    condicion: latency_p99 > 10000ms
    duracion: 10 minutos
    accion: escalar a N2, revisar modelo/batch size

  - nombre: "Coste diario excedido"
    condicion: daily_cost > budget_limit
    duracion: inmediato
    accion: activar modelo mas barato, alerta a finance

Punto clave

Las alertas deben ser accionables. "Error rate alto" no basta: incluye que se para (pausar agente), quien se entera (canal Telegram) y que hacer despues (revisar logs, escalar).

CI/CD con calidad IA integrada

El pipeline de CI/CD para proyectos con IA tiene un paso extra respecto al desarrollo tradicional: las evals. El flujo completo es:

# Pipeline CI/CD con gate de calidad IA
# .github/workflows/deploy.yml

name: Deploy con eval gate
on:
  push:
    branches: [main]

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # Paso 1: Lint y formato
      - name: Lint
        run: ruff check src/

      # Paso 2: Tests unitarios clasicos
      - name: Tests
        run: pytest tests/unit/ -v

      # Paso 3: Evals de agentes (EL GATE CRITICO)
      - name: Eval agentes
        run: |
          python scripts/run_evals.py \
            --dataset tests/evals/ \
            --threshold 0.85 \
            --output eval-results.json
        env:
          VLLM_ENDPOINT: ${{ secrets.VLLM_ENDPOINT }}

      # Paso 4: Deploy solo si pasa
      - name: Deploy
        if: success()
        run: ./scripts/deploy.sh

Despues del deploy, la monitorizacion continua. Si las metricas en produccion se degradan, un canary deployment permite hacer rollback automatico:

  • Canary para prompts: enviar el 10% del trafico al nuevo prompt. Comparar metricas durante 1 hora. Si accuracy baja, rollback automatico.
  • Monitorizacion post-deploy: las primeras 2 horas tras deploy son criticas. Alertas mas sensibles (threshold mas bajo).
  • Rollback rapido: volver al prompt anterior es un cambio de fichero, no un deploy completo. Por eso los prompts versionados son tan importantes.

El mejor deploy es el que puedes deshacer en 30 segundos. Prompts versionados + feature flags + canary = control total.

Pon a prueba tus conocimientos

Completa el quiz para verificar que dominas observabilidad y calidad.

Hacer quiz