Exploring Infinite Possibilities on FrontEnd 🚀
Burak Sağlık
Search...

Tue Sep 01 2026

Model Dağıtım Kontrol Listesi: AI Üretim Hazırlığı

Model Dağıtım Kontrol Listesi: AI Üretim Hazırlığı

🎯 Model Seçimi Sadece Başlangıç: Üretim Ortamının Gerçek Yüzü

Model seçmek kolay, zor kısmı üretime almak 🎯.

Geçen yıl bir hackathon’da harika bir demo hazırladık; model %98 doğrulukla çalışıyordu, herkes alkışladı. Ama canlıya aldığımızda maliyet patladı 💸 ve gecikme 5‑10 kat arttı ⏱. O an anladım ki: AI Mühendisliği sadece model eğitmek değil, Üretim Ortamı'na uygun hale getirmek demek.

Bu yüzden bu yazının odak noktası “AI Üretim Hazırlığı Kontrol Listesi” olacak. Liste maddeleri üzerinden gidip, her birinin neden kritiğini ve nasıl çözeceğimizi konuşacağız. Hazırsan başlayalım 🚀.

Kontrol Listesi Özeti

  • Veri Doğrulama & Versiyonlama – Eğitim verisiyle canlı veri aynı mı?
  • Model Paketleme & Sürüm Yönetimi – Docker imajı, ONNX, TorchScript… ne kullanıyorsun?
  • Ölçeklenebilirlik & Otomatik Ölçeklendirme – HPA, KEDA, custom metrics.
  • Gecikme & İşlem Hızı (Latency/Throughput) – Batch vs. streaming, donanım hızlandırıcıları.
  • Maliyet İzleme & Optimizasyon – Spot instance, model quantization, distillation.
  • Güvenlik & Gizlilik – Model extraction, data leakage, RBAC.
  • İzlenebilirlik (Observability) – Metrics, logs, tracing, alerting.
  • A/B Test & Canary Deploy – Güvenli çıkış stratejileri.

Her maddeyi tek tek inceleyeceğiz, pratik ipuçları vereceğim ve “Ne oluyor burada?” diye sorarak mantığı kavrayacağız. Hadi başlayalım! 🎉


❗️ Neden Modeller Üretimde Çöküyor? Yaygın Tuzaklar

Modeli eğittiğimizde her şey mükemmel görünüyordu — metrikler yeşildi, test seti de memnun ediciydi. Ama canlıya aldığımızda hikaye tamamen değişti. İşte benim de başıma gelen, ve muhtemelen sizin de başınıza gelebilecek beş yaygın tuzak 🎯:

  • 🔇 Sessiz hatalar (silent failures)
    Ben şunu yaşadım: model bir girdi için boş ya da anlamsız bir cevap döndürdü, ama hata kodu 200 OK döndü. İzleme (monitoring) yokmuş gibi göründü, kullanıcı şikayet etene kadar fark etmedik.
    Mühendislik eksikliği: Çıktı doğrulama (output validation) ve LLM Evalüasyonu pipeline’ı koymamıştık.

  • 💸 Beklenmedik maliyet patlamaları
    Ben şunu yaşadım: birden fazla büyük model (ör. 70B parametre) aynı anda çağrılıyordu, token maliyeti geceleri 3‑4 kat arttı. Faturalar geldikten sonra anladık ki batch‑processing ya da model distillation düşünmemişiz.
    Mühendislik eksikliği: maliyet modellemesi ve rate‑limit / quota guardrail’leri eksikti.

  • Gecikme (latency) sorunları
    Ben şunu yaşadım: gerçek‑zamanlı sohbet botunda 3‑5 sn cevap süresi, kullanıcı deneyimini bozdu. Asenkron akış ya da caching stratejisi yoktu.
    Mühendislik eksikliği: SLA tanımlaması ve performance guardrails (max latency, timeout) konulmamıştı.

  • 📉 Veri sapması (data drift)
    Ben şunu yaşadım: eğitim verisinde nadir görülen bir argo/terim üretimde sıkça geçti, model halüsine girdi. Drift algılama (data drift detection) modülü yoktu, model aylarca yanlış tahmin üretti.
    Mühendislik eksikliği: Sürekli veri izleme ve re‑training trigger mekanizması koymamıştık.

  • 🔓 Güvenlik ihlalleri (prompt injection, PII leakage)
    Ben şunu yaşadım: bir kullanıcı “ignore previous instructions” payload’ı gönderdi, model hassas veri (API key, e‑posta) sızdırdı. Guardrails (input/output filter, PII maskeler) yoktu.
    Mühendislik eksikliği: Güvenlik katmanı (input sanitization, output redaction) ilk günden entegre edilmemişti.


Ne yapmalıyız? 🛠

  1. Gözlemleyin – Her model çağrısını loglayın, metrikleri (latency, token, hata oranı) real‑time takip edin.
  2. DeğerlendirinLLM Evalüasyonu (otomatik + insan) ile çıktı kalitesini sürekli ölçün.
  3. KorumayınGuardrails (input validation, output constraints, PII masking) koyun; ihlal durumunda fallback ya da redaction tetikleyin.
  4. Planlayın – Maliyet, gecikme ve drift için SLA ve alert tanımlayın; otomatik re‑train / rollback akışları kurun.
  5. İterasyon yapın – Production’dan gelen veriyle modeli sürekli iyileştirin; “set‑and‑forget” yok, mühendislik süreci devam etmeli.

Bu tuzakların hepsi “model iyi, mühendislik kötü” demekten ibaret. Modeli canlıya almak bir yazılım dağıtımıdır — aynı disiplin, aynı gözlem, aynı güvenlik gerektirir. ✅


🎯 AI Üretim Hazırlığı Kontrol Listesi: 5 Kritik Mühendislik Katmanı

Hazırsan bu kontrol listesiyle prod'a çıkmadan önce mutlaka kontrol etmemiz gereken 5 katmanı tek tek inceleyelim 👇


📋 5 Ana Başlık — Hızlı Bakış Tablosu

Katman Ne? Neden? Nasıl?
Evals 🧪 Model çıktılarını sistematik test etme "İyi görünüyor" demek yetmez, kanıt lazım Otomatik test seti + LLM-as-judge pipeline
Gözlemlenebilirlik 🔍 Log, trace, metrik toplama Sürpriz olmasın, hata anında görünsün Structured logging + distributed tracing + dashboards
Maliyet/Gecikme Token, latency, $/request takibi Bütçe patlamasın, kullanıcı beklemesin Caching, routing, model seçim stratejisi
Veri Sapması 📊 Input/output dağılım değişiklikleri Model sessizce bozulmasın Drift detection + periyodik yeniden değerlendirme
Guardrails 🛡 Güvenlik, PII, policy ihlali engelleme Riskli çıktı prod'a gitmesin Input/output filter + rule engine + fallback

✅ Senin Kontrol Listeni (Kopyala, İşaretle, Prod'a Çık)

- [ ] **LLM Evalüasyonu**: Otomatik testler ve LLM-as-judge pipeline'ı kuruldu
- [ ] **Gözlemlenebilirlik**: Loglama, tracing ve metrikler entegre edildi
- [ ] **Maliyet & Gecikme**: Token kullanımı, latency ve $/request SLA'sı tanımlandı
- [ ] **Veri Sapması**: Drift detection ve periyodik yeniden değerlendirme planı hazır
- [ ] **Guardrails**: Input/output filtreleri, PII koruması ve policy engine aktif

🎬 Bu Liste Sana Ne Kazandırır?

Bu 5 madde sadece birer checkbox değil — her biri gece uyumanı sağlayan bir sigorta poliçesi 🛡️
Hepsini tiklediğinde: "Modelim prod'da nasıl davranıyor?" sorusuna veriyle, loglarla, metriklerle cevap verebilirsin.

Hadi bir sonraki katmandan başlayalım — Evals'e dalıyoruz! 🚀


🔁 LLM Evalüasyonu: 'İyi' Modeli Nasıl Ölçersiniz?

Hazırsan başlayalım. Çoğumuz "model çalışıyor, yeter" derken aslında sürekli değişen bir hedefi vuruyoruz 🎯. Evalüasyon bir kez yazıp unuttuğumuz test dosyası değil, kodunuzla aynı repo'da yaşayan, CI/CD'den geçen, her deploy'da size "bu değişim kötü mü iyi mi?" diye çenen sürekli bir süreç.

Neden "Tek Seferlik Test" Yeterli Değil? ❗️

  • LLM'ler deterministik değil — aynı prompt'ta farklı cevaplar üretebilir 🎲
  • Prompt/değişken değişiklikleri regresyon yaratır (bugün çalışan yarın bozulur)
  • "Gözle bakıp anladım" ölçeklenmez — 1000 örnekte eliniz yorulur 😅
  • Prod'daki gerçek trafiği simüle etmez

Bu yüzden üç katmanlı bir yaklaşım benimsiyorum:

Katman Ne İşe Yarar? Hız Maliyet Güvenilirlik
Unit / Otomatik Format, yasaklı kelime, schema, latency ⚡ Çok hızlı 💰 Düşük ✅ Yüksek (deterministik)
LLM-as-Judge Semantik benzerlik, tutarlılık, ton 🚀 Orta 💰💰 Orta ⚠️ Judge de yanılabilir
Human Eval Nuans, güvenlik, ürün hissiyatı 🐢 Yavaş 💰💰💰 Yüksek ✅ En güvenilir

Pratik kural: Her PR'da unit + LLM-as-judge koşar, haftada bir human eval oturumu yaparız. Böylece "gece deploy ettim, sabah kahve içip bakıyorum" derdi bitmiş olur ☕.


CI/CD'ye Nasıl Entegre Ederiz? 🛠

GitHub Actions örneğiyle adım adım:

# .github/workflows/llm-eval.yml
name: LLM Evaluation Pipeline

on:
  pull_request:
    branches: [main, develop]
  push:
    branches: [main]

jobs:
  eval:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install deps
        run: |
          pip install -r requirements.txt
          pip install pytest pytest-asyncio

      - name: Run unit evals (fast, deterministic)
        run: pytest tests/eval_unit.py -v --tb=short

      - name: Run LLM-as-Judge evals (semantic)
        if: github.event_name == 'pull_request'
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        run: pytest tests/eval_llm_judge.py -v --tb=short

      - name: Upload eval report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: eval-report
          path: eval_results/

Ne oluyor burada?

  1. Her PR'da unit testler koşar (saniyeler sürer) ⚡
  2. PR ise LLM-as-Judge de devreye girer (dakikalar sürer) 🤖
  3. Sonuçlar eval_results/ klasörüne yazılır, artifact olarak yüklenir 📊
  4. main branch'ine merge olmadan kırmızı badge görürsünüz ⛔

Ben Şu Metrikleri Takip Ediyorum 📈

Metrik Neden Önemli? Hedef
Accuracy (doğruluk) Ground truth'a ne kadar yakın? > 0.85
Latency (p95) Kullanıcı deneyimi < 2.5s
Token Usage Maliyet kontrolü Trend izle
Hallucination Rate Güvenilirlik < 2%
Format Compliance Downstream parser'ın patlamaması 100%

Bu metrikleri Prometheus/Grafana'a push edip, "son 7 günde hallucination rate %1.2 → %3.4 çıktı" diye alarm kuruyorum 🚨.


Pytest Tabanlı Test Fonksiyonu Taslağı 🧪

Hadi pratik bir örnek üzerinden anlayalım. Aşağıdaki test üç kriteri doğrular:

  1. Cevap geçerli JSON olmalı 📋
  2. Yasaklı kelime içermemeli (örn. "hack", "bypass") 🚫
  3. Ground truth ile semantik benzerlik > 0.8 olmalı (embedding cosine similarity) 🎯
# tests/eval_unit.py
import json
import pytest
from sentence_transformers import SentenceTransformer, util

# --- Test verileri ---
TEST_CASES = [
    {
        "name": "valid_json_no_banned_high_similarity",
        "llm_response": '{"answer": "İstanbul Türkiye'nin en kalabalık şehridir.", "confidence": 0.95}',
        "ground_truth": "İstanbul Türkiye'nin en nüfuslu şehridir.",
        "banned_words": ["hack", "bypass", "exploit", "ignore"],
        "min_similarity": 0.8,
    },
    {
        "name": "invalid_json_format",
        "llm_response': 'Bu bir JSON değil, düz metin.',
        "ground_truth": "İstanbul Türkiye'nin en nüfuslu şehridir.",
        "banned_words": ["hack", "bypass"],
        "min_similarity": 0.8,
    },
    {
        "name": "contains_banned_word",
        "llm_response": '{"answer": "Bu sistemi hack etmek için şu yolu izleyin.", "confidence": 0.9}',
        "ground_truth": "İstanbul Türkiye'nin en nüfuslu şehridir.",
        "banned_words": ["hack", "bypass"],
        "min_similarity": 0.8,
    },
]

# --- Embedding modeli (hafif, hızlı) ---
@pytest.fixture(scope="session")
def embedder():
    # all-MiniLM-L6-v2: 384 dim, ~80MB, CPU'da hızlı
    return SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")


# --- Yardımcı fonksiyonlar ---
def is_valid_json(text: str) -> bool:
    """Cevap geçerli JSON mı?"""
    try:
        json.loads(text)
        return True
    except json.JSONDecodeError:
        return False


def contains_banned(text: str, banned: list[str]) -> bool:
    """Yasaklı kelime var mı? (case-insensitive)"""
    lower = text.lower()
    return any(word.lower() in lower for word in banned)


def semantic_similarity(text1: str, text2: str, model) -> float:
    """Cosine similarity (0-1 arası)"""
    emb1 = model.encode(text1, convert_to_tensor=True)
    emb2 = model.encode(text2, convert_to_tensor=True)
    return float(util.cos_sim(emb1, emb2).item())


# --- Ana test fonksiyonu ---
@pytest.mark.parametrize("case", TEST_CASES, ids=lambda c: c["name"])
def test_llm_response_quality(case, embedder):
    """
    LLM cevabı 3 kritere göre değerlendirilir:
    1. JSON formatında olmalı
    2. Yasaklı kelime içermemeli
    3. Ground truth ile semantik benzerlik > threshold
    """
    response = case["llm_response"]
    ground_truth = case["ground_truth"]
    banned = case["banned_words"]
    threshold = case["min_similarity"]

    # 1️⃣ JSON format kontrolü
    assert is_valid_json(response), f"❌ Geçersiz JSON: {response[:100]}..."

    # JSON parse edip 'answer' alanını alıyoruz (semantik karşılaştırma için)
    parsed = json.loads(response)
    answer_text = parsed.get("answer", "")

    # 2️⃣ Yasaklı kelime kontrolü
    assert not contains_banned(answer_text, banned), \
        f"❌ Yasaklı kelime tespit edildi: {answer_text}"

    # 3️⃣ Semantik benzerlik kontrolü
    sim = semantic_similarity(answer_text, ground_truth, embedder)
    assert sim >= threshold, \
        f"❌ Benzerlik düşük: {sim:.3f} < {threshold} (cevap: '{answer_text}')"

    # ✅ Hepsi geçti
    print(f"✅ [{case['name']}] similarity={sim:.3f} | JSON=OK | Banned=OK")

Bu dosyayı şu komutla çalıştırırsınız:

pytest tests/eval_unit.py -v

Çıktı örneği:

tests/eval_unit.py::test_llm_response_quality[valid_json_no_banned_high_similarity] PASSED
tests/eval_unit.py::test_llm_response_quality[invalid_json_format] FAILED
tests/eval_unit.py::test_llm_response_quality[contains_banned_word] FAILED

Bu Sayede Ne Oluyor? ✅

  • Her PR'da format/yasaklı kelime/benzerlik otomatik kontrol edilir
  • Kırmızı pipeline = merge engellenir → prod'a bozuk model gitmez ⛔
  • Metrikler zaman içinde izlenir → "son sprint'te hallucination arttı" anında görünür 📉
  • Human eval zamanını "zor örnekler"e ayırırsınız, sıkıcı tekrarları bot yapar 🤝

Sonuç olarak: Evalüasyon "test yazmak" değil, güvenle deploy etmeyi sağlayan canlı bir sistemdir. Küçük başlayın (unit testler), sonra LLM-as-judge ekleyin, CI/CD'ye bağlayın. Geleceğiniz siz size minnettar olacak 😉.


🔁 Gözlemlenebilirlik: Kara Kutuyu Aydınlatmak

Hazırsan başlayalım 🎯. Bir LLM tabanlı uygulamayı canlıya aldığında, model “kara kutu” gibi davranır: girdi veriyorsun, çıktı alıyorsun ama arada ne olduğunu bilmezsin. İşte gözlemlenebilirlik (observability) devreye giriyor — üç bacak ile bu kutuyu aydınlatıyorsun:

  • Loglar 📝: “Ne oldu?” sorusuna cevap. Hata mesajları, uyarılar, debug satırları.
  • Metrikler 📊: “Ne kadar?” sorusuna cevap. Token sayısı, gecikme, hata oranı, maliyet.
  • İzler / Tracing 🔁: “Neden?” sorusuna cevap. Bir isteğin başlangıcından bitişine kadar olan tüm adımları, ara model çağrılarını, prompt şablonlarını, Retrieval adımlarını bir zincir olarak görürsün.

LLM bağlamında tracing vazgeçilmez çünkü:

  • “Model ne düşündü?” → Prompt’un son hali, few‑shot örnekleri, sistem mesajı.
  • “Neden bu cevabı verdi?” → Hangi retrieval belgeleri kullanıldı, hangi tool çağrıldı, token limiti aşıldı mı?
  • Hata ayıklama → Bir hata aldığında trace’i açıp 5 dakikada kök nedeni bulursun ⛔➡️✅.

🎬 Gerçek bir hikaye

Geçen hafta prod’da bir “JSON parse hatası” aldık. Kullanıcı “Ürün fiyatını ver” dedi, model ise geçersiz JSON döndürdü. Langfuse trace’ini açtım:

  1. Prompt → Sistem mesajı + kullanıcı mesajı + few‑shot örnekleri.
  2. Model çağrısıgpt‑4o 1.200 token çıktı.
  3. Post‑processingjson.loads başarısız oldu.
  4. Trace detayı → Modelin çıktısında fazladan bir açıklama satırı vardı (“İşte JSON: …”).

5 dakika sonra prompt’a “Sadece geçerli JSON döndür” kuralı ekledim, yeniden deploy ettim ve hata ortadan kalktı. Trace olmasaydı günlerce loglarda boğulurdum 😅.


🛠 Pratik: OpenTelemetry ile basit bir LLM trace dekoratörü

Aşağıdaki snippet, herhangi bir LLM çağrısını (OpenAI, Anthropic, yerel model…) input, output, token sayısı, hata durumu olarak trace eder. Kendi projenizde @trace_llm ekleyip unutursunuz.

# tracing_decorator.py
import functools
import time
from opentelemetry import trace
from opentelemetry.trace import Status, StatusCode

tracer = trace.get_tracer(__name__)

def trace_llm(func):
    """LLM çağrısını OpenTelemetry span'ı içine alır."""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # Span başlat
        with tracer.start_as_current_span(f"llm.{func.__name__}") as span:
            start = time.perf_counter()
            try:
                # ---- Girdi logla ----
                span.set_attribute("llm.input.args", str(args))
                span.set_attribute("llm.input.kwargs", str(kwargs))

                # Fonksiyonu çalıştır
                result = func(*args, **kwargs)

                # ---- Çıktı ve token sayısı logla ----
                # Varsayım: result dict içerisinde 'text' ve 'usage' anahtarları var
                output_text = result.get("text", "")
                usage = result.get("usage", {})
                span.set_attribute("llm.output.text", output_text[:500])   # ilk 500 char
                span.set_attribute("llm.usage.prompt_tokens", usage.get("prompt_tokens", 0))
                span.set_attribute("llm.usage.completion_tokens", usage.get("completion_tokens", 0))
                span.set_attribute("llm.usage.total_tokens", usage.get("total_tokens", 0))

                span.set_status(Status(StatusCode.OK))
                return result

            except Exception as exc:
                # Hata durumunu span'a işle
                span.record_exception(exc)
                span.set_status(Status(StatusCode.ERROR, str(exc)))
                raise
            finally:
                duration_ms = (time.perf_counter() - start) * 1000
                span.set_attribute("llm.duration_ms", round(duration_ms, 2))

    return wrapper

Nasıl çalışıyor?

  1. Span açılırllm.<fonksiyon_adı> adıyla.
  2. Girdi (args, kwargs) attribute olarak eklenir.
  3. Fonksiyon çalışır → Modelden dönen result beklenir.
  4. Çıktı, token kullanımı, süre attribute’lara yazılır.
  5. Hata olursa record_exception ile trace’e hata stack’i düşer, span ERROR statüsüne geçer.

Kullanım örneği

@trace_llm
def call_openai(prompt: str, model: str = "gpt-4o"):
    # OpenAI SDK çağrısı (basitleştirilmiş)
    response = openai_client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": prompt}],
    )
    return {
        "text": response.choices[0].message.content,
        "usage": {
            "prompt_tokens": response.usage.prompt_tokens,
            "completion_tokens": response.usage.completion_tokens,
            "total_tokens": response.usage.total_tokens,
        },
    }

Artık her call_openai çağrısı tam bir trace bırakıyor. Langfuse, LangSmith ya da kendi OpenTelemetry backend’inde “Model ne düşündü?” sorusuna tek tıkla cevap bulursunuz 🚀.


✅ Özet

  • Log + Metrik + Trace = Gözlemlenebilirlik üçgeni.
  • Tracing LLM’de neden sorusuna cevap verir; prompt, retrieval, tool çağrıları, tokenler tek bir zincirde.
  • Prod hatasında trace aç → 5 dk da kök neden → fix → deploy.
  • Kod: Yukarıdaki @trace_llm dekoratörünü kopyala, projene ekle, her LLM çağrını sar. Gerisi otomatik 🎉.

Bir sonraki bölümde metrikleri nasıl dashboard’a dökeceğimiz ve alarmları nasıl kuracağımız konuşacağız. Görüşmek üzere! 👋


🛠 Maliyet ve Gecikme Yönetimi: Bütçeyi Bozmadan Hızlı Cevaplar

Maliyet ve gecikme sadece technical metric değil, ticari kısıtlayıcı 🎯.
Bir ürün sahibi olarak “Bu ay bütçeyi aştık” veya “Kullanıcı 10 sn bekledi” duymak istemezsiniz.
Bu yüzden her istekte para ve süreyi birden kontrol etmemiz lazım.

🔧 Kullandığım 4 ana strateji

Strateji Ne işe yarar? ✅ Avantaj ❌ Dezavantaj
Semantic Caching (GPTCache) Aynı anlama gelen sorguları vektör uzayında eşleştirip cevabı tekrar üretir. Maliyeti %40–%60 düşürebilir, gecikme ms seviyesine iner. Cache boyutu büyürse bellek/yönetim maliyeti artar; stale cevap riski var.
Model Routing Basit sorular → gpt‑3.5‑turbo, karmaşık → gpt‑4. Pahalı modeli sadece gerekli yerde çalıştırır. Routing mantığı yanlışsa kalite kaybı yaşanabilir.
Token Optimizasyonu (Prompt Compression) Prompt’u özetleyip/kırparak token sayısını azaltır. Her istekte %20–%30 token tasarrufu. Over‑compression bağlam kaybına yol açabilir.
Streaming Yanıtlar Cevabı parça parça döner, kullanıcı hemen görmeye başlar. Algılanan gecikme büyük ölçüde azalır. Sunucu tarafında biraz daha karmaşık hata yönetimi gerektirir.

Kısa bir başarı hikayesi:
Ben GPTCache entegre ettikten sonra aylık LLM maliyetim %40 düştü ve ortalama yanıt süresi 2.3 sn → 0.7 sn oldu 🚀.


🧩 Basit bir Model Router örneği (Python)

# model_router.py
import tiktoken
from openai import OpenAI

client = OpenAI()          # API key env'den alınır
ENC = tiktoken.encoding_for_model("gpt-3.5-turbo")

# -------------------------------------------------
# Karmaşıklık skorunu token sayısına + anahtar kelimeye göre hesaplıyoruz
# -------------------------------------------------
def complexity_score(prompt: str) -> int:
    tokens = len(ENC.encode(prompt))
    # Basit bir kural: 300 token altı + "özet", "çevir" gibi kelimeler → düşük
    low_keywords = {"özet", "çevir", "listele", "kısa"}
    has_low_kw = any(kw in prompt.lower() for kw in low_keywords)
    return tokens if not has_low_kw else tokens // 2   # düşükse puanı yarıya indir

# -------------------------------------------------
# Router: skor 500 altı → gpt-3.5-turbo, aksi → gpt-4
# -------------------------------------------------
def route_and_call(prompt: str) -> str:
    score = complexity_score(prompt)
    model = "gpt-3.5-turbo" if score < 500 else "gpt-4"
    print(f"🔎 Skor: {score} → Model: {model}")

    resp = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": prompt}],
        temperature=0.2,
    )
    return resp.choices[0].message.content

# -------------------------------------------------
# Hızlı test
# -------------------------------------------------
if __name__ == "__main__":
    sorular = [
        "Python'da liste nasıl sıralanır?",                     # basit
        "Yapay zeka etiği üzerine 2000 kelimelik bir makale yaz.", # karmaşık
    ]
    for s in sorular:
        print(route_and_call(s)[:200] + "…\n")

Nasıl çalışıyor?

  1. complexity_score → prompt’un token sayısını ve bazı low‑complexity anahtar kelimelerine bakar.
  2. route_and_call → skor 500 altındaysa gpt‑3.5‑turbo, aksi gpt‑4 çağrılır.
  3. Böylece pahalı modeli sadece gerçekten gerekli yerde çalıştırmış oluruz 💡.

📌 Küçük bir hatırlatma

  • Cache ve routing birbirini tamamlar: cache hit olursa router hiç çalışmaz.
  • Token optimizasyonu prompt’u router’a göndermeden önce uygulayın, skor daha gerçekçi olur.
  • Streaming frontend’de “yazıyor…” hissi verir, kullanıcıyı bekletmez.

Hazırsan bir sonraki bölümde monitoring & alerting ile bu metrikleri nasıl izleyeceğimize bakalım 🚦.


❗️ Veri Sapması ve Model Çöküşü: Sessiz Kullanıcı Değişikliklerini Yakalamak

Hazırsan başlayalım 🚀
Modelin eğitim ve doğrulama setlerinde mükemmel performans gösterdiğini varsayalım. Üç ay sorunsuz çalışıyor, aniden accuracy düşüyor, müşteriler şikayet ediyor. Ne oldu? 🤔

Veri Sapması (Data Drift) vs Kavram Sapması (Concept Drift)

Terim Ne Demek? Neden Önemli?
Data Drift 📊 Girdi özelliklerinin dağılımı (ör. yaş, gelir, tıklama sayısı) zamanla değişir. Modelin gördüğü x dağılımı eğitimdeki x’ten sapar. Model aynı f(x)’i kullanıyor ama x artık farklı → tahminler yanlışlaşır.
Concept Drift 🎯 Etiket (y) ile girdi (x) arasındaki ilişki değişir. Örn: önce "yüksek harcama = sadık müşteri", şimdi "yüksek harcama = kampanya avcısı". Modelin öğrendiği f(x) artık geçerli değil; yeniden eğitim zorunlu.

Kısaca: Data drift = girdi dağılımı değişir. Concept drift = hedef fonksiyonu değişir. İkisi de modelin sessizce çökmesine yol açar 😱


Tipik "3 Ay Sonra Kötüleşti" Senaryosu

  1. Ay 0‑3: Üretim verisi eğitim verisine benzedi → metrikler mükemmel.
  2. Ay 4: Pazarlama kampanyası başladı, yeni kullanıcı profilleri geldi.
  3. Ay 5: Girdi dağılımı kaydı (data drift) → model eski dağılım için optimize edilmişti.
  4. Ay 6: Müşteri davranışı de değişti (concept drift) → tahminler sistematik hata yapmaya başladı.

Sonuç: Model "iyi" görünüyor ama aslında eski dünyaya uygun. Biz bunu sessizce fark etmemiz lazım.


İzlememiz Gereken Metrikler 📈

  • Girdi Dağılımı (Embedding Drift) – Özellik vektörlerinin veya embedding’lerinin dağılım karşılaştırması.
  • Tahmin Dağılımı – Modelin ürettiği skor/sınıf dağılımı referansla ne kadar sapıyor?
  • Etiket Dağılımı (varsa) – Gerçek etiketlerin (ground truth) dağılımı değişiyor mu?

Bu metrikler günlük/haftalık izlenmeli ve bir eşik aşıldığında otomatik uyarı (alert) tetiklenmeli ⚠️.


Otomatik Uyarı Tetikleyicileri (Alerting) Önerisi

Metrik Yöntem Eşik Örneği Aksiyon
Feature drift KS Testi / PSI p‑value < 0.01 veya PSI > 0.2 Slack/Email → "Feature X drift tespit edildi"
Prediction drift KL Divergence KL > 0.05 Model yeniden eğitim pipeline’ını tetikle
Label drift (varsa) Chi‑square p‑value < 0.01 Veri mühendisliği ekibine bildir

İpucu: Alert’ları sessiz modda başlatın, gürültüyü azaltmak için burn-in periyodu (ör. 7 gün) uygulayın.


Kod Örneği: Evidently AI ile Data Drift Raporu 📋

Aşağıdaki script, referans (eğitim) ve mevcut (production) veri setleri arasında Data Drift Report üretir. Raporu HTML olarak kaydedip CI/CD’nize ekleyebilirsiniz.

# drift_report.py
import pandas as pd
from evidently.report import Report
from evidently.metric_preset import DataDriftPreset

# 1️⃣ Verileri yükle (CSV, Parquet, DB …)
reference_df = pd.read_parquet("data/reference.parquet")   # eğitim/doğrulama seti
current_df   = pd.read_parquet("data/current.parquet")     # üretim verisi (son 1 hafta)

# 2️⃣ Rapor nesnesi oluştur
drift_report = Report(metrics=[DataDriftPreset()])

# 3️⃣ Çalıştır
drift_report.run(reference_data=reference_df, current_data=current_df)

# 4️⃣ HTML çıktısı al
drift_report.save_html("reports/data_drift_report.html")
print("✅ Data Drift raporu oluşturuldu: reports/data_drift_report.html")

Nasıl Çalışır? 🛠

  1. Reference = modelin eğitildiği dağılım (zemin gerçeği).
  2. Current = canlı trafikten gelen son veriler.
  3. DataDriftPreset() hem istatistiksel testleri (KS, Chi‑square, PSI) hem de görselleştirmeleri (histogram, ECDF) içeren hazır bir preset’tir.
  4. HTML raporunda her feature için drift skoru ve p‑value görürsünüz. Eşikleri aşıyan feature’lar kırmızı ile vurgulanır 🔴.

Bu sayede ne oluyor?

  • Günlük bir cron/job ile bu scripti çalıştırırsınız.
  • Rapor HTML’i Grafana/Slack/Confluence’a gönderilir.
  • Otomatik alert kuralınız (ör. any(p_value < 0.01)) tetiklendiğinde ekip anında haberdar olur.

Alternatif: SciPy KS Testi ile Embedding Drift (Hızlı Prototip) ⚡

Eğer Evidently kurmak istemiyorsanız, embedding vektörleri üzerinde basit bir KS testi de işinizi görür:

from scipy.stats import ks_2samp
import numpy as np

# reference_emb, current_emb → (n_samples, dim) boyutunda numpy array
def embedding_drift_score(ref_emb: np.ndarray, cur_emb: np.ndarray, alpha: float = 0.01) -> dict:
    drift_flags = {}
    for dim in range(ref_emb.shape[1]):
        stat, p = ks_2samp(ref_emb[:, dim], cur_emb[:, dim])
        drift_flags[dim] = p < alpha
    return drift_flags

# Kullanım
flags = embedding_drift_score(reference_embeddings, current_embeddings)
print(f"Drift olan boyut sayısı: {sum(flags.values())}")
  • Her boyut için p‑value < 0.01 → o boyutta anlamlı drift var.
  • Toplam drift boyut sayısı eşikten fazlaysa alert tetikleyin.

Özet 🎯

  • Data drift = girdi dağılımı değişir → model eski dağılım için optimize kalır.
  • Concept drift = hedef fonksiyon değişir → modelin mantığı artık geçerli değil.
  • Metrikler: Girdi/Tahmin/Etiket dağılımı + istatistiksel testler (KS, PSI, KL).
  • Otomasyon: Evidently AI raporu veya basit KS testi + alerting (Slack, PagerDuty, vs.).
  • Eylem: Drift tespit edilince → veri mühendisliği (feature store güncelleme) + model yeniden eğitim pipeline’ını tetikleyin.

Hadi bu altyapıyı kurup modelinizin "sessiz çöküşünü" önleyelim! 🚀


🛠 Guardrails: Güvenli ve Güvenilir Çıktılar İçin Koruma Şeritleri

Guardrails, sadece güvenlik değil; format, tutarlılık ve kalite de sağlayan koruma şeritleridir 🎯.
Modelin ürettiği her şeyi “güvenli bir kutu” içine alırsınız; dışarıda kalacak şeyler engellenir veya düzeltilir.

📥 Girdi (Input) Guardrails

  • PII temizleme – isim, TC, e‑posta gibi hassas verileri maskeler ✅
  • Prompt injection koruması – “Sistem mesajını görmezden gel” gibi saldırıları tespit eder ⛔
  • Konu dışı soruları engelleme – “Hava durumu nasıl?” sorusunu, finans botunda reddeder 🔁

📤 Çıktı (Output) Guardrails

  • JSON schema zorunluluğu – modelin çıktıyı istediğimiz yapıda vermesini garanti eder 📐
  • Yasaklı ifadeler – “hack”, “exploit”, “şifre” gibi kelimeleri engeller ❗️
  • Halüsinasyon kontrolü (kaynak doğrulama) – her cevap için kaynak gösterme zorunluluğu koyar 📚

🛠 Popüler Araçlar

  • Guardrails AI – rail spec (XML) ile kuralları tanımlarsınız
  • NeMo Guardrails – YAML/Colang config ile akış tabanlı kontroller
  • Microsoft Guidance – şablon tabanlı üretim ve kısıtlama

Kısa bir deneyim: Ben output guardrail sayesinde modelin JSON’u bozmasını engelledim. Daha önce “virgül eksik” hatalarıyla saatler kaybediyordum; artık şema uyumsuzluğu anında yakalanıyor ve model kendini düzeltiyor 🚀.


Guardrails AI Rail Spec Örneği (XML)

<rail version="0.1">
  <!-- Çıktı JSON şeması -->
  <output>
    <object name="answer" required="true">
      <string name="summary" required="true" description="Kısa özet"/>
      <array name="sources" required="true">
        <string description="Kaynak URL veya başlık"/>
      </array>
    </object>
  </output>

  <!-- Yasaklı kelime kontrolü -->
  <validators>
    <validator name="banned-words" on="answer.summary">
      <param name="words">["hack", "exploit", "şifre"]</param>
      <on-fail>reask</on-fail>
    </validator>

    <!-- Kaynak zorunluluğu -->
    <validator name="required-field" on="answer.sources">
      <param name="min-length">1</param>
      <on-fail>reask</on-fail>
    </validator>
  </validators>
</rail>

Ne oluyor burada?

  1. <output> bloğu, modelin dönmesi gereken JSON yapısını tanımlar.
  2. banned-words validator, summary alanında “hack” gibi kelimeleri yakalayıp modeli tekrar sormaya (reask) zorlar.
  3. required-field validator, sources dizisinin en az bir öğe içermesini garanti altına alır.

Bu şemayı projenize eklediğinizde, model her seferinde istenen formatta, güvenli ve kaynaklı bir cevap üretir. 🎉


🎯 Son Söz: Mühendislik Displinli AI Üretime Taşımak

Model seçmek eğlenceli kısım 🎉
Mühendislik ise onu sürdürülebilir kılan kısım 🛠

Üretime çıkmadan önce bu kontrol listesini bir kez daha gözden geçir:

  • Evals – Her değişiklikte otomatik testleriniz çalışıyor mu?
  • 📈 Observability – Metrikler, loglar ve alarmlar yerinde mi?
  • 💰 Cost – Token ve GPU maliyetleri takip ediliyor mu?
  • 🔄 Drift – Veri ve model performansı zamanla kayma gösteriyor mu?
  • 🚧 Guardrails – Güvenlik, bias ve politika kontrolleri aktif mi?

Bu adımları atlamak "hızlı çıkış" gibi görünse de, uzun vadede felaket yaratıyor ❗️


🎤 Sizin sıra sizde

Kendi üretim felaket hikayelerinizi yorumlarda paylaşın 👇
Başka takımların aynı hataları yaşamaması için deneyimleriniz altın değerinde.


🚀 Gelecek yazıda görüşürüz

Bir sonraki yazımda bu kontrollerden birini derinlemesine kodlayacağız – birlikte pratik bir guardrail veya drift detection pipeline'ı yazalım.

Hazırsanız başlayalım, mühendislik disipliniyle AI'yi güvenle üretime taşıyalım! 💪✨


Bu içerik tamamen yapay zeka destekli otomasyon sistemi ile üretilmiştir.

Burak Sağlık

Burak Saglik

©2024 Desing and Developed by @Burak Sağlık

All rights reserved