8. State Percakapan dan Memori
Sampai sekarang, setiap interaksi LLM yang kita bangun bersifat stateless. Stateless berarti setiap request bersifat independen—model tidak memiliki memori tentang percakapan sebelumnya. Ini berfungsi dengan baik untuk tugas satu kali seperti ringkasan dokumen atau Q&A pertanyaan tunggal.
Namun membangun agen percakapan adalah cerita yang berbeda. Anda perlu agen untuk mengingat apa yang dibahas sebelumnya, memahami kata ganti seperti "itu" atau "yang itu," dan mempertahankan konteks sepanjang dialog. Untuk melakukan ini, Anda harus secara eksplisit mengelola state.
(Di sini, "state" mengacu pada informasi yang diingat oleh program. Untuk agen percakapan, riwayat percakapan sebelumnya adalah state.)
Dalam bab ini, kita akan membahas:
- Mengapa LLM tidak "mengingat" percakapan
- Cara mengimplementasikan memori percakapan menggunakan alat riwayat pesan LangChain
- Cara mengelola anggaran token untuk mencegah overflow konteks
8.1) Mengapa LLM Lupa
LLM Tidak Memiliki Memori
LLM memiliki karakteristik kritis: mereka tidak mengingat apa pun dari percakapan sebelumnya.
Ketika Anda memanggil API LLM, model memproses input Anda dan menghasilkan respons. Tetapi model tidak menyimpan catatan itu di mana pun. Tidak ada memori di dalam model yang mempertahankan state, tidak ada riwayat percakapan. Setiap panggilan API sepenuhnya independen. Ini seperti memulai dari awal setiap kali.
Ini adalah desain yang disengaja. LLM bekerja seperti fungsi stateless: Anda memberikan input, mereka menghasilkan output, dan tidak ada yang disimpan. Model yang berjalan di server OpenAI saat ini tidak memiliki catatan tentang apa yang baru saja Anda tanyakan.
Mengapa LLM Terlihat Mengingat
Tapi tunggu—ketika Anda menggunakan ChatGPT atau Claude, rasanya seperti mereka mengingat percakapan Anda. Anda bisa mengatakan "Ceritakan tentang Paris," lalu melanjutkan dengan "Berapa populasinya?" dan model tahu Anda masih berbicara tentang Paris. Bagaimana itu bekerja?
Begini caranya: aplikasi mengirimkan riwayat percakapan sebelumnya bersama dengan setiap pesan baru.
Inilah yang sebenarnya terjadi:
LLM tidak "mengingat" Anda bertanya tentang Paris sebelumnya—model hanya tahu karena aplikasi mengirimkan riwayat percakapan sebelumnya bersama dengan pesan baru. Pada akhirnya, aplikasilah yang mengelola state, bukan model.
Mengapa "State" Harus Dikelola di Aplikasi Anda, Bukan Model
Anggap LLM sebagai fungsi murni: diberikan input, ia menghasilkan output. Tidak ada state internal yang dikelola LLM di luar pesan yang Anda berikan. Ini adalah desain yang disengaja.
Untuk agen percakapan, ini berarti state harus dikelola di aplikasi Anda.
State—riwayat percakapan—berada di kode aplikasi Anda, bukan di model. Ini berarti Anda bertanggung jawab untuk:
- Menyimpan riwayat percakapan
- Mengirim riwayat yang relevan dengan setiap request baru
- Mengelola ukuran riwayat (dibahas di bagian 8.3)
Di bagian 8.2, kita akan mengimplementasikan ini menggunakan alat riwayat pesan LangChain.
Apa yang Terjadi Jika Anda Tidak Mengelola State?
Jika Anda tidak mengelola state, agen Anda (aplikasi Anda) tidak dapat mempertahankan percakapan yang koheren. Berikut adalah kegagalan yang paling umum:
1. Tidak Bisa Mengingat Percakapan Sebelumnya
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
llm = ChatOpenAI(model="gpt-4o-mini")
# Pertanyaan pertama
response1 = llm.invoke([HumanMessage(content="Nama saya Alice")])
print(response1.content) # Output: Senang bertemu dengan Anda, Alice!
# Pertanyaan kedua (tidak ada riwayat yang dikirim)
response2 = llm.invoke([HumanMessage(content="Siapa nama saya?")])
print(response2.content) # Output: Saya tidak tahu nama Anda...Model tidak tahu Anda mengatakan nama Anda adalah Alice karena kami tidak mengirimkan informasi itu dalam panggilan kedua.
2. Tidak Bisa Mengetahui Apa yang Dirujuk Kata Ganti
# Pengguna bertanya tentang topik
response1 = llm.invoke([HumanMessage(content="Ceritakan tentang Python")])
print(response1.content)
# Output: Python adalah bahasa pemrograman tingkat tinggi yang dikenal karena keterbacaannya...
# Pengguna melanjutkan dengan kata ganti
response2 = llm.invoke([HumanMessage(content="Apa fitur utamanya?")])
print(response2.content)
# Output: Saya akan senang membantu! Bisakah Anda tentukan apa yang Anda tanyakan?Tanpa pesan sebelumnya, model tidak bisa tahu apa yang dirujuk oleh "nya".
Dampak di Dunia Nyata:
Bayangkan membangun agen dukungan pelanggan tanpa manajemen state:
Pengguna: "Saya mengalami masalah dengan pesanan #12345"
Agen: "Saya turut prihatin mendengarnya. Apa masalahnya?"
Pengguna: "Alamat pengirimannya salah"
Agen: "Saya bisa membantu dengan itu. Bisakah Anda berikan nomor pesanan Anda?"
Pengguna: "Saya baru saja memberitahu Anda..."Pengalaman ini membuat pengguna frustrasi dan kehilangan kepercayaan mereka. Manajemen state bukan opsional untuk agen percakapan—ini penting untuk menciptakan interaksi yang koheren dan berguna.
Di bagian 8.2, kita akan mengimplementasikan manajemen state menggunakan alat riwayat pesan LangChain dan menambahkan memori percakapan ke chat CLI.
8.2) Mengelola State Percakapan
Sekarang setelah kita memahami mengapa manajemen state sangat penting, mari pelajari cara mengimplementasikannya. Kita akan menggunakan alat riwayat pesan bawaan LangChain untuk mengelola riwayat percakapan. Di akhir, kita akan menerapkan apa yang kita pelajari untuk menambahkan manajemen state ke chat CLI dari Bab 3.
Memahami Tipe Pesan: HumanMessage, AIMessage, dan SystemMessage
Sebelum mempelajari cara mengelola state percakapan, Anda perlu mengetahui tipe pesan yang digunakan dalam state percakapan. LangChain menggunakan tiga tipe pesan untuk merepresentasikan percakapan: HumanMessage (input pengguna), AIMessage (respons model), dan SystemMessage (instruksi). Setiap pesan memiliki role (tipe pesan) dan content (teks sebenarnya).
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
# SystemMessage: Instruksi untuk perilaku model
system_msg = SystemMessage(content="Kamu adalah asisten yang membantu yang berspesialisasi dalam pemrograman Python.")
# HumanMessage: Input pengguna
user_msg = HumanMessage(content="Bagaimana cara membaca file di Python?")
# AIMessage: Respons model
# (Dalam praktiknya, LangChain membungkus respons model dalam objek ini - ditampilkan di sini untuk ilustrasi)
ai_msg = AIMessage(content="Anda bisa menggunakan fungsi `open()` dengan context manager...")Mengapa tipe pesan terpisah?
Agar model memahami riwayat percakapan secara efektif, model perlu mengetahui tujuan setiap pesan dan siapa yang mengatakannya. Ketiga tipe pesan melayani tujuan yang berbeda:
- SystemMessage: Instruksi yang mendefinisikan bagaimana model harus berperilaku (misalnya, "Jadilah ringkas", "Kamu adalah tutor Python")
- HumanMessage: Apa yang dikatakan pengguna
- AIMessage: Apa yang sebelumnya dijawab model
Struktur ini memungkinkan model untuk membedakan antara instruksi, pertanyaan pengguna, dan jawaban masa lalunya sendiri—yang penting untuk mempertahankan percakapan multi-turn yang koheren.
Membangun Riwayat Percakapan Secara Manual
Sekarang setelah kita memahami ketiga tipe pesan, mari lihat cara membangun riwayat percakapan secara manual:
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
llm = ChatOpenAI(model="gpt-4o-mini")
# Bangun riwayat percakapan
# SystemMessage mengatur instruksi (sekali di awal)
# Kemudian: Input pengguna → Respons AI → Input pengguna (alur percakapan alami)
messages = [
SystemMessage(content="Kamu adalah tutor Python yang ringkas."),
HumanMessage(content="Apa itu list comprehension?"),
AIMessage(content="List comprehension adalah cara ringkas untuk membuat list: [x*2 for x in range(5)]"),
HumanMessage(content="Bisakah kamu tunjukkan contoh yang lebih kompleks?")
]
# Kirim seluruh riwayat dengan pertanyaan baru
response = llm.invoke(messages)
print(response.content)Output:
Tentu! Berikut adalah list comprehension yang memfilter dan mentransformasi:
[x**2 for x in range(10) if x % 2 == 0]
Ini menghasilkan:
[0, 4, 16, 36, 64]Model memahami "contoh yang lebih kompleks" mengacu pada contoh list comprehension yang lebih kompleks karena kami mengirimkan riwayat percakapan lengkap.
Menggunakan InMemoryChatMessageHistory
Membangun daftar pesan secara manual menjadi merepotkan seiring percakapan berkembang. InMemoryChatMessageHistory LangChain menyederhanakan ini dengan menyediakan metode untuk menambahkan pesan dan mengambil riwayat lengkap.
Metode kunci:
add_message(message): Menambahkan satu pesan (HumanMessage, AIMessage, SystemMessage)add_messages(messages): Menambahkan beberapa pesan sekaligusmessages: Properti yang mengembalikan daftar lengkap pesanclear(): Menghapus semua pesan (berguna untuk memulai dari awal)
Contoh:
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
# Buat penyimpanan riwayat pesan
history = InMemoryChatMessageHistory()
# Tambahkan beberapa pesan sekaligus
history.add_messages([
SystemMessage(content="Kamu adalah tutor Python yang membantu."),
HumanMessage(content="Apa itu decorator di Python?")
])
# Tambahkan pesan satu per satu
history.add_message(AIMessage(content="Decorator adalah fungsi yang memodifikasi perilaku fungsi lain..."))
history.add_message(HumanMessage(content="Bisakah kamu tunjukkan contoh?"))
# Ambil semua pesan
messages = history.messagesContoh Praktis: Manajemen State dengan InMemoryChatMessageHistory:
from langchain_openai import ChatOpenAI
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import SystemMessage, HumanMessage
llm = ChatOpenAI(model="gpt-4o-mini")
history = InMemoryChatMessageHistory()
# Atur pesan sistem dan tambahkan ke riwayat (dilakukan sekali di awal)
system_msg = SystemMessage(content="Kamu adalah tutor Python yang membantu.")
history.add_message(system_msg)
# Fungsi chat
def chat(user_input):
"""Proses input pengguna, kirim ke LLM, dan kelola riwayat percakapan secara otomatis"""
# Tambahkan input pengguna ke riwayat
history.add_message(HumanMessage(content=user_input))
# Kirim input pengguna bersama dengan riwayat percakapan sebelumnya
response = llm.invoke(history.messages)
# Tambahkan respons model ke riwayat
history.add_message(response)
return response.content
# Simulasi percakapan
print(chat("Apa itu fungsi lambda?"))
print(chat("Tunjukkan contohnya")) # Model mengingat konteks
print(chat("Apa perbedaannya dari fungsi biasa?")) # Masih mengingatOutput:
Fungsi lambda adalah fungsi anonim yang didefinisikan dengan kata kunci lambda...
Berikut contohnya: square = lambda x: x**2
Anda bisa menggunakannya seperti: square(5) # Mengembalikan 25
Fungsi lambda terbatas pada satu ekspresi, sedangkan fungsi biasa...Fungsi chat() menangani manajemen state secara otomatis: ia mengirimkan setiap request pengguna bersama dengan riwayat percakapan sebelumnya ke LLM, dan menambahkan kembali request dan respons ke riwayat. Cukup memanggil chat() mempertahankan state percakapan tanpa manajemen tambahan.
Refactoring Bab 3: Menambahkan Memori ke Chat CLI Anda
Mari ambil streaming CLI chat dari Bab 3 dan tambahkan memori percakapan. Berikut adalah versi stateless asli:
# chapter3_cli.py (asli - stateless)
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
llm = ChatOpenAI(model="gpt-4o-mini")
def chat_loop():
print("Chat dimulai. Ketik 'quit' untuk keluar.\n")
while True:
user_input = input("Anda: ")
if user_input.lower() == "quit":
break
# Stateless - tidak ada riwayat
response = llm.stream([HumanMessage(content=user_input)])
print("AI: ", end="", flush=True)
for chunk in response:
print(chunk.content, end="", flush=True)
print("\n")
if __name__ == "__main__":
chat_loop()Versi yang direfaktor dengan memori:
# chapter8_cli.py (dengan memori)
from langchain_openai import ChatOpenAI
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
llm = ChatOpenAI(model="gpt-4o-mini")
def chat_loop():
print("Chat dimulai. Ketik 'quit' untuk keluar.\n")
# Buat riwayat dan tambahkan pesan sistem
history = InMemoryChatMessageHistory()
history.add_message(SystemMessage(content="Kamu adalah asisten yang membantu."))
while True:
user_input = input("Anda: ")
if user_input.lower() == "quit":
break
# Tambahkan pesan pengguna ke riwayat
history.add_message(HumanMessage(content=user_input))
# Stream respons
print("AI: ", end="", flush=True)
full_response = ""
for chunk in llm.stream(history.messages):
print(chunk.content, end="", flush=True)
full_response += chunk.content
print("\n")
# Tambahkan respons AI ke riwayat
history.add_message(AIMessage(content=full_response))
if __name__ == "__main__":
chat_loop()Apa yang berubah:
- Menambahkan penyimpanan riwayat: Membuat instance
InMemoryChatMessageHistory()di dalamchat_loop() - Pesan sistem: Ditambahkan ke riwayat sekali di awal
- Kirim input pengguna dengan riwayat:
llm.stream(history.messages)menyertakan percakapan sebelumnya - Lacak percakapan: Tambahkan input pengguna dan respons LLM ke riwayat
Menguji chat yang direfaktor:
Chat dimulai. Ketik 'quit' untuk keluar.
Anda: Nama saya Alice
AI: Senang bertemu dengan Anda, Alice! Bagaimana saya bisa membantu Anda hari ini?
Anda: Siapa nama saya?
AI: Nama Anda adalah Alice.
Anda: Apa yang baru saja saya tanyakan?
AI: Anda bertanya kepada saya siapa nama Anda.
Anda: quitModel sekarang mempertahankan konteks di seluruh percakapan. Ia mengingat nama Anda, pertanyaan sebelumnya, dan dapat merujuk bagian awal dialog.
Penyimpanan Persisten: Melampaui Opsi In-Memory
InMemoryChatMessageHistory nyaman untuk pengembangan lokal, tetapi beralih ke produksi memerlukan penggantiannya dengan solusi penyimpanan yang menjamin persistensi.
Keterbatasan Teknis InMemory:
- RAM yang Volatile: Ketika proses server berakhir atau restart, semua riwayat percakapan yang disimpan di memori segera dihapus. Update atau pemulihan error mengakibatkan kehilangan konteks pengguna sepenuhnya.
- Tidak ada horizontal scaling: Saat layanan Anda berkembang ke beberapa instance server, setiap server mempertahankan memorinya sendiri yang terisolasi. Pengguna yang terhubung ke server berbeda tidak dapat berbagi riwayat percakapan.
- Inefisiensi Sumber Daya: Menyimpan semua riwayat percakapan di RAM memakan banyak memori dan mengancam stabilitas sistem seiring pengguna konkuren meningkat.
Alternatif Profesional:
PostgresChatMessageHistory(Direkomendasikan): Pilihan yang paling kuat dan banyak diadopsi. Menggunakan PostgreSQL untuk penyimpanan permanen dan unggul dalam query kompleks dan analisis data.SQLChatMessageHistory: Memanfaatkan database SQL yang ada seperti MySQL. Memungkinkan Anda menggunakan infrastruktur saat ini tanpa perubahan.RedisChatMessageHistory: Ideal untuk layanan di mana kecepatan respons sangat penting. Berbasis memori dengan opsi persistensi, khusus untuk penanganan traffic tinggi.
"Penyimpanan berubah, kode tetap sama"
LangChain menyediakan antarmuka terpadu di semua backend penyimpanan. Metode yang sama yang Anda gunakan dengan InMemoryChatMessageHistory—seperti add_message() dan add_messages()—bekerja identik dengan opsi penyimpanan lain. Ini berarti logika bisnis Anda (kode penanganan percakapan) tidak memerlukan perubahan saat mengganti penyimpanan.
# [Development] In-memory lokal
# from langchain_core.chat_history import InMemoryChatMessageHistory
# history = InMemoryChatMessageHistory()
# [Production] Penyimpanan persisten PostgreSQL
import psycopg
from langchain_postgres import PostgresChatMessageHistory
# Buat koneksi PostgreSQL
sync_connection = psycopg.connect(
"postgresql://user:password@10.1.1.100:5432/agent_db",
autocommit=True,
)
# Tentukan koneksi DB dan session ID
# session_id: Kunci unik untuk mengidentifikasi percakapan
# - Manajemen per-pengguna: session_id = user_id (satu percakapan per pengguna)
# - Manajemen per-sesi: session_id = uuid (ID baru untuk setiap percakapan)
history = PostgresChatMessageHistory(
table_name="chat_history",
session_id="user_123",
sync_connection=sync_connection
)
# --- Antarmuka yang sama terlepas dari tipe penyimpanan ---
history.add_message(HumanMessage(content="Tunjukkan pesanan saya sebelumnya."))
print(history.messages)Penting: Panduan ini menggunakan InMemory untuk kemajuan cepat, tetapi deployment produksi harus beralih ke penyimpanan persisten seperti PostgresChatMessageHistory.
8.3) Mengelola Panjang Percakapan
Fitur memori percakapan yang kita implementasikan di bagian sebelumnya memiliki masalah penting: ia hanya menambahkan pesan ke riwayat. Ini berarti riwayat terus bertambah, yang menciptakan dua masalah utama:
- Biaya: Seluruh riwayat dikirim dengan setiap request, jadi biaya per request terus meningkat seiring riwayat bertambah
- Batas context window: Model memiliki ukuran input maksimum yang dapat mereka proses dalam satu request (misalnya, 400K token untuk GPT-5). Ketika riwayat percakapan melebihi batas ini, model tidak dapat memproses request dengan benar.
Salah satu solusi paling sederhana adalah pola sliding window.
Pola Sliding Window (Simpan Hanya N Pesan Terakhir)
Pola sliding window menyelesaikan dua masalah di atas dengan hanya menyimpan N pesan terbaru dalam riwayat percakapan. Ia mengelola ukuran riwayat dengan membuang pesan lama, memberikan manfaat berikut:
- Kontrol biaya: Dengan menjaga riwayat di bawah ukuran tertentu terlepas dari panjang percakapan, ia mencegah biaya per-request tumbuh tanpa batas
- Tidak ada overflow: Menjaga ukuran input dalam batas maksimum model
Diagram konseptual:
Dengan ukuran window 4, kita hanya menyimpan 4 pesan terbaru (3, 4, 5, 6) dan membuang pesan yang lebih lama (1, 2). Ketika pesan baru (7) tiba, window bergerak menuju pesan terbaru, membuang pesan tertua (3) dan menyimpan pesan 4, 5, 6, 7.
Trade-off Sliding Window:
- Kelebihan: Membatasi ukuran riwayat untuk menjaga biaya konstan dan mencegah overflow context window
- Kekurangan: Pesan di luar ukuran window dibuang, jadi model tidak dapat merujuknya
Trade-off ini bisa menjadi masalah. Solusinya adalah menggunakan sliding window untuk percakapan terbaru sambil mengambil informasi masa lalu yang diperlukan dari penyimpanan terpisah saat dibutuhkan. Ini dapat diimplementasikan menggunakan RAG (Retrieval-Augmented Generation), yang akan kita bahas di Bab 9.
Unit Ukuran Window: Jumlah Pesan vs Jumlah Token
Diagram di atas menunjukkan contoh pengaturan ukuran window berdasarkan jumlah pesan. Namun, dalam lingkungan produksi, ukuran window berbasis token lebih umum digunakan karena ukuran pesan bervariasi:
Trimming Berbasis Jumlah Pesan:
Membatasi ukuran window berdasarkan jumlah pesan (misalnya, simpan hanya 20 pesan terakhir).
- Karakteristik: Jumlah pesan tetap, tetapi jumlah token total masih bisa bervariasi
- Gunakan ketika: Ukuran pesan dikontrol (SMS, chat dengan batasan karakter)
- Risiko: Satu pesan panjang masih bisa melebihi context window
Trimming Berbasis Jumlah Token (Direkomendasikan untuk Produksi):
Membatasi ukuran window berdasarkan jumlah token, unit input yang diproses oleh LLM (misalnya, simpan hanya 5.000 token terakhir).
- Karakteristik: Tidak pernah melebihi context window terlepas dari panjang pesan
- Gunakan ketika: Ukuran pesan bervariasi
Mengimplementasikan Trimming Berbasis Token: trim_messages()
LangChain menyediakan utilitas trim_messages() yang mengimplementasikan pola sliding window berbasis token.
Cara kerja trim_messages():
Fungsi ini mengambil daftar pesan lengkap dan jumlah token maksimum, mengembalikan hanya pesan terbaru yang sesuai dalam batas token.
Parameter kunci:
messages: Daftar pesan untuk di-trimmax_tokens: Jumlah token maksimum untuk dipertahankantoken_counter: Fungsi untuk menghitung jumlah token untuk setiap pesan (menggunakan tokenizer model untuk mengembalikan jumlah token pesan)include_system: Apakah selalu menyimpan SystemMessage (biasanya True)
Mengatur token counter:
import tiktoken
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, SystemMessage
from langchain_core.messages.utils import trim_messages
# Dapatkan tokenizer untuk model Anda (keluarga model berbeda menggunakan tokenizer berbeda)
# Berikan nama model untuk mendapatkan tokenizer yang sesuai
# Model Claude: Gunakan tokenizer Anthropic (tiktoken khusus OpenAI)
enc = tiktoken.encoding_for_model("gpt-4o")
def token_counter(msg: BaseMessage) -> int:
"""Hitung token dalam satu pesan."""
return len(enc.encode(msg.content or ""))
# Buat riwayat percakapan
messages = [
SystemMessage(content="Kamu adalah asisten yang membantu."),
HumanMessage(content="Hai!"),
AIMessage(content="Halo! Bagaimana saya bisa membantu?"),
HumanMessage(content="Berapa 2+2?"),
AIMessage(content="2+2 sama dengan 4."),
HumanMessage(content="Berapa 3+3?"),
AIMessage(content="3+3 sama dengan 6."),
HumanMessage(content="Berapa 4+4?"),
]
# Simpan hanya pesan dalam jumlah token maksimum
trimmed = trim_messages(
messages,
max_tokens=30,
token_counter=token_counter,
include_system=True,
)
print(f"Asli: {len(messages)} pesan")
print(f"Trimmed: {len(trimmed)} pesan")
for m in trimmed:
print(f"{type(m).__name__}: {m.content}")Catatan: Jumlah pesan yang dipertahankan tergantung pada nilai max_tokens dan jumlah token aktual dari setiap pesan. Dalam contoh di atas, max_tokens=30 adalah nilai yang sangat kecil yang dipilih untuk tujuan pengujian. Dalam produksi, Anda harus mengatur nilai yang sesuai dengan mempertimbangkan ukuran pesan rata-rata dan rentang percakapan yang diinginkan.
Contoh: Menerapkan Trimming ke Fungsi Chat
import tiktoken
from langchain_openai import ChatOpenAI
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import SystemMessage, HumanMessage, BaseMessage
from langchain_core.messages.utils import trim_messages
llm = ChatOpenAI(model="gpt-4o-mini")
history = InMemoryChatMessageHistory()
enc = tiktoken.encoding_for_model("gpt-4o-mini")
def token_counter(msg: BaseMessage) -> int:
"""Hitung token dalam satu pesan."""
return len(enc.encode(msg.content or ""))
def chat_with_trimming(user_input: str, max_tokens: int = 1000) -> str:
"""Chat dengan trimming riwayat otomatis."""
history.add_message(HumanMessage(content=user_input))
system_msg = SystemMessage(content="Kamu adalah asisten yang membantu.")
all_messages = [system_msg] + history.messages
# Trim ke jumlah token maksimum
trimmed_messages = trim_messages(
all_messages,
max_tokens=max_tokens,
token_counter=token_counter,
include_system=True,
)
response = llm.invoke(trimmed_messages)
history.add_message(response)
return response.content
# Contoh penggunaan
print(chat_with_trimming("Saya merencanakan perjalanan ke Jepang"))
print(chat_with_trimming("Apa yang harus saya kunjungi di Tokyo?"))
print(chat_with_trimming("Berapa hari saya harus menghabiskan waktu di sana?"))
# Bahkan saat riwayat bertambah, hanya percakapan terbaru dalam jumlah token maksimum yang dikirim ke LLMLangkah Selanjutnya: Keterbatasan Manajemen State Percakapan dan Solusi (Bab 9: RAG)
Memberikan riwayat percakapan ke model membantu mempertahankan konteks percakapan. Namun, ini saja tidak cukup dalam beberapa kasus. Misalnya:
- Ketika Anda perlu menemukan informasi dalam dokumen perusahaan atau manual
- Ketika Anda perlu merujuk riwayat percakapan lama yang telah terdorong keluar dari sliding window
Di sinilah RAG (Retrieval-Augmented Generation) diperlukan. RAG bekerja sebagai berikut:
- Penyimpanan: Simpan informasi dalam database vektor untuk pencarian semantik
- Retrieval: Query informasi dengan makna yang mirip dengan apa yang Anda cari
Jika menggunakan RAG untuk melengkapi keterbatasan pola sliding window:
- Sliding window: Simpan 20 pesan terakhir (konteks terbaru)
- RAG: Cari dan ambil konten yang relevan dari pesan yang terdorong keluar dari window
Daripada hanya mengingat percakapan terbaru, RAG memungkinkan Anda membuat sistem memori jangka panjang.
RAG memungkinkan agen memanfaatkan pengetahuan yang lebih luas dan konteks yang lebih panjang melalui pemanfaatan pengetahuan eksternal dan retrieval percakapan masa lalu.
Bab 9 akan membahas cara mengimplementasikan RAG secara detail.
Ringkasan Bab:
Dalam bab ini, Anda mempelajari:
- Mengapa LLM lupa: Model bersifat stateless—memori adalah ilusi yang diciptakan dengan mengirim ulang riwayat percakapan
- Tipe pesan: SystemMessage (instruksi), HumanMessage (input pengguna), AIMessage (respons model)
- Manajemen state: Mengelola riwayat percakapan dengan
InMemoryChatMessageHistory - Manajemen panjang percakapan: Mengapa riwayat tanpa batas menyebabkan masalah biaya dan context window
- Sliding window: Pola yang hanya menyimpan pesan terbaru untuk mencegah riwayat tumbuh tanpa batas
- Trimming berbasis token: Mengimplementasikan sliding window menggunakan
trim_messages()dan penghitungan token