DEV Community

Cover image for LLPY-09: Phoenix y OpenTelemetry - Observabilidad Completa
Jesus Oviedo Riquelme
Jesus Oviedo Riquelme

Posted on

LLPY-09: Phoenix y OpenTelemetry - Observabilidad Completa

🎯 El Desafío de Debugging en Sistemas RAG

Imagina que tu sistema RAG está en producción:

  • ✅ API recibe 100+ req/s
  • ✅ Pipeline complejo: Embedding → Qdrant → Reranking → LLM
  • ✅ Múltiples servicios integrados

Pero hay un problema: ¿Cómo debuggeas cuando algo sale mal?

Preguntas sin Respuesta

  1. Performance: "¿Por qué esta query tardó 5 segundos cuando debería tomar 2?"
  2. Quality: "¿Por qué el LLM generó una respuesta incorrecta?"
  3. Errors: "¿Dónde falló exactamente en el pipeline?"
  4. Cost: "¿Cuántos tokens estoy consumiendo por día?"
  5. User Experience: "¿Qué % de usuarios obtienen respuestas relevantes?"

Sin observabilidad: debugging a ciegas con print statements y logs dispersos.

La Complejidad del RAG Pipeline

User Query: "¿Cuántos días de vacaciones?"
   ↓
   ├─ [1] Embedding generation (30ms) ✅
   │   └─ Model: multilingual-e5-small, Dimensions: 384
   ↓
   ├─ [2] Qdrant search (35ms) ✅
   │   └─ Found 10 documents, scores: 0.85-0.91
   ↓
   ├─ [3] Reranking (20ms) ✅
   │   └─ Model: ms-marco-MiniLM, Top-5 kept
   ↓
   ├─ [4] LLM generation (1800ms) ⚠️ SLOW!
   │   └─ OpenAI GPT-3.5-turbo, 1500 tokens
   ↓
   └─ [5] Evaluation (async, 2500ms)
       └─ Relevance: 0.92, Hallucination: 0.05

TOTAL: 1885ms + eval async
Enter fullscreen mode Exit fullscreen mode

¿Cómo sabes si cada paso funcionó correctamente?

📊 La Magnitud del Problema

Requisitos de Observabilidad para RAG

  1. 📊 Tracing: Visualizar cada paso del pipeline end-to-end
  2. 🔍 Instrumentation: Auto-track llamadas a LLM APIs (OpenAI, Gemini)
  3. 📈 Metrics: Latency, throughput, error rates por servicio
  4. 🎯 Evaluations: Calidad automática de respuestas (LLM-as-judge)
  5. 🔗 Context Propagation: Correlacionar logs/spans de una misma request
  6. 💰 Cost Tracking: Tokens consumidos, costo por query
  7. 🐛 Error Tracking: Stack traces con contexto completo
  8. 📊 Dashboards: Visualización en tiempo real

Desafíos Técnicos

  1. 🕸️ Distributed Tracing: Correlacionar operaciones asíncronas
  2. 🔄 Async Evaluation: No bloquear respuesta del usuario
  3. 📦 Complex Payloads: Serializar metadatos complejos para OTel
  4. ⚡ Low Overhead: Tracing no debe agregar >10ms de latency
  5. 🎯 Sampling: En producción, no trackear todo (costo + overhead)

💡 La Solución: Phoenix + OpenTelemetry

¿Qué es Phoenix?

Phoenix (por Arize AI) es una plataforma de observabilidad especializada en LLMs y RAG:

  • 🎯 Purpose-built: Diseñado específicamente para aplicaciones LLM
  • 📊 Tracing: Visualización de pipelines RAG completos
  • 🤖 LLM-as-Judge: Evaluación automática de respuestas
  • 📈 Analytics: Métricas de calidad, costo, performance
  • 🔍 Debugging: Drill-down en queries problemáticas
  • 💰 Open Source: Self-hosted o cloud (Phoenix Cloud)

¿Qué es OpenTelemetry?

OpenTelemetry es el estándar de observabilidad open source:

  • 📏 Specification: Define cómo instrumentar aplicaciones
  • 📦 SDKs: Librerías para Python, JS, Go, Java, etc.
  • 🔌 Exporters: Envía datos a backends (Phoenix, Datadog, New Relic, etc.)
  • 🎯 Vendor-neutral: No lock-in a un provider específico

Arquitectura Phoenix + OpenTelemetry

┌─────────────────────────────────────────────────────────┐
│                    Lus Laboris API                      │
│                                                         │
│  ┌──────────────────────────────────────────────┐     │
│  │  PhoenixMonitoringService                    │     │
│  │  - Tracer Provider (OpenTelemetry)           │     │
│  │  - Instrumentors (OpenAI auto-instrumented)  │     │
│  │  - Custom Spans (manual tracking)            │     │
│  └───────────────┬──────────────────────────────┘     │
│                  │                                      │
│          OpenTelemetry SDK                              │
│                  │                                      │
└──────────────────┼──────────────────────────────────────┘
                   │
                   │ gRPC (4317) o HTTP (6006)
                   │
           ┌───────▼────────┐
           │  Phoenix       │
           │  Collector     │
           │                │
           │  - Receives    │
           │    traces      │
           │  - Stores      │
           │    spans       │
           │  - Runs        │
           │    evals       │
           └───────┬────────┘
                   │
           ┌───────▼────────┐
           │  Phoenix UI    │
           │  (Port 6006)   │
           │                │
           │  - Traces      │
           │  - Metrics     │
           │  - Evals       │
           │  - Analytics   │
           └────────────────┘
Enter fullscreen mode Exit fullscreen mode

🚀 Configuración Paso a Paso

1. Variables de Entorno

# .env

# Phoenix Configuration
API_PHOENIX_ENABLED=true
API_PHOENIX_ENDPOINT=http://localhost:6006      # Phoenix HTTP
API_PHOENIX_GRPC_ENDPOINT=localhost:4317        # Phoenix gRPC (más rápido)
API_PHOENIX_USE_GRPC=true                       # Preferir gRPC
API_PHOENIX_API_KEY=                             # Vacío para local, requerido para cloud
API_PHOENIX_PROJECT_NAME=lus-laboris-api

# Environment
API_ENVIRONMENT=development  # Options: development, production, testing
Enter fullscreen mode Exit fullscreen mode

Configuraciones por ambiente:

Variable Development Production
API_PHOENIX_ENABLED true true
API_PHOENIX_ENDPOINT http://localhost:6006 https://app.phoenix.arize.com
API_PHOENIX_USE_GRPC true true
API_PHOENIX_API_KEY (vacío) phx_... (cloud API key)
API_ENVIRONMENT development production

2. Gestión de Servicios con Bash Script

El proyecto separa los servicios externos de la API para mejor gestión:

Estructura:

services/
├── vectordb/
│   └── docker-compose.yml    # Qdrant (puertos 6333, 6334)
├── monitoring/
│   └── docker-compose.yml    # Phoenix (puertos 6006, 4317, 9090)
└── manage_services.sh         # Script interactivo de gestión

src/lus_laboris_api/
└── docker-compose.yml         # API (separada)
Enter fullscreen mode Exit fullscreen mode

Script de gestión de servicios:

cd services

# Hacer ejecutable (primera vez)
chmod +x manage_services.sh

# Ejecutar script interactivo
./manage_services.sh
Enter fullscreen mode Exit fullscreen mode

Menú interactivo:

================================
  Script de Gestión de Servicios
================================

Servicios disponibles:
  1. Qdrant (Base de Datos Vectorial)
  2. Phoenix (Monitoreo y Observabilidad)
  3. Todos los Servicios

Acciones:
  s) Iniciar servicio
  t) Detener servicio
  r) Reiniciar servicio
  l) Mostrar logs
  k) Mostrar estado
  q) Salir

Estado actual:
  ● Qdrant: Ejecutándose
  ● Phoenix: Ejecutándose
Enter fullscreen mode Exit fullscreen mode

Inicio rápido (comandos directos):

# Iniciar Qdrant
cd services/vectordb
docker compose up -d

# Iniciar Phoenix
cd services/monitoring
docker compose up -d

# Verificar servicios
curl http://localhost:6333      # Qdrant
curl http://localhost:6006      # Phoenix

# Acceder a UIs
open http://localhost:6333/dashboard  # Qdrant Dashboard
open http://localhost:6006            # Phoenix UI
Enter fullscreen mode Exit fullscreen mode

Ventajas de esta arquitectura:

  • Separación de responsabilidades: Servicios independientes de la API
  • Gestión simplificada: Script bash interactivo en español
  • Reutilizable: Servicios pueden usarse con otras aplicaciones
  • Desarrollo flexible: Levantar solo lo que necesitas

2.1. Opción: Docker Compose Consolidado (Sugerencia)

Para desarrollo local rápido, puedes crear un docker-compose.yml consolidado en la raíz del proyecto:

# docker-compose.dev.yml (en raíz del proyecto)
# Archivo opcional para levantar todo el stack en desarrollo

services:
  # Qdrant vector database
  qdrant:
    image: qdrant/qdrant:latest
    container_name: qdrant
    ports:
      - "6333:6333"  # HTTP API
      - "6334:6334"  # gRPC API
    volumes:
      - qdrant_storage:/qdrant/storage
    environment:
      - QDRANT__SERVICE__HTTP_PORT=6333
      - QDRANT__SERVICE__GRPC_PORT=6334
      - QDRANT__LOG_LEVEL=INFO
    networks:
      - dev-network

  # Phoenix observability
  phoenix:
    image: arizephoenix/phoenix:latest
    container_name: phoenix
    ports:
      - "6006:6006"  # HTTP UI
      - "4317:4317"  # gRPC collector
      - "9090:9090"  # Prometheus
    environment:
      - PHOENIX_WORKING_DIR=/mnt/data
    volumes:
      - phoenix_data:/mnt/data
    networks:
      - dev-network

  # Lus Laboris API (opcional)
  api:
    build:
      context: ./src/lus_laboris_api
      dockerfile: Dockerfile
    container_name: lus-laboris-api
    ports:
      - "8000:8000"
    environment:
      # Phoenix config
      - API_PHOENIX_ENABLED=true
      - API_PHOENIX_ENDPOINT=http://phoenix:6006
      - API_PHOENIX_GRPC_ENDPOINT=phoenix:4317
      - API_PHOENIX_USE_GRPC=true
      # Qdrant config
      - API_QDRANT_URL=http://qdrant:6333
      - API_QDRANT_PREFER_GRPC=true
      - API_QDRANT_GRPC_PORT=6334
    env_file:
      - .env
    depends_on:
      - qdrant
      - phoenix
    networks:
      - dev-network

volumes:
  qdrant_storage:
    driver: local
  phoenix_data:
    driver: local

networks:
  dev-network:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

Uso del stack consolidado:

# Levantar todo el stack (Qdrant + Phoenix + API)
docker-compose -f docker-compose.dev.yml up -d

# Solo servicios externos (sin API)
docker-compose -f docker-compose.dev.yml up -d qdrant phoenix

# Ver logs
docker-compose -f docker-compose.dev.yml logs -f

# Detener todo
docker-compose -f docker-compose.dev.yml down
Enter fullscreen mode Exit fullscreen mode

Verificar servicios:

# Qdrant
curl http://localhost:6333
open http://localhost:6333/dashboard

# Phoenix
curl http://localhost:6006
open http://localhost:6006

# API (si está incluida)
curl http://localhost:8000/health
Enter fullscreen mode Exit fullscreen mode

¿Cuándo usar cada opción?

Escenario Usar
Desarrollo diario Script manage_services.sh (servicios) + run API local
Testing rápido docker-compose.dev.yml (todo el stack)
Desarrollo API Solo services/ (Qdrant + Phoenix) + run API en IDE
Producción Servicios separados en GCP (Cloud Run + Compute Engine)

Ventajas del docker-compose consolidado:

  • Un solo comando levanta todo
  • 🔗 Networking automático entre servicios
  • 🧹 Cleanup fácil con un solo down
  • 🎯 Ideal para demos y onboarding

Desventajas:

  • 🔄 Menos flexible (no puedes actualizar un servicio sin afectar otros)
  • 💾 Más recursos (levanta todo siempre)
  • 🐛 Debugging más complejo (logs mezclados)

3. Settings con Pydantic

# src/lus_laboris_api/api/config.py

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    """Application settings"""

    # Phoenix Monitoring Configuration
    api_phoenix_enabled: bool = True
    api_phoenix_endpoint: str | None = None
    api_phoenix_grpc_endpoint: str | None = "localhost:4317"
    api_phoenix_use_grpc: bool = True
    api_phoenix_api_key: str | None = None
    api_phoenix_project_name: str = "lus-laboris-api"

    # Environment Configuration
    api_environment: str = "development"  # development, production, testing

    class Config:
        env_file = ".env"
        case_sensitive = False

settings = Settings()
Enter fullscreen mode Exit fullscreen mode

4. PhoenixMonitoringService

El servicio se inicializa al arrancar la aplicación y configura el stack completo de observabilidad:

¿Qué hace PhoenixMonitoringService?

  1. Lee configuración de settings

    • Project name, endpoints (gRPC/HTTP), API keys
    • Detecta ambiente (development vs production)
  2. Conecta a Phoenix con estrategia inteligente

    • Prioridad 1: gRPC (2-3x más rápido que HTTP)
    • Fallback: HTTP si gRPC no está disponible
    • Graceful degradation: Si falla, API sigue funcionando
  3. Registra tracer provider con phoenix.otel.register()

    • project_name: Para separar proyectos en Phoenix UI
    • auto_instrument=True: Tracking automático de librerías
    • batch=True en producción: Agrupa spans para eficiencia
  4. Auto-instrumenta OpenAI con OpenInference

    • OpenAI: Auto-tracked (tokens, latency, errors)
    • Gemini: Manual tracking (no hay instrumentor aún)
  5. Crea tracer para tracking manual con OpenTelemetry

Configuración según ambiente:

Aspecto Development Production
Span Processor SimpleSpanProcessor (inmediato) BatchSpanProcessor (agrupa 512 spans)
Transport gRPC o HTTP gRPC preferido (performance)
Batch false (debugging) true (eficiencia)

Beneficios clave:

  • gRPC: <2ms overhead vs 5-10ms con HTTP
  • Auto-instrumentation: OpenAI tracked sin código extra
  • Graceful: Si Phoenix falla, API no se rompe
  • Environment-aware: Optimizado por ambiente

🔍 Tracking del Pipeline RAG

1. Session Management

def create_session(self, user_id: str | None = None) -> str:
    """Create a monitoring session for a user request"""
    session_id = str(uuid.uuid4())

    self.session_tracker[session_id] = {
        "session_id": session_id,
        "user_id": user_id,
        "start_time": datetime.now(),
        "actions": [],
        "llm_calls": [],
        "metrics": {},
    }

    logger.info(f"Created monitoring session: {session_id}")
    return session_id

def end_session(self, session_id: str) -> dict[str, Any]:
    """End session and calculate final metrics"""
    if session_id not in self.session_tracker:
        return {}

    session_data = self.session_tracker[session_id]
    session_data["end_time"] = datetime.now()
    session_data["duration"] = (
        session_data["end_time"] - session_data["start_time"]
    ).total_seconds()

    # Calculate final metrics
    session_metrics = self._calculate_session_metrics(session_data)
    session_data["final_metrics"] = session_metrics

    logger.info(f"Session {session_id} ended. Duration: {session_data['duration']:.2f}s")

    # Cleanup
    del self.session_tracker[session_id]

    return session_data
Enter fullscreen mode Exit fullscreen mode

2. Track Embedding Generation

Este método registra el paso de generación de embeddings en el pipeline:

¿Qué registra?

  • Modelo usado (multilingual-e5-small)
  • Longitud del texto de entrada
  • Tiempo de generación (típicamente ~30ms)
  • Session ID para correlación

Cómo funciona:

  1. Crea un span de OpenTelemetry llamado "embedding_generation"
  2. Añade attributes (modelo, tiempo, longitud de texto)
  3. Registra en session tracker local
  4. Marca como Status.OK si todo sale bien

Visible en Phoenix UI: Como un span dentro del trace completo del request.

3. Track Vectorstore Search

Registra la búsqueda en Qdrant (vectorstore):

¿Qué registra?

  • Longitud de la query
  • Cantidad de documentos retornados (típicamente 5-10)
  • Tiempo de búsqueda (~30-35ms)

Visible en Phoenix: Span "vectorstore_search" que muestra performance de Qdrant.

4. Track Reranking

Registra el paso de reranking con cross-encoder:

¿Qué registra?

  • Cantidad de documentos rerankeados (10 → 5)
  • Tiempo de reranking (~20ms)

Visible en Phoenix: Span "document_reranking" que muestra overhead del reranking.

5. Track LLM Call

Registra llamadas a LLMs (OpenAI o Gemini):

¿Qué registra?

  • Provider y modelo (openai/gpt-3.5-turbo o gemini/gemini-1.5-flash)
  • Longitud de prompt y respuesta
  • Tokens consumidos (para cálculo de costos)
  • Métricas básicas de calidad (coherence, relevance, completeness)

Auto-instrumentation:

  • OpenAI: Tracked automáticamente por OpenInference (tokens, latency, errors)
  • Gemini: Tracking manual porque no hay instrumentor aún

Visible en Phoenix: Span "llm_call_openai_gpt-3.5-turbo" que domina el tiempo total (~1.8s de 2s total).

🎯 Integración con RAG Pipeline

En el RAGService

# src/lus_laboris_api/api/services/rag_service.py

from .phoenix_service import phoenix_service

async def answer_question(self, question: str, session_id: str | None = None):
    """Complete RAG pipeline with Phoenix tracking"""

    start_time = time.time()

    # 1. Create session
    if not session_id:
        session_id = phoenix_service.create_session()

    try:
        # 2. Retrieve documents (tracked internally)
        documents, retrieval_metadata = self._retrieve_documents(question, session_id)

        # 3. Generate response (tracked internally)
        answer = await self._generate_response(question, documents, session_id)

        # 4. Calculate total time
        processing_time = time.time() - start_time

        # 5. Enqueue evaluation (async, non-blocking)
        context = self._build_context(documents)
        evaluation_service.enqueue_evaluation(
            session_id=session_id,
            question=question,
            context=context,
            answer=answer,
            documents=documents,
            metadata={
                "processing_time": processing_time,
                "llm_provider": self.llm_provider,
                "llm_model": self.llm_model,
            }
        )

        # 6. Return response
        return {
            "success": True,
            "question": question,
            "answer": answer,
            "processing_time_seconds": round(processing_time, 3),
            "session_id": session_id,
            ...
        }

    except Exception as e:
        logger.exception(f"[{session_id}] Failed to answer question")
        return {
            "success": False,
            "error": str(e),
            "session_id": session_id,
        }
    finally:
        # End session
        phoenix_service.end_session(session_id)
Enter fullscreen mode Exit fullscreen mode

Flujo completo:

  1. Create session
  2. Track embedding → Track Qdrant search → Track reranking → Track LLM
  3. Enqueue async evaluation
  4. Return response to user
  5. End session (calculates metrics)

📊 Visualización en Phoenix UI

1. Acceder a Phoenix

# Local
open http://localhost:6006

# Cloud
open https://app.phoenix.arize.com
Enter fullscreen mode Exit fullscreen mode

2. Traces View

Muestra todos los requests con breakdown detallado:

┌────────────────────────────────────────────────────────┐
│ Trace: session_abc123 (1.8s)                          │
│                                                        │
│ ├─ embedding_generation (30ms)                        │
│ │  ├─ model: multilingual-e5-small                   │
│ │  └─ text_length: 45                                │
│                                                        │
│ ├─ vectorstore_search (35ms)                          │
│ │  ├─ results_count: 10                              │
│ │  └─ search_time: 0.035s                            │
│                                                        │
│ ├─ document_reranking (20ms)                          │
│ │  ├─ documents_count: 10                            │
│ │  └─ reranking_time: 0.020s                         │
│                                                        │
│ └─ llm_call_openai_gpt-3.5-turbo (1.7s)               │
│    ├─ provider: openai                                │
│    ├─ model: gpt-3.5-turbo                           │
│    ├─ prompt_length: 1450                            │
│    ├─ response_length: 320                           │
│    ├─ quality.coherence: 0.85                        │
│    ├─ quality.relevance: 0.92                        │
│    └─ quality.completeness: 0.78                     │
│                                                        │
│ TOTAL: 1.815s                                          │
└────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

3. LLM Calls Dashboard

┌─────────────────────────────────────────────────────┐
│ LLM Calls Summary (Last 24h)                        │
│                                                     │
│ Total Calls: 1,234                                  │
│ OpenAI: 856 (69%)                                   │
│ Gemini: 378 (31%)                                   │
│                                                     │
│ Avg Latency: 1.8s                                   │
│ p50: 1.5s | p95: 2.8s | p99: 4.2s                  │
│                                                     │
│ Total Tokens: 2.3M                                  │
│ Input: 1.5M | Output: 800K                         │
│                                                     │
│ Estimated Cost: $3.45                               │
│ OpenAI: $2.80 | Gemini: $0.65                      │
└─────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

4. Evaluations View

┌─────────────────────────────────────────────────────┐
│ Evaluation Results (Last 100 queries)              │
│                                                     │
│ Relevance (avg):    0.89 ████████████████░░ 89%   │
│ Correctness (avg):  0.85 ██████████████░░░░ 85%   │
│ Completeness (avg): 0.78 █████████████░░░░░ 78%   │
│ Hallucination rate:  5%  ░░░░░░░░░░░░░░░░░░  5%   │
│ Grounding (avg):    0.92 ████████████████▓░ 92%   │
│                                                     │
│ Top Issues:                                         │
│ - 8 queries with low completeness (<0.6)           │
│ - 3 potential hallucinations detected              │
│ - 2 queries with poor grounding (<0.7)             │
└─────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

🤖 LLM-as-a-Judge Evaluations

Evaluation Service: Calidad Automática

El EvaluationService evalúa la calidad de cada respuesta usando un LLM como juez:

¿Cómo funciona?

  1. Enqueue evaluation (no bloqueante)

    • Usuario recibe respuesta inmediatamente
    • Evaluación se ejecuta en background thread
  2. Evalúa múltiples dimensiones con prompts específicos:

    • Relevance: ¿La respuesta es relevante a la pregunta? (0-1)
    • Correctness: ¿Es factualmente correcta según el contexto? (0-1)
    • Hallucination: ¿Contiene info no presente en el contexto? (0-1)
    • Grounding: Inverso de hallucination (1 - hallucination)
  3. Envía resultados a Phoenix con phoenix_service.track_evaluation()

    • Métricas visibles en Phoenix UI
    • Historial completo para análisis de tendencias

Métricas típicas:

  • Relevance: ~0.89 (89%)
  • Correctness: ~0.85 (85%)
  • Hallucination: ~0.05 (5%)
  • Grounding: ~0.92 (92%)

Ventajas:

  • Non-blocking: No impacta latencia del usuario
  • Automated: Evalúa 100% de respuestas sin intervención humana
  • Continuous: Detecta degradación de calidad en tiempo real
  • Actionable: Identifica queries problemáticas automáticamente

🎯 Casos de Uso Reales

Para Debugging de Latency:

"¿Por qué esta query tardó 5 segundos?"

Phoenix UI:

  1. Abrir Phoenix → Traces
  2. Filtrar por processing_time > 5s
  3. Ver breakdown:
    • Embedding: 30ms ✅
    • Qdrant: 35ms ✅
    • Reranking: 20ms ✅
    • LLM: 4.8s ⚠️ CULPABLE

Acción: LLM es el cuello de botella. Opciones:

  • Cambiar a Gemini 1.5 Flash (más rápido)
  • Reducir max_tokens
  • Implementar caching de respuestas

Para Debugging de Calidad:

"¿Por qué el LLM generó una respuesta incorrecta?"

Phoenix UI:

  1. Abrir Phoenix → Evaluations
  2. Filtrar por correctness < 0.6
  3. Ver query específica:
    • Question: "¿Cuántos días de preaviso?"
    • Context: [5 documentos irrelevantes]
    • Answer: "15 días" (incorrecto)
    • Correctness: 0.4

Acción: El problema es retrieval, no LLM. Opciones:

  • Mejorar query embedding
  • Ajustar top_k o reranking
  • Revisar metadatos de documentos

Para Cost Optimization:

"¿Cuánto estoy gastando en OpenAI?"

Phoenix UI:

  1. Abrir Phoenix → LLM Calls
  2. Ver dashboard:
    • Total tokens (24h): 2.3M
    • GPT-3.5-turbo: $2.80/día
    • GPT-4: $12.50/día (70 calls)

Acción: GPT-4 es muy costoso. Opciones:

  • Usar GPT-4 solo para queries complejas
  • Implementar clasificador (simple → GPT-3.5, complex → GPT-4)
  • Migrar a Gemini 1.5 Pro (60% más barato)

Para Quality Monitoring:

"¿Qué % de respuestas tienen hallucinations?"

Phoenix UI:

  1. Abrir Phoenix → Evaluations
  2. Ver dashboard:
    • Hallucination rate: 5% (61 de 1,234 queries)

Acción: 5% es aceptable, pero revisar casos:

  • 3 queries con hallucination score >0.8
  • Común cuando context es irrelevante
  • Agregar guardrail: "No tengo información suficiente"

🚀 El Impacto Transformador

Antes de Phoenix:

  • 🐛 Debugging: print() statements en 10 archivos diferentes
  • ⏱️ Performance: "Parece lento, pero no sé por qué"
  • 💰 Cost: "Creo que gasto $50/mes, pero no estoy seguro"
  • 🎯 Quality: "Los usuarios dicen que a veces está mal, pero no sé cuándo"
  • 📊 Metrics: CSV files con logs dispersos

Después de Phoenix:

  • 🐛 Debugging: Ver trace completo en Phoenix UI en 10 segundos
  • ⏱️ Performance: "LLM tarda 1.8s avg, p99 es 4.2s"
  • 💰 Cost: "$3.45/día, OpenAI usa $2.80, Gemini $0.65"
  • 🎯 Quality: "89% relevance, 5% hallucination rate"
  • 📊 Metrics: Dashboard en tiempo real con drill-down completo

Métricas de Mejora:

Aspecto Sin Phoenix Con Phoenix Mejora
MTTR (Mean Time To Resolution) 2-4 horas 10-30 minutos -85%
Quality Detection Manual sampling (10%) Automated (100%) +900%
Cost Visibility Weekly estimates Real-time tracking N/A
Performance Insights Guessing Precise breakdown N/A

💡 Lecciones Aprendidas

1. gRPC es 2-3x Más Rápido que HTTP

Phoenix con gRPC tiene latency de <2ms para enviar spans. HTTP puede agregar 5-10ms.

2. Batch Processing en Producción es Crítico

En dev, usar SimpleSpanProcessor (inmediato). En prod, usar BatchSpanProcessor (agrupa 512 spans antes de enviar).

3. Session IDs son el Glue

Correlacionar todos los spans de una request con un session_id único permite drill-down completo en Phoenix.

4. Async Evaluation no Bloquea

Evaluar calidad en background (evaluationservice) no impacta latencia de respuesta al usuario.

5. Auto-Instrumentation > Manual

OpenInference auto-instrumenta OpenAI (tracks tokens, latency, errors). Gemini requiere tracking manual porque no tiene instrumentor aún.

6. Serialize Complex Metadata

OpenTelemetry solo acepta primitives (str, int, float, bool). Dicts/lists deben serializarse a JSON string.

🎯 El Propósito Más Grande

Phoenix + OpenTelemetry no es solo "nice to have" - es el sistema nervioso del RAG pipeline. Sin observabilidad:

  • Debugging es adivinanza
  • Performance es misterio
  • Quality es anécdota
  • Cost es estimación

Con Phoenix + OpenTelemetry:

  • Visibility: Ver cada paso del pipeline en tiempo real
  • Debuggability: Drill-down en queries problemáticas en segundos
  • Quality: Evaluación automática de 100% de respuestas
  • Optimization: Identificar cuellos de botella con precisión
  • Cost Control: Track tokens y costos en tiempo real
  • Continuous Improvement: Métricas históricas para optimización

Estamos convirtiendo un sistema RAG de "caja negra" a cristal transparente, donde cada operación es visible, medible, y optimizable.


🔗 Recursos y Enlaces

Repositorio del Proyecto

Documentación Técnica

Recursos Externos


Próximo Post: LLPY-10 - Autenticación JWT con RSA

En el siguiente post exploraremos cómo implementar autenticación segura con JWT y claves RSA, generación de tokens, validación en endpoints, y integración con GCP Secret Manager.

Top comments (0)