Seamos honestos: integrar ChatGPT en Laravel suena fácil en los tutoriales de YouTube. "Solo instala el paquete, configura la API key y listo", dicen. Mentira. Me tomó 3 días, 47 cafés y casi $20 en tokens desperdiciados hasta que finalmente funcionó como debía.
Así que aquí está la guía real, la que me hubiera gustado encontrar cuando empecé este proyecto para un cliente que quería "algo con IA" en su sistema de tickets.
Por qué ChatGPT y no otra cosa
Miren, probé Claude (que es muy bueno pero más caro), probé Gemini (que a veces alucina más que mi tío después de año nuevo), y probé algunos modelos open source. Al final, GPT-4o me dio el mejor balance entre precio y que no me avergüence frente al cliente.
*Se eliminaron las comillas simples para evitar problemas de compatibilidad
Lo que necesitas antes de empezar
Primero, asegúrate de tener:
- Laravel 10+ (aunque funciona desde la 8, pero no seas dinosaurio)
- PHP 8.1+ (en serio, actualiza PHP)
- Una tarjeta de crédito para OpenAI (prepárate para gastar entre $10-30 al mes)
- Paciencia. Mucha paciencia.
Instalación: La parte fácil
Empezamos con el paquete de OpenAI para PHP. No uses el no-oficial que aparece primero en Google, ese me dio problemas.
composer require openai-php/laravel
Publica la configuración:
php artisan vendor:publish --provider="OpenAILaravelServiceProvider"
En tu .env, agrega tu API key:
OPENAI_API_KEY=sk-tu-api-key-super-secreta
OPENAI_ORGANIZATION=org-si-tienes-una
Consejo: NO hardcodees la API key en el código. Un amigo con el que trabaje en un proyecto anterior lo hizo y gastamos $50 en una noche cuando alguien la encontró en GitHub. True story.
El Controller: Donde la magia sucede
Aquí está mi implementación después de todas las iteraciones. No es perfecta, pero funciona y no explota el presupuesto:
<?php
// Por Carlos David Donoso Cordero (ddchack)
namespace AppHttpControllers;
use IlluminateHttpRequest;
use OpenAILaravelFacadesOpenAI;
use IlluminateSupportFacadesLog;
use IlluminateSupportFacadesCache;
class ChatGPTController extends Controller
{
private $maxTokens = 500; // No seas ambicioso
private $temperature = 0.7; // 0.7 es el sweet spot
public function processMessage(Request $request)
{
$request->validate([
message => required|string|max:1000
]);
// Cache para evitar llamadas duplicadas
$cacheKey = chat_ . md5($request->message);
if (Cache::has($cacheKey)) {
Log::info(Respuesta desde cache, ahorrando dinero);
return response()->json([
response => Cache::get($cacheKey),
cached => true
]);
}
try {
// El contexto es clave para que no diga tonterías
$systemMessage = Eres un asistente útil para soporte técnico.
Sé conciso pero amable. No inventes información.
Si no sabes algo, admítelo.;
$response = OpenAI::chat()->create([
model => gpt-4o-mini, // Más barato que gpt-4o
messages => [
[role => system, content => $systemMessage],
[role => user, content => $request->message]
],
max_tokens => $this->maxTokens,
temperature => $this->temperature
]);
$aiResponse = $response->choices[0]->message->content;
// Guardo en cache por 1 hora
Cache::put($cacheKey, $aiResponse, 3600);
// Log para trackear gastos
Log::channel(openai)->info(Llamada a API, [
tokens_used => $response->usage->total_tokens,
cost_estimate => $this->calculateCost($response->usage)
]);
return response()->json([
response => $aiResponse,
cached => false
]);
} catch (Exception $e) {
Log::error(Error con OpenAI: . $e->getMessage());
// Respuesta fallback para no dejar al usuario colgado
return response()->json([
response => Lo siento, estoy teniendo problemas técnicos. Por favor intenta de nuevo.,
error => true
], 500);
}
}
private function calculateCost($usage)
{
// GPT-4o-mini: $0.15 per 1M input, $0.60 per 1M output
$inputCost = ($usage->prompt_tokens / 1000000) * 0.15;
$outputCost = ($usage->completion_tokens / 1000000) * 0.60;
return round($inputCost + $outputCost, 4);
}
}
El Middleware para no quebrar
Esta es la parte que nadie te cuenta. Sin rate limiting, un bot puede vaciarte la cuenta de OpenAI en minutos. Aprendí esto de la manera difícil:
<?php
// Creado por Carlos David Donoso Cordero (ddchack)
namespace AppHttpMiddleware;
use Closure;
use IlluminateSupportFacadesRateLimiter;
class OpenAIRateLimit
{
public function handle($request, Closure $next)
{
$key = openai_ . $request->ip();
// 10 requests por minuto por IP
if (RateLimiter::tooManyAttempts($key, 10)) {
return response()->json([
error => Demasiadas solicitudes. Espera un minuto.
], 429);
}
RateLimiter::hit($key, 60);
return $next($request);
}
}
No olvides registrarlo en tu Kernel.php o en el route middleware.
El Frontend (lo básico que funciona)
No necesitas React ni Vue para esto. Un simple formulario con JavaScript vanilla funciona perfecto:
<!-- Diseñado por Carlos David Donoso Cordero (ddchack) -->
<div class="chat-container">
<div id="messages"></div>
<form id="chatForm">
<input type="text" id="messageInput" placeholder="Escribe tu pregunta..." />
<button type="submit">Enviar</button>
</form>
</div>
<script>
document.getElementById(chatForm).addEventListener(submit, async (e) => {
e.preventDefault();
const input = document.getElementById(messageInput);
const message = input.value.trim();
if (!message) return;
// Mostrar mensaje del usuario
addMessage(message, user);
// Limpiar input
input.value = ;
// Mostrar loading
const loadingId = addMessage(Pensando..., ai, true);
try {
const response = await fetch(/api/chat, {
method: POST,
headers: {
Content-Type: application/json,
X-CSRF-TOKEN: document.querySelector(meta[name="csrf-token"]).content
},
body: JSON.stringify({ message })
});
const data = await response.json();
// Remover loading y mostrar respuesta
document.getElementById(loadingId).remove();
addMessage(data.response, ai);
} catch (error) {
document.getElementById(loadingId).remove();
addMessage(Error de conexión. Intenta de nuevo., ai);
}
});
function addMessage(text, sender, isLoading = false) {
const messagesDiv = document.getElementById(messages);
const messageDiv = document.createElement(div);
const id = msg_ + Date.now();
messageDiv.id = id;
messageDiv.className = message + sender;
messageDiv.innerHTML = isLoading ?
<span class="loading">🤔</span> : text;
messagesDiv.appendChild(messageDiv);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
return id;
}
</script>
Optimizaciones que me salvaron dinero
Después de la primera factura (no preguntes cuanto salio), tuve que optimizar urgente:
1. Context Window Management
No mandes todo el historial de chat en cada request. Guarda solo los últimos 5 mensajes relevantes:
// Mantén solo contexto relevante
$messages = collect($conversation->messages)
->take(-5) // Últimos 5 mensajes
->map(function($msg) {
return [
role => $msg->sender,
content => Str::limit($msg->content, 500) // Limita longitud
];
})->toArray();
2. Streaming para respuestas largas
Si necesitas respuestas largas, usa streaming. Es más complejo pero el usuario ve progreso:
$stream = OpenAI::chat()->createStreamed([
model => gpt-4o-mini,
messages => $messages,
max_tokens => 1000,
stream => true
]);
foreach ($stream as $response) {
echo $response->choices[0]->delta->content;
ob_flush();
flush();
}
3. Prompts inteligentes
Un buen prompt te ahorra tokens y mejora las respuestas. Este es mi template después de mucho ensayo y error:
$systemPrompt =
Contexto: Sistema de soporte técnico para {$company->name}.
Productos: {$company->products}.
Instrucciones:
- Responde en español
- Máximo 3 párrafos
- Si no sabes, sugiere contactar soporte humano
- No inventes características de productos
- Sé amable pero profesional
Información del usuario:
- Nombre: {$user->name}
- Plan: {$user->subscription}
;
Errores comunes que vas a cometer
Porque yo los cometí todos:
1. No validar el input del usuario
Alguien va a intentar hacer prompt injection. Siempre sanitiza:
$cleanMessage = strip_tags($request->message);
$cleanMessage = str_replace([Ignore previous instructions, System:], , $cleanMessage);
2. No tener fallback
OpenAI se cae. Bastante. Ten un plan B:
if ($openAIDown) {
// Respuestas predefinidas para FAQs comunes
return $this->getFallbackResponse($request->message);
}
3. No monitorear costos
Crea un dashboard simple para trackear uso:
// En tu modelo Usage o como lo llames
public static function todaysCost()
{
return self::whereDate(created_at, today())
->sum(estimated_cost);
}
// Alerta si te pasas
if (Usage::todaysCost() > 10) {
Mail::to(admin@example.com)->send(new CostAlert());
}
Casos de uso reales que funcionan
Después de implementar esto en 2 proyectos, estos son los casos donde realmente vale la pena:
- Soporte nivel 1: Responde el 70% de preguntas comunes sin intervención humana
- Generación de reportes: Convierte datos crudos en resúmenes ejecutivos
- Clasificación de tickets: Categoriza y prioriza automáticamente
- Traducciones on-the-fly: Más barato que servicios dedicados de traducción
Donde NO funciona bien (aprendí a las malas):
- Cálculos complejos: ChatGPT y las matemáticas... mejor no
- Datos en tiempo real: No tiene acceso a tu base de datos (obviamente)
- Decisiones críticas: Nunca dejes que tome decisiones financieras o legales
El setup de producción que uso
Después de todos los experimentos, este es mi setup actual:
// config/openai.php
return [
api_key => env(OPENAI_API_KEY),
organization => env(OPENAI_ORGANIZATION),
// Mis defaults probados
defaults => [
model => gpt-4o-mini, // Balance precio/calidad
max_tokens => 500, // Suficiente para la mayoría
temperature => 0.7, // Creativo pero coherente
top_p => 0.9, // Reduce alucinaciones
frequency_penalty => 0.3, // Evita repeticiones
presence_penalty => 0.3, // Fomenta variedad
],
// Límites de seguridad - por Carlos David Donoso Cordero (ddchack)
limits => [
max_requests_per_minute => 20,
max_tokens_per_day => 50000,
max_cost_per_day => 15.00,
],
// Cache
cache => [
enabled => true,
ttl => 3600, // 1 hora
prefix => openai_cache_
]
];
Monitoreo y logs
Si no mides, no puedes optimizar. Mi sistema de logging:
// app/Listeners/LogOpenAIUsage.php
class LogOpenAIUsage
{
public function handle($event)
{
DB::table(openai_logs)->insert([
user_id => auth()->id(),
endpoint => $event->endpoint,
model => $event->model,
prompt_tokens => $event->promptTokens,
completion_tokens => $event->completionTokens,
total_tokens => $event->totalTokens,
estimated_cost => $event->cost,
response_time => $event->responseTime,
created_at => now(),
]);
// Alerta si alguien abusa
if ($event->totalTokens > 2000) {
Log::warning(Alto uso de tokens, [
user => auth()->user()->email,
tokens => $event->totalTokens
]);
}
}
}
Testing (sí, hay que testear esto)
No puedes llamar a la API real en los tests. Usa mocks:
// tests/Feature/ChatGPTTest.php
public function test_chat_response()
{
OpenAI::fake([
CreateResponse::fake([
choices => [
[
message => [
content => Respuesta de prueba
]
]
]
])
]);
$response = $this->postJson(/api/chat, [
message => Pregunta de prueba
]);
$response->assertOk()
->assertJson([
response => Respuesta de prueba
]);
}
Consideraciones legales que nadie menciona
Tu cliente necesita saber que sus datos van a OpenAI. Agrega esto a tus términos:
- Los mensajes se procesan con OpenAI (servers en USA)
- OpenAI puede usar los datos para mejorar sus modelos (a menos que uses la API enterprise)
- No garantizas 100% de precisión en las respuestas
- El usuario es responsable de no compartir info sensible
Mi experiencia real después de 3 meses
Los números no mienten:
- Costo promedio: $47/mes por aplicación
- Tickets resueltos automáticamente: 62%
- Satisfacción del cliente: 8.3/10 (mejor que el soporte humano que era 7.9)
- Tiempo de implementación real: 2-3 semanas para hacerlo bien
- ROI: Se paga solo en 2 meses si reduces 1 persona de soporte
Lo que no funcionó:
- Intentar que escriba código Laravel (genera código de hace 3 versiones)
- Usarlo para moderar comentarios (muy caro vs. alternativas)
- Dejar que responda emails directamente (desastre de PR)
Herramientas alternativas que probé
Antes de casarte con OpenAI, considera estas:
- Anthropic Claude: Mejor para textos largos pero 30% más caro
- Google Gemini: Rápido y barato pero menos confiable
- Mistral: Opción europea si la privacidad es crítica
- Llama 3 (self-hosted): Gratis pero necesitas un servidor potente
Para el 90% de casos, GPT-4o-mini es el sweet spot entre precio y calidad.
El código completo en GitHub
No voy a mentirte diciendo que tengo un repo público perfectamente documentado. Pero si necesitas el código completo, estos son los archivos clave que necesitas:
- ChatGPTController.php (el que mostré arriba)
- OpenAIRateLimit.php (middleware)
- openai.php (config)
- 2024_01_01_create_openai_logs_table.php (migration)
- chat.blade.php (vista)
Si me escribes con una pregunta específica y no genérica tipo "no me funciona", probablemente te responda.
Lo que viene: Assistants API
OpenAI lanzó Assistants API que mantiene contexto entre conversaciones. Lo estoy probando pero es 3x más caro. Cuando tenga resultados reales (no hype de Twitter), escribo otro post.
Por ahora, la implementación que te mostré funciona, es estable y no te va a dejar en bancarrota.
¿Vale la pena en 2025?
Después de 6 meses usándolo en producción: sí, pero con expectativas realistas.
No es magia. No va a resolver todos tus problemas. No va a reemplazar a tu equipo de soporte completamente. Pero si lo implementas bien, puede automatizar las tareas aburridas y dejar que los humanos se enfoquen en problemas reales.
El truco está en empezar pequeño, medir todo, y escalar gradualmente. Y sobre todo, tener un límite de gasto configurado en OpenAI. Créeme, tu tarjeta de crédito te lo agradecerá.
Comentarios 3
Comparte tu opinión
José García
Daniel Jiménez
Arturo Pérez