# CXInsights - Stack Tecnológico ## Resumen de Decisiones | Componente | Elección | Alternativas Soportadas | |------------|----------|-------------------------| | **STT (Speech-to-Text)** | AssemblyAI (default) | Whisper, Google STT, AWS Transcribe (via adapter) | | **LLM** | OpenAI GPT-4o-mini | Claude 3.5 Sonnet (fallback) | | **Data Processing** | pandas + DuckDB | - | | **Visualization** | Streamlit (internal dashboard) | - | | **PDF Generation** | ReportLab | - | | **Config Management** | Pydantic Settings | - | | **PII Handling** | Presidio (opcional) + redaction pre-LLM | - | --- ## 1. Speech-to-Text: Arquitectura con Adapter ### Decisión: **AssemblyAI (default)** + alternativas via STT Provider Adapter El sistema usa una **interfaz abstracta `Transcriber`** que permite cambiar de proveedor sin modificar el código del pipeline. ``` ┌─────────────────────────────────────────────────────────────────┐ │ STT PROVIDER ADAPTER │ ├─────────────────────────────────────────────────────────────────┤ │ Interface: Transcriber │ │ └─ transcribe(audio) → TranscriptContract │ │ │ │ Implementations: │ │ ├─ AssemblyAITranscriber (DEFAULT - mejor calidad español) │ │ ├─ WhisperTranscriber (local, offline, $0) │ │ ├─ GoogleSTTTranscriber (alternativa cloud) │ │ └─ AWSTranscribeTranscriber (alternativa cloud) │ │ │ │ Config: STT_PROVIDER=assemblyai|whisper|google|aws │ └─────────────────────────────────────────────────────────────────┘ ``` ### Comparativa de Proveedores | Criterio | AssemblyAI | Whisper (local) | Google STT | AWS Transcribe | |----------|------------|-----------------|------------|----------------| | **Calidad español** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | | **Speaker diarization** | ✅ Incluido | ❌ Requiere pyannote | ✅ Incluido | ✅ Incluido | | **Coste/minuto** | $0.015 | $0 (GPU local) | $0.016 | $0.015 | | **Setup complexity** | Bajo (API key) | Alto (GPU, modelos) | Medio | Medio | | **Batch processing** | ✅ Async nativo | Manual | ✅ | ✅ | | **Latencia** | ~0.3x realtime | ~1x realtime | ~0.2x realtime | ~0.3x realtime | ### Por qué AssemblyAI como Default 1. **Mejor modelo para español**: AssemblyAI Best tiene excelente rendimiento en español latinoamericano y castellano 2. **Speaker diarization incluido**: Crítico para separar agente de cliente sin código adicional 3. **API simple**: SDK Python bien documentado, async nativo 4. **Batch processing**: Configurable concurrency, poll por resultados 5. **Sin infraestructura**: No necesitas GPU ni mantener modelos ### Cuándo usar alternativas | Alternativa | Usar cuando... | |-------------|----------------| | **Whisper local** | Presupuesto $0, tienes GPU (RTX 3080+), datos muy sensibles (offline) | | **Google STT** | Ya usas GCP, necesitas latencia mínima | | **AWS Transcribe** | Ya usas AWS, integración con S3 | ### Estimación de Costes STT (AHT = 7 min) ``` AssemblyAI pricing: $0.015/minuto 5,000 llamadas × 7 min = 35,000 min ├─ Estimación baja (sin retries): $525 ├─ Estimación media: $550 └─ Estimación alta (+10% retries): $580 20,000 llamadas × 7 min = 140,000 min ├─ Estimación baja: $2,100 ├─ Estimación media: $2,200 └─ Estimación alta: $2,400 RANGO TOTAL STT: ├─ 5K calls: $525 - $580 └─ 20K calls: $2,100 - $2,400 ``` --- ## 2. LLM: OpenAI GPT-4o-mini ### Decisión: **GPT-4o-mini** (primary) + **Claude 3.5 Sonnet** (fallback) ### Comparativa | Criterio | GPT-4o-mini | GPT-4o | Claude 3.5 Sonnet | |----------|-------------|--------|-------------------| | **Coste input** | $0.15/1M tokens | $2.50/1M tokens | $3.00/1M tokens | | **Coste output** | $0.60/1M tokens | $10.00/1M tokens | $15.00/1M tokens | | **Calidad español** | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | | **JSON structured** | ✅ Excelente | ✅ Excelente | ✅ Muy bueno | | **Context window** | 128K | 128K | 200K | | **Rate limits** | Depende del tier | Depende del tier | Depende del tier | ### Rate Limits y Throttling **Los rate limits dependen del tier de tu cuenta OpenAI:** | Tier | RPM (requests/min) | TPM (tokens/min) | |------|-------------------|------------------| | Tier 1 (free) | 500 | 200K | | Tier 2 | 5,000 | 2M | | Tier 3 | 5,000 | 4M | | Tier 4+ | 10,000 | 10M | **Requisitos obligatorios en el código:** - Implementar throttling con tasa configurable (`LLM_REQUESTS_PER_MINUTE`) - Exponential backoff en errores 429 (rate limit exceeded) - Retry con jitter para evitar thundering herd - Logging de rate limit warnings ```python # Configuración recomendada (conservadora) LLM_REQUESTS_PER_MINUTE=300 # Empezar bajo, escalar según tier LLM_BACKOFF_BASE=2.0 # Segundos base para backoff LLM_BACKOFF_MAX=60.0 # Máximo backoff LLM_MAX_RETRIES=5 ``` ### Estimación de Costes LLM por Llamada **IMPORTANTE**: Estos cálculos asumen **compresión previa del transcript** (Module 2). #### Escenario A: Con compresión (RECOMENDADO) ``` Transcript comprimido: ~1,200-1,800 tokens input Prompt template: ~400-600 tokens Output esperado: ~250-400 tokens Total por llamada (comprimido): ├─ Input: ~2,000 tokens × $0.15/1M = $0.0003 ├─ Output: ~350 tokens × $0.60/1M = $0.0002 └─ Total: $0.0004 - $0.0006 por llamada RANGO (5K calls): $2 - $3 RANGO (20K calls): $8 - $12 ``` #### Escenario B: Sin compresión (full transcript) ``` Transcript completo: ~4,000-8,000 tokens input (x3-x6) Prompt template: ~400-600 tokens Output esperado: ~250-400 tokens Total por llamada (full transcript): ├─ Input: ~6,000 tokens × $0.15/1M = $0.0009 ├─ Output: ~350 tokens × $0.60/1M = $0.0002 └─ Total: $0.0010 - $0.0020 por llamada RANGO (5K calls): $5 - $10 RANGO (20K calls): $20 - $40 ⚠️ RECOMENDACIÓN: Siempre usar compresión para reducir costes 3-6x ``` ### Por qué GPT-4o-mini 1. **Coste-efectividad**: 17x más barato que GPT-4o, calidad suficiente para clasificación 2. **Structured outputs**: JSON mode nativo, reduce errores de parsing 3. **Consistencia**: Respuestas muy consistentes con prompts bien diseñados ### Cuándo escalar a GPT-4o - Análisis que requiera razonamiento complejo - Casos edge con transcripciones ambiguas - Síntesis final de RCA trees (pocas llamadas, coste marginal) ### Claude 3.5 Sonnet como fallback Usar cuando: - OpenAI tiene downtime - Necesitas segunda opinión en casos difíciles - Contexto muy largo (>100K tokens) --- ## 3. Data Processing: pandas + DuckDB ### Decisión: **pandas** (manipulación) + **DuckDB** (queries analíticas) ### Por qué esta combinación | Componente | Uso | Justificación | |------------|-----|---------------| | **pandas** | Load/transform JSON, merge data | Estándar de facto, excelente para datos semi-estructurados | | **DuckDB** | Queries SQL sobre datos, aggregations | SQL analítico sin servidor, integra con pandas | ### Por qué NO Polars - Polars es más rápido, pero pandas es suficiente para 20K filas - Mejor ecosistema y documentación - Equipo probablemente ya conoce pandas ### Por qué NO SQLite/PostgreSQL - DuckDB es columnar, optimizado para analytics - No requiere servidor ni conexión - Syntax SQL estándar - Lee/escribe parquet nativamente ### Ejemplo de uso ```python import pandas as pd import duckdb # Cargar todos los labels labels = pd.read_json("data/processed/*.json") # via glob # Query analítico con DuckDB result = duckdb.sql(""" SELECT lost_sale_driver, COUNT(*) as count, COUNT(*) * 100.0 / SUM(COUNT(*)) OVER () as pct FROM labels WHERE outcome = 'no_sale' GROUP BY lost_sale_driver ORDER BY count DESC """).df() ``` --- ## 4. Visualization: Streamlit ### Decisión: **Streamlit** (dashboard interno) ### Alcance y Limitaciones ``` ┌─────────────────────────────────────────────────────────────────┐ │ STREAMLIT - ALCANCE │ ├─────────────────────────────────────────────────────────────────┤ │ ✅ ES: │ │ ├─ Dashboard interno para equipo de análisis │ │ ├─ Visualización de resultados de batch procesado │ │ ├─ Drill-down por llamada individual │ │ └─ Exportación a PDF/Excel │ │ │ │ ❌ NO ES: │ │ ├─ Portal enterprise multi-tenant │ │ ├─ Aplicación de producción con SLA │ │ ├─ Dashboard para >50 usuarios concurrentes │ │ └─ Sistema con autenticación compleja │ └─────────────────────────────────────────────────────────────────┘ ``` ### Comparativa | Criterio | Streamlit | Plotly Dash | FastAPI+React | |----------|-----------|-------------|---------------| | **Setup time** | 1 hora | 4 horas | 2-3 días | | **Interactividad** | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | | **Aprendizaje** | Bajo | Medio | Alto | | **Customización** | Limitada | Alta | Total | | **Usuarios concurrentes** | ~10-50 | ~50-100 | Sin límite | ### Deploy ``` ┌─────────────────────────────────────────────────────────────────┐ │ OPCIONES DE DEPLOY │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ OPCIÓN 1: Local (desarrollo/análisis personal) │ │ $ streamlit run src/visualization/dashboard.py │ │ → http://localhost:8501 │ │ │ │ OPCIÓN 2: VM/Servidor interno (equipo pequeño) │ │ $ streamlit run dashboard.py --server.port 8501 │ │ → Sin auth, acceso via VPN/red interna │ │ │ │ OPCIÓN 3: Con proxy + auth básica (recomendado producción) │ │ Nginx/Caddy → Basic Auth → Streamlit │ │ → Auth configurable via .htpasswd o OAuth proxy │ │ │ │ OPCIÓN 4: Streamlit Cloud (demos/POC) │ │ → Gratis, pero datos públicos (no para producción) │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ### Configuración de Auth (opcional) ```nginx # nginx.conf - Basic Auth para Streamlit server { listen 443 ssl; server_name dashboard.internal.company.com; auth_basic "CXInsights Dashboard"; auth_basic_user_file /etc/nginx/.htpasswd; location / { proxy_pass http://localhost:8501; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } ``` ### Alternativa futura Si necesitas dashboard enterprise: - Migrar a FastAPI backend + React frontend - Reusar lógica de aggregation - Añadir auth, multi-tenant, RBAC --- ## 5. PII Handling ### Decisión: **Redaction pre-LLM obligatoria** + retención controlada ``` ┌─────────────────────────────────────────────────────────────────┐ │ PII HANDLING STRATEGY │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ PRINCIPIO: Minimizar PII enviado a APIs externas │ │ │ │ 1. REDACTION PRE-LLM (obligatorio) │ │ ├─ Nombres → [NOMBRE] │ │ ├─ Teléfonos → [TELEFONO] │ │ ├─ Emails → [EMAIL] │ │ ├─ DNI/NIE → [DOCUMENTO] │ │ ├─ Tarjetas → [TARJETA] │ │ └─ Direcciones → [DIRECCION] │ │ │ │ 2. RETENCIÓN POR BATCH │ │ ├─ Transcripts raw: borrar tras 30 días o fin de proyecto │ │ ├─ Transcripts compressed: borrar tras procesamiento │ │ ├─ Labels (sin PII): retener para análisis │ │ └─ Aggregated stats: retener indefinidamente │ │ │ │ 3. LOGS │ │ ├─ NUNCA loguear transcript completo │ │ ├─ Solo loguear: call_id, timestamps, errores │ │ └─ Logs en volumen separado, rotación 7 días │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ### Implementación ```python # Opción 1: Regex básico (mínimo viable) REDACTION_PATTERNS = { r'\b\d{8,9}[A-Z]?\b': '[DOCUMENTO]', # DNI/NIE r'\b\d{9}\b': '[TELEFONO]', # Teléfono r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b': '[EMAIL]', r'\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b': '[TARJETA]', } # Opción 2: Presidio (recomendado para producción) # Más preciso, soporta español, detecta contexto from presidio_analyzer import AnalyzerEngine from presidio_anonymizer import AnonymizerEngine ``` --- ## 6. Dependencias Python ### Core Dependencies ```toml [project] dependencies = [ # STT "assemblyai>=0.26.0", # LLM "openai>=1.40.0", "anthropic>=0.34.0", # fallback # Data Processing "pandas>=2.2.0", "duckdb>=1.0.0", "pydantic>=2.8.0", # Visualization "streamlit>=1.38.0", "plotly>=5.24.0", "matplotlib>=3.9.0", # PDF/Excel Export "reportlab>=4.2.0", "openpyxl>=3.1.0", "xlsxwriter>=3.2.0", # Config & Utils "pydantic-settings>=2.4.0", "python-dotenv>=1.0.0", "pyyaml>=6.0.0", "tqdm>=4.66.0", "tenacity>=8.5.0", # retry logic # JSON (performance + validation) "orjson>=3.10.0", # Fast JSON serialization "jsonschema>=4.23.0", # Schema validation # Async "aiofiles>=24.1.0", "httpx>=0.27.0", ] [project.optional-dependencies] # PII detection (opcional pero recomendado) pii = [ "presidio-analyzer>=2.2.0", "presidio-anonymizer>=2.2.0", "spacy>=3.7.0", "es-core-news-sm @ https://github.com/explosion/spacy-models/releases/download/es_core_news_sm-3.7.0/es_core_news_sm-3.7.0-py3-none-any.whl", ] dev = [ "pytest>=8.3.0", "pytest-asyncio>=0.24.0", "pytest-cov>=5.0.0", "ruff>=0.6.0", "mypy>=1.11.0", ] ``` ### Justificación de cada dependencia | Dependencia | Propósito | Por qué esta | |-------------|-----------|--------------| | `assemblyai` | SDK oficial STT | Mejor integración, async nativo | | `openai` | SDK oficial GPT | Structured outputs, streaming | | `anthropic` | SDK oficial Claude | Fallback LLM | | `pandas` | Manipulación datos | Estándar industria | | `duckdb` | Queries SQL | Analytics sin servidor | | `pydantic` | Validación schemas | Type safety, JSON parsing | | `streamlit` | Dashboard | Rápido, Python-only | | `plotly` | Gráficos interactivos | Mejor para web | | `matplotlib` | Gráficos estáticos | Export PNG | | `reportlab` | PDF generation | Maduro, flexible | | `openpyxl` | Excel read/write | Pandas integration | | `pydantic-settings` | Config management | .env + validation | | `tqdm` | Progress bars | UX en CLI | | `tenacity` | Retry logic | Rate limits, API errors | | `orjson` | JSON serialization | 10x más rápido que json stdlib | | `jsonschema` | Schema validation | Validar outputs LLM | | `httpx` | HTTP client async | Mejor que requests | | `presidio-*` | PII detection | Precisión en español, contexto | --- ## 7. Versiones de Python ### Decisión: **Python 3.11+** ### Justificación - 3.11: 10-60% más rápido que 3.10 - 3.11: Better error messages - 3.12: Algunas libs aún no compatibles - Match pattern (3.10+) útil para parsing --- ## 8. Consideraciones de Seguridad ### API Keys ```bash # .env (NUNCA en git) ASSEMBLYAI_API_KEY=xxx OPENAI_API_KEY=sk-xxx ANTHROPIC_API_KEY=sk-ant-xxx # opcional ``` ### Rate Limiting (implementación obligatoria) ```python # src/inference/client.py from tenacity import retry, wait_exponential, stop_after_attempt @retry( wait=wait_exponential(multiplier=2, min=1, max=60), stop=stop_after_attempt(5), retry=retry_if_exception_type(RateLimitError) ) async def call_llm(prompt: str) -> str: # Throttle requests await self.rate_limiter.acquire() # ... llamada a API ``` ### Checklist de seguridad - [ ] API keys en .env, nunca en código - [ ] .env en .gitignore - [ ] PII redactado antes de LLM - [ ] Logs sin transcripts completos - [ ] Rate limiting implementado - [ ] Backoff exponencial en errores 429 --- ## 9. Alternativas Descartadas ### Whisper Local - **Pro**: Gratis, offline, datos sensibles - **Contra**: Necesita GPU, sin diarization nativo, más lento - **Decisión**: Soportado via adapter, no es default ### LangChain - **Pro**: Abstracciones útiles, chains - **Contra**: Overhead innecesario para este caso, complejidad - **Decisión**: Llamadas directas a SDK son suficientes ### PostgreSQL/MySQL - **Pro**: Persistencia, queries complejas - **Contra**: Requiere servidor, overkill para batch - **Decisión**: DuckDB + archivos JSON/parquet ### Celery/Redis - **Pro**: Job queue distribuida - **Contra**: Infraestructura adicional - **Decisión**: asyncio + checkpointing es suficiente --- ## 10. Resumen de Costes ### Parámetros base - **AHT (Average Handle Time)**: 7 minutos - **Compresión de transcript**: Asumida (reducción ~60% tokens) ### Por 5,000 llamadas | Servicio | Cálculo | Rango | |----------|---------|-------| | AssemblyAI STT | 35,000 min × $0.015/min | $525 - $580 | | OpenAI LLM (comprimido) | 5,000 × $0.0005 | $2 - $3 | | OpenAI RCA synthesis | ~10 calls × $0.02 | $0.20 | | **TOTAL** | | **$530 - $590** | ### Por 20,000 llamadas | Servicio | Cálculo | Rango | |----------|---------|-------| | AssemblyAI STT | 140,000 min × $0.015/min | $2,100 - $2,400 | | OpenAI LLM (comprimido) | 20,000 × $0.0005 | $8 - $12 | | OpenAI RCA synthesis | ~10 calls × $0.02 | $0.20 | | **TOTAL** | | **$2,110 - $2,420** | ### Sin compresión (escenario pesimista) | Volumen | STT | LLM (full transcript) | Total | |---------|-----|----------------------|-------| | 5,000 calls | $525-580 | $5-10 | **$530 - $590** | | 20,000 calls | $2,100-2,400 | $20-40 | **$2,120 - $2,440** | ### Coste de infraestructura | Opción | Coste | |--------|-------| | Local (tu máquina) | $0 | | VM cloud (procesamiento) | $20-50/mes | | Streamlit Cloud (demos) | Gratis | | VM + Nginx (producción) | $30-80/mes |