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.
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
messagesyang terus bertambah, terikat ke satuconversation_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()
);
| Desain | Alasan |
|---|---|
| Satu baris per pesan, bukan satu baris JSON per percakapan | Bisa 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_id | Satu 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.