8.3 KiB
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.mdcomo 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 (2–5s), 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
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:
- Crear
app/AI/Tools/NuevaTool.php - 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:
GeniusAgentServicevalidasession('admin_logged_in')antes de incluir tools. - Rate limiting:
throttle:20,1en la ruta/agent/chat. - Validación de input:
messagerequerido, 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)
# 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
# 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)
# 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
GEMINI_API_KEY=tu_api_key_de_google_ai_studio
# Opcional: ajustar timeout HTTP de Prism
PRISM_HTTP_TIMEOUT=30