Kredi bittiğinde yapay zekâ özelliğiniz ne yapıyor
Model sağlayıcısı bakiye, kota veya kesinti yüzünden hata döner. Fallback yoksa herkes aynı genel hatayı görür. Kademeli düşüş nasıl tasarlanır.
Dün çalışan özellik bugün herkese aynı özür metnini dönmeye başlar. Log'lar on bin kez tekrarlanan tek bir satırla doludur, servisin kendisi sağlıklıdır ve sebep, gece boyunca sıfıra inen ve başkasının panelinde duran bir sayıdır. Model sağlayıcısı çağrı kabul etmeyi bırakmıştır, kodun ise bu durum için tek bir planı vardır.
Sağlayıcının hayır demesinin dört yolu
Hatalar aynı kanaldan gelir ve stack trace'te birbirine benzer, ama farklı muamele isterler:
- Bakiye. Hesapta kredi kalmamıştır, kart reddedilmiştir ya da paket bitmiştir. Bu kalıcıdır. Bir saniye sonra da bir saat sonra da aynı şekilde başarısız olur.
- Kota. Rate limit, dakika başına token tavanı veya günlük hak. Geçicidir ve genelde ne zaman dönmeniz gerektiğine dair bir ipucu taşır.
- Kesinti. Sağlayıcı yavaşlamıştır veya bir bölge düşmüştür. Geçici, öngörülemez ve çoğu zaman kısmidir: bir model cevap verirken diğeri vermez.
- Kendi hatamız. Bozuk istek, limiti aşan bağlam, ucun kabul etmediği bir parametre. İstek değişmeden tekrar denemek anlamsızdır.
Çoğu kod tabanı bu dördünü tek dala indirir, çünkü entegrasyonun ilk sürümünde çağrının etrafında bir try bloğu ve "bir şeyler ters gitti" diyen bir toast vardı. O dal, dört durumdan tam olarak sıfırı için doğrudur. Özellikle rate limit kendi yolunu hak eder, gerekçesi hız sınırını kalıcı hata saymanın bedeli yazısında.
Sorunun ikinci yarısı, parayı kimsenin izlememesi. Uptime izleme süreçlere, portlara ve yanıt kodlarına bakar. Sıfıra doğru inen ön ödemeli bakiye ise sıfıra değene kadar hiç alarm üretmez, değdiğinde de hepsini birden üretir. Üstelik bu genelde mesai dışında olur, çünkü bakiyeyi bitiren şey gece çalışan toplu işlerdir; sabah ilk giren kişi de elinde tek satırlık bir hata mesajıyla kalır.
Hangisiyle karşı karşıya olduğunuzu görmek
Yanıt gövdesini yutmayı bırakın. Tek başına durum kodu belirsizdir: sağlayıcılar 429'u hem dakika limiti hem tükenmiş paket için kullanır, bazıları bakiye sorununu 403 olarak bildirir. Durum koduyla gövdeyi birlikte sınıflandırın ve sınıfı loglayın:
type ProviderFailure =
| { kind: 'billing' } // biri para yüklemeden bitmez
| { kind: 'quota'; retryAfterMs: number } // sonra tekrar gel
| { kind: 'transient' } // backoff ile tekrar dene
| { kind: 'request' }; // bizim hatamız, çağrıyı düzelt
function classify(status: number, body: string): ProviderFailure {
const text = body.toLowerCase();
if (status === 402 || /credit|balance|billing|payment/.test(text)) return { kind: 'billing' };
if (status === 429) return { kind: 'quota', retryAfterMs: parseRetryAfter(body) ?? 20_000 };
if (status >= 500 || status === 408) return { kind: 'transient' };
return { kind: 'request' };
}Bu sınıf başarısız her çağrıya yazıldığında, olay anında önemli olan soruyu tek sorgu cevaplar:
SELECT failure_kind, count(*), max(created_at) AS last_seen
FROM model_calls
WHERE created_at >= now() - interval '30 minutes' AND failure_kind IS NOT NULL
GROUP BY failure_kind
ORDER BY 2 DESC;On bin satır billing ve başka hiçbir şey yoksa bu, transient ile quota karışımından tamamen farklı bir olaydır. Kod düzenlemeye başlamadan önce hangisinin içinde olduğunuzu bilmek istersiniz.
Çözüm: dal değil merdiven
Kademeli düşüşü sıralı bir deneme listesi olarak yazın. Her basamak bir öncekinden daha ucuz ve daha az yeteneklidir, ayrıca aşağı inmenin ne zaman duracağını söyleyen bir kural vardır:
const ladder = [
{ name: 'primary_large', run: () => call(primary, 'large', messages) },
{ name: 'secondary_large', run: () => call(secondary, 'large', messages) },
{ name: 'primary_small', run: () => call(primary, 'small', messages) },
{ name: 'cached', run: () => cachedAnswer(cacheKey) },
{ name: 'static', run: () => templateAnswer(intent) }
];
for (const step of ladder) {
const result = await attempt(step);
if (result.ok) return { ...result, servedBy: step.name };
if (result.failure.kind === 'request') break; // bizim hatamız, alt basamak da düşer
}
return honestMessage();Bu listeyi sadece derli toplu değil işe yarar yapan dört şey var.
Sıralama fiyat listesini değil hatayı izler. Birincil sağlayıcıda bakiye hatası, oradaki bütün modellerin gittiği anlamına gelir, dolayısıyla bir sonraki basamak aynı sağlayıcının küçük modeli değil başka bir sağlayıcı olmalıdır. Kota hatasıysa genelde küçük modeli açık bırakır ve sıralama tersine döner. Tek bir diziyi koda gömmek yerine hata sınıfını seçiciye parametre olarak verin.
İstek seviyesindeki hata merdiveni durdurur. Bağlam çok uzunsa veya şema yanlışsa alttaki her basamak aynı sebeple, beş kat yavaş ve beş kat pahalı şekilde düşer.
Cevap nereden geldiğini taşır. Yanıtla birlikte servedBy dönün, kayda yazın ve önemli olduğu yerde gösterin. Küçük modelin yazdığı taslakla şablondan üretilmiş taslak, onu göndermek üzere olan insan için aynı şey değildir.
Özelliğin sonucu değil durumu vardır. Yol başına küçük bir circuit breaker tutun, böylece birkaç bakiye hatasından sonra birincil sağlayıcı birkaç dakika tamamen atlanır. Bu durumu da dışarı açın:
curl -s http://localhost:3000/health/ai | jq .
# {
# "primary": { "state": "open", "reason": "billing", "since": "2026-04-06T02:14:11Z" },
# "secondary": { "state": "closed", "reason": null },
# "serving": "secondary_large"
# }Sonra alarmı çökmeye değil bakiyeye bağlayın. Kalan krediyi düzenli aralıkla okuyun, son yedi günün harcamasına bölün ve kaç günlük ömür kaldığına göre uyarı üretin:
# kalan gün, maliyet tablosundan
days=$(psql -At -c "SELECT round(:balance / (sum(cost_micros)/1e6/7.0), 1)
FROM model_calls WHERE created_at >= now() - interval '7 days'")
[ "${days%.*}" -lt 7 ] && notify "Yapay zeka kredisi ${days} gun yeter"Yedi gün, bir insanın harekete geçebileceği bir eşiktir. Sıfır ise gece ikide birini uyandıran eşiktir. Bu hesabın ihtiyaç duyduğu sayılar, yapay zekâ özelliğini makul maliyette tutmak yazısındaki özellik başına maliyet tablosunda zaten duruyor.
Çalıştığını nasıl doğrularsınız
Gerçek kesintiyi beklemeyin. Hatayı staging'de enjekte edin ve istisna çıkmamasını değil, dönen cevabı doğrulayın:
AI_FAULT=billing:primary npm run test:integration
# beklenen çıktı
# birincil 402 doner -> merdiven secondary_large basamagina duser ok
# ikincil de 402 doner -> merdiven cached basamagina duser ok
# cache bos -> durust mesaj, HTTP 200 ok
# bu sure icinde birincile giden retry sayisi: 0 okSon satır herkesin atladığı satırdır. Ölü bir sağlayıcıya dakikada yüz beyhude istek göndermeye devam eden fallback, yalnızca kâğıt üzerinde fallback'tir.
Nelere dikkat etmeli
- İkincil sağlayıcının kendi kredisi, kendi kotası ve kendi izlemesi olmalı. Üç aydır hiç çağrılmamış yedek, test edilmemiş yedektir; her gün canlı trafiğin küçük bir payını oradan geçirin.
- Cache'lenmiş cevaplar bayatlar. Etiketleyin, yaşlarına üst sınır koyun ve girdisi önemli bir noktada farklı olan bir istek için asla cache'ten cevap vermeyin.
- Akış yanıtının kendi fallback'i olmalı. Akışın ortasında düşen bir çağrı tarayıcıya çoktan token göndermiştir, istemcinin bunları değiştirebilmesi gerekir. O yolun tuzakları kapatılmayan akış yanıtı yazısındakiyle aynıdır.
- Prompt'lar birbirinden ayrışır. Birincil yol için prompt değişip kimse değerlendirme setini ikincil yolda koşturmayınca, fallback tam ihtiyaç duyacağınız güne kadar sessizce kötüleşir.
Çalıştırmadığınız her şeyin göremediğiniz bir durumu vardır ve para bunun en sık rastlanan hallerinden biridir. İşe yarayan alışkanlık, sağlayıcıyı kendi sağlığı olan bir bağımlılık gibi ele almak: hatalarını sınıflandırın, onunla aynı arıza sebeplerini paylaşmayan bir yol bulundurun ve alarmı kesintinin kendisine değil kesintiyi önceden haber veren şeye kurun. Küçük modelle cevap veren ve bunu söyleyen bir özellik hâlâ çalışan bir özelliktir. Genel hata mesajı dönen özellik ise, deneyen her kullanıcı için bir destek talebidir.
Sorular ve cevaplar
- Sağlayıcı kredi yok diyorsa retry etmeli miyim?
- Hayır. Bakiye veya yetersiz kredi hatası kalıcıdır, biri para yüklemeden hiçbir deneme sonucu değiştirmez. Retry hem istek bütçesini harcar hem de cevap üretebilecek fallback'i geciktirir. Geçici ağ hatalarını ve rate limit'i tekrar deneyin, bakiyeyi değil.
- Küçük bir özellik için ikinci sağlayıcı zahmete değer mi?
- Özellik çöktüğünde ne olduğuna bağlı. Kullanıcı bir mesaj görüp yoluna devam ediyorsa dürüst bir mesaj yeter. Ama çökme bir siparişin sınıflandırılmaması ya da kuyruğun boşalmaması demekse, ikinci yol birincisinin kötü bir saat geçirdiği ilk gün kendini amorti eder.
- Aynı prompt'u iki sağlayıcıda nasıl çalışır tutarım?
- Prompt metnini tek yerde tutun, sağlayıcıya özgü kısımları (mesaj biçimi, parametre adları, tool formatı) sağlayıcı başına ince bir adaptörün arkasına alın. Prompt her değiştiğinde aynı değerlendirme setini iki yolda da koşturun, yoksa fallback sessizce çürür ve bunu tam olay anında öğrenirsiniz.
- Her şey başarısız olursa kullanıcı ne görmeli?
- Asistanın şu an çalışmadığını ve bunun yerine ne yapabileceğini söyleyen kısa bir cümle, artı işin başarılı olan kısmı. Bilgi vermeyen genel hata mesajı destek talebi üretir. Özelliğin geçici olarak kapalı olduğunu söyleyip manuel yolu göstermek üretmez.