Files
OnAPB-Carrere_Demartin/docs/superpowers/specs/2026-04-09-genius-agent-design.md
Laucha1312 90c5f85512 2
2026-06-04 15:15:23 -03:00

245 lines
8.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# OnAPB Genius Agent — Spec de Diseño
**Fecha:** 2026-04-09
**Rama:** `feature/genius-agent`
**Stack:** Laravel 12, Prism PHP (prism-php/prism), Google Gemini 1.5 Flash, MySQL, Alpine.js
---
## 1. Objetivo
Implementar un agente de IA conversacional ("OnAPB Genius") integrado en onapb.com con dos modos de operación según el rol del usuario:
- **Público** (no logueado, aficionado, jugador): asistente de navegación y consultas usando el `MANUAL_USUARIO.md` como contexto RAG simplificado. Sin tools. Sin persistencia entre sesiones.
- **Admin** (SuperAdmin rol=1 / GeneralAdmin rol=2): automatización de tareas mediante function calling (Tools). Historial persistente en MySQL, auto-purgado a los 30 días.
---
## 2. Decisiones de Arquitectura
| Decisión | Elección | Razón |
|---|---|---|
| SDK de AI | `prism-php/prism` | Soporte probado de Gemini 1.5 Flash + tools. Más estable que `laravel/ai` (nuevo). |
| Modelo | `gemini-1.5-flash` | Velocidad (25s), costo bajo, function calling. |
| Streaming | No — JSON completo | Hosting compartido Hostinger con límites PHP desconocidos. Evita problemas de output buffer. |
| Memoria admin | MySQL (`agent_threads`) | Sin Redis. JSON column para mensajes. Purge automático 30 días. |
| Memoria público | Session PHP | Stateless entre sesiones. Simple, sin overhead de BD. |
| Tools | Solo si admin_logged_in | Validado en `GeniusAgentService`, no en Gemini. |
---
## 3. Estructura de Archivos
```
app/
├── AI/
│ ├── Tools/
│ │ ├── CrearPartidoTool.php
│ │ ├── CargarPuntajeTool.php
│ │ ├── RedactarNoticiaTool.php
│ │ ├── ListarEquiposTool.php
│ │ └── ListarEventosTool.php
│ └── Prompts/
│ ├── SystemPromptAdmin.php
│ └── SystemPromptPublic.php
├── Services/
│ └── GeniusAgentService.php
├── Http/Controllers/
│ └── GeniusAgentController.php
├── Models/
│ └── AgentThread.php
└── Console/Commands/
└── PurgeAgentThreads.php
database/migrations/
└── xxxx_create_agent_threads_table.php
resources/views/components/
└── genius-chat.blade.php
routes/web.php
└── POST /agent/chat (throttle: 20/min por IP)
```
---
## 4. Schema de Base de Datos
```sql
CREATE TABLE agent_threads (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
thread_id VARCHAR(36) NOT NULL UNIQUE, -- UUID generado en frontend
admin_id INT NOT NULL, -- session('admin_id')
messages JSON NOT NULL, -- array [{role, content}]
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL -- created_at + 30 días
);
```
---
## 5. Tools Disponibles (Admin)
Cada tool implementa la interfaz Prism `Tool`. El agent loop permite que Gemini llame la misma tool N veces para operaciones batch (ej. cargar puntajes de todos los partidos de una jornada).
| Tool | Parámetros | Acción en BD |
|---|---|---|
| `CrearPartidoTool` | `id_equipo_local`, `id_equipo_visitante`, `fecha_evento`, `hora_inicio`, `hora_fin`, `sede`, `id_torneo` | `Evento::create(...)` |
| `CargarPuntajeTool` | `id_evento`, `marcador_local`, `marcador_visitante` | `Evento::find()->update(...)` |
| `RedactarNoticiaTool` | `titulo`, `contenido`, `id_torneo?`, `categoria?` | `Noticia::create(...)` |
| `ListarEquiposTool` | `id_torneo?`, `id_club?`, `grupo?` | `Equipo::query()` con join a pivot `torneo_equipo` filtrando por `grupo` (solo lectura) |
| `ListarEventosTool` | `fecha_desde?`, `fecha_hasta?`, `id_torneo?` | `Evento::query()->get()` (solo lectura) |
**Nota sobre `grupo`:** No es un modelo separado. Es una columna en la tabla pivot `torneo_equipo` (relación `Torneo::equipos()->withPivot('grupo')`). `ListarEquiposTool` filtra con `wherePivot('grupo', $grupo)`.
**Agregar una nueva tool en el futuro:**
1. Crear `app/AI/Tools/NuevaTool.php`
2. Registrarla en `GeniusAgentService::getAdminTools()`
---
## 6. Flujo de Datos
### Usuario público
```
POST /agent/chat { message }
→ session()->get('agent_messages', [])
→ GeniusAgentService::chatPublic(message, history)
→ System prompt: navegación + MANUAL_USUARIO.md completo como contexto (el archivo es pequeño; si crece >50KB usar solo las primeras 200 líneas)
→ Prism → Gemini (sin tools)
→ Respuesta de texto
→ session()->put('agent_messages', [...])
→ return JSON { reply }
```
### Admin
```
POST /agent/chat { message, thread_id? }
→ AgentThread::findOrCreate(thread_id, admin_id)
→ GeniusAgentService::chatAdmin(message, thread)
→ System prompt: automatización de tareas OnAPB
→ Prism → Gemini con tools
→ [si Gemini llama tool] → Tool::handle() → resultado
→ [Gemini puede llamar N tools] → agent loop
→ Respuesta final de texto
→ thread->appendMessages([...])
→ thread->save()
→ return JSON { reply, thread_id }
```
---
## 7. Seguridad
- **Tools bloqueadas a no-admins:** `GeniusAgentService` valida `session('admin_logged_in')` antes de incluir tools.
- **Rate limiting:** `throttle:20,1` en la ruta `/agent/chat`.
- **Validación de input:** `message` requerido, string, máx. 1000 caracteres.
- **Aislamiento de threads:** cada thread valida `admin_id = session('admin_id')`. No hay acceso cruzado entre admins.
- **API key:** solo en `.env` (`GEMINI_API_KEY`). Nunca en código ni en BD.
---
## 8. Manejo de Errores
| Caso | Comportamiento |
|---|---|
| Gemini timeout / 5xx | `catch RequestException` → JSON `{ error: "El agente no responde, reintentá en un momento." }` |
| Tool falla (ej. evento no existe) | Tool retorna `{ error: "..." }` → Gemini lo incorpora en su respuesta |
| Thread expirado / no encontrado | Se crea un nuevo thread automáticamente |
| API key inválida / quota excedida | Log en Laravel + respuesta genérica (sin exponer detalles de la API) |
| Timeout de Hostinger | `set_time_limit(120)` al inicio del controller action |
---
## 9. Purge de Threads
Comando `php artisan agent:purge-threads` que elimina `agent_threads` donde `expires_at < NOW()`.
**Schedule:** Se registra en `routes/console.php` para ejecutarse diariamente. En Hostinger sin queue worker, se puede configurar como cron en hPanel:
```
# Reemplazar /home/TU_USUARIO/public_html con la ruta real de tu cuenta Hostinger
0 3 * * * cd /home/TU_USUARIO/public_html && php artisan agent:purge-threads >> /dev/null 2>&1
```
---
## 10. Frontend — Chat Bubble
Componente Blade `genius-chat.blade.php` incluido al final de `resources/views/layouts/app.blade.php`:
- Botón flotante (bottom-right) con ícono de chat
- Panel slide-up con historial de mensajes de la sesión actual
- Alpine.js para estado local (open/closed, messages, loading spinner)
- Tailwind para estilos (sin dependencias adicionales)
- Rol-aware: el frontend no diferencia, la diferencia la hace el backend
---
## 11. Comandos de Deploy
### Instalación inicial (local y producción)
```bash
# 1. Instalar Prism PHP
composer require prism-php/prism
# 2. Publicar config de Prism (opcional)
php artisan vendor:publish --provider="EchoLabs\Prism\PrismServiceProvider"
# 3. Agregar en .env
GEMINI_API_KEY=tu_api_key_aqui
# 4. Ejecutar migration de agent_threads
php artisan migrate
# 5. Registrar el comando de purge (se hace automáticamente con el código)
# Verificar que está en routes/console.php
# 6. Limpiar cachés después del deploy
php artisan config:clear
php artisan cache:clear
php artisan view:clear
php artisan route:clear
# 7. Re-cachear para producción
php artisan config:cache
php artisan route:cache
php artisan view:cache
```
### Comandos de mantenimiento
```bash
# Purge manual de threads expirados
php artisan agent:purge-threads
# Ver logs del agente
php artisan pail --filter="GeniusAgent"
# Limpiar todos los threads (emergencia)
php artisan tinker --execute="App\Models\AgentThread::truncate();"
```
### Deploy en Hostinger (via SSH o File Manager)
```bash
# Subir archivos nuevos y correr:
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
```
---
## 12. Configuración `.env` requerida
```env
GEMINI_API_KEY=tu_api_key_de_google_ai_studio
# Opcional: ajustar timeout HTTP de Prism
PRISM_HTTP_TIMEOUT=30
```