Prečo vlastný chatbot, nie hotové riešenie
Hotové chatboty (Tidio, Intercom, Crisp) majú jednu zásadnú nevýhodu: nepoznajú váš konkrétny katalóg, ceny ani podmienky. Vyškolíte ich na firemné dokumenty, ale akonáhle zákazník položí otázku mimo trénovacej sady, dostane generickú odpoveď — alebo presmeruje na živého operátora, čo ste chceli práve eliminovať.
Priama integrácia cez OpenAI API (alebo Anthropic Claude API) vám dáva úplnú kontrolu:
- System prompt — presne definujete, čo chatbot vie a čo hovorí, keď nevie
- Náklady — platíte len za skutočne spracované tokeny, nie paušál
- Dáta — konverzácie zostávajú na vašom serveri, nie u tretej strany
- Rozšíriteľnosť — neskôr pridáte RAG (vyhľadávanie v databáze) alebo function calling
| Riešenie | Cena/mesiac | Prispôsobiteľnosť | Kontrola dát |
|---|---|---|---|
| Tidio AI | ~59 EUR | Obmedzená | ❌ U dodávateľa |
| Intercom Fin | od 89 EUR | Stredná | ❌ U dodávateľa |
| Vlastná integrácia (OpenAI) | 1–10 EUR* | ✅ Plná | ✅ Váš server |
* Pri bežnej záťaži e-shopu s GPT-4o-mini modelom (~500 konverzácií/mesiac).
Čo budete potrebovať
- Laravel 10 alebo novší (tutoriál funguje aj na Laravel 12)
- API kľúč od OpenAI alebo Anthropic
- PHP 8.2+ a Composer
- Asi 2 hodiny sústredenia
Inštalácia a konfigurácia
Existuje niekoľko PHP klientov pre OpenAI API. Najrozšírenejší je openai-php/client — má Laravel service provider a dá sa nakonfigurovať za minútu:
composer require openai-php/laravel
Publikujte konfiguračný súbor a doplňte kľúč do .env:
php artisan vendor:publish --provider="OpenAI\Laravel\ServiceProvider"
# .env
OPENAI_API_KEY=sk-proj-...
OPENAI_ORGANIZATION= # voliteľné
composer require anthropic-sdk-php/sdk. Celá logika zostane rovnaká — zmeníte len metódu volania a štruktúru správ. Claude 3.5 Haiku je cenovo porovnateľný s GPT-4o-mini.
ChatController — prvé volanie API
Vytvorte controller s jedným POST endpointom. Vstup je JSON so správou od používateľa, výstup je odpoveď modelu.
php artisan make:controller ChatController
// app/Http/Controllers/ChatController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use OpenAI\Laravel\Facades\OpenAI;
class ChatController extends Controller
{
private const SYSTEM_PROMPT = <<validate(['message' => 'required|string|max:500']);
$response = OpenAI::chat()->create([
'model' => 'gpt-4o-mini',
'messages' => [
['role' => 'system', 'content' => self::SYSTEM_PROMPT],
['role' => 'user', 'content' => $request->input('message')],
],
'max_tokens' => 300,
'temperature' => 0.4, // nižšia = konzistentnejšie odpovede
]);
return response()->json([
'reply' => $response->choices[0]->message->content,
]);
}
}
Route pridajte do routes/api.php:
Route::middleware('throttle:chat')->post('/chat', [ChatController::class, 'send']);
nikdy nevymýšľaj ceny) — LLM modely majú tendenciu „halucinovať" čísla, ak ich neobmedzíte.
SSE streaming — živé odpovede znak po znaku
Jednorázový request funguje, ale odpoveď príde naraz po 2–5 sekundách. Používatelia to vnímajú ako zaseknutie. Server-Sent Events (SSE) riešia problém elegantne — model „píše" a klient zobrazuje znaky postupne, presne ako ChatGPT.
Backend: StreamedResponse
// app/Http/Controllers/ChatController.php — pridajte metódu stream()
public function stream(Request $request): \Symfony\Component\HttpFoundation\StreamedResponse
{
$request->validate(['message' => 'required|string|max:500']);
$message = $request->input('message');
return response()->stream(function () use ($message) {
$stream = OpenAI::chat()->createStreamed([
'model' => 'gpt-4o-mini',
'messages' => [
['role' => 'system', 'content' => self::SYSTEM_PROMPT],
['role' => 'user', 'content' => $message],
],
'max_tokens' => 300,
]);
foreach ($stream as $response) {
$delta = $response->choices[0]->delta->content ?? '';
if ($delta !== '') {
echo 'data: ' . json_encode(['token' => $delta]) . "\n\n";
ob_flush();
flush();
}
}
echo "data: [DONE]\n\n";
ob_flush();
flush();
}, 200, [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'X-Accel-Buffering' => 'no', // vypnúť Nginx buffering
]);
}
Frontend: EventSource (vanilla JS)
// Žiadna knižnica — čistý Fetch + ReadableStream
async function sendMessage(message) {
const output = document.getElementById('chat-output');
const response = await fetch('/api/chat/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content,
},
body: JSON.stringify({ message }),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop(); // posledný nedokončený riadok
for (const line of lines) {
if (!line.startsWith('data: ')) continue;
const payload = line.slice(6);
if (payload === '[DONE]') return;
const { token } = JSON.parse(payload);
output.textContent += token; // alebo innerHTML += escapeHtml(token)
}
}
}
X-Accel-Buffering: no je kľúčová — bez nej Nginx akumuluje SSE správy a posiela ich naraz, čo streaming úplne zruší. Na Apache doplňte Header set X-Accel-Buffering "no" do .htaccess.
Kontextové okno a pamäť konverzácie
Aktuálny controller zabudne každú správu ihneď po odpovedi. Pre zmysluplný chatbot potrebujete uchovávať históriu konverzácie a posielať ju s každým requestom.
Jednoduchá historia cez session
public function send(Request $request): JsonResponse
{
$request->validate(['message' => 'required|string|max:500']);
$userMsg = $request->input('message');
// Načítanie histórie (max. posledných 10 výmen = 20 správ)
$history = session()->get('chat_history', []);
$messages = array_merge(
[['role' => 'system', 'content' => self::SYSTEM_PROMPT]],
array_slice($history, -20), // sliding window
[['role' => 'user', 'content' => $userMsg]]
);
$response = OpenAI::chat()->create([
'model' => 'gpt-4o-mini',
'messages' => $messages,
'max_tokens' => 300,
]);
$assistantMsg = $response->choices[0]->message->content;
// Uloženie do histórie
$history[] = ['role' => 'user', 'content' => $userMsg];
$history[] = ['role' => 'assistant', 'content' => $assistantMsg];
session()->put('chat_history', $history);
return response()->json(['reply' => $assistantMsg]);
}
Redis pre dlhodobejšiu pamäť
Session funguje dobre pre jednu reláciu. Pre chatbotov, kde chcete pamäť naprieč reláciami (napr. VIP zákazník sa prihlási a chatbot si pamätá jeho predchádzajúci dopyt), uložte históriu do Redisu kľúčovaného ID zákazníka. Viac o Redis v Laraveli nájdete v článku Redis a Laravel: cache, session a queues.
// Pre autentifikovaných používateľov — historia naprieč reláciami
$key = 'chat:' . auth()->id();
$history = Cache::get($key, []);
// ... po odpovedi ...
Cache::put($key, array_slice($history, -20), now()->addDays(7));
Rate limiting a náklady pod kontrolou
Každé volanie API niečo stojí. Bez rate limitingu vás môže jeden zákazník (alebo bot) pripojiť na desiatky dolárov za hodinu. Laravel má vstavaný rate limiter — stačí ho nakonfigurovať:
// app/Providers/RouteServiceProvider.php (alebo bootstrap/app.php v Laravel 11+)
RateLimiter::for('chat', function (Request $request) {
return [
// Max. 20 správ za minútu na IP
Limit::perMinute(20)->by($request->ip()),
// Max. 200 správ za deň na autentifikovaného používateľa
Limit::perDay(200)->by(optional($request->user())->id ?? $request->ip()),
];
});
Monitorovanie nákladov
OpenAI vracia v každej odpovedi počet spotrebovaných tokenov. Logujte ich do databázy — budete vedieť, ktoré konverzácie sú najdrahšie a kde optimalizovať system prompt:
// Po každom volaní
$usage = $response->usage;
Log::info('OpenAI usage', [
'user_id' => auth()->id(),
'prompt_tokens' => $usage->promptTokens,
'completion_tokens'=> $usage->completionTokens,
'total_tokens' => $usage->totalTokens,
// GPT-4o-mini: input $0.15/1M, output $0.60/1M tokenov
'cost_usd' => round(
($usage->promptTokens * 0.00000015) +
($usage->completionTokens * 0.0000006),
6
),
]);
Záver: checklist pred spustením
- System prompt otestovaný na 20+ rôznych otázkach vrátane "edge cases"
- Rate limiting nastavený (
throttle:chatmiddleware) - Max tokeny obmedzené na rozumnú hodnotu (300–500 pre chatbot)
- História konverzácie obmedzená (sliding window, nie nekonečný rast)
- SSE: hlavička
X-Accel-Buffering: nona serveri - Logging spotreby tokenov a nákladov
- CSRF ochrana na POST endpointoch
- API kľúč nikdy v repozitári (len v
.env)
Čo ďalej: RAG a function calling
Tento tutoriál pokrýva základ. V ďalšom kroku môžete pridať:
- RAG (Retrieval Augmented Generation) — chatbot hľadá odpovede vo vašej databáze produktov cez vektory (pgvector, Pinecone)
- Function calling — model zavolá váš PHP endpoint na overenie dostupnosti produktu v reálnom čase
- Queue spracovanie — dlhé konverzácie cez Laravel Jobs a Queues asynchrónne
Integrácia AI do e-shopu nie je veda — je to 150 riadkov Laravelu a jedno popoludnie. Väčšina nákladov ide na premýšľanie nad správnym system promptom, nie na kód.