Bir bankacılık chatbot projesinin ilk toplantısından çıktığımda, işimin ne kadar kolay olduğunu düşünüyordum. Node.js geliştiricisi olarak görevim basitti: kullanıcı bağlandığında WebSocket aç, mesajı AI servisine gönder, yanıtı akışla geri döndür. WebSockets benim için ikinci doğa gibiydi; yıllardır gerçek zamanlı sistemler kuruyordum. Bu projenin benim için bir esinti olacağını sanıyordum. Yazılım mühendisliğindeki en büyük yalandı bu.
Projenin üçüncü gününde, AI ekibiyle yaptığımız bir senkronizasyon toplantısında içgüdüsel bir soru sordum: 'Birisi bot'a dünkü IPL maçını kimin kazandığını sorarsa ne olur?' Kimse hemen cevap vermedi. Birisi güldü, şaka yaptığımı sandı. Ciddiydim. Uygulamayı açıp denediğimizde, bot soruyu aldı, FD oranları ve kredi koşullarıyla dolu bir bilgi tabanında aradı, hiçbir şey bulamadı ve yine de kendinden emin bir şekilde cevap verdi. 'Maçla ilgili sorularınız için lütfen şubenizle iletişime geçin' gibi anlamsız bir yanıt. On saniye komikti, sonrasında değildi.
Bu deneyim, chatbot'umuzun 'bu benim cevaplayacağım bir şey değil' kavramına sahip olmadığını gösterdi. Yılların deneyimiyle biliyordum ki, bir sohbet kutusu verildiğinde, birileri mutlaka garip bir soru soracaktır. Kötü niyetle değil, sadece sohbet kutusu bunu davet ettiği için. Bu yüzden bir bekçiye ihtiyacımız vardı: intent tespiti. AI ekibi bana bir uç nokta verdi: sorguyu gönder, intent ve meta verileri al. Benim tarafımda sadece on satır kod vardı. Mutluydum. Ama bu, daha uzun bir hikayenin başlangıcıydı.
Teknik Analiz
Intent katmanı çalıştı, demo iyi geçti. Sonra birisi gerçek ölçümleri yaptı. Her istek, 'bugünkü FD oranı nedir?' gibi sıkıcı bir soru bile, cevaplayacak LLM'ye ulaşmadan önce, sorgunun nereye gitmesi gerektiğini belirlemek için tam bir LLM çağrısından geçmek zorundaydı. Bu dolambaçlı yol her seferinde 200-300ms ek maliyet getiriyordu. Bir sonraki değerlendirmede, AI lideri 'Suraj, bunu önbelleğe alabilir misin?' diye sordu. 'Evet, tabii ki, Redis' dedim kendimden emin bir şekilde. Ama hemen ardından 'Aslında, neyi önbelleğe alacağız?' diye sordum. Oda güldü, ben de güldüm ama ciddiydim. Henüz bir cevabım yoktu.
İlk içgüdüm, her backend geliştiricisinin yapacağı gibi sorguyu ve yanıtı anahtar-değer olarak saklamaktı. 'Bugünkü FD oranı nedir?' sorgusu önbellekte isabet aldı. Harika! Ta ki bir sonraki test gelene kadar: 'Bana bugünkü sabit mevduat faiz oranını söyler misiniz?' Aynı soru, aynı intent, farklı string. Kullanıcılar hayatlarında aynı cümleyi iki kere yazmazlar ve tam eşleşme önbelleklemesi sadece karakterleri bilir, anlamı değil. İkinci denemem daha akıllıcaydı: intent'e göre önbelleğe almak. Sınıflandırıcı bunun bir FD_RATE sorusu olduğunu söylüyorsa, neden önbellek anahtarı olarak intent'i kullanmayalım? Bir gün harika göründü. Sonra birisi arka arkaya iki soru sordu: 'FD oranı nedir?' ve 'FD avantajları nelerdir?' İkisi de FD_RATE intent'ine düştü. Tamamen farklı sorular, tamamen farklı cevaplar. Birine FD avantajlarını sorarken FD oranını önbellekten verirsem, yanlış cevap vermiş olurdum ki bu, hiç önbellek olmamasından daha kötü.
Haftanın çoğunu bu sorunu çözmeye çalışarak geçirdim. Bir kahve molasında mimarımız bir peçeteye iki kelime yazıp masanın üzerinden itti: 'Semantik Önbellek'. O gece Redis dokümantasyonunu açtım, vektör arama sayfasını okudum. Redis vektör aramayı destekliyor muydu? Ne zamandan beri? Değişiklik günlüğünü kontrol ettim. Redis bir gecede değişmemişti; benim anlayışım değişmişti. Gece yarısı civarında işler oturdu: sorguyu metin olarak eşleştirmek yerine, onu anlamını yakalayan bir vektöre (embedding) dönüştürüyorsunuz ve bunu önceden önbelleğe alınmış embedding'lerle karşılaştırıyorsunuz. Yeterince yakınsa, önbelleğe alınmış cevabı döndürüyorsunuz. Böylece 'bugünkü FD oranı' ile 'bana bugünkü sabit mevduat faiz oranını söyler misiniz' artık aynı önbellek girişine isabet edebiliyordu. Çözüm, sorguları anlamlarına göre gruplamaktı. Bu, projenin en büyük dönüm noktasıydı.
Kaynak: dev.to