Yunus Emre Alpak

LLM ile Konuşan Bir API — MCP, Hybrid Search ve Biyolojik Decay · Mnemo Serisi 3/5

2026 · 04 · 289 dk okuma

Bir cuma akşamı oturup düşündüm: "Bu API'yi gerçekten kim çağıracak?" Cevap "Claude" olduğunda, REST + OpenAPI yazmayı bırakıp MCP'yi öğrenmeye başladım. O cuma akşam, Mnemo'nun en doğru kararlarından birini almıştım.

Serinin üçüncüsü. İlk iki yazıda "neden" ve "temel mimari" vardı. Şimdi Mnemo'nun zekâ kısmı: dış dünya ile nasıl konuştuğu, içeride nasıl aradığı ve neden zamanla unuttuğu.

API'nin gerçek kullanıcısı kim?

Yıllarca API tasarlarken zihnimde aynı kullanıcı vardı: ya başka bir backend, ya bir frontend, ya en kötüsü Postman'le elle test eden bir geliştirici. Hepsi insan ya da insan-programladığı-kod.

Mnemo'da bu kabul artık geçerli değildi. Çünkü Mnemo'yu çağıracak olan birincil "kullanıcı" Claude Code'du. Yani bir LLM. İnsan değil, kod değil, prompt ile yönlendirilen ama kendi başına karar veren bir şey.

Bu fark teknik olarak önemli. Çünkü:

Yani API'nın sözleşmesi (contract) sadece teknik bir şey değil, dilsel bir şey oldu. Bu fark Mnemo'nun ilk büyük tasarım kararını yön verdi: REST + OpenAPI yerine MCP (Model Context Protocol).

REST yerine MCP seçmek

İlk içgüdü tabii REST yazmaktı. OpenAPI spec çıkar, Claude'a verilirse o da kullanabilir — neden olmasın?

Üç gün denedim, sonra geri çekildim. Çünkü:

MCP, "LLM-native bir RPC protokolü" gibi. Tool'ları sunucu tarafında tanımlıyorsun (isim, açıklama, parametre şeması), client (Claude Code, ChatGPT desktop, vs.) bunları kendi tool registry'sine ekliyor. Discovery otomatik, schema parsing otomatik, çağrı standardize edilmiş.

Mnemo'da POST /mcp tek endpoint var. İçinde 9 tool yaşıyor: remember, recall, get_context, get_memory, list_recent, list_by_tag, forget, update_memory, stats. Claude bağlandığında bu 9'unu görüyor, ne zaman hangisini çağıracağını description'lara bakarak kendi karar veriyor.

Tool description'lar yazılımın en önemli cümleleri

Bunu yeterince vurgulayamam: tool description'ın LLM'in araç seçim hatasını yapıp yapmamasını belirler.

İlk yazdığım description'lar şöyleydi:

"Searches memories using semantic similarity."

Teknik olarak doğru. Pratik olarak işe yaramaz. Çünkü Claude, "geçen hafta Vault'a ne yükledim" diye soran kullanıcı için bunun ilgili tool olduğunu çıkaramıyordu. "Semantic similarity" cümlesi bir kullanıcı sorusuna eşlenmiyor.

Şimdiki hali:

"Searches the user's personal memory using hybrid semantic + keyword search. Contains: file uploads/deletions from Vault, share links, PR reviews and digests from Foreman, plus manually saved notes, decisions, and facts. Use this when the user asks about past events ('geçen hafta ne yaptık', 'en son yüklediğim dosya', 'PR review sonuçları'), or when you need context about something previously discussed or done."

Üç şey değişti:

  1. Korpusun içeriği açıkça yazıyor — "şu var, şu var". LLM hangi sorularda buraya geleceğini biliyor.
  2. Tetikleyici örnekler (Türkçe + İngilizce karışık) var. "geçen hafta ne yaptık" ifadesini gördüğünde araç seçim olasılığı tavan yapıyor.
  3. Use case doğrudan tanımlı — "when the user asks about past events". Hipotezsel değil, somut.

Bu değişiklikten sonra Claude'un Mnemo'yu çağırma doğruluğu fark edilir biçimde arttı. Çünkü description, kod değil prompt.

Çıkardığım kural şudur: tool description'ı yazdıktan sonra bir LLM'e "bu aracı kullanmak için 3 farklı kullanıcı sorusu yaz" diye sor. Eğer çeşitlilik az çıkıyorsa, description hâlâ belirsiz.

Voyage seçimi: asimetrik retrieval önemli bir şey

Embedding modelinin seçimi tahmin edebileceğin gibi uzun bir araştırma değildi. Yine de bazı şeyleri filtrelemem gerekti.

Voyage'ı seçmemin nedeni sadece kalite değildi. Asıl ayırt eden şey asimetrik retrieval desteğiydi. Voyage API'sinde input_type parametresi var:

Aynı metni iki farklı input_type ile gönderdiğinde farklı vektör çıkıyor. Çünkü model "bu uzun bir doküman" ile "bu kısa bir soru" arasındaki dilsel farkı temsil ediyor.

Mnemo'da Embedder interface'inde iki ayrı method var bu yüzden:

Bu API kontratı çağıranı "yanlışlıkla query'i document gibi embed etmekten" koruyor. Ve fark gerçekten retrieval kalitesinde hissediliyor — symmetric embedding'e göre yaklaşık %5-10 recall iyileşmesi.

Çıkardığım ders: embedding modelinin kalitesi tek başına yetmez, modelin API tasarımı da retrieval kalitesini şekillendirir. Asymmetric retrieval desteklemeyen bir model, sen ne kadar dikkatli kod yazarsan yaz, aynı recall'a ulaşmayacak.

Hybrid search: vector tek başına neden yetmez?

Bu bölümde biraz teknik olacak ama matematiksiz anlatmaya çalışacağım.

Vector similarity güzel, ama bir kör noktası var: anahtar kelimeyi tam yakalayamaz.

Örnek: korpusunda "PROJ-1234 ticket'ı için API endpoint düzeltildi" diye bir memory var. Sen "PROJ-1234" diye arıyorsun. Vector embedding, PROJ-1234'ün anlamsal komşuluğunu pek bilmez — çünkü bu rastgele bir ID, dilsel anlamı yok. Cosine similarity zayıf gelir.

Ama pg_trgm gibi trigram-based fuzzy search PROJ-1234'ü hemen yakalar — string olarak orada.

Mnemo bu yüzden hybrid search yapıyor. Tek query'de iki ayrı CTE çalıştırıyor:

İkisini FULL OUTER JOIN ile birleştiriyor. Her aday için iki skor da hesaplanmış oluyor: vector_score, keyword_score. Sonra in-process bir scorer bunları ağırlıklı olarak birleştirip final ranking yapıyor.

Default ağırlıklar:

Toplam 1.0. Config validation startup'ta toplamı kontrol ediyor — yanlış ayar bug değil, erken yakalanmış bir hata.

Buradan çıkardığım ders: vector search bir araç, hybrid search bir tasarım. Vector tek başına PROJ-1234'leri kaçırır. Keyword tek başına "geçen hafta Vault'a ne yükledim" gibi semantic sorgularda kaybeder. İkisini birleştirmek, ikisinin de zayıf yönünü kapatır.

Biyolojik decay: metafordan tasarıma

Mnemo'nun her hafızası "strength" adında bir alana sahip değil aslında — runtime'da hesaplanıyor. Formül:

strength = importance + (1 - importance) * exp(-ln2 * Δt / halfLife)

Δt = now - max(created_at, last_accessed_at). Yani memory ne kadar eski (veya ne kadar uzun süredir bakılmamış) ise, strength o kadar düşük.

Half-life her kind için farklı:

Bu sayıların biyolojik beyinde tam karşılığı yok. Ama metafor sezgisel: working memory hızlı uçar, semantic gerçek uzun yaşar, procedural bilgi neredeyse hiç solmaz.

İki tasarım inceliği var:

1. Importance asymptote olarak çalışıyor. importance=1.0 olan bir hafıza asla unutulmaz — strength hep 1'de kalır. Çünkü formülde (1 - importance) çarpanı sıfır oluyor. Yani kullanıcı bir şeye gerçekten "bunu hatırla" derse, decay devreden çıkıyor.

2. Recall, decay saatini sıfırlıyor. Bir hafızaya bakıldığında (recall veya get_context tool'ları) last_accessed_at güncelleniyor. Bir sonraki strength hesaplamasında Δt o noktadan başlıyor. Yani kullandığın hafıza güçlenir, kullanmadığın solar. Bu Hebb'in "birlikte ateşlenen birlikte bağlanır" prensibinin pratik bir uygulaması.

Bu reinforcement'ı yapmak için handler'lar recall döndüğü her hafıza için goroutine ile UpdateAccess çağırıyor. Asenkron, kullanıcının cevabını geciktirmiyor. "Context iptal olursa bile reinforcement tamamlansın" diye context.Background() kullanılıyor — küçük bir karar ama önemli.

"Akıllı API" denen şey

Geriye bakınca, Mnemo'nun bu kısmı bir API tasarımından çok bir konuşma tasarımı olarak hissetiriyor. Çünkü:

Bu sayıların hiçbiri matematik teoremi değil. Hepsi tasarım kararı. Yanlış ayarlandığında Mnemo aptal görünür, doğru ayarlandığında "tam ihtiyacım olan şeyi getiriyor" hissini verir.

Ve bu his — "tam ihtiyacımı bilen bir hafıza" hissi — Mnemo'nun aslında ne yapmaya çalıştığının özeti. Bir veri tabanı değil, bir asistanın iç dünyası.

Sonraki yazıda

Dördüncü yazıda Mnemo'nun "yaşayan" tarafına bakacağız: NATS üzerinden Vault ve Foreman'ın olaylarını otomatik tüketmeye nasıl başladığı, neden iki aşamalı bir pipeline kurduğum, pull vs push consumer kararı, ve Redis eklemeden dedup'ı nasıl çözdüğüm.

Ama bu yazının ana mesajı şu kalsın: bir LLM için API yazıyorsan, sözleşmenin sadece tipinin değil, dilinin de doğru olması gerekiyor. OpenAPI spec'i geçer not değil. Her endpoint'in description'ı bir prompt mühendisliği egzersizi. Bunu kabul ettiğin an, API tasarımı bambaşka bir disiplin haline geliyor — ve aslında daha eğlenceli olabiliyor.