Sohbet Geçmişi Saklama: LangChain Memory Modülleri

Bir LLM uygulaması geliştirdiğinizde, kullanıcıyla konuşmanın ortasında “sen kimsin?” sorusuna “Merhaba! Ben bir yapay zeka asistanıyım, nasıl yardımcı olabilirim?” cevabını alırsanız, muhtemelen bellek yönetimini atlamışsınızdır. Gerçek anlamda kullanılabilir bir sohbet uygulaması, önceki konuşmaları hatırlayabilmeli, bağlamı koruyabilmeli ve kullanıcıya kişiselleştirilmiş bir deneyim sunabilmelidir. LangChain’in Memory modülleri tam da bu sorunu çözmek için tasarlanmıştır.

Neden Bellek Yönetimi Bu Kadar Kritik?

LLM’ler doğaları gereği stateless’tır. Her API çağrısı bağımsız bir işlemdir ve model önceki ne konuştuğunuzu bilmez. Bu durum, basit bir chatbot senaryosunda bile ciddi sorunlara yol açar. Kullanıcı adını söyledi, sonraki mesajda sordunuz, tekrar söyledi, siz tekrar unuttunuz. Bu deneyim, kullanıcıları hızla sindirir ve uygulamanızı terk etmelerine neden olur.

LangChain Memory modülleri bu problemi birkaç farklı stratejiyle çözer. Kısa vadeli bellek için konuşma geçmişini tutar, uzun vadeli bellek için vektör veritabanlarıyla entegre olur ve hibrit yaklaşımlarla her ikisini birleştirebilirsiniz.

Gerçek dünyadan bir örnek verelim: Bir müşteri destek botu geliştiriyorsunuz. Kullanıcı ilk mesajında sipariş numarasını verdi, sonra ürün hakkında şikayet etti, ardından iade sürecini sordu. Eğer bellek yoksa, her adımda sipariş numarasını tekrar sormak zorunda kalırsınız. Bellek varsa, konuşma boyunca bu bağlamı taşırsınız.

LangChain Memory Ekosistemi

LangChain, farklı kullanım senaryoları için birden fazla Memory sınıfı sunar. Her birinin kendine özgü avantajları ve dezavantajları vardır.

ConversationBufferMemory

En temel memory tipidir. Tüm konuşma geçmişini olduğu gibi saklar ve her prompt’a ekler.

from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="gpt-3.5-turbo",
    temperature=0.7,
    openai_api_key="your-api-key"
)

memory = ConversationBufferMemory(
    memory_key="history",
    return_messages=True
)

chain = ConversationChain(
    llm=llm,
    memory=memory,
    verbose=True
)

# İlk mesaj
response1 = chain.invoke({"input": "Merhaba, ben Ahmet. Bir Python geliştiricisiyim."})
print(response1["response"])

# İkinci mesajda ismi hatırlıyor mu?
response2 = chain.invoke({"input": "Hangi dili kullanıyorum?"})
print(response2["response"])

Bu yapı çok basit ama bir sorunu var: konuşma uzadıkça token sayısı patlar. 10 mesajlık bir konuşma, her yeni mesajda tüm geçmişi prompt’a ekler. Bu hem maliyetli hem de model context window’unu doldurur.

ConversationBufferWindowMemory

Sadece son N mesajı saklar. Kaydırmalı pencere mantığıyla çalışır.

from langchain.memory import ConversationBufferWindowMemory

# Sadece son 5 mesaj çiftini sakla
memory = ConversationBufferWindowMemory(
    k=5,
    memory_key="chat_history",
    return_messages=True
)

# Bellek durumunu manuel kontrol etmek için
memory.save_context(
    {"input": "Redis cluster kurulumu hakkında bilgi ver"},
    {"output": "Redis Cluster, verileri birden fazla node'a dağıtır..."}
)

memory.save_context(
    {"input": "Minimum kaç node gerekiyor?"},
    {"output": "Redis Cluster için minimum 3 master node gereklidir..."}
)

# Mevcut belleği görüntüle
print(memory.load_memory_variables({}))

k parametresi: Kaç konuşma çiftinin (human + AI) saklanacağını belirtir. k=5 derseniz, 10 mesaj (5 soru + 5 cevap) tutulur.

Üretim ortamı için gerçekçi bir senaryo: Bir DevOps asistanı geliştiriyorsunuz. Kullanıcılar genellikle 5-6 mesajlık bağlam içinde çalışır, daha önceki konuşmaları nadiren referans alır. k=10 genellikle yeterlidir ve token maliyetlerini makul tutar.

ConversationSummaryMemory

Uzun konuşmalar için ideal çözümdür. Eski konuşmaları özetler ve özeti saklar. Her mesajda tüm geçmişi taşımak yerine kompakt bir özet kullanır.

from langchain.memory import ConversationSummaryMemory
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-3.5-turbo")

memory = ConversationSummaryMemory(
    llm=llm,
    memory_key="history",
    return_messages=False,
    human_prefix="Kullanıcı",
    ai_prefix="Asistan"
)

# Birkaç mesaj ekleyelim
memory.save_context(
    {"input": "Kubernetes cluster'ımda pod'lar sürekli CrashLoopBackOff hatası veriyor"},
    {"output": "Bu sorunu çözmek için önce kubectl describe pod <pod-name> komutunu çalıştırın ve Events bölümüne bakın."}
)

memory.save_context(
    {"input": "Events bölümünde OOMKilled yazıyor"},
    {"output": "Bu, pod'un bellek limitini aştığı anlamına gelir. resources.limits.memory değerini artırmanız gerekiyor."}
)

memory.save_context(
    {"input": "Memory limiti 512Mi, bunu 1Gi yaparsam yeterli olur mu?"},
    {"output": "Önce gerçek bellek kullanımını kubectl top pod komutuyla ölçün, sonra limit belirleyin."}
)

# Özeti görüntüle
variables = memory.load_memory_variables({})
print(variables["history"])

Bu yaklaşımın dezavantajı, özet oluşturmak için ek LLM çağrısı yapmasıdır. Yani her konuşmada biraz daha token harcar. Ama uzun vadede, saf buffer yaklaşımına göre çok daha ekonomiktir.

ConversationSummaryBufferMemory

İki yaklaşımın en iyi kombinasyonudur. Belirli bir token limitine kadar tüm mesajları tutar, limit aşılınca eski mesajları özetler.

from langchain.memory import ConversationSummaryBufferMemory

memory = ConversationSummaryBufferMemory(
    llm=llm,
    max_token_limit=500,  # Bu limiti geçince özetlemeye başlar
    memory_key="history",
    return_messages=True
)

chain = ConversationChain(
    llm=llm,
    memory=memory,
    verbose=False
)

# Uzun bir teknik konuşma simüle ediyoruz
konusma_konulari = [
    "Nginx load balancer konfigürasyonu hakkında yardım lazım",
    "Upstream server'ların health check konfigürasyonu nasıl yapılır?",
    "SSL termination için en iyi practice nedir?",
    "Rate limiting eklemek istiyorum, nasıl yapabilirim?",
    "Log formatını JSON olarak ayarlamak için ne yapmalıyım?"
]

for konu in konusma_konulari:
    response = chain.invoke({"input": konu})
    print(f"Soru: {konu}")
    print(f"Cevap: {response['response'][:100]}...")
    print(f"Token kullanımı: {memory.moving_summary_buffer}")
    print("---")

Redis ile Kalıcı Bellek

Yukarıdaki tüm örnekler in-memory çalışır. Uygulama yeniden başlatıldığında veya farklı bir process’te çalıştırıldığında tüm bellek uçar. Production ortamı için Redis gibi kalıcı bir backend şarttır.

# Redis kurulumu (Ubuntu/Debian)
sudo apt update
sudo apt install redis-server

# Redis'i etkinleştir ve başlat
sudo systemctl enable redis-server
sudo systemctl start redis-server

# Test et
redis-cli ping
# PONG dönmeli

# Python kütüphanelerini kur
pip install langchain langchain-openai redis langchain-community
from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain

# Her kullanıcı oturumu için benzersiz bir session_id kullanıyoruz
session_id = "user_ahmet_session_001"

# Redis'e bağlan
message_history = RedisChatMessageHistory(
    session_id=session_id,
    url="redis://localhost:6379",
    ttl=86400  # 24 saat sonra otomatik sil
)

memory = ConversationBufferMemory(
    chat_memory=message_history,
    memory_key="history",
    return_messages=True
)

llm = ChatOpenAI(model="gpt-3.5-turbo")

chain = ConversationChain(
    llm=llm,
    memory=memory
)

# Bu konuşma Redis'e kaydedilir
response = chain.invoke({"input": "Merhaba! Sunucu izleme sistemleri hakkında konuşmak istiyorum."})
print(response["response"])

# Uygulamayı yeniden başlatsan bile bu oturum devam eder
# session_id aynı olduğu sürece geçmiş korunur

Gerçek dünya senaryosu: Bir IT destek portalı geliştiriyorsunuz. Her kullanıcı kendi ticket’ını açıp çözüm sürecinde AI asistanla konuşuyor. Session ID olarak ticket ID’sini kullanabilirsiniz. Böylece kullanıcı birkaç gün sonra aynı ticket’a dönse bile konuşma geçmişi korunur.

Vektör Tabanlı Uzun Vadeli Bellek

Bazı durumlarda sadece son birkaç mesajı değil, aylar önce yapılan konuşmalardan bilgi çekmeniz gerekir. Bu durumda vektör veritabanları devreye girer.

from langchain.memory import VectorStoreRetrieverMemory
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
import chromadb

# ChromaDB kurulumu
# pip install chromadb

embeddings = OpenAIEmbeddings()

# Kalıcı ChromaDB oluştur
chroma_client = chromadb.PersistentClient(path="./memory_db")

vectorstore = Chroma(
    client=chroma_client,
    collection_name="conversation_memory",
    embedding_function=embeddings
)

retriever = vectorstore.as_retriever(
    search_kwargs={"k": 3}  # En ilgili 3 sonucu getir
)

memory = VectorStoreRetrieverMemory(
    retriever=retriever,
    memory_key="relevant_history",
    input_key="input"
)

# Geçmiş bilgileri kaydet
memory.save_context(
    {"input": "Prometheus kurulumunu tamamladım"},
    {"output": "Harika! Prometheus varsayılan olarak 9090 portunda çalışır."}
)

memory.save_context(
    {"input": "Grafana'yı da kurdum, dashboard oluşturdum"},
    {"output": "Grafana için Prometheus data source eklemek için Settings > Data Sources bölümüne gidin."}
)

memory.save_context(
    {"input": "Alertmanager konfigürasyonunu da bitirdim"},
    {"output": "Alertmanager için Slack veya email notification konfigüre edebilirsiniz."}
)

# Aylar sonra benzer bir konu geldiğinde ilgili geçmişi çeker
relevant = memory.load_memory_variables({"input": "monitoring stack kurulumu"})
print(relevant["relevant_history"])

Özel Bellek Yapısı Oluşturma

Bazen standart memory sınıfları yetmez. Örneğin, kullanıcı profilini, tercihlerini ve teknik seviyesini ayrı ayrı takip etmek isteyebilirsiniz.

from langchain.memory import ConversationBufferMemory
from langchain.schema import BaseMemory
from typing import Dict, List, Any
from pydantic import BaseModel, Field

class UserProfileMemory(BaseMemory):
    """Kullanıcı profili ve konuşma geçmişini birleştiren özel bellek sınıfı"""
    
    user_profile: Dict[str, Any] = Field(default_factory=dict)
    conversation_history: List[Dict] = Field(default_factory=list)
    max_history: int = 10
    
    @property
    def memory_variables(self) -> List[str]:
        return ["history", "user_profile"]
    
    def load_memory_variables(self, inputs: Dict) -> Dict:
        # Son N mesajı al
        recent_history = self.conversation_history[-self.max_history:]
        
        history_text = ""
        for msg in recent_history:
            history_text += f"Kullanıcı: {msg['human']}n"
            history_text += f"Asistan: {msg['ai']}nn"
        
        # Profil bilgisini düzenli formatta oluştur
        profile_text = ""
        if self.user_profile:
            profile_text = "Kullanıcı Profili:n"
            for key, value in self.user_profile.items():
                profile_text += f"- {key}: {value}n"
        
        return {
            "history": history_text,
            "user_profile": profile_text
        }
    
    def save_context(self, inputs: Dict, outputs: Dict) -> None:
        human_message = inputs.get("input", "")
        ai_message = outputs.get("output", "")
        
        self.conversation_history.append({
            "human": human_message,
            "ai": ai_message
        })
        
        # Basit profil çıkarımı (gerçekte LLM ile yapabilirsiniz)
        if "python" in human_message.lower():
            self.user_profile["preferred_language"] = "Python"
        if "linux" in human_message.lower():
            self.user_profile["os_preference"] = "Linux"
        if "junior" in human_message.lower() or "yeni başlıyorum" in human_message.lower():
            self.user_profile["experience_level"] = "junior"
        elif "senior" in human_message.lower() or "yıldır" in human_message.lower():
            self.user_profile["experience_level"] = "senior"
    
    def clear(self) -> None:
        self.conversation_history.clear()
        self.user_profile.clear()


# Kullanım örneği
custom_memory = UserProfileMemory(max_history=5)

custom_memory.save_context(
    {"input": "Merhaba, 10 yıldır Linux sysadmin olarak çalışıyorum"},
    {"output": "Hoş geldiniz! Deneyimli bir sysadmin olduğunuzu görüyorum."}
)

custom_memory.save_context(
    {"input": "Python ile otomasyon scriptleri yazıyorum"},
    {"output": "Python, sysadmin otomasyonu için mükemmel bir seçim."}
)

print(custom_memory.load_memory_variables({}))

LCEL ile Modern Bellek Yönetimi

LangChain’in yeni Expression Language (LCEL) yaklaşımıyla bellek yönetimi daha esnek hale geldi.

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-3.5-turbo")

# Prompt template
prompt = ChatPromptTemplate.from_messages([
    ("system", "Sen deneyimli bir Linux sistem yöneticisi asistanısın. Türkçe cevap ver."),
    MessagesPlaceholder(variable_name="history"),
    ("human", "{input}")
])

# Temel chain
chain = prompt | llm

# Session storage
session_store = {}

def get_session_history(session_id: str) -> ChatMessageHistory:
    if session_id not in session_store:
        session_store[session_id] = ChatMessageHistory()
    return session_store[session_id]

# Bellek ile sarılmış chain
chain_with_history = RunnableWithMessageHistory(
    chain,
    get_session_history,
    input_messages_key="input",
    history_messages_key="history"
)

# Kullanım
config = {"configurable": {"session_id": "sysadmin_session_42"}}

response1 = chain_with_history.invoke(
    {"input": "Disk doluluk oranını nasıl izleyebilirim?"},
    config=config
)
print(response1.content)

response2 = chain_with_history.invoke(
    {"input": "Peki bu izleme için alert nasıl kurarım?"},
    config=config
)
print(response2.content)

# Aynı session_id ile farklı bir yerden bağlanırsanız geçmiş korunur
response3 = chain_with_history.invoke(
    {"input": "Az önce bahsettiğin yöntemle ilgili daha fazla detay verir misin?"},
    config=config
)
print(response3.content)

Production Ortamı için En İyi Pratikler

Üretim ortamına geçmeden önce dikkat edilmesi gereken bazı kritik noktalar vardır.

Token maliyeti yönetimi: ConversationBufferMemory kullanıyorsanız, uzun konuşmalarda token maliyeti hızla artar. ConversationSummaryBufferMemory ile max_token_limit parametresini dikkatli belirleyin. Genellikle 1000-1500 token makul bir sınırdır.

Session izolasyonu: Farklı kullanıcıların belleklerinin birbirine karışmaması kritik öneme sahiptir. Her kullanıcı için benzersiz session_id kullanın. UUID, kullanıcı ID + timestamp kombinasyonu veya ticket numarası gibi değerler kullanabilirsiniz.

TTL yönetimi: Redis kullanıyorsanız mutlaka TTL (time-to-live) belirleyin. Kullanılmayan session’lar zamanla birikir ve bellek sorununa yol açar.

  • Kısa vadeli destek botları için 24 saat TTL yeterlidir
  • Proje bazlı asistanlar için 30 gün mantıklıdır
  • Kalıcı kişisel asistanlar için TTL yerine vektör veritabanı tercih edin

Hassas veri yönetimi: Konuşma geçmişi hassas bilgiler içerebilir. Redis’i mutlaka şifreli bağlantıyla kullanın, production’da plaintext Redis bağlantısı ciddi güvenlik riski oluşturur. Ayrıca kişisel verileri memory’ye kaydetmeden önce KVKK/GDPR uyumluluğunu kontrol edin.

Hata yönetimi: Redis’e bağlanamadığınızda veya vektör veritabanı yanıt vermediğinde ne olacak? Bellek başarısız olsa bile temel uygulama çalışmaya devam etmeli. Fallback olarak in-memory çözüme geçebilen bir wrapper yazmanız önerilir.

Bellek boyutu izleme: Uzun süreli çalışan uygulamalarda bellek boyutunu izleyin. Özellikle ConversationBufferMemory’de her konuşma belleğe yığılır. Prometheus ile memory_token_count gibi custom metric’ler ekleyebilirsiniz.

Sonuç

LangChain Memory modülleri, tek seferlik sorgu-cevap modelinden gerçek anlamda bağlam farkında olan konuşma uygulamalarına geçişin anahtarıdır. Hangi memory tipini seçeceğiniz kullanım senaryonuza bağlıdır.

Basit bir chatbot için ConversationBufferWindowMemory ile başlayın, k=10 genellikle başlangıç için yeterlidir. Uzun ve karmaşık teknik konuşmalar için ConversationSummaryBufferMemory’yi deneyin. Çok kullanıcılı, session bazlı uygulamalar için Redis backend’iyle LCEL yaklaşımı en sağlam çözümdür. Uzun vadeli bilgi birikimi gerektiren uygulamalar için vektör veritabanı tabanlı memory’yi göz önünde bulundurun.

En önemli nokta şudur: Geliştirme aşamasında in-memory çözümler işe yarar, ama production’a geçerken mutlaka kalıcı bir backend seçin. Aksi takdirde uygulama her yeniden başlatıldığında kullanıcılar sıfırdan başlamak zorunda kalır ve bu, modern bir AI uygulaması için kabul edilemez bir kullanıcı deneyimidir. Token maliyetlerini göz önünde bulundurun, session izolasyonunu doğru yapın ve hassas verileri koruyun. Bu temelleri doğru attığınızda, gerçekten akıllı ve kullanılabilir konuşma uygulamaları geliştirebilirsiniz.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir