Dunia pengembangan aplikasi terus berevolusi, dan integrasi Artificial Intelligence (AI) kini menjadi fitur yang semakin fundamental. Salah satu skenario yang sering dicari adalah kemampuan untuk mengunggah dokumen dan kemudian berinteraksi dengan isinya melalui chatbot AI. Bayangkan memiliki asisten AI yang bisa menjawab pertanyaan berdasarkan manual produk, dokumen kebijakan perusahaan, atau bahkan kumpulan riset yang Anda unggah sendiri.
Dalam artikel ini, kita akan membahas secara mendalam bagaimana Anda bisa membangun aplikasi semacam itu menggunakan Laravel. Kita akan menelusuri setiap langkah, mulai dari proses upload dokumen, ekstraksi teks, pembuatan embeddings, hingga integrasi model bahasa besar (LLM) untuk menciptakan pengalaman chat yang interaktif dan cerdas. Ini bukan sekadar tutorial, melainkan panduan praktis yang disertai dengan pertimbangan-pertimbangan layaknya seorang developer profesional.
Mengapa Integrasi AI dengan Dokumen Itu Penting untuk Aplikasi Modern?
Di era informasi yang masif, kemampuan untuk mengekstrak dan memahami informasi dari dokumen telah menjadi kunci. Namun, pencarian teks biasa seringkali terbatas pada kata kunci literal. AI membawa dimensi baru: pemahaman kontekstual dan kemampuan menjawab pertanyaan secara natural.
- Akses Informasi Cepat: Bayangkan Anda punya ribuan halaman dokumen legal, manual teknis, atau laporan keuangan. Daripada membaca satu per satu, Anda bisa bertanya langsung ke AI.
- Peningkatan Produktivitas: Developer atau pengguna akhir bisa lebih cepat mendapatkan jawaban tanpa harus membuang waktu mencari di antara tumpukan dokumen. Ini sangat berguna untuk knowledge base internal, dukungan pelanggan, atau analisis data.
- Pembuatan Konten Otomatis: AI bisa membantu meringkas dokumen, menghasilkan poin-poin penting, atau bahkan membuat draft jawaban berdasarkan informasi yang ada di dokumen.
- Pengambilan Keputusan Lebih Baik: Dengan kemampuan bertanya secara spesifik dan mendapatkan jawaban yang relevan dari data internal, keputusan bisa diambil dengan lebih cepat dan berbasis data.
Singkatnya, kemampuan berinteraksi langsung dengan dokumen melalui AI mengubah cara kita mengakses dan memanfaatkan informasi, membuka peluang baru untuk aplikasi yang lebih cerdas dan efisien.
Memahami Konsep Inti: Retrieval Augmented Generation (RAG)
Untuk memungkinkan AI “berchat” dengan dokumen Anda, kita tidak melatih ulang model AI (fine-tuning) dari awal. Metode ini terlalu mahal dan tidak praktis untuk data yang sering berubah. Pendekatan yang lebih efisien dan umum digunakan adalah Retrieval Augmented Generation (RAG).
Bagaimana RAG bekerja secara sederhana:
- Dokumen Diunggah & Diproses: Saat Anda mengunggah dokumen (misalnya PDF), teksnya diekstrak dan dipecah menjadi bagian-bagian kecil yang disebut “chunk”.
- Vektorisasi (Embeddings): Setiap chunk teks ini kemudian diubah menjadi representasi numerik multidimensi yang disebut “embedding” menggunakan model embedding khusus. Embeddings ini menangkap makna semantik dari teks, sehingga teks dengan makna serupa akan memiliki embeddings yang “dekat” dalam ruang vektor.
- Penyimpanan Vektor: Embeddings ini, bersama dengan teks aslinya, disimpan di tempat yang memungkinkan pencarian cepat, seperti database vektor atau database relasional dengan ekstensi vektor (contoh:
pgvectordi PostgreSQL). - Pengguna Bertanya: Saat pengguna mengajukan pertanyaan (query), pertanyaan tersebut juga diubah menjadi embedding.
- Retrieval (Pencarian Konteks): Sistem mencari embeddings dokumen yang paling mirip (secara semantik) dengan embedding pertanyaan pengguna. Ini akan mengembalikan beberapa “chunk” dokumen yang paling relevan sebagai konteks.
- Augmentation & Generation: Pertanyaan asli pengguna, bersama dengan chunk dokumen yang relevan, digabungkan menjadi satu prompt yang kemudian dikirim ke model bahasa besar (LLM) seperti GPT-4 atau Claude. LLM menggunakan konteks ini untuk menghasilkan jawaban yang akurat dan relevan.
Manfaat utama RAG adalah bahwa LLM tidak perlu “mengingat” semua informasi dari dokumen Anda. Ia hanya perlu “membaca” konteks yang paling relevan yang diberikan kepadanya saat itu juga. Ini mengurangi halusinasi, membuat jawaban lebih akurat, dan memungkinkan model untuk bekerja dengan data terbaru tanpa perlu pelatihan ulang yang mahal.
Persiapan Proyek Laravel
Sebelum masuk ke logika AI, kita perlu menyiapkan proyek Laravel kita. Anggap saja Anda sudah familiar dengan dasar-dasar Laravel.
1. Instalasi Proyek Laravel Baru
Jika belum, buat proyek Laravel baru:
composer create-project laravel/laravel ai-doc-chat
cd ai-doc-chat
2. Konfigurasi Database
Kita akan memerlukan beberapa tabel: satu untuk dokumen yang diunggah dan satu lagi untuk menyimpan riwayat chat. Gunakan PostgreSQL dengan ekstensi pgvector jika Anda ingin menyimpan embeddings langsung di database dan melakukan pencarian vektor secara efisien. Atau, Anda bisa menggunakan MySQL dan menyimpan embeddings sebagai JSON, namun pencarian kemiripan akan lebih lambat jika dilakukan secara manual.
Untuk PostgreSQL dengan pgvector (rekomendasi untuk skala menengah):
- Pastikan ekstensi
pgvectorsudah terinstal di server PostgreSQL Anda. - Ubah konfigurasi database di file
.envAnda:
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=ai_doc_chat
DB_USERNAME=postgres
DB_PASSWORD=secret
3. Migrasi Database
Kita perlu tabel untuk menyimpan informasi dokumen dan riwayat chat.
Buat migrasi untuk tabel documents:
php artisan make:migration create_documents_table
Isi file migrasi database/migrations/..._create_documents_table.php:
// ...
public function up(): void
{
Schema::create('documents', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('name');
$table->string('file_path');
$table->string('original_filename');
$table->string('mime_type')->nullable();
$table->timestamps();
});
}
// ...
Kemudian, buat migrasi untuk tabel document_chunks (untuk menyimpan potongan teks dan embeddings-nya). Ini krusial untuk RAG.
php artisan make:migration create_document_chunks_table
Isi file migrasi database/migrations/..._create_document_chunks_table.php:
// ...
public function up(): void
{
Schema::create('document_chunks', function (Blueprint $table) {
$table->id();
$table->foreignId('document_id')->constrained()->onDelete('cascade');
$table->text('content'); // The text chunk itself
$table->jsonb('embedding')->nullable(); // Store embedding as JSONB
$table->integer('chunk_order'); // Order of chunk in document
$table->timestamps();
});
}
// ...
Jika Anda menggunakan pgvector, Anda bisa menambahkan ini di migrasi document_chunks setelah Schema::create:
DB::statement('CREATE EXTENSION IF NOT EXISTS vector;');
DB::statement('ALTER TABLE document_chunks ALTER COLUMN embedding TYPE VECTOR(1536) USING embedding::vector;'); // 1536 for OpenAI ada-002
Catatan: Ukuran vektor (1536) harus sesuai dengan dimensi output model embedding yang Anda gunakan.
Dan satu lagi untuk riwayat chat:
php artisan make:migration create_chats_table
Isi file migrasi database/migrations/..._create_chats_table.php:
// ...
public function up(): void
{
Schema::create('chats', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->foreignId('document_id')->constrained()->onDelete('cascade');
$table->text('query');
$table->text('response');
$table->timestamps();
});
}
// ...
Jalankan migrasi:
php artisan migrate
Jangan lupa untuk menambahkan relasi di model User, Document, DocumentChunk, dan Chat.
Langkah 1: Upload Dokumen di Laravel
Bagian ini akan fokus pada bagaimana pengguna bisa mengunggah file ke aplikasi Anda.
1. Model Document dan DocumentChunk
Buat model untuk tabel yang baru saja kita migrasi:
php artisan make:model Document
php artisan make:model DocumentChunk
php artisan make:model Chat
Isi model Document.php:
// ...
class Document extends Model
{
use HasFactory;
protected $fillable = ['user_id', 'name', 'file_path', 'original_filename', 'mime_type'];
public function user()
{
return $this->belongsTo(User::class);
}
public function chunks()
{
return $this->hasMany(DocumentChunk::class);
}
}
Isi model DocumentChunk.php:
// ...
class DocumentChunk extends Model
{
use HasFactory;
protected $fillable = ['document_id', 'content', 'embedding', 'chunk_order'];
protected $casts = [
'embedding' => 'array', // Cast to array if not using pgvector, otherwise pgvector handles it
];
public function document()
{
return $this->belongsTo(Document::class);
}
}
2. Controller untuk Upload
Buat controller untuk menangani upload file:
php artisan make:controller DocumentController
Isi method store di DocumentController.php:
// ...
use Illuminate\Http\Request;
use App\Models\Document;
use Illuminate\Support\Facades\Storage;
// ...
public function store(Request $request)
{
$request->validate([
'document' => 'required|file|mimes:pdf,txt,docx|max:10240', // Max 10MB
'name' => 'required|string|max:255',
]);
$file = $request->file('document');
$filename = time() . '_' . $file->getClientOriginalName();
$filePath = $file->storeAs('documents', $filename, 'public'); // Store in storage/app/public/documents
$document = Document::create([
'user_id' => auth()->id(),
'name' => $request->name,
'file_path' => $filePath,
'original_filename' => $file->getClientOriginalName(),
'mime_type' => $file->getMimeType(),
]);
// Trigger processing for AI in the background (e.g., a Job)
// ProcessDocumentForAI::dispatch($document);
return back()->with('success', 'Document uploaded successfully. It will be processed for AI chat soon.');
}
Jangan lupa untuk menjalankan php artisan storage:link agar file yang diunggah dapat diakses secara publik.
3. View (Form Upload)
Buat file resources/views/documents/upload.blade.php (atau integrasikan ke dashboard Anda):
<!-- resources/views/documents/upload.blade.php -->
<form action="{{ route('documents.store') }}" method="POST" enctype="multipart/form-data">
@csrf
<label for="name">Nama Dokumen:</label>
<input type="text" name="name" id="name" required><br><br>
<label for="document">Pilih Dokumen (PDF, TXT, DOCX):</label>
<input type="file" name="document" id="document" accept=".pdf,.txt,.docx" required><br><br>
<button type="submit">Upload Dokumen</button>
</form>
4. Rute
Tambahkan rute di routes/web.php:
// ...
use App\Http\Controllers\DocumentController;
// ...
Route::middleware(['auth'])->group(function () {
Route::get('/documents/upload', [DocumentController::class, 'create'])->name('documents.create');
Route::post('/documents', [DocumentController::class, 'store'])->name('documents.store');
// ... other document routes (list, show, chat)
});
Langkah 2: Memproses Dokumen untuk AI (Ekstraksi & Vektorisasi)
Ini adalah bagian yang paling teknis. Setelah dokumen diunggah, kita perlu mengekstrak teksnya, memecahnya menjadi chunk, dan membuat embedding.
1. Menginstal Library yang Dibutuhkan
Untuk memproses PDF dan DOCX, Anda memerlukan library khusus. Contoh:
- Untuk PDF:
smalot/pdfparser - Untuk DOCX:
phpword/phpword(atau library lain yang lebih sederhana untuk ekstraksi teks)
composer require smalot/pdfparser
composer require phpoffice/phpword
Untuk berinteraksi dengan API OpenAI (atau LLM lain), kita akan pakai HTTP client Laravel (Guzzle).
composer require guzzlehttp/guzzle
2. Job untuk Pemrosesan Dokumen
Karena pemrosesan ini bisa memakan waktu, idealnya dilakukan di background menggunakan Laravel Job.
php artisan make:job ProcessDocumentForAI
Isi method handle di app/Jobs/ProcessDocumentForAI.php:
// ...
use App\Models\Document;
use App\Models\DocumentChunk;
use OpenAI; // Or your preferred AI client
use Smalot\PdfParser\Parser as PdfParser;
use PhpOffice\PhpWord\IOFactory;
use Illuminate\Support\Facades\Storage;
// ...
class ProcessDocumentForAI implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $document;
public function __construct(Document $document)
{
$this->document = $document;
}
public function handle(): void
{
$filePath = Storage::disk('public')->path($this->document->file_path);
$content = '';
// 1. Ekstraksi Teks Berdasarkan Tipe File
if ($this->document->mime_type === 'application/pdf') {
$parser = new PdfParser();
$pdf = $parser->parseFile($filePath);
$content = $pdf->getText();
} elseif ($this->document->mime_type === 'text/plain') {
$content = file_get_contents($filePath);
} elseif ($this->document->mime_type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
$phpWord = IOFactory::load($filePath);
$sections = $phpWord->getSections();
foreach ($sections as $section) {
foreach ($section->getElements() as $element) {
if (method_exists($element, 'getText')) {
$content .= $element->getText() . ' ';
}
}
}
}
if (empty($content)) {
// Handle error: could not extract content
return;
}
// 2. Chunking Teks
$chunks = $this->chunkText($content);
$openai = OpenAI::client(env('OPENAI_API_KEY'));
// 3. Generate Embeddings & Simpan Chunks
foreach ($chunks as $index => $chunk) {
$response = $openai->embeddings()->create([
'model' => 'text-embedding-ada-002', // Model embedding OpenAI
'input' => $chunk,
]);
DocumentChunk::create([
'document_id' => $this->document->id,
'content' => $chunk,
'embedding' => $response->embeddings[0]->embedding,
'chunk_order' => $index,
]);
}
}
private function chunkText(string $text, int $maxTokens = 500, int $overlapTokens = 50): array
{
// Simple character-based chunking for demonstration.
// For production, consider token-based chunking or more sophisticated methods.
$chunks = [];
$currentChunk = '';
$words = explode(' ', $text);
$wordCount = 0;
foreach ($words as $word) {
$tempChunk = $currentChunk . ' ' . $word;
if (strlen($tempChunk) > $maxTokens * 4) { // Approximate character count for tokens
$chunks[] = trim($currentChunk);
$currentChunk = $word;
// Simple overlap strategy: add last few words to next chunk
$overlapWords = array_slice(explode(' ', $chunks[count($chunks)-1]), -$overlapTokens);
$currentChunk = implode(' ', $overlapWords) . ' ' . $currentChunk;
} else {
$currentChunk = $tempChunk;
}
}
if (!empty($currentChunk)) {
$chunks[] = trim($currentChunk);
}
return array_filter($chunks);
}
}
Penting: Instal openai-php/client untuk integrasi OpenAI.
composer require openai-php/client
Dan tambahkan kunci API OpenAI Anda di .env:
OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Jangan lupa untuk mengkonfigurasi sistem queue Laravel (misalnya, dengan driver redis atau database) dan menjalankan php artisan queue:work untuk memproses job.
Langkah 3: Mengintegrasikan Model AI (LLM) untuk Chat
Setelah dokumen diproses, kita siap untuk berinteraksi dengan LLM. Kita akan membuat sebuah service yang mengelola interaksi ini.
1. Service untuk Interaksi AI
Buat sebuah service:
php artisan make:service AIService
Isi app/Services/AIService.php:
// ...
use App\Models\DocumentChunk;
use OpenAI;
use Pgvector\Laravel\Vector; // If using pgvector
// ...
class AIService
{
protected $openai;
public function __construct()
{
$this->openai = OpenAI::client(env('OPENAI_API_KEY'));
}
public function getEmbedding(string $text): array
{
$response = $this->openai->embeddings()->create([
'model' => 'text-embedding-ada-002',
'input' => $text,
]);
return $response->embeddings[0]->embedding;
}
public function getResponseFromDocument(string $query, int $documentId): string
{
// 1. Dapatkan embedding dari query pengguna
$queryEmbedding = $this->getEmbedding($query);
$vectorQuery = new Vector($queryEmbedding); // If using pgvector
// 2. Cari chunks dokumen yang paling relevan (top-k)
$relevantChunks = DocumentChunk::where('document_id', $documentId)
// For pgvector, use the following:
->orderByRaw('embedding <-> ?', [$vectorQuery])
// For other databases without native vector support, you'd fetch all and calculate similarity in PHP
->limit(3) // Ambil 3 chunk teratas
->get();
$context = $relevantChunks->pluck('content')->implode("\n\n");
// 3. Buat prompt untuk LLM
$prompt = "Berikut adalah beberapa informasi yang relevan dari dokumen:\n\n" . $context . "\n\nBerdasarkan informasi di atas, jawab pertanyaan berikut:\n\n" . $query;
// 4. Kirim prompt ke LLM dan dapatkan jawaban
$chatResponse = $this->openai->chat()->create([
'model' => 'gpt-3.5-turbo', // Atau 'gpt-4', 'gpt-4o'
'messages' => [
['role' => 'system', 'content' => 'Anda adalah asisten yang membantu menjawab pertanyaan berdasarkan dokumen yang diberikan. Berikan jawaban yang ringkas dan relevan.'],
['role' => 'user', 'content' => $prompt],
],
'temperature' => 0.7,
'max_tokens' => 500,
]);
return $chatResponse->choices[0]->message->content;
}
}
2. Registrasi Service (opsional tapi baik)
Anda bisa registrasikan AIService di app/Providers/AppServiceProvider.php:
// ...
public function register(): void
{
$this->app->singleton(AIService::class, function ($app) {
return new AIService();
});
}
Ini memungkinkan Anda untuk dengan mudah menginjeksikan AIService ke dalam controller atau kelas lain.
Langkah 4: Membangun Antarmuka Chat
Sekarang, kita akan membuat tampilan di mana pengguna dapat melihat dokumen mereka dan memulai sesi chat.
1. Model Chat
Isi model Chat.php:
// ...
class Chat extends Model
{
use HasFactory;
protected $fillable = ['user_id', 'document_id', 'query', 'response'];
public function user()
{
return $this->belongsTo(User::class);
}
public function document()
{
return $this->belongsTo(Document::class);
}
}
2. Controller Chat
Buat controller untuk mengelola chat:
php artisan make:controller ChatController
Isi ChatController.php:
// ...
use App\Models\Document;
use App\Models\Chat;
use App\Services\AIService;
use Illuminate\Http\Request;
// ...
class ChatController extends Controller
{
protected $aiService;
public function __construct(AIService $aiService)
{
$this->aiService = $aiService;
}
public function show(Document $document)
{
// Pastikan dokumen milik pengguna yang sedang login
if ($document->user_id !== auth()->id()) {
abort(403);
}
$chats = $document->chats()->where('user_id', auth()->id())->get();
return view('chats.show', compact('document', 'chats'));
}
public function send(Request $request, Document $document)
{
if ($document->user_id !== auth()->id()) {
abort(403);
}
$request->validate(['query' => 'required|string|max:1000']);
$userQuery = $request->query;
// Panggil service AI untuk mendapatkan jawaban
$aiResponse = $this->aiService->getResponseFromDocument($userQuery, $document->id);
// Simpan chat ke database
Chat::create([
'user_id' => auth()->id(),
'document_id' => $document->id,
'query' => $userQuery,
'response' => $aiResponse,
]);
return back()->with('success', 'Pesan terkirim!');
}
}
3. View Antarmuka Chat
Buat file resources/views/chats/show.blade.php:
<!-- resources/views/chats/show.blade.php -->
<h1>Chat dengan Dokumen: {{ $document->name }}</h1>
<div class="chat-area">
@foreach($chats as $chat)
<div class="message user-message">
<p><strong>Anda:</strong> {{ $chat->query }}</p>
</div>
<div class="message ai-message">
<p><strong>AI:</strong> {{ $chat->response }}</p>
</div>
@endforeach
</div>
<form action="{{ route('chats.send', $document) }}" method="POST">
@csrf
<input type="text" name="query" placeholder="Tanyakan sesuatu tentang dokumen ini..." required>
<button type="submit">Kirim</button>
</form>
4. Rute Chat
Tambahkan rute di routes/web.php:
// ...
use App\Http\Controllers\ChatController;
// ...
Route::middleware(['auth'])->group(function () {
// ... document routes
Route::get('/documents/{document}/chat', [ChatController::class, 'show'])->name('chats.show');
Route::post('/documents/{document}/chat', [ChatController::class, 'send'])->name('chats.send');
});
Workflow RAG End-to-End
Mari kita rangkum bagaimana semua komponen ini bekerja sama dalam skenario nyata:
- Pengguna Mengunggah Dokumen: Pengguna masuk, navigasi ke halaman upload, dan mengunggah dokumen (misalnya, file PDF tentang kebijakan perusahaan) dengan nama “Kebijakan Cuti”.
- Laravel Menyimpan Dokumen: Controller
DocumentController@storemenerima file, menyimpannya distorage/app/public/documents, dan membuat entri di tabeldocuments. - Pemrosesan Dokumen Dimulai (Background Job): Segera setelah dokumen disimpan, sebuah
ProcessDocumentForAIJob di-dispatch ke antrean. - Ekstraksi Teks & Chunking: Job ini mengambil file PDF yang baru diunggah, menggunakan
smalot/pdfparseruntuk mengekstrak semua teks, dan memecahnya menjadi beberapa “chunk” (misalnya, setiap 500 kata). - Vektorisasi & Penyimpanan Chunk: Setiap chunk teks kemudian dikirim ke API OpenAI (model
text-embedding-ada-002) untuk mendapatkan representasi vektornya (embedding). Embedding ini, bersama dengan teks chunk-nya, disimpan ke tabeldocument_chunks. - Pengguna Memulai Sesi Chat: Pengguna menavigasi ke halaman chat untuk dokumen “Kebijakan Cuti” tersebut (
/documents/1/chat). - Pengguna Mengajukan Pertanyaan: Pengguna mengetik pertanyaan seperti, “Berapa lama jatah cuti tahunan saya?” dan mengirimkannya.
- Sistem Mencari Konteks (Retrieval): Controller
ChatController@sendmenerima pertanyaan. Ia memanggilAIService, yang pertama-tama mengubah pertanyaan pengguna menjadi embedding. Kemudian,AIServicemencari di tabeldocument_chunksuntuk menemukan chunk dokumen (dari dokumen “Kebijakan Cuti”) yang memiliki embedding paling mirip dengan embedding pertanyaan pengguna. - Konteks Dikirim ke LLM (Augmentation): Beberapa chunk teratas yang relevan (misalnya, yang menjelaskan jatah cuti, prosedur, dan cuti sakit) diambil.
AIServicemembangun sebuah prompt yang berisi instruksi untuk AI, pertanyaan pengguna, dan chunk-chunk dokumen yang relevan ini. - LLM Menghasilkan Jawaban (Generation): Prompt tersebut dikirim ke API OpenAI (model
gpt-3.5-turbo). OpenAI memproses prompt dan menghasilkan jawaban berdasarkan konteks yang diberikan. - Jawaban Ditampilkan & Disimpan: Jawaban dari AI dikembalikan ke aplikasi Laravel, disimpan ke tabel
chats(bersama dengan pertanyaan pengguna), dan kemudian ditampilkan di antarmuka chat pengguna.
Dengan workflow ini, Anda telah berhasil membuat aplikasi Laravel yang memungkinkan pengguna mengunggah dokumen dan berinteraksi cerdas dengan isinya menggunakan kekuatan AI.
Masalah yang Sering Terjadi dan Solusinya
Membangun sistem seperti ini pasti akan menghadapi beberapa tantangan. Berikut adalah beberapa yang sering muncul:
1. Batasan Ukuran File dan Tipe Dokumen
- Gejala: Upload file gagal, atau file yang diunggah tidak dapat diproses.
- Penyebab: Konfigurasi server PHP (
upload_max_filesize,post_max_size) atau validasi Laravel yang terlalu ketat. Library parser dokumen hanya mendukung format tertentu. - Solusi:
- Sesuaikan
upload_max_filesizedanpost_max_sizediphp.ini. - Perluas validasi Laravel di controller (
'mimes:pdf,txt,docx,xlsx,pptx') dan tambahkan dukungan parser untuk tipe file baru. - Gunakan layanan pihak ketiga untuk parsing dokumen yang lebih robust jika diperlukan (misalnya, Cloudmersive, GroupDocs).
- Sesuaikan
2. Kualitas Ekstraksi Teks
- Gejala: Teks yang diekstrak dari PDF atau DOCX berantakan, urutannya salah, atau banyak karakter aneh.
- Penyebab: Struktur dokumen yang kompleks (misalnya, banyak tabel, kolom, gambar), font yang tidak standar, atau kualitas library parser yang kurang baik.
- Solusi:
- Coba library parser yang berbeda (misalnya,
textractvia shell, atau layanan OCR/parsing cloud). - Implementasi pre-processing teks (pembersihan karakter, normalisasi spasi) setelah ekstraksi.
- Informasikan pengguna tentang format dokumen yang optimal untuk hasil terbaik.
- Coba library parser yang berbeda (misalnya,
3. Batasan Konteks LLM (Context Window)
- Gejala: Jawaban AI tidak relevan atau mengatakan “informasi tidak ditemukan” meskipun ada di dokumen.
- Penyebab: Jumlah chunk yang diambil terlalu banyak, melebihi batas token yang bisa diproses oleh LLM (misalnya, 4096, 8192, 16384 token).
- Solusi:
- Optimalkan strategi chunking: ukuran chunk lebih kecil, overlap yang tepat.
- Tingkatkan jumlah
limit()chunk yang diambil ke LLM, tapi pastikan tidak melebihi batas token LLM yang digunakan. - Gunakan LLM dengan context window yang lebih besar (misalnya, GPT-4 Turbo, Claude 2.1).
- Implementasi reranking untuk chunk yang paling relevan (misalnya, menggunakan model cross-encoder) sebelum dikirim ke LLM.
4. Biaya API AI yang Tinggi
- Gejala: Penggunaan API OpenAI membengkak.
- Penyebab: Terlalu banyak panggilan embedding atau chat, ukuran prompt yang besar, model LLM yang mahal.
- Solusi:
- Pilih model embedding dan LLM yang lebih murah untuk penggunaan non-kritikal (misalnya,
gpt-3.5-turbolebih murah darigpt-4). - Cache embeddings. Setelah embedding sebuah chunk dibuat, jangan buat lagi.
- Optimalkan ukuran prompt: kirim hanya informasi yang benar-benar relevan.
- Terapkan kuota penggunaan atau batasi jumlah pertanyaan per pengguna.
- Pertimbangkan LLM open source yang bisa di-host sendiri jika volume sangat tinggi dan Anda memiliki infrastruktur yang memadai.
- Pilih model embedding dan LLM yang lebih murah untuk penggunaan non-kritikal (misalnya,
Pengalaman dan Pertimbangan Praktis
Setelah mencoba berbagai pendekatan, ada beberapa insight yang perlu Anda pertimbangkan saat membangun sistem RAG di Laravel:
1. Strategi Chunking Adalah Kunci
Ini mungkin aspek yang paling sering diabaikan tapi paling berdampak. Ukuran chunk yang terlalu besar bisa melebihi context window LLM, sementara yang terlalu kecil bisa kehilangan konteks penting. Saya sering menggunakan ukuran 500-1000 token (bukan karakter) dengan overlap 10% – 20%. Pertimbangkan juga: apakah Anda memecah berdasarkan paragraf, judul, atau struktur semantik lainnya? Ini semua mempengaruhi kualitas jawaban.
2. Pemilihan Model Embedding dan LLM
Tidak semua model embedding atau LLM sama. Model embedding seperti OpenAI text-embedding-ada-002 sangat baik untuk tujuan umum. Namun, jika Anda bekerja dengan domain spesifik (medis, legal), mungkin ada model embedding yang lebih cocok. Untuk LLM, gpt-3.5-turbo adalah pilihan yang solid untuk keseimbangan biaya dan performa, tetapi untuk akurasi kritis, gpt-4o atau gpt-4-turbo jelas lebih unggul. Sesuaikan pilihan Anda dengan kebutuhan aplikasi dan anggaran.
3. Manajemen Vector Database
Untuk project skala kecil atau demo, menyimpan embeddings di jsonb PostgreSQL dengan pgvector sudah cukup baik. Namun, untuk skala produksi dengan jutaan dokumen atau miliaran chunk, Anda mungkin perlu mempertimbangkan database vektor khusus seperti Pinecone, Weaviate, Milvus, atau Qdrant. Database ini dirancang untuk pencarian kemiripan vektor berkecepatan tinggi dan skalabilitas.
4. Keamanan Data dan Privasi
Saat berurusan dengan dokumen pengguna, keamanan adalah prioritas utama. Pastikan file disimpan dengan aman, akses terbatas, dan data embeddings juga terlindungi. Jika Anda menggunakan API pihak ketiga, pahami kebijakan privasi mereka terkait data yang dikirimkan (teks chunk dan query).
5. User Experience (UX) Chatbot
Antarmuka chat harus intuitif. Tunjukkan kepada pengguna dokumen mana yang sedang mereka “ajak bicara”. Berikan indikator loading saat AI memproses jawaban. Pertimbangkan fitur seperti riwayat chat, kemampuan untuk mengedit pertanyaan, atau bahkan fitur “saran pertanyaan” untuk memandu pengguna.
6. Mempertimbangkan Integrasi LangChain (Konseptual)
Meskipun kita membangun secara “manual” di sini, framework seperti LangChain (ada implementasi PHP-nya) dirancang untuk menyederhanakan workflow RAG, manajemen prompt, dan integrasi LLM. Jika proyek Anda tumbuh lebih kompleks, mempelajari framework semacam ini bisa sangat membantu.
FAQ
Apakah bisa menggunakan LLM lokal (on-premise) daripada API berbayar?
Ya, sangat bisa. Anda bisa menghosting LLM open-source (misalnya, Llama 2, Mistral, Gemma) menggunakan framework seperti Ollama, vLLM, atau text-generation-webui. Kemudian, Anda bisa mengarahkan panggilan API Guzzle ke endpoint lokal Anda daripada ke OpenAI. Ini memberikan kontrol penuh atas data dan biaya, tetapi membutuhkan infrastruktur server yang lebih kuat (GPU) dan keahlian DevOps.
Bagaimana dengan ukuran dokumen yang sangat besar (ratusan MB atau GB)?
Untuk dokumen yang sangat besar, proses ekstraksi teks dan pembuatan embedding bisa memakan waktu dan sumber daya yang besar. Pertimbangkan hal berikut:
- Gunakan layanan cloud yang dioptimalkan untuk pemrosesan dokumen besar.
- Implementasikan pemrosesan paralel (misalnya, membagi dokumen menjadi beberapa bagian dan memprosesnya secara bersamaan).
- Optimalkan storage (S3) dan sistem queue Laravel Anda agar mampu menangani beban.
Apa itu vector database dan apakah wajib?
Vector database adalah jenis database yang dirancang khusus untuk menyimpan dan mencari embeddings (vektor) dengan cepat. Mereka memungkinkan pencarian “tetangga terdekat” (nearest neighbor search) yang sangat efisien untuk menemukan vektor yang paling mirip. Untuk proyek kecil, menyimpan di PostgreSQL dengan pgvector sudah cukup. Namun, untuk skala produksi dengan volume data yang sangat besar, vector database khusus menjadi wajib karena performa pencariannya yang jauh lebih superior.
Bagaimana cara mengoptimalkan biaya saat menggunakan API AI?
Selain tips di bagian masalah yang sering terjadi, Anda bisa:
- Monitoring: Selalu pantau penggunaan API Anda.
- Batasan Penggunaan: Terapkan batasan harian atau bulanan per pengguna.
- Model Cerdas: Gunakan model LLM yang lebih kecil dan lebih cepat untuk tugas-tugas sederhana, dan hanya gunakan model yang lebih besar untuk pertanyaan yang kompleks.
- Reranking: Gunakan model reranker yang lebih murah untuk memfilter chunk yang relevan sebelum mengirimkannya ke LLM yang lebih mahal.
Kesimpulan
Mengintegrasikan fitur upload dokumen dan chat dengan AI di Laravel adalah salah satu cara paling efektif untuk membuat aplikasi yang cerdas dan memberikan nilai tambah nyata bagi pengguna. Dengan memahami konsep RAG, mengelola proses ekstraksi dan embedding dokumen, serta memanfaatkan API LLM, Anda bisa membangun sistem yang mampu menjawab pertanyaan kompleks dan merangkum informasi dari data yang Anda miliki.
Proses ini mungkin terlihat panjang, tetapi setiap langkahnya logis dan modular. Sebagai seorang developer, ini adalah kesempatan emas untuk mengasah kemampuan Anda dalam domain AI, otomasisasi, dan pengembangan aplikasi web modern. Jangan ragu untuk bereksperimen dengan berbagai model AI, strategi chunking, dan optimasi lainnya untuk menemukan kombinasi terbaik yang sesuai dengan kebutuhan proyek Anda.
TAGS: Laravel, AI, RAG, Document Upload, Chatbot, PHP, Developer Tools, OpenAI, Vector Embeddings, Automation