โ† Semua pembelajaran / AI Engineer Nol โ†’ Production
Fase 6 ยท Use Case: Chatbot

Arsitektur chatbot production

Semua bahan sudah ada: prompting, streaming, tool use, agent, RAG. Fase ini menyatukannya jadi bentuk yang paling umum ditemui: chatbot yang benar-benar bisa dipakai banyak orang sekaligus.

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

Intisari

  • Messages API bersifat stateless โ€” server LLM tidak menyimpan riwayat percakapan apa pun. Aplikasimu yang bertanggung jawab menyimpan & mengirim ulang seluruh riwayat di setiap panggilan.
  • Satu sesi percakapan = satu array messages yang terus bertambah, terikat ke satu conversation_id, disimpan di database โ€” bukan variabel di memori server.
  • Riwayat yang terus tumbuh menabrak masalah yang sama dengan agent di Fase 3: context window & biaya token โ€” butuh strategi pemangkasan/ringkasan untuk percakapan panjang.
  • Server harus menangani banyak percakapan dari banyak pengguna secara bersamaan โ€” desain backend-nya harus stateless per-request (state ada di database, bukan di memori proses) supaya bisa di-scale horizontal.
  • Streaming (Fase 1) dan tool use/RAG (Fase 2-5) semuanya bersatu di sini: satu turn chatbot bisa berarti beberapa panggilan API internal (tool call, retrieval) sebelum akhirnya stream jawaban final ke pengguna.

Messages API tidak menyimpan apa pun

Ini prinsip yang sudah muncul berkali-kali sejak Fase 0, dan di sinilah konsekuensinya paling terasa: setiap panggilan ke Messages API berdiri sendiri. Server LLM tidak "ingat" percakapan kemarin, atau bahkan pesan sebelumnya dalam sesi yang sama. Seluruh riwayat percakapan harus dikirim ulang di setiap panggilan, lewat array messages.

def kirim_pesan(conversation_id: str, pesan_baru: str) -> str:
    riwayat = ambil_riwayat_dari_db(conversation_id)  # [] kalau percakapan baru
    riwayat.append({"role": "user", "content": pesan_baru})

    response = client.messages.create(
        model="claude-sonnet-4-5", max_tokens=1024, messages=riwayat,
    )

    balasan = response.content[0].text
    riwayat.append({"role": "assistant", "content": balasan})
    simpan_riwayat_ke_db(conversation_id, riwayat)  # WAJIB disimpan, bukan cuma di memori

    return balasan

Jebakan paling umum saat pertama membangun chatbot: menyimpan riwayat percakapan di variabel Python biasa (mis. dictionary global). Ini bekerja di demo lokal, lalu rusak begitu ada dua instance server berjalan (deploy di belakang load balancer) โ€” pengguna yang requestnya mendarat di instance berbeda kehilangan riwayatnya. Riwayat harus disimpan di tempat yang dibagi semua instance: database.

Skema data minimal untuk sesi percakapan

CREATE TABLE percakapan (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    pengguna_id BIGINT NOT NULL REFERENCES pengguna(id),
    dibuat_pada TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE pesan (
    id BIGSERIAL PRIMARY KEY,
    percakapan_id UUID NOT NULL REFERENCES percakapan(id),
    peran TEXT NOT NULL CHECK (peran IN ('user', 'assistant')),
    isi TEXT NOT NULL,
    dibuat_pada TIMESTAMPTZ NOT NULL DEFAULT now()
);
DesainAlasan
Satu baris per pesan, bukan satu baris JSON per percakapanBisa query per pesan (mis. cari kata kunci), audit trail lebih jelas, dan tidak perlu menulis ulang seluruh blob tiap pesan baru
percakapan_id terpisah dari pengguna_idSatu pengguna bisa punya banyak percakapan (thread) berbeda โ€” sama seperti pola thread di aplikasi chat biasa

Riwayat panjang: masalah yang sama dengan agent

Persis seperti loop agent di Fase 3, percakapan yang panjang membuat array messages terus membesar โ€” setiap turn baru membayar ulang token untuk seluruh riwayat sebelumnya. Strategi yang sama berlaku: sliding window (kirim N pesan terakhir saja) atau ringkasan berkala untuk pesan-pesan lama, dikombinasikan dengan prompt caching (Fase 7) untuk menekan biaya riwayat yang berulang di setiap panggilan.

Satu turn chatbot bisa berarti beberapa panggilan API

Chatbot produksi jarang sesederhana satu panggilan Messages API per pesan pengguna. Satu "giliran bicara" bisa melibatkan beberapa langkah internal sebelum jawaban final dikirim:

Pesan pengguna masuk
       โ”‚
       โ–ผ
[Opsional] Retrieval RAG โ€” cari konteks relevan dari knowledge base (Fase 5)
       โ”‚
       โ–ผ
Panggilan ke model dengan konteks + tools yang tersedia
       โ”‚
       โ–ผ
[Opsional] Model minta tool dipanggil (Fase 2) โ†’ jalankan โ†’ kirim hasil balik ke model
       โ”‚           (bisa berulang beberapa kali, seperti agent loop di Fase 3)
       โ–ผ
Jawaban final di-stream ke pengguna (Fase 1)
       โ”‚
       โ–ผ
Riwayat lengkap (termasuk tool call di tengah) disimpan ke database

Yang disimpan ke riwayat bukan cuma teks yang dilihat pengguna. Tool call & hasilnya di tengah proses juga perlu ikut tersimpan sebagai bagian riwayat messages โ€” kalau tidak, model akan "lupa" apa yang sudah dilakukannya di turn berikutnya, dan berpotensi memanggil tool yang sama berulang tanpa perlu.

Latihan: buat skema tabel percakapan dan pesan seperti di atas (SQLite cukup untuk latihan), lalu tulis fungsi kirim_pesan yang benar-benar membaca & menulis riwayat dari database โ€” bukan variabel Python. Uji dengan mengirim 3 pesan berurutan dalam satu percakapan, restart proses Python-mu di antara pesan kedua dan ketiga, dan pastikan riwayatnya tetap utuh.

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