SEJATIDIMEDIA Logo
Tersedia Sesi Konsultasi Gratis
SejatiDimedia Logo

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.

Timur Dian Radha Sejati

Timur Dian Radha Sejati

Founder SejatiDimedia•22 September 2026•6 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 Sistem RAG Sederhana dengan Laravel + Vector Database

Cara Kerja RAG, dengan Bahasa Sederhana

Berikut alur dasarnya, tanpa istilah yang ribet:

  1. Ambil dokumenmu (FAQ, artikel, catatan internal, apa pun yang berbasis teks) dan pecah jadi potongan-potongan kecil.
  2. Ubah tiap potongan jadi deretan angka yang disebut embedding, yang merepresentasikan makna dari teks itu.
  3. Simpan embedding-embedding itu di database yang mendukung pencarian kemiripan (similarity search).
  4. Saat user tanya sesuatu, ubah juga pertanyaannya jadi embedding.
  5. Cari di database potongan mana yang embedding-nya paling mirip maknanya dengan pertanyaan tadi.
  6. Kirim potongan-potongan yang cocok itu, bersama pertanyaan aslinya, ke AI sebagai konteks.
  7. AI-nya jawab pakai konteks itu, bukan menebak-nebak dari data training umum.

Bayangkan ini seperti kasih AI setumpuk sticky notes yang relevan sebelum kamu tanya sesuatu, bukannya berharap dia ingat semuanya dari memori.

Kenapa PostgreSQL + pgvector

Ada vector database khusus seperti Pinecone atau Weaviate, dan search engine seperti Meilisearch juga sudah mendukung vector search sekarang. Di artikel ini, kita pakai PostgreSQL dengan extension pgvector karena:

  • Kalau aplikasi Laravel kamu sudah pakai PostgreSQL, kamu nggak perlu database service terpisah lagi.
  • Setup-nya cukup mudah baik di lokal maupun di kebanyakan hosting.
  • Ada package PHP yang solid untuk ini dan cocok dipakai bareng Eloquent.

Langkah 1: Aktifkan pgvector

Pastikan kamu pakai PostgreSQL sebagai database. Lalu install extension pgvector di server database-mu. Di kebanyakan sistem yang sudah ada PostgreSQL, caranya:

Terminalbash
1
# Contoh untuk Ubuntu/Debian, sesuaikan dengan versi PostgreSQL kamu
2
sudo apt install postgresql-16-pgvector
2 baris•108 chars
UTF-8•Spaces: 4

Kalau kamu pakai Docker, cara paling gampang adalah ganti image Postgres-mu ke

PLAINTEXT Snippetplaintext
1
pgvector/pgvector:pg16
1 baris•22 chars
UTF-8•Spaces: 4
, yang sudah datang dengan extension-nya terinstall.

Langkah 2: Install Package PHP

Terminalbash
1
composer require pgvector/pgvector
1 baris•34 chars
UTF-8•Spaces: 4

Publish dan jalankan migration yang mengaktifkan extension-nya di database kamu:

Terminalbash
1
php artisan vendor:publish --tag="pgvector-migrations"
2
php artisan migrate
2 baris•74 chars
UTF-8•Spaces: 4

Langkah 3: Buat Tabel Documents

Buat migration untuk menyimpan potongan dokumen dan embedding-nya:

Terminalbash
1
php artisan make:migration create_document_chunks_table
1 baris•55 chars
UTF-8•Spaces: 4
extends.phpphp
1
use Illuminate\Database\Migrations\Migration;
2
use Illuminate\Database\Schema\Blueprint;
3
use Illuminate\Support\Facades\Schema;
4
 
5
return new class extends Migration
6
{
7
public function up(): void
8
{
9
Schema::create('document_chunks', function (Blueprint $table) {
10
$table->id();
11
$table->string('source')->nullable();
12
$table->text('content');
13
$table->vector('embedding', 1536);
14
$table->timestamps();
15
});
16
 
17
// Index untuk pencarian kemiripan yang cepat
18
DB::statement('CREATE INDEX document_chunks_embedding_idx ON document_chunks USING hnsw (embedding vector_cosine_ops)');
19
}
20
 
21
public function down(): void
22
{
23
Schema::dropIfExists('document_chunks');
24
}
25
};
25 baris•767 chars
UTF-8•Spaces: 4

Kita pakai

PLAINTEXT Snippetplaintext
1
1536
1 baris•4 chars
UTF-8•Spaces: 4
dimensi di sini karena itu cocok dengan model
PLAINTEXT Snippetplaintext
1
text-embedding-3-small
1 baris•22 chars
UTF-8•Spaces: 4
milik OpenAI. Kalau kamu pakai model embedding yang berbeda, sesuaikan angka ini.

Langkah 4: Buat Model

DocumentChunk.phpphp
1
<?php
2
3
namespace App\Models;
4
5
use Illuminate\Database\Eloquent\Model;
6
use Pgvector\Laravel\Vector;
7
use Pgvector\Laravel\HasNeighbors;
8
9
class DocumentChunk extends Model
10
{
11
use HasNeighbors;
12
13
protected $fillable = ['source', 'content', 'embedding'];
14
15
protected $casts = [
16
'embedding' => Vector::class,
17
];
18
}
18 baris•328 chars
UTF-8•Spaces: 4

Langkah 5: Generate Embedding dan Simpan Chunk

Buat service untuk menangani proses ubah teks jadi embedding dan menyimpannya:

RagService.phpphp
1
<?php
2
3
namespace App\Services;
4
5
use App\Models\DocumentChunk;
6
use Illuminate\Support\Facades\Http;
7
use Illuminate\Support\Str;
8
9
class RagService
10
{
11
public function embed(string $text): array
12
{
13
$response = Http::withToken(config('services.openai.key'))
14
->post('https://api.openai.com/v1/embeddings', [
15
'model' => 'text-embedding-3-small',
16
'input' => $text,
17
]);
18
19
return $response->json('data.0.embedding');
20
}
21
22
public function ingest(string $source, string $content): void
23
{
24
// Pecah konten jadi chunk kasar berdasarkan paragraf
25
$chunks = Str::of($content)->trim()->explode("\n\n");
26
27
foreach ($chunks as $chunk) {
28
$chunk = trim($chunk);
29
30
if ($chunk === '') {
31
continue;
32
}
33
34
DocumentChunk::create([
35
'source' => $source,
36
'content' => $chunk,
37
'embedding' => $this->embed($chunk),
38
]);
39
}
40
}
41
}
41 baris•1032 chars
UTF-8•Spaces: 4

Ini strategi chunking sederhana berdasarkan paragraf. Untuk dokumen beneran, apalagi yang panjang, kamu butuh chunking yang lebih pintar (panjang karakter tetap dengan overlap, atau dipecah berdasarkan heading), tapi ini sudah cukup untuk memahami konsepnya.

Sekarang kamu bisa ingest dokumen seperti ini:

script.phpphp
1
$rag = app(RagService::class);
2
$rag->ingest('company-faq.md', file_get_contents(storage_path('docs/company-faq.md')));
2 baris•118 chars
UTF-8•Spaces: 4

Langkah 6: Ambil Chunk yang Relevan dan Tanya ke AI

Tambahkan method untuk mencari chunk paling relevan dan menyusun prompt dengan chunk-chunk itu:

script.phpphp
1
public function ask(string $question): ?string
2
{
3
$questionEmbedding = $this->embed($question);
4
 
5
$relevantChunks = DocumentChunk::query()
6
->nearestNeighbors('embedding', $questionEmbedding, \Pgvector\Laravel\Distance::Cosine)
7
->take(4)
8
->get();
9
 
10
$context = $relevantChunks->pluck('content')->implode("\n\n");
11
 
12
$prompt = "Jawab pertanyaan berikut hanya berdasarkan konteks di bawah ini. Kalau jawabannya tidak ada di konteks, katakan kamu tidak tahu.\n\n"
13
. "Konteks:\n{$context}\n\n"
14
. "Pertanyaan: {$question}";
15
 
16
$response = Http::withToken(config('services.openai.key'))
17
->post('https://api.openai.com/v1/chat/completions', [
18
'model' => config('services.openai.model'),
19
'messages' => [
20
['role' => 'user', 'content' => $prompt],
21
],
22
]);
23
 
24
return $response->json('choices.0.message.content');
25
}
25 baris•923 chars
UTF-8•Spaces: 4

Perhatikan instruksi di prompt-nya: "kalau jawabannya tidak ada di konteks, katakan kamu tidak tahu." Ini penting. Tanpa instruksi itu, AI-nya bisa saja balik menebak dari pengetahuan umumnya, bukannya mengakui kalau jawabannya memang nggak ada di dokumenmu, yang malah menghilangkan tujuan dari RAG itu sendiri.

Cara Mengetesnya

script.phpphp
1
$rag = app(RagService::class);
2
echo $rag->ask('Bagaimana kebijakan refund kami?');
2 baris•82 chars
UTF-8•Spaces: 4

Kalau kamu sudah ingest dokumen yang menyebutkan kebijakan refund, AI-nya seharusnya jawab pakai konten itu, bukan ngarang sendiri.

Hal yang Perlu Ditingkatkan Nanti

Ini versi dasar yang sudah jalan. Untuk pemakaian production, ada beberapa hal yang perlu kamu pikirkan nanti:

  • Chunking yang lebih baik. Pecah per paragraf itu masih kasar. Pertimbangkan chunk ukuran tetap dengan overlap supaya konteksnya nggak terpotong secara janggal.
  • Ingestion lewat queue. Generate embedding untuk dokumen besar butuh waktu. Pindahkan ini ke queued job, jangan dijalankan secara synchronous.
  • Re-ranking hasil. Kadang embedding yang paling dekat bukan berarti chunk yang paling berguna. Beberapa setup menambahkan langkah re-ranking setelah retrieval.
  • Mencantumkan sumber. Kembalikan info dokumen mana asal jawabannya, supaya user bisa verifikasi informasinya.

Selanjutnya

Sekarang, chatbot kita nunggu seluruh respons AI selesai dulu baru ditampilkan, yang bisa kerasa lambat untuk jawaban panjang. Di artikel selanjutnya, kita akan implementasikan streaming response, supaya teksnya muncul kata demi kata seiring AI-nya generate, mirip seperti interface ChatGPT.


Ini adalah Part 5 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
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
Membangun Chatbot Sederhana dengan Laravel + LLM API
Backend, AI
5 menit baca

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.

Baca Artikel
Membangun Sistem RAG Sederhana dengan Laravel + Vector Database | SejatiDimedia Insights | SejatiDimedia