370 lines
8.2 KiB
Markdown
370 lines
8.2 KiB
Markdown
# Sistema de Categorización Automática con LLM
|
|
|
|
## Descripción
|
|
|
|
Este sistema utiliza **ExLlamaV2** con un modelo de lenguaje local (LLM) para categorizar automáticamente las noticias del feed RSS.
|
|
|
|
### ¿Qué hace?
|
|
|
|
1. **Recopila 10 noticias** sin categorizar de la base de datos
|
|
2. **Envía al LLM local** con un prompt especializado
|
|
3. **El LLM discrimina/categoriza** cada noticia en una de las categorías predefinidas
|
|
4. **Actualiza la base de datos** con las categorías asignadas
|
|
|
|
### Ventajas
|
|
|
|
- ✅ **100% Local**: No envía datos a APIs externas
|
|
- ✅ **Optimizado para RTX 3060 12GB**: Modelos cuantizados eficientes
|
|
- ✅ **Categorización inteligente**: Entiende contexto, no solo keywords
|
|
- ✅ **Escalable**: Procesa lotes de 10 noticias automáticamente
|
|
- ✅ **Integrado**: Se ejecuta como un worker más del sistema
|
|
|
|
---
|
|
|
|
## Instalación
|
|
|
|
### Paso 1: Descargar el Modelo
|
|
|
|
El sistema necesita un modelo LLM compatible. Recomendamos **Mistral-7B-Instruct GPTQ** para RTX 3060 12GB.
|
|
|
|
```bash
|
|
# Ejecutar el script de descarga
|
|
./scripts/download_llm_model.sh
|
|
```
|
|
|
|
El script te mostrará opciones:
|
|
1. **Mistral-7B-Instruct-v0.2 (GPTQ)** - RECOMENDADO
|
|
2. Mistral-7B-Instruct-v0.2 (EXL2)
|
|
3. OpenHermes-2.5-Mistral-7B (GPTQ)
|
|
4. Neural-Chat-7B (GPTQ)
|
|
|
|
**Tiempo estimado de descarga**: 10-30 minutos (según conexión)
|
|
|
|
**Espacio en disco**: ~4.5 GB
|
|
|
|
### Paso 2: Verificar la instalación
|
|
|
|
```bash
|
|
# Verificar que el modelo se descargó correctamente
|
|
ls -lh models/llm/
|
|
|
|
# Deberías ver archivos como:
|
|
# - model.safetensors o *.safetensors
|
|
# - config.json
|
|
# - tokenizer.json
|
|
# - etc.
|
|
```
|
|
|
|
### Paso 3: Probar el sistema (opcional)
|
|
|
|
Antes de levantar el contenedor, puedes probar que funciona:
|
|
|
|
```bash
|
|
# Instalar dependencias localmente (solo para prueba)
|
|
pip3 install exllamav2 torch
|
|
|
|
# Ejecutar script de prueba
|
|
python3 scripts/test_llm_categorizer.py
|
|
```
|
|
|
|
---
|
|
|
|
## Uso
|
|
|
|
### Iniciar el servicio
|
|
|
|
```bash
|
|
# Construir y levantar el contenedor
|
|
docker compose up -d llm-categorizer
|
|
|
|
# Ver logs en tiempo real
|
|
docker compose logs -f llm-categorizer
|
|
```
|
|
|
|
### Verificar funcionamiento
|
|
|
|
```bash
|
|
# Ver estado del contenedor
|
|
docker compose ps llm-categorizer
|
|
|
|
# Ver últimas 50 líneas de log
|
|
docker compose logs --tail=50 llm-categorizer
|
|
|
|
# Ver categorías asignadas en la base de datos
|
|
docker exec -it rss2_db psql -U rss -d rss -c \
|
|
"SELECT llm_categoria, COUNT(*) FROM noticias WHERE llm_processed = TRUE GROUP BY llm_categoria;"
|
|
```
|
|
|
|
### Detener el servicio
|
|
|
|
```bash
|
|
docker compose stop llm-categorizer
|
|
```
|
|
|
|
---
|
|
|
|
## Configuración
|
|
|
|
### Variables de Entorno
|
|
|
|
Puedes ajustar el comportamiento editando `docker-compose.yml`:
|
|
|
|
```yaml
|
|
environment:
|
|
# Número de noticias a procesar por lote (default: 10)
|
|
LLM_BATCH_SIZE: 10
|
|
|
|
# Tiempo de espera cuando no hay noticias (segundos, default: 30)
|
|
LLM_SLEEP_IDLE: 30
|
|
|
|
# Longitud máxima de contexto (default: 4096)
|
|
LLM_MAX_SEQ_LEN: 4096
|
|
|
|
# Modo de caché: FP16 o Q4 (default: FP16)
|
|
# Q4 usa menos VRAM pero puede ser más lento
|
|
LLM_CACHE_MODE: FP16
|
|
|
|
# Distribución de GPU: "auto" para single GPU
|
|
LLM_GPU_SPLIT: auto
|
|
```
|
|
|
|
### Categorías
|
|
|
|
Las categorías están definidas en `workers/llm_categorizer_worker.py`:
|
|
|
|
```python
|
|
CATEGORIES = [
|
|
"Política",
|
|
"Economía",
|
|
"Tecnología",
|
|
"Ciencia",
|
|
"Salud",
|
|
"Deportes",
|
|
"Entretenimiento",
|
|
"Internacional",
|
|
"Nacional",
|
|
"Sociedad",
|
|
"Cultura",
|
|
"Medio Ambiente",
|
|
"Educación",
|
|
"Seguridad",
|
|
"Otros"
|
|
]
|
|
```
|
|
|
|
Para modificarlas, edita el archivo y reconstruye el contenedor:
|
|
|
|
```bash
|
|
docker compose up -d --build llm-categorizer
|
|
```
|
|
|
|
---
|
|
|
|
## Base de Datos
|
|
|
|
### Nuevas columnas en `noticias`
|
|
|
|
El worker añade automáticamente estas columnas:
|
|
|
|
- `llm_categoria` (VARCHAR): Categoría asignada
|
|
- `llm_confianza` (FLOAT): Nivel de confianza (0.0 - 1.0)
|
|
- `llm_processed` (BOOLEAN): Si ya fue procesada
|
|
- `llm_processed_at` (TIMESTAMP): Fecha de procesamiento
|
|
|
|
### Consultas útiles
|
|
|
|
```sql
|
|
-- Ver distribución de categorías
|
|
SELECT llm_categoria, COUNT(*) as total, AVG(llm_confianza) as confianza_media
|
|
FROM noticias
|
|
WHERE llm_processed = TRUE
|
|
GROUP BY llm_categoria
|
|
ORDER BY total DESC;
|
|
|
|
-- Ver noticias de una categoría específica
|
|
SELECT id, titulo, llm_categoria, llm_confianza, fecha
|
|
FROM noticias
|
|
WHERE llm_categoria = 'Tecnología'
|
|
AND llm_processed = TRUE
|
|
ORDER BY fecha DESC
|
|
LIMIT 20;
|
|
|
|
-- Ver noticias con baja confianza (revisar manualmente)
|
|
SELECT id, titulo, llm_categoria, llm_confianza
|
|
FROM noticias
|
|
WHERE llm_processed = TRUE
|
|
AND llm_confianza < 0.6
|
|
ORDER BY llm_confianza ASC
|
|
LIMIT 20;
|
|
|
|
-- Resetear procesamiento (para reprocesar)
|
|
UPDATE noticias SET llm_processed = FALSE WHERE llm_categoria = 'Otros';
|
|
```
|
|
|
|
---
|
|
|
|
## Monitorización
|
|
|
|
### Prometheus/Grafana
|
|
|
|
El worker está integrado con el stack de monitorización. Puedes ver:
|
|
|
|
- Uso de GPU (VRAM)
|
|
- Tiempo de procesamiento por lote
|
|
- Tasa de categorización
|
|
|
|
Accede a Grafana: http://localhost:3001
|
|
|
|
### Logs
|
|
|
|
```bash
|
|
# Ver logs en tiempo real
|
|
docker compose logs -f llm-categorizer
|
|
|
|
# Buscar errores
|
|
docker compose logs llm-categorizer | grep ERROR
|
|
|
|
# Ver estadísticas de categorización
|
|
docker compose logs llm-categorizer | grep "Distribución"
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Error: "Out of memory"
|
|
|
|
**Causa**: El modelo es demasiado grande para tu GPU.
|
|
|
|
**Solución**:
|
|
1. Usa un modelo más pequeño (ej: EXL2 con menor bpw)
|
|
2. Reduce el batch size: `LLM_BATCH_SIZE: 5`
|
|
3. Usa cache Q4 en lugar de FP16: `LLM_CACHE_MODE: Q4`
|
|
|
|
```yaml
|
|
environment:
|
|
LLM_BATCH_SIZE: 5
|
|
LLM_CACHE_MODE: Q4
|
|
```
|
|
|
|
### Error: "Model not found"
|
|
|
|
**Causa**: El modelo no se descargó correctamente.
|
|
|
|
**Solución**:
|
|
```bash
|
|
# Verificar directorio
|
|
ls -la models/llm/
|
|
|
|
# Debería contener config.json y archivos .safetensors
|
|
# Si está vacío, ejecutar de nuevo:
|
|
./scripts/download_llm_model.sh
|
|
```
|
|
|
|
### El worker no procesa noticias
|
|
|
|
**Causa**: Posiblemente ya están todas procesadas.
|
|
|
|
**Solución**:
|
|
```bash
|
|
# Verificar cuántas noticias faltan
|
|
docker exec -it rss2_db psql -U rss -d rss -c \
|
|
"SELECT COUNT(*) FROM noticias WHERE llm_processed = FALSE;"
|
|
|
|
# Si es 0, resetear algunas para probar
|
|
docker exec -it rss2_db psql -U rss -d rss -c \
|
|
"UPDATE noticias SET llm_processed = FALSE WHERE id IN (SELECT id FROM noticias ORDER BY fecha DESC LIMIT 20);"
|
|
```
|
|
|
|
### Categorización incorrecta
|
|
|
|
**Causa**: El prompt puede necesitar ajustes o el modelo no es adecuado.
|
|
|
|
**Soluciones**:
|
|
1. Ajustar el prompt en `workers/llm_categorizer_worker.py` (método `_build_prompt`)
|
|
2. Probar un modelo diferente (ej: OpenHermes es mejor generalista)
|
|
3. Ajustar la temperatura (más baja = más determinista):
|
|
|
|
```python
|
|
self.settings.temperature = 0.05 # Muy determinista
|
|
```
|
|
|
|
---
|
|
|
|
## Rendimiento
|
|
|
|
### RTX 3060 12GB
|
|
|
|
- **Modelo recomendado**: Mistral-7B-Instruct GPTQ 4-bit
|
|
- **VRAM utilizada**: ~6-7 GB
|
|
- **Tiempo por noticia**: ~2-5 segundos
|
|
- **Throughput**: ~120-300 noticias/hora
|
|
|
|
### Optimizaciones
|
|
|
|
Para mejorar el rendimiento:
|
|
|
|
1. **Aumentar batch size** (si sobra VRAM):
|
|
```yaml
|
|
LLM_BATCH_SIZE: 20
|
|
```
|
|
|
|
2. **Cache Q4** (menos VRAM, ligeramente más lento):
|
|
```yaml
|
|
LLM_CACHE_MODE: Q4
|
|
```
|
|
|
|
3. **Modelo EXL2 optimizado**:
|
|
- Usar Mistral EXL2 4.0bpw
|
|
- Es más rápido que GPTQ en ExLlamaV2
|
|
|
|
---
|
|
|
|
## Integración con la Web
|
|
|
|
Para mostrar las categorías en la interfaz web, modifica `routers/search.py` o crea una nueva vista:
|
|
|
|
```python
|
|
# Ejemplo de endpoint para estadísticas
|
|
@app.route('/api/categories/stats')
|
|
def category_stats():
|
|
query = """
|
|
SELECT llm_categoria, COUNT(*) as total
|
|
FROM noticias
|
|
WHERE llm_processed = TRUE
|
|
GROUP BY llm_categoria
|
|
ORDER BY total DESC
|
|
"""
|
|
# ... ejecutar query y devolver JSON
|
|
```
|
|
|
|
---
|
|
|
|
## Roadmap
|
|
|
|
Posibles mejoras futuras:
|
|
|
|
- [ ] Subcategorías automáticas
|
|
- [ ] Detección de temas trending
|
|
- [ ] Resúmenes automáticos por categoría
|
|
- [ ] Alertas personalizadas por categoría
|
|
- [ ] API REST para categorización bajo demanda
|
|
- [ ] Fine-tuning del modelo con feedback de usuario
|
|
|
|
---
|
|
|
|
## Soporte
|
|
|
|
Para problemas o preguntas:
|
|
|
|
1. Revisar logs: `docker compose logs llm-categorizer`
|
|
2. Verificar GPU: `nvidia-smi`
|
|
3. Consultar documentación de ExLlamaV2: https://github.com/turboderp/exllamav2
|
|
|
|
---
|
|
|
|
## Licencia
|
|
|
|
Este componente se distribuye bajo la misma licencia que el proyecto principal RSS2.
|
|
|
|
Los modelos LLM tienen sus propias licencias (generalmente Apache 2.0 o MIT para los recomendados).
|