Satış ekibindeki biri müşteriyle telefonda konuşuyor. Müşteri diyor ki: “yirmilik hatta takılacak, ultrasonik olsun, uzaktan okunsun.”
Satışçının bulması gereken şey katalogdaki bir kalem. Katalogda dört binden fazla ürün var ve arama kutusu yalnız ürün adında ve kodunda arıyor. “Ultrasonik” yazınca 180 sonuç geliyor.
Bu boşluğu kapatmak için CRM’e bir semantik arama katmanı yazdım.
Önce yanlış yolu söyleyeyim
Kolay çözüm şu: cümleyi bir dil modeline ver, “bana uygun ürünleri listele” de.
Bu, çalışıyormuş gibi görünen ve satılamayacak bir sonuç üretiyor. Model kataloğu ezbere bilmediği için mantıklı görünen ürün kodları uyduruyor. Satışçı o kodu teklifle gönderiyor. Öyle bir ürün yok.
Bir kere bile olsa bu, sistemin tamamına olan güveni bitirir.
Kural: model veri üretmez, filtre üretir
Mimarinin tek cümlelik özeti bu.
serbest metin
↓ (niyet ayrıştırıcı)
{ sert filtreler, yumuşak filtreler }
↓ (veritabanı sorgusu)
gerçek ürünler
Model — ya da regex tabanlı ayrıştırıcı — yalnız ortadaki adımı yapıyor. Ürün listesi her zaman veritabanından geliyor. Katalogda yoksa sonuçta da yok.
Ontoloji: neyin ne olduğunu tanımlamak
Bunun işe yaraması için sistemin ürün özelliklerini anlaması gerekiyor. Onu da elle tanımladım: her özellik anahtarı, tipi, ve ağırlığı.
| alan | tür | neden |
|---|---|---|
diameter (çap) | sert | DN20 hattına DN25 takılmaz. Fiziksel. |
product_type | sert | Su sayacı isteyene ısı sayacı gösterilmez. |
pipe_connection | sert | Dişli/flanşlı — takılmıyorsa takılmıyor. |
pressure_class | sert | Basınç sınıfı yetmiyorsa ürün geçersiz. |
technology | yumuşak | Ultrasonik tercih; mekanik de iş görebilir. |
communication | yumuşak | wM-Bus tercih; M-Bus da uzaktan okunur. |
certification | yumuşak | Varsa artı, yoksa eksi — ama eleme değil. |
Sert filtre sağlanmıyorsa ürün listeden çıkar. Yumuşak filtre yalnız sıralamayı etkiler. Bu ayrım, sonucun hem doğru hem kullanışlı olmasını sağlıyor: eleme fiziksel gerçeklerle, sıralama tercihlerle yapılıyor.
Niyet ayrıştırıcı
Serbest metni filtrelere çeviren parça. Şaşırtıcı biçimde büyük kısmı model değil, kalıp eşleme:
"yirmilik" → diameter = DN20
"DN 20" → diameter = DN20
"20'lik" → diameter = DN20
"ultrasonik"→ technology = ultrasonic
"uzaktan" → communication ∈ {wmbus, mbus, lora}
Buradaki incelik, çakışmaları çözmek. “wmbus” eşleştiğinde “mbus” listeden çıkarılıyor — yoksa iki ayrı haberleşme tipi aynı anda isteniyor gibi görünüyor ve sonuç bulanıklaşıyor.
Ve en önemli kural: ayrıştırıcı ontolojide tanımlı olmayan bir anahtar üretemiyor. Tanımsız bir alan gelirse istek reddediliyor. Halüsinasyon için bir kapı bırakılmıyor.
Sonra MCP: Claude’un doğrudan bağlanması
Bu katman hazır olunca bir adım daha attım: CRM’e bir MCP sunucusu yazdım. Böylece bir AI istemcisi doğrudan bağlanıp katalogda arama yapabiliyor.
Sunucu araçlarını listeliyor, istemci onları çağırıyor. Veri hiçbir zaman modele toplu olarak verilmiyor — model yalnız sorduğu sorunun cevabını görüyor.
Eski veritabanı katmanı, bir hata durumunda die() ile süreci sonlandırıyordu. MCP bir JSON-RPC protokolü olduğu için bu, istemciye yarım kalmış bozuk bir yanıt gönderiyor ve bağlantıyı düşürüyordu.
Çözüm çıktıyı tamponlayıp bir kapanış kancası koymak oldu: süreç beklenmedik şekilde biterse tampon temizleniyor ve yerine geçerli bir JSON-RPC hata yanıtı yazılıyor. İstemci “bir şeyler ters gitti” diyor; “protokol bozuldu” demiyor.
Eski bir kod tabanını yeni bir protokole bağlarken en çok zaman alan şey özellikler değil, bu tür çıkış yolları oluyor.
Ne öğrendim
Bir sisteme AI eklerken doğru soru “model ne kadar akıllı?” değil. “Model yanılırsa ne olur?”
Burada cevabı şu: yanılırsa yanlış filtre üretir. Sonuçta ya alakasız ürünler çıkar ya da hiç çıkmaz. İkisi de fark edilir ve düzeltilebilir.
Modelin ürün uydurmasına izin verseydim, yanlış cevap doğru cevapla aynı görünecekti. Aradaki tüm fark bu.