Tüm yazılara dön

Ollama ile Üretimde Yerel LLM: AI İlan Koçu Nasıl Kuruldu

11 Eylül 20262 dk okuma
Yerel AIOllamaASP.NET Core

TÜBİTAK 2209-A destekli bir freelancer pazaryeri projesinde, kullanıcıların ilan açarken ne yazacağını bilememesi büyük bir sorundu — boş bir metin kutusuyla karşılaşan kullanıcıların önemli bir kısmı ilanı hiç tamamlamıyordu. Çözüm bir "AI ilan koçu" ekleyip kullanıcıya öneriler sunmaktı. Ama bütçe kısıtlı bir girişim için OpenAI API çağrı başına maliyeti (özellikle her taslak revize edildiğinde tekrar çağrı yapılması) sürdürülebilir değildi. Bu yüzden yerel çalışan bir LLM'e yöneldim: Ollama üzerinde Qwen Coder.

Neden "yerel" gerçekten yerel olmalıydı

Yerel LLM demek, sadece geliştirme ortamında çalışan bir demo değil, üretim sunucusunda 7/24 ayakta duran bir servis demekti. Bunun getirdiği üç somut kısıt vardı:

  1. Bellek yönetimi: Model, sunucu belleğinde sürekli yüklü tutulmalı mıydı, yoksa istek geldiğinde mi yüklenmeli? Sürekli yükleme daha hızlı yanıt verir ama diğer servislerle bellek çekişmesine girer. Test sunucusunda ölçtüğümde ilk yükleme ~4 saniye sürüyordu; bu, kullanıcı bekleme süresine kabul edilemezdi. Çözüm Ollama'nın keep_alive parametresini kullanıp modeli belirli bir süre (30 dakika) bellekte tutmak, boşta kaldığında serbest bırakmaktı.
  2. Eşzamanlı istek sınırlaması: Tek GPU/CPU kaynağını paylaşan birden fazla kullanıcı aynı anda istek attığında, Ollama istekleri sıraya alıyor ama yanıt süresi katlanarak artıyordu. Bunu bir SemaphoreSlim(maxConcurrency: 2) ile API katmanında sınırlayıp, sıra dolduğunda kullanıcıya "öneriniz hazırlanıyor" durumunu gösterdim.
  3. Zaman aşımı ve geri düşüş (fallback): Model bazen 15 saniyeyi geçen yanıtlar üretebiliyordu. Kullanıcı deneyimini bozmamak için 10 saniyelik bir timeout koydum; süre dolarsa kullanıcıya "şu an öneri oluşturulamadı, ilanını manuel tamamlayabilirsin" mesajı gösterip akışı bloklamadım — AI bir zorunluluk değil, bir yardımcı olmalı.
public async Task<string?> GenerateSuggestionAsync(string draftText, CancellationToken ct)
{
    using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
    cts.CancelAfter(TimeSpan.FromSeconds(10));
 
    await _concurrencyLimiter.WaitAsync(cts.Token);
    try
    {
        var response = await _ollamaClient.GenerateAsync(new()
        {
            Model = "qwen2.5-coder:7b",
            Prompt = BuildPrompt(draftText),
            KeepAlive = "30m",
        }, cts.Token);
 
        return response?.Text;
    }
    catch (OperationCanceledException)
    {
        return null; // Fallback: kullanıcı akışı bloklanmaz.
    }
    finally
    {
        _concurrencyLimiter.Release();
    }
}

Prompt mühendisliği: kısa ve sınırlayıcı

Genel amaçlı bir "bana bir ilan yaz" prompt'u, modelin alakasız ya da aşırı uzun metinler üretmesine yol açıyordu. İşe yarayan yaklaşım, prompt'u kesin sınırlarla kısıtlamaktı: maksimum kelime sayısı, hedef ton (profesyonel ama samimi), ve örnek format. Ayrıca modelin çıktısını doğrudan kullanıcıya göstermeden önce basit bir uzunluk/küfür filtresinden geçirdim — yerel model olsa da, güvenlik kontrolünü atlamak için bir sebep yok.

Neden sohbet botu da aynı altyapıyı kullandı

İlan koçu ile platform içi soru-cevap sohbet botu aynı Ollama instance'ını paylaşıyor, farklı prompt şablonlarıyla ayrışıyorlar. Bu, ikinci bir model instance'ı çalıştırmanın getireceği ek bellek/CPU maliyetinden kaçınmayı sağladı — tek model, birden fazla "persona" ile kullanılabiliyor.

Sonuç

Yerel LLM'i üretime almanın zor kısmı modeli çalıştırmak değil, onu diğer üretim kısıtlarıyla (bellek, eşzamanlılık, zaman aşımı, güvenli geri düşüş) uyumlu hale getirmekti. Bu projenin CSRF ve retry politikaları gibi diğer üretim kararlarını proje sayfasında bulabilirsin.

İlgili Proje

Bu yazının anlattığı projeyi detaylıca incele.

Projeyi İncele