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

Wed Aug 12 2026

LLM Değerlendirme Pipeline Kurulumu: Veriden CI/CD'ye Tam Rehber

LLM Değerlendirme Pipeline Kurulumu: Veriden CI/CD'ye Tam Rehber

🎯 Giriş: Neden "Vibe Check" Yeterli Değil?

Hey! 👋
Birkaç ay önce bir mikro servisimizde “her şey yolunda gibi hissediyorum” diyip canlıya göndermiştik. Sonuç? %12 hata oranı, kullanıcı şikayetleri patlamıştı ve ekibimiz gece yarısı tekrar deploy etmek zorunda kalmıştı. 😅

Neden sadece hissiyat (vibe check) yetmez?

  • Gözden kaçan edge-case’ler → İnsan beynimiz pattern’leri hızlı tarar ama nadir senaryoları atlar.
  • Tekrarlanabilirlik yok → “Bugün iyim” demenin bir sonraki hafta da geçerli olması için bir standart lazım.
  • Takım içinde paylaşım zor → Başka bir geliştirici “nasıl test ettin?” diye sorduğunda cevap “hissettim” olursa işler karışır.
  • Ölçülebilir metrik yok → Hangi kriterleri karşıladığını kanıtlayamazsın; bu da CI/CD’ye entegre olmaz.

Gerçek hayattan bir örnek 📦

Senaryo: Ödeme servisinde yeni bir indirim kodu özelliği ekledik.
Vibe check: “Kod çalışıyor, testler geçti, iyi gidiyor.”
Gerçeklik: Prod’da %5’lik bir race condition ortaya çıktı, çünkü eşzamanlı istekler test ortamında simüle edilmemişti.
Sonuç: Müşteriler double-charge oldular, destek hattı çöktü. 🚨

Sistematik bir değerlendirme hattı (pipeline) neden kritik? ✅

  1. Otomatik testler → Unit, integration, contract testleri her push’ta çalışır.
  2. Statik analiz & lint → Kod stilleri, güvenlik açıkları erken yakalanır.
  3. Canary / blue‑green deploy → Küçük bir kullanıcı grubunda doğrulama, riski minimize eder.
  4. Gözlemlenebilirlik → Metrikler, loglar, trace’ler anlık geri bildirim verir.
  5. Rollback stratejisi → Bir hata olursa saniyeler içinde eski versiyona dönersin.

Özetle: Hissiyat harika bir başlangıçtır ama tek başına yeterli değildir. Güvenilir bir ürün için tekrarlanabilir, ölçülebilir ve otomatik bir pipeline kurmamız lazım. 🚀

Hazırsan bir sonraki bölümde bu pipeline’ı nasıl adım adım kuracağımızı inceleyelim! 🎉


🔁 Değerlendirme Hattının Temel Bileşenleri

Hazırsan birlikte keşfedelim 🚀
LLM değerlendirme hattı (evaluation pipeline) aslında dört ana taş üzerine kurulu. Her biri birbirine bağlanır ve eksikliği hissettirir. İşte o dört temel bileşen:

1️⃣ Veri (Data) 📦

  • Ne işe yarar? Modelinizi test edecek girdi‑çıkış çiftlerini, referans cevapları ve olası edge‑case’leri barındırır.
  • Etkileşim: Metrikler bu veriler üzerinde hesaplanır; otomasyon veri setlerini otomatik çekebilir; observability veri kalitesini izler.

2️⃣ Metrik (Metrics) 📏

  • Ne işe yarar? Model performansını sayısallaştırır: BLEU, ROUGE, factuality, latency, token‑cost vb.
  • Etkileşim: Veriden beslenir; otomasyon metrikleri sürekli hesaplayıp raporlar; observability metrik trendlerini alarm olarak takip eder.

3️⃣ Otomasyon (Automation) ⚙️

  • Ne işe yarar? Veri hazırlama, metrik hesaplama, raporlama ve hatta model yeniden eğitim tetikleme gibi tekrarlayan işleri CI/CD boru hattında çalıştırır.
  • Etkileşim: Veri ve metrik modüllerini çağırır; observability’den gelen uyarılara göre pipeline’ı durdurur veya yeniden başlatır.

4️⃣ Observability (Gözlemlenebilirlik) 👁️

  • Ne işe yarar? Pipeline’ın sağlığını, gecikmelerini, hata oranlarını ve veri sapmalarını gerçek zamanlı görmenizi sağlar (log, trace, dashboard, alert).
  • Etkileşim: Diğer üç bileşenden telemetri toplar; anomali tespit ettiğinde otomasyona müdahale sinyali gönderir.

🔄 Bileşenler Arası Basit Akış Şeması

flowchart LR
    A[📦 Veri] --> B[📏 Metrik]
    B --> C[⚙️ Otomasyon]
    C --> D[👁️ Observability]
    D -->|Alert / Feedback| C
    D -->|Veri Kalite| A
    C -->|Rapor| B

Ne oluyor burada?

  1. Veri akışı başlar.
  2. Metrik modülü veriyi işler ve skor üretir.
  3. Otomasyon bu skorları alır, raporlar ve gerekirse modeli yeniden eğitir.
  4. Observability her adımı izler; bir sorun çıkarsa (ör. veri kayması, metrik düşüşü) otomasyona alert gönderir ve döngü kapanır.

Bu dört taş birbirini besler; birinde kopuk olursa tüm hattı sarsar. Bu yüzden hepsini birlikte tasarlamak ve sürekli gözlemlemek kritik ✅


🛠 Veri Toplama ve Etiketleme Stratejisi

Hazırsan başlayalım 🚀
Üretimdeki veriyi nasıl toplarız, insan geri bildirimini nasıl sistematik hale getiririz ve veri sürümleme araçlarını (DVC, MLflow) nasıl entegre ederiz? İşte pratik bir yol haritası:

1️⃣ Veri toplama pipeline’ı kur

  • Kaynakları belirle: API’ler, log dosyaları, veritabanları, dış sağlayıcılar.
  • Otomatik çekim: cron / Airflow / Prefect ile periyodik görevler.
  • İlk temizlik: eksik değerler, duplicate’lar, format hataları.

2️⃣ İnsan geri bildirimi (Human‑in‑the‑Loop) ekle

Adım Ne yapılır? Araç önerisi
Etiketleme arayüzü Annotator’lar için basit web UI Label Studio, Prodigy
Kalite kontrol Çoklu etiketçi konsensüsü, gold‑set testleri sklearn.metrics.cohen_kappa_score
Geri besleme Yanlış etiketleri model eğitimine hard‑negative olarak ekle MLflow log_artifact

3️⃣ Veri sürümleme (Versioning)

  • DVC → büyük dosyaları Git’ten ayır, dvc push/pull ile paylaş.
  • MLflow → deney takibi + veri seti artefaktı olarak logla.

İpucu: Veri seti her değiştiğinde dvc add data/raw/dvc commitgit commit -m "v1.2 raw data" akışını otomatikleştir.


🐍 Pratik Python betiği: Çek → Temizle → DVC sürümle

# fetch_and_clean.py
import requests
import pandas as pd
from pathlib import Path

RAW_DIR = Path("data/raw")
RAW_DIR.mkdir(parents=True, exist_ok=True)

def fetch_api(url: str, out_path: Path) -> None:
    """API’den ham veriyi indir ve kaydet."""
    resp = requests.get(url, timeout=30)
    resp.raise_for_status()
    out_path.write_bytes(resp.content)
    print(f"✅ İndirildi: {out_path}")

def clean_csv(in_path: Path, out_path: Path) -> None:
    """Basit temizlik: duplicate drop, boşlukları strip et."""
    df = pd.read_csv(in_path)
    df = df.drop_duplicates()
    df = df.applymap(lambda x: x.strip() if isinstance(x, str) else x)
    df.to_csv(out_path, index=False)
    print(f"🧹 Temizlendi: {out_path}")

if __name__ == "__main__":
    # 1️⃣ Veri çek
    api_url = "https://example.com/dataset.csv"
    raw_file = RAW_DIR / "dataset_raw.csv"
    fetch_api(api_url, raw_file)

    # 2️⃣ Temizle
    clean_file = RAW_DIR / "dataset_clean.csv"
    clean_csv(raw_file, clean_file)

DVC komutları (terminalde çalıştır)

# 1️⃣ DVC init (ilk kez)
dvc init

# 2️⃣ Ham veriyi DVC takip altına al
dvc add data/raw/dataset_raw.csv

# 3️⃣ Temizlenmiş veriyi de ekle
dvc add data/raw/dataset_clean.csv

# 4️⃣ Değişiklikleri Git’e kaydet
git add .gitignore data/raw/.gitignore
git commit -m "v1.0 raw & clean data added"

# 5️⃣ Uzak depoya (ör. S3, GCS) push
dvc push

Ne oldu?

  • fetch_and_clean.py çalıştırdığında ham veri data/raw/dataset_raw.csv olarak iner.
  • Temizleme sonrası dataset_clean.csv oluşur.
  • dvc add ile her dosya .dvc meta dosyası alır, Git büyük dosyaları depolamaz.
  • dvc push ile takım arkadaşların dvc pull diyerek aynı sürümü alır 🎯.

Özet kontrol listesi ✅

  • Veri çekme scripti idempotent (tekrar çalıştırılabilir).
  • Temizleme adımları unit test ile doğrulanmış.
  • Her yeni veri sürümü için dvc commit + git tag vX.Y.
  • Etiketleme arayüzünden gelen onaylar MLflow run olarak loglanıyor.

Bu akışı kurunca veri kümen güvenilir, takip edilebilir ve tekrar üretilebilir hale gelir 🚀.


❗️ Metrik Seçimi: Doğruluk, Tutarlılık, Güvenlik

Hadi, modelimizin ne kadar iyi çıktığını ölçmek için hangi metrikleri seçmemiz gerektiğini konuşalım 🎯. Yanlış metrik seçmek, modeli "iyi" sanmamıza ama aslında tehlikeli çıktılar üretmesine yol açabilir ❗️.

1️⃣ Yaygın Metrikler ve Ne Zaman Anlamlı?

Metrik Ne Ölçer? Hangi Senaryoda Parlak?
BLEU n‑gram örtüşmesi (genelde makine çevirisi) Çeviri, özetleme gibi kelime sırası önemli görevler
ROUGE‑L / ROUGE‑1 En uzun ortak alt dizi / unigram örtüşmesi Özetleme, başlık üretimi – recall odaklı
BERTScore Contextual embedding benzerliği (F1) Paraphrase, diyalog, anlamsal benzerlik gereken yerler
Toxicity (Perspective API vb.) Zararlı / küfürlü içerik oranı Kullanıcı yanıtı, chatbot, içerik moderasyonu
Hallucination Rate Gerçeklere dayanmayan bilgi oranı RAG, bilgi odaklı QA, raporlama

Kural: Tek bir metrik yeterli değildir. Çok boyutlu bir skor tablosu kurun.

2️⃣ Metrik Seçiminde Yaygın Yanlışlar 🚫

  • Sadece BLEU/ROUGE'ya güvenmek → Anlamsal kaliteyi kaçarak "kelime oyunu" yapar.
  • Threshold'ları sabit tutmak → Veri dağılımı değişince model metric gaming yapar (ör. BLEU yüksek ama anlam bozuk).
  • Güvenlik metriklerini (toxicity, hallucination) sonradan eklemek → Üretimde sürprizler yaşarsınız.
  • Ağırlıkları (weight) rasgele belirlemek → Bir metrik baskın olursa diğeri görmezden gelinir.

3️⃣ "Metric Gaming" Tehlikesi ⚠️

Model, optimize edilen metriği maksimize etmek için yol bulur:

  • BLEU için tekrar eden n‑gramlar üretir.
  • BERTScore için yaygın vektörleri kopyalar.
  • Toxicity düşürmek için "safe" ama anlamsız cevaplar verir.

Çözüm:
Çoklu metrik + ağırlıklı skor
Düzenli insan değerlendirmesi (human eval)
Adversarial test setleri ile metriği sınayın.

4️⃣ Kendi Proje İçin Metrik Seti Oluşturma Checklist 📋

  • Görev türünü netleştir (çeviri, özet, QA, chat…).
  • Birincil kalite metriklerini seç (BLEU/ROUGE/BERTScore).
  • Güvenlik metriklerini ekle (toxicity, bias, hallucination).
  • Her metrik için threshold ve weight belirle.
  • A/B test planı yap: metrik değişimi → kullanıcı deneyimi nasıl etkilendi?
  • İnsan değerlendirme protokolü tanımla (ör. 3 annotatör, Cohen’s κ).
  • Monitoring dashboard'a metrikleri entegre et.
  • Periyodik gözden geçirme takvimi koy (ayda bir).

5️⃣ Örnek Metrik Konfigürasyonu (YAML)

metrics:
  - name: bleu
    threshold: 0.35
    weight: 0.3
  - name: rougeL
    threshold: 0.45
    weight: 0.2
  - name: bertscore_f1
    threshold: 0.80
    weight: 0.3
  - name: toxicity
    threshold: 0.10
    weight: 0.1
  - name: hallucination_rate
    threshold: 0.05
    weight: 0.1

Nasıl çalışıyor?

  • Her metrik threshold üstünde ise weight kadar puan kazanır.
  • Toplam skor 1.0'a yakınsa model kabul edilebilir sayılır.
  • Threshold/weight değerlerini proje ihtiyacına göre ince ayar yapın 🛠.

Özet: Doğru metrik seti = Kalite + Güvenlik + Tutarlılık. Tek bir sayıya takılmayın, çok boyutlu bir tablo kurun ve sürekli insan geri bildirimiyle doğrulayın ✅.


🛠 Kod Tabanlı Değerlendirme Scriptleri Yazma

Değerlendirmeyi notebook'ta ad-hoc yapmak iş görür ama; prod'a alacaksan modüler, test edilebilir ve yeniden kullanılabilir bir yapı lazım. Hadi evaluate.py modülünü adım adım yazalım — kopyala, yapıştır, çalıştır 🚀


🎯 Neden bir modül?

  • Tek sorumluluk: Prompt hazırlama, model çağrısı, metrik hesaplama birbirinden ayrı
  • Hata toleransı: Bir örnek hata verse tüm batch'i yıkmaz
  • Loglama: Ne yapıldığını, ne kadar sürdüğü, nerede patladığını görürsün
  • CI/CD dostu: pytest ile test edilebilir, pipeline'a sokulabilir

📦 Dosya yapısı

project/
├── evaluate.py          # ← yazacağımız modül
├── metrics.py           # opsiyonel: metrik fonksiyonları
├── prompts/
│   └── eval_prompt.txt  # prompt şablonu
└── main.py              # kullanım örneği

🧩 evaluate.py — Tam kod

"""
evaluate.py
-----------
Model çıktılarını otomatik değerlendiren, loglayan ve metrik üreten modül.
"""

from __future__ import annotations

import json
import logging
import time
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Callable, Any

# ── Logger ayarı ──────────────────────────────────────────────
logger = logging.getLogger("evaluator")
logger.setLevel(logging.INFO)

if not logger.handlers:
    handler = logging.StreamHandler()
    handler.setFormatter(
        logging.Formatter(
            "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
            datefmt="%H:%M:%S",
        )
    )
    logger.addHandler(handler)


# ── Veri sınıfları ────────────────────────────────────────────
@dataclass
class EvalSample:
    """Tek bir değerlendirme örneği."""
    id: str
    input_text: str
    reference: str | None = None          # ground-truth (varsa)
    metadata: dict | None = None          # ekstra alanlar


@dataclass
class EvalResult:
    """Değerlendirme sonucu."""
    sample_id: str
    prediction: str
    score: float | None = None            # metrik skoru (0-1 arası örn.)
    passed: bool | None = None            # eşik geçiş durumu
    latency_ms: int = 0
    error: str | None = None


# ── Yardımcı fonksiyonlar ─────────────────────────────────────
def load_prompt(template_path: str | Path) -> str:
    """Prompt şablonunu dosyadan okur."""
    path = Path(template_path)
    if not path.exists():
        raise FileNotFoundError(f"Prompt dosyası bulunamadı: {path}")
    return path.read_text(encoding="utf-8")


def render_prompt(template: str, sample: EvalSample) -> str:
    """Şablondaki placeholder'ları doldurur."""
    return template.format(
        input_text=sample.input_text,
        reference=sample.reference or "",
        **(sample.metadata or {}),
    )


def call_model(prompt: str, model_fn: Callable[[str], str]) -> str:
    """
    Model fonksiyonunu çağırır.
    `model_fn` signature: (prompt: str) -> response: str
    """
    start = time.perf_counter()
    response = model_fn(prompt)
    elapsed = int((time.perf_counter() - start) * 1000)
    logger.debug(f"Model çağrısı {elapsed} ms sürdü")
    return response, elapsed


def compute_metrics(
    prediction: str,
    reference: str | None,
    metric_fns: dict[str, Callable[[str, str], float]],
) -> dict[str, float]:
    """Tüm metrikleri hesaplar, reference yoksa boş dict döner."""
    if reference is None:
        return {}
    return {name: fn(prediction, reference) for name, fn in metric_fns.items()}


def evaluate_single(
    sample: EvalSample,
    model_fn: Callable[[str], str],
    prompt_template: str,
    metric_fns: dict[str, Callable[[str, str], float]],
    threshold: float = 0.7,
    main_metric: str = "f1",
) -> EvalResult:
    """
    Tek bir örnek için tam değerlendirme akışı.
    """
    sample_start = time.perf_counter()

    try:
        # 1️⃣ Prompt hazırla
        prompt = render_prompt(prompt_template, sample)

        # 2️⃣ Modeli çağır
        prediction, latency_ms = call_model(prompt, model_fn)

        # 3️⃣ Metrikleri hesapla
        metrics = compute_metrics(prediction, sample.reference, metric_fns)

        # 4️⃣ Geç/kal kararı (ana metrik varsa)
        passed = None
        score = metrics.get(main_metric)
        if score is not None:
            passed = score >= threshold

        return EvalResult(
            sample_id=sample.id,
            prediction=prediction,
            score=score,
            passed=passed,
            latency_ms=latency_ms,
        )

    except Exception as exc:  # ❗️ tek örnek hata verse batch durmaz
        logger.exception(f"Örnek {sample.id} değerlendirilemedi")
        return EvalResult(
            sample_id=sample.id,
            prediction="",
            error=str(exc),
            latency_ms=int((time.perf_counter() - sample_start) * 1000),
        )


# ── Ana fonksiyon ─────────────────────────────────────────────
def evaluate_model(
    samples: list[EvalSample],
    model_fn: Callable[[str], str],
    prompt_template: str,
    metric_fns: dict[str, Callable[[str, str], float]],
    threshold: float = 0.7,
    main_metric: str = "f1",
    output_path: str | Path | None = None,
) -> list[EvalResult]:
    """
    Toplu değerlendirme çalıştırır.

    Args:
        samples: Değerlendirilecek örnekler
        model_fn: (prompt) -> response fonksiyonu
        prompt_template: Format string (input_text, reference, metadata)
        metric_fns: {metrik_adı: (pred, ref) -> float} sözlüğü
        threshold: Geçiş eşiği (ana metrik için)
        main_metric: Karar verilecek metrik adı
        output_path: JSONL olarak kaydetmek istersen dosya yolu

    Returns:
        EvalResult listesi
    """
    logger.info(f"🚀 Değerlendirme başlıyor — {len(samples)} örnek")
    results: list[EvalResult] = []

    for idx, sample in enumerate(samples, 1):
        logger.info(f"[{idx}/{len(samples)}] İşleniyor: {sample.id}")
        result = evaluate_single(
            sample=sample,
            model_fn=model_fn,
            prompt_template=prompt_template,
            metric_fns=metric_fns,
            threshold=threshold,
            main_metric=main_metric,
        )
        results.append(result)

        # Küçük bir ilerleme logu
        status = "✅" if result.passed else "❌" if result.passed is False else "⚠️"
        logger.info(f"   {status} {sample.id} — skor: {result.score:.3f}{result.latency_ms} ms")

    # Özet
    successful = [r for r in results if r.error is None]
    if successful:
        avg_score = sum(r.score or 0 for r in successful) / len(successful)
        pass_rate = sum(1 for r in successful if r.passed) / len(successful)
        logger.info(f"📊 Ortalama {main_metric}: {avg_score:.3f} | Geçme oranı: {pass_rate:.1%}")

    # İsteğe bağlı kaydetme
    if output_path:
        _save_results(results, output_path)
        logger.info(f"💾 Sonuçlar kaydedildi: {output_path}")

    return results


def _save_results(results: list[EvalResult], path: str | Path) -> None:
    """Sonuçları JSONL formatında yazar."""
    path = Path(path)
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("w", encoding="utf-8") as f:
        for r in results:
            f.write(json.dumps(asdict(r), ensure_ascii=False) + "\n")

🔍 Kodun önemli noktaları

Parça Ne işe yarar?
EvalSample / EvalResult Tip güvenliği, IDE autocomplete, temiz veri taşımacılığı
logger Konsola renkli, zaman damgalı log — prod'da dosyaya da yönlendirebilirsin
evaluate_single Tek sorumluluk: bir örnek = bir try/except bloğu. Batch'in geri kalanı etkilenmez
metric_fns dict Yeni metrik eklemek için kod değiştirmezsin, sadece dict'e eklersin 🔌
output_path CI/CD artefactı olarak JSONL alırsın, sonra pandas.read_json(lines=True) ile analiz edersin

🧪 Hemen test edelim — main.py

# main.py
from evaluate import evaluate_model, EvalSample, load_prompt
from metrics import f1_score, exact_match  # kendi metrik dosyan

# 1️⃣ Basit bir model stub (gerçekte OpenAI/Local LLM wrapper'ın olacak)
def dummy_model(prompt: str) -> str:
    return "Bu bir test cevabıdır."

# 2️⃣ Örnek veri
samples = [
    EvalSample(id="ex-1", input_text="Türkiye'nin başkenti?", reference="Ankara"),
    EvalSample(id="ex-2", input_text="2+2=?", reference="4"),
]

# 3️⃣ Prompt şablonu (prompts/eval_prompt.txt dosyasından da yükleyebilirsin)
template = """Kullanıcı sorusu: {input_text}
Referans cevap: {reference}
Model cevabını değerlendir."""

# 4️⃣ Çalıştır
results = evaluate_model(
    samples=samples,
    model_fn=dummy_model,
    prompt_template=template,
    metric_fns={"f1": f1_score, "em": exact_match},
    threshold=0.75,
    main_metric="f1",
    output_path="eval_results.jsonl",
)

# 5️⃣ Sonuçları incele
for r in results:
    print(f"{r.sample_id}: passed={r.passed} score={r.score:.2f}")

Çıktı örneği:

14:32:10 | INFO     | evaluator | 🚀 Değerlendirme başlıyor — 2 örnek
14:32:10 | INFO     | evaluator | [1/2] İşleniyor: ex-1
14:32:10 | INFO     | evaluator |    ❌ ex-1 — skor: 0.000 — 12 ms
14:32:10 | INFO     | evaluator | [2/2] İşleniyor: ex-2
14:32:10 | INFO     | evaluator |    ❌ ex-2 — skor: 0.000 — 9 ms
14:32:10 | INFO     | evaluator | 📊 Ortalama f1: 0.000 | Geçme oranı: 0.0%
14:32:10 | INFO     | evaluator | 💾 Sonuçlar kaydedildi: eval_results.jsonl

✅ Bu yapı sana ne kazandırdı?

  • Tek dosya evaluate.py — her projeye kopyalarsın, import evaluate dersin
  • Model bağımsızmodel_fn istediğin LLM client'ını sarmalar
  • Metrik bağımsızmetrics.py değişse bile değerlendirme kodu dokunmaz
  • Gözlemlenebilir — loglar, latency, hata detayları elinin altında
  • Otomatik rapor — JSONL çıkışı → Pandas / Grafana / Notebook ile anında analiz

🎁 İpucu: Prompt şablonunu dosyadan ayır

prompts/eval_prompt.txt içeriği:

Aşağıdaki soruya verilen cevabı, referans cevapla karşılaştır.
Sadece JSON formatında skor ver: {{"f1": 0.0-1.0, "em": 0 veya 1}}

Soru: {input_text}
Referans: {reference}
Model Cevabı: {prediction}

Sonra load_prompt("prompts/eval_prompt.txt") ile yükle — prompt mühendisliği koddan tamamen ayrılır 🧠


Hazırsan bir sonraki bölümde CI/CD pipeline'ına nasıl entegre edeceğimizi (GitHub Actions + artifact + PR comment bot) anlatabiliriz. Soru varsa slayt at! 🙌


🔁 Otomatik Test Çalıştırma ve CI/CD Entegrasyonu

Hazırsan, değerlendirme (evaluation) sürecimizi her push ve her pull request’ta otomatik çalıştıralım 🚀.
GitHub Actions (veya GitLab CI) ile basit, anlaşılır bir pipeline kurmak aslında çok zor değil. Aşağıda adım adım neler yaptığını, nasıl hata aldığımızda geri bildirim alabileceğimizi anlatıyorum.


🎯 Pipeline’in Genel Akışı

Aşama Ne Yapıyor? Neden Önemli?
install Gerekli Python paketlerini ve CLI araçlarını kurar Ortam her çalıştırmada temiz ve tekrar edilebilir olur
data‑pull Test verilerini (golden set, prompt şablonları) indirir Veri kaynağı merkezi, versiyonlanmış ve herkes aynı veriyi kullanır
eval Modeli/servisi test eder, metrikleri hesaplar Gerçek performans ölçümü, regresyon yakalama
report Sonuçları özetler, PR yorumuna ve/veya Slack’e gönderir Geliştirici anında durumdan haberdar olur, kod incelemesi hızlanır

🛠 GitHub Actions Workflow Dosyası

Aşağıdaki YAML dosyasını .github/workflows/llm-eval.yml yoluna koyarsan, her push ve pull_request tetiklenir.

name: LLM Evaluation Pipeline

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

jobs:
  eval:
    runs-on: ubuntu-latest
    timeout-minutes: 30

    # 1️⃣ Ortamı hazırla
    steps:
      - name: 📥 Checkout repository
        uses: actions/checkout@v4

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

      - name: 📦 Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt   # eval scriptlerinizin gerektirdiği paketler

      # 2️⃣ Test verilerini çek
      - name: 📂 Pull evaluation data
        env:
          DATA_REPO_TOKEN: ${{ secrets.DATA_REPO_TOKEN }}
        run: |
          # Örnek: DVC veya basit bir curl/wget ile veri indirme
          dvc pull data/eval_dataset.json   # veya kendi veri çekme scriptiniz

      # 3️⃣ Değerlendirmeyi çalıştır
      - name: 🧪 Run evaluation
        id: eval
        run: |
          python scripts/run_eval.py \
            --model-path ${{ secrets.MODEL_PATH }} \
            --data-path data/eval_dataset.json \
            --output results/eval_results.json

      # 4️⃣ Rapor oluştur & PR yorumla
      - name: 📝 Post results as PR comment
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const results = JSON.parse(fs.readFileSync('results/eval_results.json', 'utf8'));
            const markdown = `
### 📊 LLM Evaluation Results
| Metric | Value |
|--------|-------|
| Accuracy | ${results.accuracy.toFixed(4)} |
| F1‑Score | ${results.f1.toFixed(4)} |
| Latency (ms) | ${results.latency_ms.toFixed(2)} |
            `;
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: markdown
            });

      # 5️⃣ Slack bildirimi (başarı / başarısızlık)
      - name: 📢 Send Slack notification
        if: always()   # her durumda çalışır
        uses: slackapi/slack-github-action@v1.23.0
        with:
          payload: |
            {
              "text": "${{ job.status == 'success' && '✅' || '❌' }} *LLM Evaluation* ${{ job.status }} for `${{ github.repository }}` (run `${{ github.run_id }}`)",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "${{ job.status == 'success' && '✅' || '❌' }} *LLM Evaluation* ${{ job.status }} for `${{ github.repository }}` (run `${{ github.run_id }}`)"
                  }
                },
                {
                  "type": "section",
                  "fields": [
                    { "type": "mrkdwn", "text": "*Branch:* `${{ github.ref_name }}`" },
                    { "type": "mrkdwn", "text": "*Commit:* `${{ github.sha }}`" }
                  ]
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

🤔 Bu YAML Ne Sağlıyor?

  • on bloğu: main / develop branch’lerine push ya da PR açıldığında tetikler.
  • install adımları: Python 3.11 kurup requirements.txt ile bağımlılıkları yüklüyor.
  • data-pull: DVC (veya kendi scriptiniz) ile merkezi veri deposundan test setini indiriyor.
  • eval: run_eval.py scriptinizi çalıştırıp results/eval_results.json üretir.
  • report:
    • PR açıksa GitHub PR yorumu olarak özet metrikleri basar.
    • Slack webhook ile hem başarılı hem de başarısız durumda anında bildirim gönderir (always() sayesinde).

⚡️ Hata Durumunda Ne Olur?

  1. Pipeline kırmızıya döner → GitHub Actions arayüzünde “Failed” rozeti görürsünüz.
  2. PR yorumu yine oluşturulur (eğer eval adımı çıkış dosyasını yazdıysa) – hata mesajı da içerebilir.
  3. Slack’e ❌ emoji ile “LLM Evaluation failed” mesajı gider; ilgili commit/branch linkleriyle birlikte.
  4. Geliştirici “Re-run jobs” butonuyla düzeltme sonrası tekrar tetikleyebilir.

🛠️ İpuçları & En İyi Uygulamalar

  • Secret yönetimi: MODEL_PATH, DATA_REPO_TOKEN, SLACK_WEBHOOK_URL gibi hassas bilgileri Settings → Secrets altına ekleyin, asla kodda yazmayın.
  • Cache: pip install süresini kısaltmak için actions/cache@v4 ile ~/.cache/pip önbelleğini saklayın.
  • Matrix: Farklı Python sürümleri veya model variantları test etmek isterseniz strategy.matrix ekleyin.
  • Artifacts: results/eval_results.json dosyasını actions/upload-artifact ile kaydedip, daha sonra raporlama dashboard’ında kullanın.

✅ Özet

  • GitHub Actions ile her değişiklikte otomatik LLM değerlendirmesi çalışır.
  • Pipeline install → data‑pull → eval → report sırasıyla ilerler.
  • Başarısızlıkta PR yorumu + Slack bildirimi sayesinde ekip anında haberdar olur.
  • YAML dosyasını repoya ekleyip secret’ları tanımladığınızda sistem dakikalar içinde canlıya alınır.

Hadi şimdi .github/workflows/llm-eval.yml dosyasını commit edip, bir PR açarak test edelim! 🎉


🛠 Observability: Loglama, Metrikler ve Uyarılar

Hazırsanız model performansını canlı ortamda sürekli takip etmek için birkaç pratik adım atalım 🚀.
Ben genelde Prometheus + Grafana kombinasyonunu tercih ederim; OpenTelemetry de aynı metrikleri toplamak için harika bir alternatif.

🎯 Hangi metrikler dashboard’da olmalı?

  • Latency (gecikme) – p50, p95, p99 değerleri ile isteğin ne kadar sürdüğünü görürüz.
  • Error rate – 5xx / 4xx oranı, modelin hata döndürme sıklığı.
  • Drift – Girdi dağılımı (feature distribution) ile eğitim verisindeki dağılım arasındaki KL‑divergence veya PSI metriği.
  • Throughput – Saniyedeki istek sayısı (RPS), kapasite planlaması için.
  • Resource usage – CPU, GPU, memory; model sunucusunun darboğazını erken yakalamak için.

İpucu: Bu metrikleri OpenTelemetry Collector ile toplatıp Prometheus’a push ederseniz, hem log hem trace hem metric tek bir pipeline’dan gelir 🔁.


📄 Prometheus uyarı kuralları (YAML)

groups:
- name: model-serving-alerts
  rules:
  - alert: HighLatency
    expr: histogram_quantile(0.95, sum(rate(model_request_latency_seconds_bucket[5m])) by (le)) > 0.5
    for: 2m
    labels:
      severity: critical
    annotations:
      summary: "Model latency p95 > 500ms"
      description: "{{ $labels.instance }} üzerinde p95 latency {{ $value }}s aştı."

  - alert: HighErrorRate
    expr: sum(rate(model_requests_total{status=~"5.."}[5m])) / sum(rate(model_requests_total[5m])) > 0.02
    for: 1m
    labels:
      severity: warning
    annotations:
      summary: "Error rate > 2%"
      description: "{{ $labels.instance }} error rate {{ $value | humanizePercentage }}."

  - alert: DataDriftDetected
    expr: model_data_drift_psi > 0.2
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "Feature drift PSI > 0.2"
      description: "{{ $labels.feature }} drift değeri {{ $value }}."

Ne oluyor burada?

  • HighLatency → p95 500 ms’i geçerse 2 dk süreyle tetiklenir.
  • HighErrorRate → 5xx oranı %2’yi geçerse 1 dk sonra uyarı verir.
  • DataDriftDetected → PSI 0.2’yi aşarsa 5 dk izler, modelin yeniden eğitilmesi gerekebilir.

📊 Grafana dashboard JSON parçası (latency + error + drift)

{
  "dashboard": {
    "title": "Model Serving Overview",
    "panels": [
      {
        "type": "graph",
        "title": "Request Latency (p50, p95, p99)",
        "datasource": "Prometheus",
        "targets": [
          { "expr": "histogram_quantile(0.50, sum(rate(model_request_latency_seconds_bucket[5m])) by (le))", "legendFormat": "p50" },
          { "expr": "histogram_quantile(0.95, sum(rate(model_request_latency_seconds_bucket[5m])) by (le))", "legendFormat": "p95" },
          { "expr": "histogram_quantile(0.99, sum(rate(model_request_latency_seconds_bucket[5m])) by (le))", "legendFormat": "p99" }
        ]
      },
      {
        "type": "stat",
        "title": "Error Rate (5xx)",
        "datasource": "Prometheus",
        "targets": [
          { "expr": "sum(rate(model_requests_total{status=~\"5..\"}[5m])) / sum(rate(model_requests_total[5m]))", "legendFormat": "error_rate" }
        ],
        "fieldConfig": {
          "defaults": { "unit": "percentunit", "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "red", "value": 0.02 }] } }
        }
      },
      {
        "type": "table",
        "title": "Feature Drift (PSI)",
        "datasource": "Prometheus",
        "targets": [
          { "expr": "model_data_drift_psi", "format": "table" }
        ],
        "fieldConfig": {
          "defaults": { "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "orange", "value": 0.1 }, { "color": "red", "value": 0.2 }] } }
        }
      }
    ]
  }
}

Nasıl çalışıyor?

  • İlk panel latency quantile’leri çizer, anlık spike’ları görürsünüz.
  • İkinci panel error rate’i % cinsinden gösterir, eşik %2’de kırmızı olur.
  • Üçüncü panel PSI değerlerini tablo halinde listeler, hangi feature’dan drift geldiğini hızla tespit edersiniz.

🚨 Alertmanager + PagerDuty entegrasyonu

  1. Alertmanager config (alertmanager.yml) – receivers altında PagerDuty ekleyin:
receivers:
- name: pagerduty
  pagerduty_configs:
  - service_key: "<PAGERDUTY_INTEGRATION_KEY>"
    severity: "{{ .Labels.severity }}"
    details:
      firing: "{{ .Annotations.description }}"
      runbook_url: "https://wiki.example.com/runbooks/model-serving"
  1. Route kısmında group_by: [alertname, instance] ve receiver: pagerduty olarak ayarlayın.
  2. Inhibit rules ile HighLatency ve HighErrorRate aynı anda gelirse sadece bir bildirim gider (bildirim gürültüsünü azaltır) ⛔.

Pratik tavsiye: severity: critical alerts için escalation policy (5 dk, 15 dk, 1 sa) tanımlayın; warning seviyesi için sadece e-posta/Slack yeterli olabilir.


✅ Özet checklist

  • Prometheus scrape config’ine model exporter endpoint’ini ekle.
  • OpenTelemetry Collectorprometheusremotewrite exporter ile metrikleri gönder.
  • Yukarıdaki alert rules dosyasını rule_files: altına koy.
  • Grafana’da JSON’ı Import edip dashboard’u canlıya al.
  • Alertmanager → PagerDuty/Slack receiver’larını test et (amtool alert add ...).

Bu adımları uyguladığınızda, modeliniz production’da şeffaf hale gelir; anomali anında fark edilir, müşteri etkilenmeden müdahale edersiniz 🎉.


🔁 Sürekli İyileştirme Döngüsü: Geri Besleme ve Model Güncelleme

Hazırsan bu döngüyü adım adım kurup, nasıl otomatikleştireceğimize bakalım 🚀

Neden bir geri besleme döngüsü?

  • Model üretimde hata yapıyor → bu hataları toplayıp analiz etmeliyiz.
  • Yeni veriler gelince veri setini zenginleştirmek (data augmentation) performansı artırır.
  • Prompt’ları yeniden yazmak veya fine‑tuning kararı almak, modelin sürekli öğrenmesini sağlar.

Döngünün temel bileşenleri

Bileşen Ne yapar? Pratik ipucu
Hata analizi 🎯 Üretimdeki tahminleri, gerçek etiketlerle karşılaştır → hata türlerini (false positive, false negative, hallucination…) sınıflandır. pandas + matplotlib ile hızlı bir hata matrisi çiz.
Veri artırma 🔁 Hatalı örnekleri + doğru örnekleri birleştir → yeni eğitim seti oluştur. nlpaug, back‑translation veya basit synonym replacement kullan.
Prompt yeniden yazma ✍️ Hata analizi sonucu çıkan “zayıf” prompt’ları, daha net talimatlarla güncelle. Prompt şablonlarını Jinja2 ile parametrize et, versiyonla.
Fine‑tuning kararı ⚙️ Hata oranı eşik değerin üstündeyse → yeniden eğitim tetikle, aksi halde prompt güncellemesi yeterli. Eşik: örn. F1 < 0.85 → fine‑tuning.

Özet: Hata → Analiz → Veri zenginleştirme → Prompt/Model güncelleme → Tekrar değerlendirme. Bu bir sürekli döngü 🔄


Otomatikleştirme: Airflow DAG örneği

Aşağıdaki DAG her gece 02:00 çalışır:

  1. Değerlendirme script’ini çalıştırır (hata analizi + metrik hesaplama).
  2. Metrikler eşik altındaysa fine‑tuning task’ını tetikler.
  3. Aksi takdirde sadece prompt güncelleme task’ını çalıştırır.
from datetime import datetime, timedelta
from airflow import DAG
from airflow.operators.python import PythonOperator, BranchPythonOperator
from airflow.operators.dummy import DummyOperator

# ---- Yardımcı fonksiyonlar ----
def evaluate_model(**context):
    """Üretimdeki tahminleri değerlendir, metrikleri XCom'a yaz."""
    # Burada gerçek değerlendirme kodunuz olacak (ör. sklearn.metrics)
    f1_score = 0.82                # örnek değer
    context['ti'].xcom_push(key='f1', value=f1_score)
    print(f"📊 Güncel F1: {f1_score}")

def decide_next_step(**context):
    """F1 skoruna göre hangi branch'e gideceğimize karar ver."""
    f1 = context['ti'].xcom_pull(key='f1', task_ids='evaluate')
    if f1 < 0.85:
        return 'fine_tune'
    return 'update_prompt'

def fine_tune(**context):
    """Modeli yeniden eğit (fine‑tune)."""
    print("🛠 Fine‑tuning başlatıldı…")
    # trainer.train() vb.
    print("✅ Fine‑tuning tamamlandı.")

def update_prompt(**context):
    """Prompt şablonlarını güncelle."""
    print("✍️ Prompt yeniden yazılıyor…")
    # prompt_template.render(...)
    print("✅ Prompt güncellendi.")

# ---- DAG tanımı ----
default_args = {
    'owner': 'ml-engineer',
    'retries': 1,
    'retry_delay': timedelta(minutes=5),
}

with DAG(
    dag_id='continuous_improvement_loop',
    default_args=default_args,
    description='Günlük değerlendirme ve model güncelleme döngüsü',
    schedule_interval='0 2 * * *',   # Her gece 02:00
    start_date=datetime(2025, 1, 1),
    catchup=False,
    tags=['mlops', 'feedback-loop'],
) as dag:

    evaluate = PythonOperator(
        task_id='evaluate',
        python_callable=evaluate_model,
        provide_context=True,
    )

    branch = BranchPythonOperator(
        task_id='decide_next_step',
        python_callable=decide_next_step,
        provide_context=True,
    )

    fine_tune_task = PythonOperator(
        task_id='fine_tune',
        python_callable=fine_tune,
        provide_context=True,
    )

    update_prompt_task = PythonOperator(
        task_id='update_prompt',
        python_callable=update_prompt,
        provide_context=True,
    )

    end = DummyOperator(task_id='end')

    # Akış
    evaluate >> branch
    branch >> [fine_tune_task, update_prompt_task] >> end

Ne oldu burada?

  • evaluate task’ı metrikleri hesaplar ve XCom ile paylaşır.
  • decide_next_step branch yaparak, F1 < 0.85 ise fine_tune, aksi halde update_prompt çalıştırır.
  • Her iki yol da end dummy task’ında buluşur → DAG temiz bitecek.

Bu yapıyı Prefect, Dagster ya da basit bir cron + script ile de kurabilirsiniz; mantık aynı kalır 🎯


Sonuç: Geri besleme döngüsünü kod içinde (Airflow DAG) canlı tutarak, modeliniz her gün biraz daha akıllı hale gelir. Bir sonraki bölümde bu döngüyü nasıl gözlemleyip (monitoring) ve alert kuracağımızı anlatacağım. Görüşmek üzere! 🚀


❗️ Yaygın Tuzaklar ve Nasıl Kaçınılır

Üretimde sık sık gördüğümüz dört ana tuzak var. Her biri için ne yapmalı ve ne yapmamalı ikilisini hazırladım; sonuna da hızlı bir kontrol listesi ekledim 🎯


1️⃣ Veri Sızıntısı (Data Leakage) 🚨

  • Ne yapmalı?
    • Eğitim ve test setlerini kesinlikle ayır (zaman serisi için time‑based split kullan).
    • Özellik mühendisliğinde sadece geçmiş veriyi kullan; gelecek bilgisi (ör. hedef değişkenin kendisi) sızmasın.
    • Pipeline içinde pre‑processing adımlarını (scaling, encoding) fit sadece eğitim verisinde yap, test verisinde transform et.
  • Ne yapmamalı?
    • Test verisini eğitim öncesi herhangi bir işlemde (ör. outlier temizliği) dahil etme.
    • Modeli değerlendirirken train‑score’a güvenme; her zaman hold‑out / cross‑validation sonuçlarına bak.

2️⃣ Metrik Yanlılığı (Metric Misalignment) 📉

  • Ne yapmalı?
    • İş hedefiyle uyumlu birincil metrik seç (ör. fraud detection’da recall veya PR‑AUC).
    • Metriği çevrimiçi A/B testiyle doğrula; offline metric sadece bir proxy.
    • Confidence interval veya bootstrap ile metrik belirsizliğini raporla.
  • Ne yapmamalı?
    • Sadece accuracy ya da RMSE gibi genel metrikleri tek başına kullanma.
    • Modeli tek bir metrik için optimize edip, diğer kritik metrikleri (latency, fairness) göz ardı etme.

3️⃣ CI/CD Hızı ve Güvenilmezliği (Slow / Flaky Pipelines) 🐢

  • Ne yapmalı?
    • Paralel test koş (unit, integration, contract) → toplam süre < 10‑15 dk.
    • Cacheleme: bağımlılıklar, Docker layer’ları, test verileri.
    • Canary / blue‑green deploy ile riski küçült; rollback < 2 dk.
  • Ne yapmamalı?
    • Tek bir monolitik pipeline’da her şeyi sıralı koşturma.
    • Flaky test’leri ignor etme; hemen düzelt veya quarantine et.
    • Manuel onay adımlarını production’a kadar uzatma.

4️⃣ Maliyet Patlaması (Cost Explosion) 💸

  • Ne yapmalı?
    • Resource quotas ve budget alerts (CloudWatch, Prometheus + Alertmanager) ayarla.
    • Spot/Preemptible instance’ları batch eğitim için kullan; critical path için on‑demand.
    • Model serving için auto‑scaling + request batching etkinleştir.
  • Ne yapmamalı?
    • Sürekli GPU’ları boşta tutma; idle timeout ayarla.
    • Veri湖 (data lake) üzerinde partition/size kontrolü olmadan büyük tarama sorguları çalıştırma.
    • Log / metric retention sürelerini sonsuz bırakma.

✅ Pratik Kontrol Listesi (Haftalık / Release Öncesi)

✔️ Kontrol Açıklama
Veri bölünmesi Train/val/test temporal olarak ayrılmış mı?
Leakage testi Feature importance’ta hedef değişkeni veya gelecek bilgisi var mı?
Metrik doğrulaması Birincil metrik iş hedefiyle uyumlu mu? A/B planı var mı?
CI/CD süresi Toplam pipeline < 15 dk mı? Flaky test sayısı 0 mı?
Rollback testi Son 2 release’de rollback < 2 dk gerçekleşti mi?
Maliyet alarmı Günlük/monthly budget %80’e ulaştığında alert geliyor mu?
Resource kullanımı GPU/CPU utilization %70‑80 bandında mı? Idle instance yok mu?
Güvenlik / gizlilik PII verisi model artefact’ında yok mu?

İpucu: Bu listeyi GitHub Actions / GitLab CI içindeki bir job olarak otomatikleştir; her PR’de yeşil tick görmediğin sürece merge etme 🚀


Özet:

  • Veri sızıntısını pipeline seviyesinde engelle.
  • Metriği iş hedefine bağla, offline‑online farkını kapat.
  • CI/CD’yi hızlı, paralel, geri alınabilir kurgula.
  • Maliyetleri görünür, sınırlı, otomatik yönet.

Bu dört tuzak ve kontrol listesi yanında, üretimde sürprizler yerine önceden planlanmış aksiyonlar olacak 🎉


🎯 Bonus Tavsiye: Küçük Takım İçin Hızlı Başlangıç Şablonu

Küçük bir ekiple (1‑3 kişi) LLM değerlendirme pipeline’ı kurmak istiyorsanız, şu an elinizde tek bir bash betiği yeterli 🎉.
Aşağıdaki init_llm_eval.sh dosyasını çalıştırdığınızda; klasör yapısı, requirements.txt ve örnek config dosyaları otomatik oluşur.

Ne kazanırsınız?

  • 📁 Temiz, genişletilebilir klasör hiyerarşisi
  • 📦 Ortak bağımlılıklar tek dosyada (requirements.txt)
  • ⚙️ Hazır config şablonları (model, prompt, eval ayarları)
  • 🚀 Hemen pytest / make ile test edebileceğiniz iskelet

Kurulum Betiği – init_llm_eval.sh

#!/usr/bin/env bash
# init_llm_eval.sh – Küçük takım için minimal LLM eval repo şablonu
# Kullanım: chmod +x init_llm_eval.sh && ./init_llm_eval.sh

set -euo pipefail

REPO_ROOT="llm-eval-starter"

echo "🛠  Repo kök dizini oluşturuluyor: $REPO_ROOT"
mkdir -p "$REPO_ROOT"/{data/{raw,processed},prompts,eval,configs,scripts,.github/workflows}

# 1️⃣ requirements.txt
cat > "$REPO_ROOT/requirements.txt" <<'EOF'
openai>=1.0.0
tiktoken
pytest>=7.0
pyyaml
python-dotenv
tqdm
EOF

# 2️⃣ Örnek config.yaml
cat > "$REPO_ROOT/configs/config.yaml" <<'EOF'
model:
  name: "gpt-4o-mini"
  temperature: 0.2
  max_tokens: 512

prompt:
  template_path: "prompts/qa_template.txt"
  few_shot_examples: 3

eval:
  metrics:
    - "exact_match"
    - "f1"
    - "latency_ms"
  dataset: "data/processed/eval_set.jsonl"
EOF

# 3️⃣ Basit prompt şablonu
cat > "$REPO_ROOT/prompts/qa_template.txt" <<'EOF'
Soru: {question}
Cevap:
EOF

# 4️⃣ Basit eval script iskeleti
cat > "$REPO_ROOT/scripts/run_eval.py" <<'EOF'
#!/usr/bin/env python3
"""Basit LLM değerlendirme çalıştırıcısı."""
import yaml, sys, pathlib

CONFIG_PATH = pathlib.Path(__file__).parents[1] / "configs" / "config.yaml"

def load_cfg():
    with open(CONFIG_PATH) as f:
        return yaml.safe_load(f)

def main():
    cfg = load_cfg()
    print("⚙️  Config yüklendi:", cfg["model"]["name"])
    # TODO: gerçek eval mantığını buraya ekle
    print("✅  Eval tamamlandı (placeholder)")

if __name__ == "__main__":
    main()
EOF
chmod +x "$REPO_ROOT/scripts/run_eval.py"

# 5️⃣ GitHub Actions CI iskeleti
cat > "$REPO_ROOT/.github/workflows/ci.yml" <<'EOF'
name: CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    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
      - name: Run tests
        run: pytest -q
EOF

# 6️⃣ README.md – hızlı başlangıç
cat > "$REPO_ROOT/README.md" <<'EOF'
# LLM Eval Starter 🎯

Minimal, küçük takım dostu bir değerlendirme iskeleti.

## Kurulum
```bash
git clone <repo>
cd llm-eval-starter
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

Çalıştırma

python scripts/run_eval.py

Yapı

  • data/ – ham & işlenmiş veriler
  • prompts/ – prompt şablonları
  • eval/ – metrik implementasyonları (gelicek)
  • configs/ – YAML config dosyaları
  • scripts/ – çalıştırılabilir betikler
  • .github/workflows/ – CI/CD

Hadi birlikte inşa edelim! 🚀 EOF

echo "✅ Şablon hazır: $REPO_ROOT" echo "👉 İçine girip sanal ortam oluşturun, bağımlılıkları yükleyin ve python scripts/run_eval.py ile test edin."


### Nasıl kullanılır?  

1. **Betiği kaydedin** → `init_llm_eval.sh`  
2. **Çalıştırılabilir yapın** → `chmod +x init_llm_eval.sh`  
3. **Çalıştırın** → `./init_llm_eval.sh`  

Artık `llm-eval-starter/` klasöründe **her şey hazır**.  
Kendi promptlarınızı `prompts/` altına, veri setlerinizi `data/raw/` içine atın, `configs/config.yaml`’ı ihtiyacınıza göre düzenleyin ve `scripts/run_eval.py`’i geliştiremeye başlayın.  

**Küçük takım, büyük etki** – şimdi siz de kendi değerlendirme hattınızı dakikalar içinde ayağa kaldırın.  

**Hadi birlikte inşa edelim!** 🚀✨

---
*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