← Semua pembelajaran / AI Engineer Nol → Production
Fase 6 · Use Case: Chatbot

RAG + chatbot — menjawab dari knowledge base sendiri

RAG di Fase 5 dibahas sebagai satu pertanyaan sekali jalan. Chatbot menambahkan satu lapisan kompleksitas: riwayat percakapan yang harus ikut memengaruhi apa yang dicari, bukan cuma pesan terakhir saja.

Sumber asli docs.claude.com Resmi Rangkuman ~7 menit baca

Intisari

  • Menggabungkan RAG dengan chatbot berarti setiap giliran bicara melakukan retrieval berdasarkan konteks percakapan, bukan cuma pesan terakhir yang berdiri sendiri.
  • Masalah klasik: pertanyaan lanjutan seperti 'kalau untuk yang paruh waktu?' tidak masuk akal dicari sendirian tanpa tahu topik sebelumnya ('cuti melahirkan') — perlu query rewriting sebelum retrieval.
  • Query rewriting: pakai LLM untuk menyusun ulang pertanyaan jadi mandiri (self-contained) berdasarkan riwayat percakapan, baru dipakai sebagai query pencarian.
  • Chunk hasil retrieval disisipkan sebagai konteks tambahan di system prompt atau pesan saat itu — bukan disimpan permanen sebagai bagian riwayat, supaya tidak menumpuk di setiap turn berikutnya.
  • Selalu tampilkan atau simpan sumber jawaban (dokumen/chunk mana yang dipakai) — penting untuk kepercayaan pengguna dan untuk debugging saat jawabannya salah.

Masalah baru: pertanyaan lanjutan yang tidak berdiri sendiri

RAG satu-pertanyaan di Fase 5 mencari langsung berdasarkan teks pertanyaan. Dalam percakapan multi-turn, ini gagal untuk pertanyaan lanjutan yang bergantung konteks sebelumnya:

User: "Berapa lama cuti melahirkan di perusahaan ini?"
Bot:  "Cuti melahirkan di perusahaan ini adalah 4 bulan..." [dari RAG]

User: "Kalau untuk yang paruh waktu?"
      ↑ dicari sendirian, embedding-nya TIDAK tahu ini masih soal "cuti melahirkan"
      → retrieval kemungkinan besar menemukan chunk yang salah/tidak relevan

Solusinya: query rewriting

Sebelum melakukan retrieval, susun ulang pertanyaan terakhir jadi versi yang berdiri sendiri (self-contained) dengan bantuan LLM, memakai riwayat percakapan sebagai konteks:

def tulis_ulang_query(riwayat: list, pertanyaan_baru: str) -> str:
    ringkasan_riwayat = "\n".join(f"{m['role']}: {m['content']}" for m in riwayat[-4:])

    prompt = f"""Berdasarkan riwayat percakapan ini, tulis ulang PERTANYAAN TERAKHIR
menjadi pertanyaan yang berdiri sendiri (tanpa perlu konteks sebelumnya untuk dipahami).
Jawab HANYA dengan pertanyaan hasil tulis ulang, tanpa penjelasan.

Riwayat:
{ringkasan_riwayat}

Pertanyaan terakhir: {pertanyaan_baru}"""

    return client.messages.create(
        model="claude-haiku-4-5", max_tokens=100, temperature=0,  # model kecil cukup, tugas sederhana
        messages=[{"role": "user", "content": prompt}],
    ).content[0].text

# "Kalau untuk yang paruh waktu?" → "Berapa lama cuti melahirkan untuk karyawan paruh waktu?"

Ini contoh nyata model routing dari Fase 0: tugas menulis ulang query cukup sederhana sehingga model kecil & murah (mis. Claude Haiku) sudah memadai — tidak perlu model paling mahal untuk setiap langkah internal. Jawaban final ke pengguna baru memakai model yang lebih mampu kalau memang dibutuhkan.

Pipeline lengkap: chatbot + RAG

def jawab_chatbot_rag(conversation_id: str, pesan_baru: str) -> str:
    riwayat = ambil_riwayat_dari_db(conversation_id)

    query_pencarian = tulis_ulang_query(riwayat, pesan_baru) if riwayat else pesan_baru
    chunks = cari_dokumen_mirip(query_pencarian, k=3)  # dari Fase 4-5
    konteks = "\n\n".join(c["isi"] for c in chunks)

    riwayat.append({"role": "user", "content": pesan_baru})  # riwayat simpan pesan ASLI, bukan hasil rewrite
    response = client.messages.create(
        model="claude-sonnet-4-5", max_tokens=1024,
        system=f"Jawab berdasarkan konteks berikut kalau relevan:\n<konteks>{konteks}</konteks>",
        messages=riwayat,
    )

    balasan = response.content[0].text
    riwayat.append({"role": "assistant", "content": balasan})
    simpan_riwayat_ke_db(conversation_id, riwayat)
    return balasan, chunks  # kembalikan juga sumbernya

Konteks RAG disisipkan lewat system, bukan ditambah permanen ke riwayat. Kalau chunk hasil retrieval ikut disimpan sebagai bagian permanen messages, riwayat akan membengkak dengan konteks yang mungkin sudah tidak relevan di turn berikutnya. Cari ulang & sisipkan segar di setiap turn — sedikit lebih mahal per turn, tapi konteksnya selalu relevan dengan pertanyaan saat itu.

Tampilkan sumbernya

Mengembalikan chunks yang dipakai (seperti di kode di atas) bukan cuma untuk debugging — di UI, menunjukkan "jawaban ini berdasarkan dokumen X, bagian Y" membangun kepercayaan pengguna dan memberi mereka jalan untuk memverifikasi sendiri, terutama untuk domain yang konsekuensinya nyata (kebijakan HR, medis, hukum).

Latihan: pakai knowledge base fiktif dari latihan Fase 5, lalu simulasikan percakapan 3 giliran di mana giliran kedua dan ketiga adalah pertanyaan lanjutan yang butuh konteks giliran sebelumnya (seperti contoh "paruh waktu" di atas). Bandingkan hasil retrieval dengan dan tanpa tulis_ulang_query.

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.