
Wed Aug 12 2026

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ı. 😅
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ü. 🚨
Ö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! 🎉
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:
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?
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 ✅
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ı:
cron / Airflow / Prefect ile periyodik görevler.| 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 |
dvc push/pull ile paylaş.İpucu: Veri seti her değiştiğinde
dvc add data/raw/→dvc commit→git commit -m "v1.2 raw data"akışını otomatikleştir.
# 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)
# 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.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 🎯.dvc commit + git tag vX.Y.Bu akışı kurunca veri kümen güvenilir, takip edilebilir ve tekrar üretilebilir hale gelir 🚀.
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 ❗️.
| 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.
Model, optimize edilen metriği maksimize etmek için yol bulur:
Çözüm:
✅ Çoklu metrik + ağırlıklı skor
✅ Düzenli insan değerlendirmesi (human eval)
✅ Adversarial test setleri ile metriği sınayın.
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?
Ö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 ✅.
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 🚀
pytest ile test edilebilir, pipeline'a sokulabilirproject/
├── 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")
| 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 |
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
evaluate.py — her projeye kopyalarsın, import evaluate dersinmodel_fn istediğin LLM client'ını sarmalarmetrics.py değişse bile değerlendirme kodu dokunmazprompts/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! 🙌
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.
| 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 |
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 }}
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:
always() sayesinde).eval adımı çıkış dosyasını yazdıysa) – hata mesajı da içerebilir.MODEL_PATH, DATA_REPO_TOKEN, SLACK_WEBHOOK_URL gibi hassas bilgileri Settings → Secrets altına ekleyin, asla kodda yazmayın.pip install süresini kısaltmak için actions/cache@v4 ile ~/.cache/pip önbelleğini saklayın.strategy.matrix ekleyin.results/eval_results.json dosyasını actions/upload-artifact ile kaydedip, daha sonra raporlama dashboard’ında kullanın.Hadi şimdi .github/workflows/llm-eval.yml dosyasını commit edip, bir PR açarak test edelim! 🎉
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.
İpucu: Bu metrikleri OpenTelemetry Collector ile toplatıp Prometheus’a
pushederseniz, hem log hem trace hem metric tek bir pipeline’dan gelir 🔁.
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.{
"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?
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"
group_by: [alertname, instance] ve receiver: pagerduty olarak ayarlayın.HighLatency ve HighErrorRate aynı anda gelirse sadece bir bildirim gider (bildirim gürültüsünü azaltır) ⛔.Pratik tavsiye:
severity: criticalalerts için escalation policy (5 dk, 15 dk, 1 sa) tanımlayın;warningseviyesi için sadece e-posta/Slack yeterli olabilir.
prometheusremotewrite exporter ile metrikleri gönder.rule_files: altına koy.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 🎉.
Hazırsan bu döngüyü adım adım kurup, nasıl otomatikleştireceğimize bakalım 🚀
| 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ü 🔄
Aşağıdaki DAG her gece 02:00 çalışı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.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! 🚀
Ü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 🎯
| ✔️ 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:
Bu dört tuzak ve kontrol listesi yanında, üretimde sürprizler yerine önceden planlanmış aksiyonlar olacak 🎉
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.
requirements.txt)pytest / make ile test edebileceğiniz iskeletinit_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
python scripts/run_eval.py
data/ – ham & işlenmiş verilerprompts/ – prompt şablonlarıeval/ – metrik implementasyonları (gelicek)configs/ – YAML config dosyalarıscripts/ – çalıştırılabilir betikler.github/workflows/ – CI/CDHadi 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.*
All rights reserved