feat: Add Streamlit dashboard with Blueprint compliance (v2.1.0)
Dashboard Features: - 8 navigation sections: Overview, Outcomes, Poor CX, FCR, Churn, Agent, Call Explorer, Export - Beyond Brand Identity styling (colors #6D84E3, Outfit font) - RCA Sankey diagram (Driver → Outcome → Churn Risk flow) - Correlation heatmaps (driver co-occurrence, driver-outcome) - Outcome Deep Dive (root causes, correlation, duration analysis) - Export functionality (Excel, HTML, JSON) Blueprint Compliance: - FCR: 4 categories (Primera Llamada/Rellamada × Sin/Con Riesgo de Fuga) - Churn: Binary view (Sin Riesgo de Fuga / En Riesgo de Fuga) - Agent: Talento Para Replicar / Oportunidades de Mejora - Fixed FCR rate calculation (only FIRST_CALL counts as success) Technical: - Streamlit + Plotly for interactive visualizations - Light theme configuration (.streamlit/config.toml) - Fixed Plotly colorbar titlefont deprecation Documentation: - Updated PROJECT_CONTEXT.md, TODO.md, CHANGELOG.md - Added 4 new technical decisions (TD-014 to TD-017) - Created TROUBLESHOOTING.md with 10 common issues Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
579
docs/TECH_STACK.md
Normal file
579
docs/TECH_STACK.md
Normal file
@@ -0,0 +1,579 @@
|
||||
# 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 |
|
||||
Reference in New Issue
Block a user