13 / 13Proje
AI Alışveriş Asistanı
Araç çağırma, kullanıcı onayı, prompt injection koruması, rate limit ve maliyet ölçümüyle çalışan bir alışveriş asistanı kurun.
Bu projede ürün arayan ve onayınızla sepete ekleyen bir sohbet asistanı kuracaksınız. Araçları, onay adımını, güvenlik sınırlarını ve maliyet ölçümünü siz yazarsınız. Hazır testler "bitti" demenizi sağlar.
Dosyalar GitHub'da hazır: shopping-assistant.
İpucu
Proje varsayılan olarak sahte bir modelle çalışır (src/lib/fake-model.ts). Gerçek modeller ücretli olduğu için bu tercih edildi. Anahtar gerekmez ve testler her seferinde aynı sonucu verir. Sahte model yalnızca önceden yazılmış senaryoları oynatır, gerçek bir model gibi akıl yürütmez.
Maliyeti sorun etmiyorsanız proje köküne .env.local ekleyin: AI_MODEL için AI Gateway'deki bir model adını, AI_GATEWAY_API_KEY için kendi anahtarınızı yazın.1
Proje next@16.4, ai@7.0, @ai-sdk/react@4.0, zod@4, vitest@5, @playwright/test@1.64 ve @axe-core/playwright@4.14 ile Node 22'de çalıştırıldı. Örnek çözümde 14 birim ve 6 uçtan uca test geçti.
Hazırlık
git clone https://github.com/virastack/academy
cd academy/examples/ai/shopping-assistant/starter
npm install
npx playwright install chromium
npm run test:unit14 birim testin tamamı kırmızı çıkmalı, çünkü araçlar ve route henüz yazılmadı. npm run test:e2e ile arayüz testlerini ayrıca çalıştırın. Beşi kırmızı olmalı. Hedef hepsini yeşile çevirmek.
Başlangıç klasöründeki CLAUDE.md kuralları ajana zaten söylüyor. data/products.ts içinde 12 ürün var. İkisi stokta yok, biri de açıklamasında saldırı metni taşıyor. Bu ürün sonraki adımlarda testlerin hedefi olacak.
Adım 1: Araçları yazdırın
src/lib/tools.ts içindeki createTools'u yaz.
searchProducts: query, maxPrice ve category alsın, src/lib/products.ts'teki findProducts'ı çağırsın.
addToCart: productId ve 1 ile 5 arası tamsayı quantity alsın, src/lib/cart.ts'teki addItem'ı userId için çağırsın.
Hata olursa gerçek mesajı sunucuda logla, modele yalnızca "Ürün sepete eklenemedi." dön.İki kural önemli:
Ürünün yalnızca bazı alanları modele gitsin.
id,name,category,priceveinStockyeter.descriptiongitmesin. Dışarıdan gelen metin modelin önüne girerse talimat gibi okunabilir. Bu dolaylı prompt injection'dır ve araç sonuçları da bu yoldur.23Adet sınırını şemaya koyun. Model yanlış ya da kandırılmış olsa bile
quantity: 50şemadan geçemez.
Kontrol: npm run test:unit içinde araç testleri yeşile döndü mü? "açıklama modele gitmez" ve "adet sınırı" testleri bunu doğrular.
Adım 2: Route'u yazdırın
src/lib/chat-handler.ts içindeki createChatHandler'ı yaz.
Kullanıcıyı x-user-id başlığından al.
streamText ile ağ üzerinden akıt: araçlar createTools(userId), addToCart için kullanıcı onayı (toolApproval), en fazla 5 adım, maxOutputTokens 1024, istek iptal edilince model de dursun.
Talimatta araç sonuçlarının güvenilmeyen veri olduğunu söyle.
Bitince onUsage ile userId ve token sayılarını ve maliyeti bildir.Onay mekanizması toolApproval ile gelir. addToCart için 'user-approval' verirseniz araç, kullanıcı onaylamadan çalışmaz.2 Model "onaysız ekle" diyen bir metne kanıp aracı çağırsa bile sepet değişmez. Bu, saldırının etkisini sınırlar.
Kontrol: "onay ister, onaylayınca ekler", "reddedince sepet değişmez" ve "kandırılmış model onaysız ekleyemez" testleri yeşil mi? Son test modeli bilerek p-02 ürününden 5 adet ekletmeye çalıştırır.
Adım 3: Arayüzü yazdırın
src/components/Chat.tsx içindeki useChat'i kur, src/components/ProductCard.tsx içinde ürün kartını yaz.
searchProducts sonucu ürün kartları olarak gösterilsin. Karta basınca "<id> sepete ekle" mesajı gitsin.
addToCart onay beklerken Onayla ve Vazgeç butonları çıksın. Cevap verilince otomatik devam etsin.
Asistan metnini düz metin olarak göster, markdown render etme.
Sepet sayısı "Sepet: N ürün" olarak /api/cart'tan gelsin.useChat araç parçalarını tool-<ad> türüyle verir. Onay beklerken durum approval-requested olur ve addToolApprovalResponse ile cevap gönderilir.4
Asistan metnini markdown olarak render etmeyin. Modelin yazdığı  gibi bir görsel adresi, sayfa açılınca tarayıcıyı o adrese götürür ve sohbet verisini dışarı sızdırabilir.5 Sahte model, "özet geç" yazdığınızda tam bunu dener.
Kontrol: Uçtan uca testlerde "onay ister", "vazgeçince sepet değişmez" ve "dış görsel için istek atılmaz" yeşil mi? Tarayıcıda npm run dev ile de deneyin: koşu ayakkabısı göster, sonra bir kartta Sepete ekle, sonra Onayla.
Adım 4: Sınırları yazdırın
src/lib/rate-limit.ts içindeki createRateLimiter'ı kayan pencereyle yaz.
Sınırı aşan istekte success: false ve kaç saniye sonra denenebileceğini dön.
chat-handler'da bunu 429 ve retry-after başlığıyla yanıtla.
Ayrıca 50'den fazla mesaj ya da 4000 karakterden uzun mesaj gelirse modeli çağırmadan 413 dön.Sınırsız tüketim, saldırgana faturanızı büyütme yolu verir. Rate limit, girdi sınırı ve çıktı sınırı (maxOutputTokens) buna karşı üç ayrı katmandır.6 Bu projedeki rate limit tek süreçte bellekte tutulur. Birden çok sunucuda çalışacaksa paylaşılan bir depo gerekir. Ayrıntısı Güvenlik ve Maliyet rehberinde.
Kontrol: "sınır aşılınca 429 ve retry-after", "pencere sıfırlanır" ve "uzun girdi 413" testleri yeşil mi? 413 testi modelin hiç çağrılmadığını da denetler.
Adım 5: Maliyeti ölçtürün
src/lib/cost.ts içindeki estimateCost'u yaz.
Girdi, önbellekten okunan, önbelleğe yazılan ve çıktı token'larını PRICE tablosuyla dolara çevirsin.Fiyatlar Anthropic'in fiyat sayfasından alındı: Sonnet 5.5 için milyon token başına 2 dolar girdi, 10 dolar çıktı, 0,10 dolar önbellek okuma, 2,50 dolar önbellek yazma. Kodunuza gömmeden önce sayfaya bakın.7
Kontrol: 200 normal girdi, 700 önbellekten okunan, 100 önbelleğe yazılan ve 300 çıktı token'ı için sonuç yaklaşık 0,00372 dolar çıkmalı. Test bunu denetler.
Hepsini çalıştırın
npm testÖnce birim, sonra uçtan uca testler çalışır. Tarayıcı bulunamazsa CHROME_PATH ortam değişkenine bir Chrome yolu verin.
| Test | Neyi denetler |
|---|---|
| Araç argümanları | Arama filtresi ve alanların beyaz listesi |
| Açıklama sızmaz | Saldırı metni modelin ikinci isteminde yok |
| Adet sınırı | 1–5 dışı adet şemadan geçmez |
| Onay | Onaysız ekleme yok, reddedince sepet aynı |
| Kandırılmış model | Model p-02×5 isterse bile sepet boş |
| Hata sızmaz | İç hata mesajı modele gitmez |
| Kullanıcı yalıtımı | Her kullanıcının sepeti ayrı |
| Rate limit | 429, retry-after, pencere sıfırlanması |
| Girdi sınırı | 51 mesaj ya da 4001 karakter için 413 |
| Çıktı sınırı | maxOutputTokens 1024 ya da altı |
| Maliyet | estimateCost ve onUsage raporu |
| Arayüz | Konsol temiz, tek h1, kartlar, onay akışı, dış görsel isteği yok, axe ihlali yok |
Bitti sayılır
npm testtamamen yeşil.descriptionhiçbir yerde modele gitmiyor.addToCartonaysız çalışmıyor.Asistan metni düz metin olarak gösteriliyor.
Rate limit, girdi sınırı ve
maxOutputTokensaçık.
İpucu
Takılırsanız solution/ klasörüyle karşılaştırın. Önce kendiniz deneyin. Ajan yanlış yazdığında testin hata mesajını ona yapıştırmak çoğu zaman yeter.
Kaynaklar
`AI_GATEWAY_API_KEY` ve düz dize model adıyla istek atma.
`tool()`, `inputSchema`, `stopWhen` ve `toolApproval`.
Doğrudan ve dolaylı prompt injection, etkiler ve azaltma yöntemleri.
`useChat`'te araç parçaları, durumları ve onay arayüzü.
Model çıktısına sıfır güven, kodlama ve CSP.
Denial of wallet, girdi sınırı, rate limit ve kotalar.
Model başına girdi, çıktı ve önbellek fiyatları.
Son güncelleme tarihi: 15 Ekim 2026
