Django ve React'te LLM Yanıtlarını Streaming ile Sunmak
Django'da Server-Sent Events ile LLM yanıtlarını akış halinde sunmanın tam uygulaması: Gunicorn worker'ları, Nginx tamponlama ve Celery entegrasyonu dahil.
Bir Django uygulamasına eklenen AI özelliği, klasik POST-bekle-render yaklaşımıyla kullanıcıları memnun etmedi: 8 saniyelik bekleme sonrası gelen tek parça metin, ChatGPT'ye alışmış kullanıcılar için yavaş hissettirdi. Çözüm, WebSocket yerine tek yönlü iletişime uygun olan Server-Sent Events (SSE) ile Django'nun StreamingHttpResponse'unu kullanmak oldu. Sunucu tarafında LLM'den gelen her token, generator üzerinden SSE formatında client'a aktarılıyor; React tarafında ise EventSource yerine fetch + ReadableStream tercih ediliyor çünkü EventSource yalnızca GET destekliyor ve JSON body gönderilemiyor.
Üretim ortamında görünmeyen ama gerçek dünyada projeyi kıran üç detay öne çıkıyor: Nginx'in varsayılan tamponlama davranışı X-Accel-Buffering: no header'ı olmadan streaming'i tamamen etkisiz kılıyor; Gunicorn'un sync worker tipi stream süresince worker'ı kilitleyip eşzamanlı istekleri tıkıyor (gevent/gthread worker'a geçiş gerekiyor); ve AWS ALB gibi load balancer'ların 60 saniyelik varsayılan idle timeout'u uzun LLM yanıtlarında bağlantıyı kesebiliyor. Celery üzerinden yapılan AI çağrılarında ise task'in doğrudan stream edememesi nedeniyle, chunk'ların Redis'e yazılıp Django view'ının Redis'ten client'a stream ettiği bir ara katman kullanılıyor.
Streaming'in çözmediği noktalar da var: ilk token'a kadar geçen süre (1-3 saniye) değişmiyor, sadece algılanan gecikme azalıyor; gerçek zamanlı autocomplete gibi kullanım senaryolarında bu yeterli olmayabilir. Ayrıca stream ortasında hata oluşursa kısmi yanıt zaten client'a gönderilmiş oluyor — bu durum genelde 'yanıt kesildi' bildirimiyle ve kullanıcının yeniden denemesiyle çözülüyor. Sonuç olarak streaming, kullanıcı deneyimini önemli ölçüde iyileştiren ama altyapı detaylarına dikkat gerektiren bir mühendislik problemidir.