Agrego archivos iniciales
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
# 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 (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
|
||||
|
||||
```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
|
||||
```
|
||||
Reference in New Issue
Block a user