SEJATIDIMEDIA Logo
Tersedia Sesi Konsultasi Gratis
SejatiDimedia Logo

Membangun Chatbot Sederhana dengan Laravel + LLM API

Artikel ini mengembangkan service AI dasar dari Part 2 menjadi chatbot yang bisa mengingat riwayat percakapan. Dibahas kenapa API LLM itu stateless, cara update service class agar menerima seluruh riwayat pesan, membuat chat controller dan view Blade sederhana dengan penyimpanan di session, sampai catatan praktis soal batasan session dan cara menangani batas token saat percakapan makin panjang.

Timur Dian Radha Sejati

Timur Dian Radha Sejati

Founder SejatiDimedia•15 September 2026•5 menit baca
Silabus Seri Rekayasa

Laravel & AI Integration

Menghubungkan Laravel dengan model AI sekarang makin gampang, tapi bikin fiturnya stabil dan hemat resource itu cerita lain. Seri ini fokus ngebahas arsitektur praktis integrasi AI di Laravel: mulai dari manajemen queue untuk task berat, implementasi RAG sederhana, parsing structured output, sampai tips ngamanin API cost biar nggak boncos

Lihat Silabus Lengkap
Membangun Chatbot Sederhana dengan Laravel + LLM API

Di artikel sebelumnya, kita sudah bikin koneksi dasar antara Laravel dan API AI lewat service class. Itu fondasi yang bagus, tapi chatbot beneran butuh satu hal lagi: ingatan. Tanpa itu, setiap pesan yang kamu kirim dianggap sebagai percakapan baru, dan AI-nya nggak tahu apa yang kalian bahas semenit lalu.

Di artikel ini, kita akan bikin chatbot sederhana yang:

  • Ingat pesan-pesan sebelumnya dalam percakapan
  • Mengirim seluruh riwayat percakapan ke AI di setiap request
  • Punya UI dasar yang bisa langsung dipakai chat di browser
  • Menyimpan riwayat percakapan di session (sederhana, belum perlu database)

Kenapa Riwayat Percakapan Itu Penting

API LLM itu stateless. API-nya nggak ingat apa-apa dari request sebelumnya. Kalau kamu cuma kirim pesan terbaru, AI-nya akan jawab tanpa konteks apa pun dari sebelumnya.

Supaya kerasa seperti percakapan beneran, kamu perlu kirim seluruh riwayat pesan setiap kali, contohnya seperti ini:

config.jsonjson
1
[
2
{"role": "user", "content": "Nama saya Budi."},
3
{"role": "assistant", "content": "Senang berkenalan, Budi!"},
4
{"role": "user", "content": "Siapa nama saya?"}
5
]
5 baris•167 chars
UTF-8•Spaces: 4

AI-nya cuma bisa jawab "Budi" di pertanyaan terakhir itu karena kita ikutkan juga pesan-pesan sebelumnya.

Langkah 1: Update Service Class

Mari update

PLAINTEXT Snippetplaintext
1
AiService.php
1 baris•13 chars
UTF-8•Spaces: 4
dari artikel sebelumnya supaya bisa menerima seluruh riwayat percakapan, bukan cuma satu prompt saja:

AiService.phpphp
1
<?php
2
3
namespace App\Services;
4
5
use Illuminate\Support\Facades\Http;
6
use Illuminate\Support\Facades\Log;
7
8
class AiService
9
{
10
public function chat(array $messages): ?string
11
{
12
$response = Http::withToken(config('services.openai.key'))
13
->timeout(30)
14
->post('https://api.openai.com/v1/chat/completions', [
15
'model' => config('services.openai.model'),
16
'messages' => $messages,
17
]);
18
19
if ($response->failed()) {
20
Log::error('AI API request failed', [
21
'status' => $response->status(),
22
'body' => $response->body(),
23
]);
24
25
return null;
26
}
27
28
return $response->json('choices.0.message.content');
29
}
30
}
30 baris•759 chars
UTF-8•Spaces: 4

Perubahannya sederhana: dari

PLAINTEXT Snippetplaintext
1
ask(string $prompt)
1 baris•19 chars
UTF-8•Spaces: 4
, sekarang jadi
PLAINTEXT Snippetplaintext
1
chat(array $messages)
1 baris•21 chars
UTF-8•Spaces: 4
yang menerima seluruh riwayat.

Langkah 2: Buat Chat Controller

Buat file

PLAINTEXT Snippetplaintext
1
app/Http/Controllers/ChatController.php
1 baris•39 chars
UTF-8•Spaces: 4
:

ChatController.phpphp
1
<?php
2
3
namespace App\Http\Controllers;
4
5
use App\Services\AiService;
6
use Illuminate\Http\Request;
7
8
class ChatController extends Controller
9
{
10
public function index()
11
{
12
$history = session('chat_history', []);
13
14
return view('chat', ['history' => $history]);
15
}
16
17
public function send(Request $request, AiService $ai)
18
{
19
$request->validate([
20
'message' => 'required|string|max:1000',
21
]);
22
23
$history = session('chat_history', []);
24
25
// Tambahkan pesan baru dari user ke riwayat
26
$history[] = ['role' => 'user', 'content' => $request->message];
27
28
// Kirim seluruh riwayat ke AI
29
$reply = $ai->chat($history);
30
31
if ($reply === null) {
32
$reply = 'Maaf, ada yang salah. Silakan coba lagi.';
33
}
34
35
// Tambahkan juga balasan dari AI ke riwayat
36
$history[] = ['role' => 'assistant', 'content' => $reply];
37
38
session(['chat_history' => $history]);
39
40
return redirect()->route('chat.index');
41
}
42
43
public function reset()
44
{
45
session()->forget('chat_history');
46
47
return redirect()->route('chat.index');
48
}
49
}
49 baris•1161 chars
UTF-8•Spaces: 4

Beberapa catatan:

  • Kita validasi panjang pesan. Jangan biarkan user kirim teks tanpa batas, karena itu berpengaruh ke biaya per token.
  • Seluruh riwayat, termasuk pesan baru dari user, dikirim ke AI supaya ada konteksnya.
  • Kalau pemanggilan API-nya gagal, kita tetap kasih respons yang wajar, bukan halaman error yang berantakan.

Langkah 3: Tambahkan Route

Di

PLAINTEXT Snippetplaintext
1
routes/web.php
1 baris•14 chars
UTF-8•Spaces: 4
:

script.phpphp
1
use App\Http\Controllers\ChatController;
2
 
3
Route::get('/chat', [ChatController::class, 'index'])->name('chat.index');
4
Route::post('/chat', [ChatController::class, 'send'])->name('chat.send');
5
Route::post('/chat/reset', [ChatController::class, 'reset'])->name('chat.reset');
5 baris•272 chars
UTF-8•Spaces: 4

Langkah 4: Buat View Sederhana

Buat file

PLAINTEXT Snippetplaintext
1
resources/views/chat.blade.php
1 baris•30 chars
UTF-8•Spaces: 4
:

BLADE Snippetblade
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<title>Chatbot Sederhana</title>
5
</head>
6
<body style="font-family: sans-serif; max-width: 600px; margin: 40px auto;">
7
 
8
<h2>Chatbot Sederhana</h2>
9
 
10
<div style="border: 1px solid #ddd; padding: 16px; height: 400px; overflow-y: auto; margin-bottom: 16px;">
11
@foreach ($history as $message)
12
<p>
13
<strong>{{ $message['role'] === 'user' ? 'Kamu' : 'AI' }}:</strong>
14
{{ $message['content'] }}
15
</p>
16
@endforeach
17
</div>
18
 
19
<form method="POST" action="{{ route('chat.send') }}">
20
@csrf
21
<input type="text" name="message" placeholder="Ketik pesan..." style="width: 80%; padding: 8px;" required>
22
<button type="submit" style="padding: 8px 16px;">Kirim</button>
23
</form>
24
 
25
<form method="POST" action="{{ route('chat.reset') }}" style="margin-top: 8px;">
26
@csrf
27
<button type="submit">Reset Percakapan</button>
28
</form>
29
 
30
</body>
31
</html>
31 baris•983 chars
UTF-8•Spaces: 4

Selesai. Sekarang kamu punya chatbot yang jalan dan punya ingatan, semuanya berjalan di atas session storage.

Catatan Soal Pakai Session Storage

Menyimpan riwayat chat di session cukup oke untuk demo atau tool internal kecil, tapi ada beberapa batasan yang perlu diingat:

  • Data session akan hilang kalau session-nya expired atau user hapus cookie.
  • Kurang cocok kalau kamu butuh riwayat chat yang bertahan lintas perangkat atau perlu direview nanti (misalnya untuk tiket support).
  • Untuk produk beneran, sebaiknya riwayat percakapan disimpan di database, terkait dengan user ID tertentu.

Kita sengaja bikin sederhana dulu di sini. Riwayat chat berbasis database bisa jadi topik bahasan lanjutan setelah kamu sudah nyaman dengan dasar-dasarnya.

Jangan Lupa: Batasan Token

Setiap model AI punya batas panjang context maksimum. Kalau riwayat percakapan kamu makin panjang, cepat atau lambat kamu akan kena batas itu dan dapat error. Dua cara umum untuk menanganinya:

  1. Potong pesan lama. Simpan cuma N pesan terakhir sebelum dikirim ke API.
  2. Ringkas pesan lama. Sesekali minta AI meringkas percakapan sebelumnya jadi versi lebih pendek, lalu pakai ringkasan itu menggantikan riwayat lengkap.

Kita belum implementasikan ini di artikel supaya bahasannya tetap fokus, tapi ini hal yang perlu direncanakan begitu chatbot-mu menangani percakapan yang lebih panjang.

Selanjutnya

Sekarang kita sudah punya chatbot yang jalan, tapi masih terikat ke satu provider AI saja. Di artikel selanjutnya, kita akan bahas Prism PHP, package yang memungkinkan kamu gonta-ganti antara OpenAI, Claude, dan provider lain tanpa harus menulis ulang service class setiap kali.


Ini adalah Part 3 dari series "Laravel + AI Integration".

Topik Terkait:LaravelArchitecture
Timur Dian Radha Sejati

Timur Dian Radha Sejati

Penulis & Lead Engineer

Software engineer dan konsultan sistem di SejatiDimedia. Berfokus pada perancangan arsitektur berkinerja tinggi, refactoring backend skala enterprise, hingga pengembangan aplikasi mobile, web modern dan integrasi AI.

Punya Masalah Arsitektur atau Ingin Membangun Sistem yang Benar?

Kami siap membantu mengaudit kode, me-refactor arsitektur yang lemot, atau membangun aplikasi bisnis Anda dengan standar enterprise sejak awal.

EKSPLORASI LANJUTAN

Artikel Rekayasa Terkait

Lihat Semua
Streaming Respons AI di Laravel (SSE)
Backend, AI
5 menit baca

Streaming Respons AI di Laravel (SSE)

Sekarang, chatbot kita nunggu AI-nya selesai generate seluruh respons dulu baru ditampilkan di layar. Untuk jawaban pendek ini nggak masalah, tapi untuk respons yang panjang, user cuma bengong lihat loading kosong selama beberapa detik. Itu bukan pengalaman yang enak, dan ini salah satu alasan kenapa tool seperti ChatGPT nampilin teks muncul kata demi kata, bukan langsung semuanya sekaligus. Efek kata-demi-kata itu disebut streaming. Di artikel ini, kita akan implementasikan itu di Laravel pakai Server-Sent Events (SSE), yang setup-nya lebih sederhana dibanding WebSocket dan sudah cukup untuk kebanyakan use case chatbot.

Baca Artikel
Membangun Sistem RAG Sederhana dengan Laravel + Vector Database
Backend, AI
6 menit baca

Membangun Sistem RAG Sederhana dengan Laravel + Vector Database

Sejauh ini chatbot kita cuma bisa jawab berdasarkan apa yang sudah diketahui model AI-nya dari training. Coba tanya soal dokumentasi internal perusahaanmu, harga spesifik produkmu, atau catatan rapat minggu lalu, AI-nya nggak akan bisa jawab, karena memang nggak pernah dilatih dengan data itu. Di sinilah RAG berperan, singkatan dari Retrieval Augmented Generation. Daripada cuma mengandalkan data training model, kita ambil dulu potongan-potongan relevan dari dokumen kita sendiri, lalu kasih itu ke AI sebagai konteks sebelum kita ajukan pertanyaan. Di artikel ini, kita akan bangun versi sederhana dari ini pakai Laravel dan PostgreSQL dengan extension pgvector.

Baca Artikel
Menggunakan Prism PHP untuk Abstraksi Provider AI
Backend, AI
5 menit baca

Menggunakan Prism PHP untuk Abstraksi Provider AI

Artikel ini mengenalkan Prism PHP, package yang memberikan satu interface konsisten untuk banyak provider AI, alih-alih menulis kode custom per provider. Dibahas cara instalasi, penggunaan dasar, menulis ulang service class chatbot dari Part 3 pakai message chain milik Prism, membuat provider bisa dikonfigurasi lewat .env, fitur bawaan yang berguna seperti retry dan pelacakan usage, sampai kapan sebenarnya perlu pakai Prism dibanding manggil API langsung.

Baca Artikel