Masalah Halusinasi Tidak Bersifat Acak
Ketika saya pertama kali membuat prototipe chatbot AI untuk portfolio ini, saya melakukan kesalahan standar: saya memberi model sebuah system prompt berisi satu paragraf tentang diri saya dan memintanya menjawab pertanyaan. Itu berhasil untuk pertanyaan umum. Untuk pertanyaan spesifik — apa topik tesis Anda, di perusahaan mana saja Anda pernah bekerja, tech stack apa yang dipakai proyek Anda saat ini — model itu entah mengatakan tidak tahu, atau dengan percaya diri mengarang jawaban.
Karangan adalah hasil yang lebih buruk. Pengunjung yang mendapat jabatan yang dibuat-buat atau publikasi hasil halusinasi tidak pergi dengan kesan netral; mereka pergi dengan kesan yang salah.
Masalahnya bukan LLM yang tidak bisa diandalkan. Masalahnya, konteks di system prompt diperlakukan sebagai input yang kabur, lalu diinterpolasi oleh model. Ketika faktanya habis, interpolasi mengambil alih. Solusinya adalah membuat model mengambil fakta, bukan menciptakannya.
Tool Calling sebagai Mekanisme Grounding
Chatbot ini memakai tool calling yang kompatibel dengan OpenAI melalui OpenRouter, sehingga modelnya cukup diatur lewat konfigurasi, bukan ditanam di kode. Alih-alih memuat semua informasi tentang saya ke jendela konteks, model diberi empat tool yang masing-masing mengembalikan satu irisan profil saya:
get_about— bio, peran saat ini, positioning, domain industri yang pernah saya tangani, dan beberapa poin kredibilitasget_experience— setiap peran beserta tanggal, lokasi, highlight, dan skill-nya, ditambah pendidikan formal sayaget_projects— studi kasus proyek (masalah, pendekatan, cara kerja, yang saya bangun, dampak, metrik, stack) dan matriks skillget_publications— paper jurnal dan konferensi saya, beserta metrik dampak riset
Ketika pengunjung bertanya "stack apa yang Anda pakai di Accenture," model memanggil get_experience, membaca hasilnya, dan menjawab dari data tersebut. Output tool adalah sumber kebenarannya.
Ini model mental yang berbeda dari RAG. Saya tidak melakukan embedding teks lalu mengambil potongan berdasarkan kemiripan semantik. Saya memberi model beberapa tool dengan nama yang jelas dan membiarkannya memutuskan mana yang dipanggil. Untuk portfolio pribadi dengan konten yang terbatas dan terstruktur, cara ini lebih sederhana dan lebih bisa diprediksi daripada pipeline retrieval.
Satu Sumber Kebenaran untuk Situs dan Bot
Tool-tool ini tidak membaca basis pengetahuan terpisah. Mereka mengimpor modul konten TypeScript bertipe yang sama dengan yang merender halaman yang sedang Anda baca: persona, timeline pengalaman, studi kasus proyek, dan daftar publikasi. Setiap tool memformat irisannya sebagai teks polos yang ringkas dan berlabel — Role:, Period:, Highlights:, dan seterusnya — yang mudah dibaca model dan hemat token.
Dampak praktisnya, chatbot tidak bisa menyimpang dari isi situs. Ketika saya memperbarui tanggal di halaman pengalaman atau menambah studi kasus, jawaban berikutnya langsung mencerminkannya. Tidak ada langkah "refresh embedding", dan tidak ada salinan CV kedua yang harus disinkronkan.
Alur Permintaan
Sebuah pertanyaan melewati dua fase di server.
Pertama, loop tool calling yang singkat. Model dipanggil tanpa streaming dengan tool yang tersedia. Jika model meminta tool, server menjalankannya, menambahkan hasilnya ke percakapan, lalu bertanya lagi. Loop ini dibatasi empat putaran, sehingga model yang bingung tidak bisa berputar tanpa henti.
Kedua, jawaban akhir di-stream. Begitu model berhenti meminta tool, server melakukan satu panggilan lagi dengan tool dimatikan dan mengalirkan jawaban token demi token sebagai JSON yang dipisahkan baris baru. Browser merendernya sebagai Markdown sambil jawabannya tiba. Jika model tidak mengembalikan apa pun, klien mendapat jawaban sederhana "I don't have information about that", bukan gelembung kosong.
Aturan yang Diikuti Model
System prompt-nya singkat dan ketat. Model diminta memanggil tool yang relevan sebelum menjawab pertanyaan apa pun tentang saya alih-alih mengandalkan pengetahuan bawaan, hanya menjawab pertanyaan tentang saya dan menolak topik lain dengan sopan, tidak pernah mengarang fakta, kredensial, tanggal, pemberi kerja, atau angka di luar yang dikembalikan tool, dan — jika tool tidak memuat jawabannya — mengatakannya terus terang alih-alih menebak.
Itu tetap menyisakan ruang untuk berguna. "Proyek mana yang paling mirip dengan membangun voice agent?" bukan pencarian fakta, tetapi model bisa menjawabnya dengan membandingkan studi kasus dari get_projects. Yang tidak boleh dilakukan adalah mengisi kekosongan dengan isi CV AI engineer pada umumnya. Jawaban mengikuti bahasa pengunjung, Inggris atau Indonesia.
Rate Limiting dan Kontrol Biaya
Endpoint LLM publik di situs pribadi membawa dua risiko: penyalahgunaan dan biaya. Keduanya ditangani di server sebelum model dipanggil:
- Batas input — maksimal 1.000 karakter per pesan, 40 pesan riwayat, dan 20.000 karakter secara total. Yang lebih besar akan ditolak.
- Dua rate limit — jendela per IP (default 20 permintaan per menit) dan jendela per percakapan (10 permintaan per lima menit), dengan kunci berupa ID acak yang dibuat browser untuk setiap chat. Jika melewati batas, API mengembalikan 429 dan chat menampilkan pesan ramah "harap tunggu sebentar", bukan error.
- Output terbatas — jawaban akhir dibatasi jumlah token yang bisa dikonfigurasi, dan loop tool dibatasi empat putaran.
Limiter-nya berupa fixed window di memori. Situs berjalan sebagai satu proses Node, jadi itu cukup; restart hanya mereset penghitungnya. API key tetap di server, dan itulah alasan utama situs ini di-render di server, bukan diekspor sebagai situs statis.
Yang Akan Saya Ubah
Jika kontennya tumbuh jauh — misalnya lima puluh tulisan blog dan dua puluh studi kasus mendetail — mengembalikan satu bagian utuh per pemanggilan tool akan menjadi mahal. Saya akan menambahkan tool search_blog dan search_projects yang didukung pencarian semantik. Tool calling dan retrieval bisa digabungkan dengan rapi: tool terstruktur tetap untuk pertanyaan faktual, dan tool pencarian menangani eksplorasi terbuka.
Versi sekarang belum membutuhkannya karena kontennya terbatas. Itulah prinsip desain yang terus saya pegang: bangun arsitektur paling sederhana yang menangani konten yang benar-benar Anda miliki, bukan konten yang mungkin Anda miliki suatu hari nanti.
Pendekatan ini cocok untuk asisten domain spesifik mana pun yang faktanya terbatas dan terstruktur. Tool calling menggeser tugas model dari mengingat menjadi menalar: ia mencari, bukan mengarang.