Yapay Zeka Ajanınız Neden Hafızasını Kaybetmeye Devam Ediyor (Ve Bunu Nasıl Çözdük?)
Yapay zeka ajanınız bir kullanıcıyla yeni 30 dakikalık bir görüşme yaptı. Proje gereksinimlerini tartıştılar, tercihleri paylaştılar, kararlar aldılar. Ardından kullanıcı, yeni bir oturum başlatmak için /new yazdı.
Ajan, bu konuşmayı uzun vadeli belleğe aktarmaya (consolidate) çalışır. LLM çağrısı başarısız olur. Hız sınırı (rate limit). Zaman aşımı (timeout). Veya model, gerekli tool'u çağırmak yerine metin döndürür.
Hafıza gitti. Otuz dakikalık bağlam (context) buharlaştı.
Bu, düşündüğünüzden daha sık gerçekleşiyor. Bunu LemonClaw örneklerimizde takip ettik: Hafıza birleştirme (memory consolidation), herhangi bir tek modelde yaklaşık %15 başarısızlık oranına sahipti. Görünmez bir altyapı olması gereken bir özellik için bu kabul edilemez.
Diğer Framework'ler Bunu Nasıl Ele Alıyor (Almıyorlar)
Çoğu yapay zeka ajanı framework'ü, hafıza birleştirmeyi basit bir LLM çağrısı olarak görür. Çalışırsa harika. Çalışmazsa hafıza kaybolur.
En popüler açık kaynaklı ajan framework'ü olan OpenClaw, birleştirme için konuşma ile aynı modeli kullanır. Sadece kullanıcının asla görmeyeceği bir sohbeti özetlemek için 0,003 $ maliyetli ve 8+ saniye süren bir Claude Sonnet çağrısı yapılır. Bu çağrı başarısız olduğunda (hız sınırı, zaman aşımı, model hatası), framework bir uyarı günlüğü (log) tutar ve devam eder. Kullanıcının bağlamı yok olmuştur.
Bir diğer popüler framework olan nanobot da aynı mimariye sahiptir. Tek model, tek deneme, fallback (yedek plan) yok. Birleştirme fonksiyonunun bir zaman aşımı süresi bile yoktur. Yavaş bir upstream (Cloudflare 524 hataları yaygındır), bağlantı kopana kadar tüm oturumu bloke eder.
Hiçbir framework birleştirmeyi ana modelden ayırmaz. Hiçbirinin hafıza işlemleri için fallback mantığı yoktur. Hiçbiri "API çağrısı başarısız oldu" ile "API çağrısı başarılı oldu ama model istediğimizi yapmadı" arasındaki farkı ayırt etmez.
Bunlar uç durumlar (edge cases) değil. Herhangi bir tek modeldeki %15'lik başarısızlık oranıyla, günde 100 birleştirme işlemi yürüten bir framework bunlardan 15'inde hafıza kaybı yaşar. Bir hafta içinde bu, ajanın her şeyi unuttuğu 105 konuşma demektir.
Sorun, Yeniden Deneme (Retry) Mantığından Daha Derin
Bariz çözüm, üstel geri çekilme (exponential backoff) ile yeniden denemedir. Bizde bu vardı. Geçici HTTP hatalarını düzgünce hallediyordu:
# Yeniden deneme döngüsü: 1s → 2s → 4s geri çekilme
for attempt in range(3):
try:
response = await acompletion(**kwargs)
return await self._collect_stream(response)
except (RateLimitError, APIConnectionError) as e:
await asyncio.sleep(RETRY_DELAYS[attempt])
bu 429'ları ve ağ aksaklıklarını yakalar. Ancak iki hata modu aradan sızar:
Hata modu 1: Model tool calling yapamıyor. Bazı modeller, özellikle hızlı çıkarım (inference) motorlarında çalışan daha küçük olanlar, karmaşık prompt'larda bazen geçerli fonksiyon çağrıları üretemez. API, içinde MidStreamFallbackError barındıran bir ServiceUnavailableError ile 200 döner. Yeniden deneme mantığınız bir istisna (exception) görür, aynı modeli tekrar dener ve aynı hatayı alır.
Hata modu 2: Model "başarılı" olur ama tool'u çağırmaz. LLM mükemmel derecede geçerli bir yanıt döner. HTTP 200. Hata yok. Ancak yapılandırılmış verilerle save_memory çağırmak yerine, düz metin bir özet yazar. Yeniden deneme motorunuz bunu başarı olarak kabul eder. Birleştirme fonksiyonu tool çağrılarını kontrol eder, hiç bulamaz ve pes eder.
İkinci hata modu sinsi olandır. Taşıma katmanı (transport layer) her şeyin çalıştığını düşünür. İş katmanı (business layer) çalışmadığını bilir. Hiçbir HTTP düzeyindeki yeniden deneme, tool schema'nızı anlamayan bir modeli düzeltemez.
Çift Katmanlı Fallback Mimarisi
Bunu, farklı seviyelerde çalışan iki bağımsız fallback döngüsüyle çözdük:
Kullanıcı /new gönderir
│
▼
consolidate() ─── İş Katmanı Fallback'i
│ "Model save_memory'yi çağırdı mı?"
│ Hayır → zincirdeki bir sonraki modeli dene
│
▼
_chat_with_retry() ─── Taşıma Katmanı Fallback'i
│ HTTP hataları → üstel geri çekilme
│ Tüm denemeler tükendi → fallback zincirini tara
│
▼
MODEL_MAP fallback zinciri:
llama-3.3-70b ─$0.59/M─→ qwen3-32b ─$0.29/M─→ llama-4-scout ─$0.11/M─→ gpt-4.1-mini ─→ claude-haiku
(394 TPS) (662 TPS) (594 TPS) (güvenilir) (son çare)
Katman 1 taşıma hatalarını yönetir. Katman 2 iş mantığı hatalarını yönetir. Fallback zinciri her iki katman arasında paylaşılır ve merkezi bir katalogda bir kez tanımlanır.
Bu, "aynı modeli yeniden dene" yaklaşımından temelden farklıdır. Bir model bir tool'u çağırmayı başaramadığında, onu aynı prompt ile yeniden denemek nadiren işe yarar. Farklı ağırlıklara ve farklı tool calling davranışlarına sahip farklı bir modele geçmek ise işe yarar.
Model Kataloğu: Tek Gerçeklik Kaynağı
Kataloğumuzdaki her modelin, denenecek bir sonraki modeli gösteren isteğe bağlı bir fallback alanı vardır:
@dataclass(frozen=True)
class ModelEntry:
id: str
label: str
tier: str
description: str
fallback: str | None = None
hidden: bool = False # Kullanıcıya yönelik /model listesinde gizli
MODEL_CATALOG = [
# Kullanıcı tarafından görülebilen modeller (kullanıcıların geçiş yapabileceği 16 model)
ModelEntry("claude-sonnet-4-6", "Claude Sonnet 4.6", "standard",
"Önerilen", fallback="claude-sonnet-4-5"),
ModelEntry("gpt-4.1-mini", "GPT-4.1 Mini", "economy",
"Kararlı tool calling", fallback="claude-haiku-4-5"),
# Gizli birleştirme modelleri (sadece dahili kullanım)
ModelEntry("llama-3.3-70b-versatile", "Llama 3.3 70B (Groq)", "economy",
"394 TPS", fallback="qwen3-32b", hidden=True),
ModelEntry("qwen3-32b", "Qwen3 32B (Groq)", "economy",
"662 TPS", fallback="llama-4-scout-17b-16e-instruct", hidden=True),
# ...
]
hidden=True bayrağı, dahili modelleri kullanıcıya yönelik /model komutundan uzak tutarken hala fallback zincirlerine katılmalarını sağlar. Kullanıcılar geçiş yapabilecekleri 16 model görürler. Sistem ise 19 model kullanır. Üç gizli model, sadece hız ve maliyetin konuşma kalitesinden daha önemli olduğu hafıza birleştirme gibi arka plan görevleri için mevcuttur.
Bu katalog, tüm model yönlendirmeleri için tek gerçeklik kaynağıdır. Fallback zincirine yeni bir model eklemek, tek bir satır eklemek anlamına gelir. Senkronize edilecek yapılandırma dosyaları, güncellenecek ortam değişkenleri veya değiştirilecek dağıtım scriptleri yoktur.
Taşıma Katmanı: Döngü Tespiti ile Zincirleme Fallback
Yeniden deneme motoru, sonsuz döngüleri önlemek için bir ziyaret edilenler (visited) kümesi kullanarak fallback zincirini tarar:
async def _chat_with_retry(self, kwargs, original_model):
# Aşama 1: Birincil modelde üstel geri çekilme
for attempt in range(3):
try:
response = await acompletion(**kwargs)
return await self._collect_stream(response)
except (RateLimitError, APIConnectionError, APIError) as e:
await asyncio.sleep(RETRY_DELAYS[attempt])
except AuthenticationError:
return LLMResponse(content="API anahtarı geçersiz.", finish_reason="error")
# Aşama 2: Fallback zincirini tara
visited = {original_model}
current = original_model
while True:
entry = MODEL_MAP.get(current)
if not entry or not entry.fallback or entry.fallback in visited:
break
current = entry.fallback
visited.add(current)
# Bu model için doğru gateway'i çöz
gw = self._resolve_gateway_for_model(current)
resolved = self._resolve_model(current, gateway=gw)
fb_kwargs = {**kwargs, "model": resolved}
# Hedef modelin protokolü için api_base'i düzelt
if gw and gw.default_api_base:
fb_kwargs["api_base"] = gw.default_api_base
try:
response = await acompletion(**fb_kwargs)
return await self._collect_stream(response)
except Exception:
continue # Zincirdeki bir sonrakini dene
return LLMResponse(content="Hizmet kullanılamıyor.", finish_reason="error")
visited kümesi kritiktir. O olmadan, A→B→A gibi bir zincir sonsuza kadar dönerdi. Onunla birlikte, motor her modeli tam olarak bir kez dener.
Gateway çözünürlüğü de önemlidir. Farklı modeller farklı API formatlarına ihtiyaç duyar. Claude modelleri bir Anthropic formatlı gateway üzerinden ( /v1 soneki olmadan) yönlendirilir. GPT modelleri OpenAI uyumlu bir gateway ( /v1 ile) üzerinden yönlendirilir. Groq modelleri ise bambaşka bir endpoint kullanır. Fallback motoru, zincirdeki her model için doğru gateway'i çözerek, OpenAI endpoint'ine Anthropic istekleri göndermek gibi protokol uyuşmazlıklarını önler.
Bu, çoğu framework'ün tamamen görmezden geldiği bir detaydır. Tüm modellerin aynı protokolü konuştuğunu varsayarlar. Üretim ortamında, 4 farklı API formatında 19 modelle, bu varsayım anında çöker.
İş Katmanı: Tool Call Doğrulaması
Birleştirme fonksiyonu, üzerine kendi fallback döngüsünü ekler:
async def consolidate(self, session, provider, model, **kwargs):
visited = set()
current_model = model
while current_model and current_model not in visited and len(visited) <= 3:
visited.add(current_model)
response = await asyncio.wait_for(
provider.chat(messages=messages, tools=SAVE_MEMORY_TOOL, model=current_model),
timeout=30,
)
if response.has_tool_calls:
# Başarı: hafızayı ayıkla ve kaydet
args = response.tool_calls[0].arguments
self.write_long_term(args["memory_update"])
self.append_history(args["history_entry"])
return True
# Model tool'u çağırmadı — zincirdeki bir sonrakini dene
entry = MODEL_MAP.get(current_model)
next_model = entry.fallback if entry else None
if next_model and next_model not in visited:
current_model = next_model
continue
return False # Başka fallback kalmadı
return False
Bu, _chat_with_retry fonksiyonunun başarılı bir yanıt (HTTP 200, geçerli içerik) döndürdüğü ancak modelin tool'u kullanmadığı durumları yakalar. Birleştirme fonksiyonu has_tool_calls kontrolü yapar ve eksikse zincirdeki bir sonraki modele geçer.
Zaman aşımı sarmalayıcısı (asyncio.wait_for) da fallback'i tetikler. Eğer bir model 30 saniyeden uzun sürerse (yavaş upstream'lerdeki Cloudflare 524 hatalarında yaygındır), fonksiyon TimeoutError'u yakalar ve kullanıcının oturumunu süresiz olarak bloke etmek yerine bir sonraki modeli dener.
Birleştirme İçin Neden Groq?
Hafıza birleştirme bir arka plan görevidir. Kullanıcı çıktıyı görmez. Sadece çalışmasına ihtiyaç duyarlar. Bu, onu hızlı ve ucuz modeller için mükemmel bir aday yapar.
Çoğu framework her şey için aynı pahalı modeli kullanır. Konuşma için Claude Sonnet çalıştırıyorsanız, hafıza birleştirme için de Claude Sonnet çalıştırıyorsunuz demektir. Bu, hiçbir insanın asla okumadığı bir çıktı üreten bir görev için giriş token başına 3 $/M ve birleştirme başına 8+ saniye demektir.
Birleştirmeyi konuşma modelinden tamamen ayırdık. Konuşma, kullanıcının seçtiği modeli kullanır. Birleştirme ise Groq üzerinde barındırılan özel bir model zinciri kullanır:
| Model | Hız | Giriş Maliyeti | Çıkış Maliyeti |
|---|---|---|---|
| llama-3.3-70b-versatile | 394 TPS | $0.59/M | $0.79/M |
| qwen3-32b | 662 TPS | $0.29/M | $0.59/M |
| llama-4-scout-17b-16e | 594 TPS | $0.11/M | $0.34/M |
| gpt-4.1-mini (önceki) | ~150 TPS | $0.40/M | $1.60/M |
Birincil model (llama-3.3-70b), 60 mesajlık bir oturumu yaklaşık 5 saniyede birleştirir. Önceki varsayılan (gpt-4.1-mini) 8+ saniye sürüyordu. Birleştirme başına maliyet yaklaşık 0,003 dolardan yaklaşık 0,001 dolara düştü.
Dezavantajı: Groq modelleri karmaşık prompt'larda daha az güvenilir tool calling performansına sahiptir. Çift katmanlı fallback tam da bu yüzden var. llama-3.3-70b tool'u çağıramadığında qwen3-32b devreye girer. O da başarısız olursa llama-4-scout dener. Eğer üç Groq modeli de başarısız olursa, gpt-4.1-mini bunu neredeyse %100 tool calling güvenilirliği ile halleder.
Üretim ortamında, birincil modelin vakaların yaklaşık %85'inde başarılı olduğunu görüyoruz. Zincir, birleştirmelerin %2'sinden azında gpt-4.1-mini'ye ulaşır. Toplam başarısızlık oranı: etkili bir şekilde sıfır.
Üretim Sonuçları
Bunu iki LemonClaw örneğine uyguladık ve gerçek Telegram konuşmalarıyla test ettik.
İlk dağıtım (sadece tek katmanlı fallback):
Hafıza birleştirme (archive_all): 56 mesaj
llama-3.3-70b-versatile → "Fonksiyon çağrılamadı"
Fallback yapılıyor → qwen3-32b
qwen3-32b: LLM save_memory'yi çağırmadı, atlanıyor
→ "Hafıza arşivleme başarısız oldu, oturum temizlenmedi."
Taşıma katmanı ilk hatayı yakaladı ve fallback yaptı. Ancak qwen3-32b tool'u çağırmadan metin döndürdü. Tek katmanlı fallback bunu halledemedi. Bu, diğer tüm framework'lerin sessizce hafıza kaybedeceği senaryonun ta kendisidir.
İkinci dağıtım (çift katmanlı fallback):
Hafıza birleştirme (archive_all): 60 mesaj
model=llama-3.3-70b-versatile → başarılı
Hafıza birleştirme tamamlandı: 60 mesaj kaldı
Aynı model, aynı mesaj hacmi. Bu sefer ilk denemede çalıştı. Tool calling hatasının aralıklı doğası, neden tek bir yedek model yerine bir fallback zincirine ihtiyacınız olduğunun tam olarak nedenidir.
Birincil model başarısız olduğunda, zincir onu yakalar:
llama-3.3-70b → tool çağrısı başarısız
→ consolidate() fallback → qwen3-32b
→ qwen3-32b tool'u çağırmadı
→ consolidate() fallback → llama-4-scout
→ llama-4-scout tool'u çağırmadı
→ consolidate() fallback → gpt-4.1-mini
→ gpt-4.1-mini save_memory'yi çağırdı ✓
Hafıza birleştirme tamamlandı
Dört model denendi, hafıza kaydedildi. Kullanıcı "Yeni oturum başlatıldı." mesajını görür ve tüm bunlardan haberi bile olmaz.
Mimari Boşluğu
LemonClaw hafıza sistemi ve alternatiflerin özellik karşılaştırması:
| Yetenek | Tipik Yapay Zeka Ajanı Framework'ü | LemonClaw |
|---|---|---|
| Birleştirme modeli | Konuşma ile aynı (pahalı, yavaş) | Bağımsız model zinciri, Groq hızlandırmalı |
| Hata yönetimi | Uyarıyı günlüğe yaz, hafızayı kaybet | Çift katmanlı fallback, 5 model derinliğinde |
| Taşıma fallback | Aynı modeli 3 kez yeniden dene | Farklı modeller arasında zincirleme fallback |
| İş mantığı fallback | Yok | Tool call doğrulaması + model değiştirme |
| Zaman aşımı koruması | Yok (Cloudflare 524 oturumu bloke eder) | asyncio.wait_for(timeout=30) + fallback |
| Oturum kısaltma | Yok (bağlam sonsuza kadar büyür) | Birleştirmeden sonra eski mesajları kısalt |
| Geçmiş araması | Yok | HISTORY.md hareketli pencere, grep ile aranabilir |
| Dahili modeller | Desteklenmiyor | Sadece sistem modelleri için hidden=True |
| Döngü önleme | Gerekli değil (zincir yok) | visited kümesi A→B→A döngülerini önler |
| Gateway çözünürlüğü | Tek API formatı varsayılır | Protokol tespiti ile model başına gateway |
Bu tablodaki her satır, bizzat deneyimlediğimiz veya diğer framework'lerin hata takipçilerinde gözlemlediğimiz bir üretim hatasını temsil eder. Çift katmanlı fallback, gizli model kataloğu, model başına gateway çözünürlüğü, zaman aşımı tetiklemeli fallback: Bunların hiçbiri OpenClaw, nanobot veya incelediğimiz diğer herhangi bir açık kaynaklı ajan framework'ünde mevcut değildir.
Ne Öğrendik?
"İstek başarılı oldu", "görev başarılı oldu" demek değildir. Genel yeniden deneme motorları HTTP düzeyinde çalışır. Geçerli JSON içeren bir 200 yanıtının aslında bir başarısızlık olduğunu bilemezler çünkü model istediğiniz tool'u kullanmamıştır. İş açısından kritik işlemlerin kendi başarı kriterlerine ve kendi fallback mantıklarına ihtiyacı vardır.
Küçük modeller, büyük modellerden farklı şekilde başarısız olur. Büyük modeller (GPT-4.1, Claude Sonnet) istendiğinde neredeyse her zaman tool çağırır. Hızlı çıkarım motorlarındaki küçük modeller bazen tool schema'sını tamamen görmezden gelen geçerli görünümlü yanıtlar üretir. Bu, prompt engineering ile düzeltebileceğiniz bir hata değildir. Mimari müdahale gerektiren bir yetenek açığıdır.
Sentetik verilerle değil, üretim verileriyle test edin. 6 sentetik mesajla yaptığımız ilk testimiz her modelde geçti. Tool call geçmişi, zaman damgaları ve karışık diller içeren gerçek 60 mesajlık oturum, üç Groq modelinden ikisinde başarısız oldu. Gerçek verilerin karmaşıklığı, temiz test verilerinin asla ortaya çıkaramayacağı hata modlarını açığa çıkarır.
LemonClaw, yerleşik çoklu model yönlendirme, kalıcı bellek ve 10'dan fazla sohbet platformu entegrasyonuna sahip açık kaynaklı bir yapay zeka ajanı framework'üdür. Burada açıklanan çift katmanlı fallback sisteminin tamamı açık kaynaklı sürümde mevcuttur. Kendi sunucunuzda çalıştırın: github.com/hedging8563/lemonclaw
Tek bir API anahtarı üzerinden 300'den fazla yapay zeka modeline mi ihtiyacınız var? lemondata.cc OpenAI, Anthropic, Google, DeepSeek, Groq ve daha fazlasına birleşik erişim sağlar.
