Preparar repositorio para despliegue: código fuente limpio
This commit is contained in:
parent
866f5c432d
commit
3eca832c1a
76 changed files with 5434 additions and 3496 deletions
370
docs/LLM_CATEGORIZER.md
Normal file
370
docs/LLM_CATEGORIZER.md
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
# 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).
|
||||
Loading…
Add table
Add a link
Reference in a new issue