Mert Özen Her Satırda Daha İleri
Yapay Zeka Prompt Engineering Uygulamada — #09

Prompt'u Ölçerek İyileştirmek: Test Seti, Kural Tabanlı Kontroller, LLM-as-Judge, Microsoft.Extensions.AI.Evaluation ve promptfoo ile CI'da Regresyon

Mert Özen 15 Kas 2026 16 dk 11 görüntülenme
Prompt'u Ölçerek İyileştirmek: Test Seti, Kural Tabanlı Kontroller, LLM-as-Judge, Microsoft.Extensions.AI.Evaluation ve promptfoo ile CI'da Regresyon

Prompt'u ölçmek için referans cevaplı ve insan etiketli bir altın set, her pull request'te koşan kural kontrolleri ve insanla uyuşması ölçülmüş bir LLM-as-judge birlikte gerekir. Bookstore'un iade politikası sorularında kısa cevap kuralı küçük modelde doğruluğu 8/10'dan 5/10'a düşürdü; kurallar ve iki judge insanla 30 cevabın en fazla 23'ünde uyuştu, pairwise judge on çiftin altısında sıraya bağlı kaldı ya da karar vermedi.

Sürüm notu: Bu yazıdaki kodlar .NET 10 ile derlenip denenmiştir; Microsoft.Extensions.AI 11.0.0, Microsoft.Extensions.AI.Evaluation ve Microsoft.Extensions.AI.Evaluation.Quality 11.0.0, OllamaSharp 5.5.2, xUnit v3 4.0.3 ve promptfoo 0.125.1 (Node.js 24) kullanılmıştır. Model çıktıları yerelde Ollama 0.34.4 üzerinde çalışan iki küçük açık modelden alınmıştır: gemma3:4b (4,3 milyar parametre) ve qwen3:4b (4 milyar parametre), ikisi de Q4_K_M; bulut sağlayıcıyla koşulmamıştır. GitHub Actions workflow'u çalıştırılmamıştır.

Önce Tahmin Et

Serinin ilk yazısındaki backlog'da bir görev kalmıştı: sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) iade politikasıyla ilgili müşteri sorularını politika metnine dayanarak cevaplamak. Politika sekiz maddelik kısa bir metin: 14 günlük cayma hakkı, hasarlı kitapta 30 gün, iade kargosu 49,90 TL, para iadesi en geç 10 iş günü, kurumsal siparişlerde yüzde 10 sınırı ve birkaç istisna. Emre iki prompt yazdı. v1 tek cümle ve politika. v2 kurallı: en fazla üç cümle, mümkünse "Evet" ya da "Hayır" ile başla, gereken hesabı yap, politikada olmayan bir şeyi uydurma.

On soruluk bir altın test setiyle ölçeceğiz. Üç tahmin: v2, v1'den daha mı doğru cevap verir? Cevapları "gereken bilgi geçiyor mu" diye kontrol eden basit kurallar, bir insanın doğru dediği cevaplarla ne kadar örtüşür? Ve cevapları bir başka küçük modele "doğru mu?" diye sorarsak, o model insanla kaç cevapta aynı fikirde olur?

Altın Test Seti: Referans Cevap, Zorunlu Bilgi ve İnsan Etiketi

Altın test seti (golden set; doğru cevabı ekip tarafından yazılmış ve onaylanmış test vakaları) bu seride kurduğumuz setlerin bir adım ötesi. Her soru için üç şey var: iade ekibinin yazdığı referans cevap, cevapta mutlaka geçmesi gereken bilgiler ve cevap üretildikten sonra bir insanın verdiği "doğru/yanlış" etiketi. Sorular gerçek müşteri mesajlarına benziyor ve sınırları zorluyor: 10 gün sonra iade (süre içinde), 20 gün sonra iade (süre dışında), 200 kitaplık siparişten 40 kitap (hesap gerekiyor), politikada hiç geçmeyen yurt dışı iadesi (doğru cevap "bilmiyorum").

{"id": "pq-04", "input": "Okulumuz 200 kitaplık kurumsal sipariş verdi. 40 kitabı iade edebilir miyiz?", "expected": {"reference": "Hayır, 40 kitap iade edilemez. Kurumsal siparişlerde iade, sipariş adedinin en fazla yüzde 10'u, yani 200 kitapta en fazla 20 kitapla sınırlıdır ve kurumsal satış ekibinin onayı gerekir.", "facts": ["20", "kurumsal satış"], "label": ""}}
{"id": "pq-09", "input": "Yurt dışındaki adresime gönderilen kitabı nasıl iade ederim?", "expected": {"reference": "Politikada yurt dışı gönderimlerin iadesiyle ilgili bir kural yok; bu soruyu müşteri hizmetlerine iletmek gerekir.", "facts": ["müşteri hizmetleri|yer almıyor|bilgi bulunmuyor|belirtilmemiş|kapsamıyor"], "label": ""}}

Cevaplar bir kez üretilip saklanıyor; aşağıdaki bütün ölçümler aynı kayıtlı cevapları okuyor. Böylece farklı grader'ları modeli yeniden çağırmadan aynı cevaplar üzerinde karşılaştırabiliyorsun. Otuz cevabın (v1 ve v2 gemma3:4b ile, v2 bir de qwen3:4b ile) her birini politikayı bilen biri olarak okuyup etiketledim. Sonuç: v1 gemma3:4b 8/10, v2 gemma3:4b 5/10, v2 qwen3:4b 10/10.

İlk tahminin cevabı burada. v2'nin "Evet ya da Hayır ile başla" kuralı küçük modelde ters tepti. 10 gün önce aldığı kitabı soran müşteriye (pq-01) "iade süreniz 10 gün olduğundan bu hakkı kullanamazsınız" dedi; 20 gün sonra soran müşteriye (pq-08) "Evet" diye başlayıp ikinci cümlede "iade hakkınız bulunmamaktadır" dedi. v1'in iki hatası ise başka türden: süresi geçmiş iadede "kargo ücretini öderseniz iade edebilirsiniz" ima etti, yurt dışı sorusuna politikada olmayan bir prosedür uydurdu.

Kural Tabanlı Kontroller: Ucuz, Hızlı, Kör Noktalı

İlk ölçüm katmanı deterministik: zorunlu bilgiler geçiyor mu, cevap 400 karakteri aşıyor mu, cevapta politikada, soruda ya da referansta olmayan bir sayı var mı?

public static List<string> Check(string question, string answer, PolicyExpectation expected, string policy)
{
    List<string> issues = [];
    foreach (string fact in expected.Facts)
    {
        // "a|b": iki biçimden biri geçerse yeter
        if (!fact.Split('|').Any(f => answer.Contains(f, StringComparison.OrdinalIgnoreCase)))
        {
            issues.Add($"missing fact: {fact}");
        }
    }
    if (answer.Length > MaxLength)
    {
        issues.Add($"too long: {answer.Length} chars");
    }
    HashSet<string> known = [.. Numbers(policy), .. Numbers(question), .. Numbers(expected.Reference)];
    List<string> invented = [.. Numbers(answer).Where(n => !known.Contains(n)).Distinct()];
    if (invented.Count > 0)
    {
        issues.Add($"numbers not in the policy: {string.Join(", ", invented)}");
    }
    return issues;
}
dotnet run --project src/Bookstore.Prompts.Lab -- policy-qa check v2
FAIL pq-01 missing fact: 49,90|müşteriye ait|size ait
FAIL pq-04 missing fact: kurumsal satış
FAIL pq-09 missing fact: müşteri hizmetleri|yer almıyor|bilgi bulunmuyor|belirtilmemiş|kapsamıyor
FAIL pq-10 missing fact: Bookstore öde|hasarlı
policy-qa v2 | gemma3:4b | checks 6/10
KoşuKurallarİnsanİkisinin aynı karar verdiği cevap
v1, gemma3:4b5/108/107/10
v2, gemma3:4b6/105/105/10
v2, qwen3:4b7/1010/107/10

Kurallar iki yönde de yanılıyor. v2'nin pq-08 cevabı "14 gün" bilgisini içerdiği için geçti, ama "Evet" diye başlayan çelişkili bir cevap. Öte yandan doğru olan birçok cevap, referanstaki bir ayrıntıyı (kurumsal satış onayı, hasarlı kitapta kargo) içermediği için kaldı. Kurallar yine de işe yarıyor: v1'in 812 karakterlik cevabını, uydurulmuş bir sayıyı ya da tamamen eksik bir bilgiyi model çağırmadan, milisaniyeler içinde yakalayabiliyorlar. Her pull request'te koşacak katman bu.

LLM-as-Judge: Rubric de Bir Prompt'tur

Anlamı kontrol etmek için bir modelden cevabı referansla karşılaştırmasını istiyoruz. Anthropic'in test geliştirme rehberi LLM ile notlamada ayrıntılı bir rubric, kısa ve ayrıştırılabilir bir karar ve karardan önce gerekçe yazdırmayı öneriyor; mümkünse cevabı üreten modelden farklı bir model kullanmayı da. Judge prompt'u bu yüzden katalogdaki diğer prompt'lar gibi versiyonlu bir dosya:

---
task: policy-judge
version: v1
model: qwen3:4b
model-digest: 359d7dd4bcda
temperature: 0
seed: 7
---
# system
You grade one answer of a customer assistant against a reference answer written by the
Bookstore returns team.

## Rubric
- correct: the answer reaches the same conclusion as the reference and states every number the
  question needs (days, fees, quantities) with the same value. Extra wording is fine.
- incorrect: the conclusion differs, a needed number is missing or different, or the answer adds a
  claim that contradicts the reference or a promise that the reference does not make.

Write your reasoning first, then the verdict.

Judge'ın değeri, insan etiketiyle ne kadar örtüştüğüyle ölçülüyor. Aynı otuz cevabı iki judge ile notladık:

dotnet run --project src/Bookstore.Prompts.Lab -- policy-qa judge gemma3:4b
judge gemma3:4b: agrees with the person on 21/30; passes the person failed 2, fails the person passed 7, no verdict 0
dotnet run --project src/Bookstore.Prompts.Lab -- policy-qa judge qwen3:4b
DISAGREE pq-08 v1 gemma3:4b: judge no verdict, person incorrect
DISAGREE pq-03 v2 gemma3:4b: judge incorrect, person correct
DISAGREE pq-05 v2 gemma3:4b: judge correct, person incorrect
DISAGREE pq-06 v2 gemma3:4b: judge correct, person incorrect
DISAGREE pq-08 v2 gemma3:4b: judge no verdict, person incorrect
DISAGREE pq-10 v2 gemma3:4b: judge incorrect, person correct
DISAGREE pq-01 v2 qwen3:4b: judge incorrect, person correct
judge qwen3:4b: agrees with the person on 23/30; passes the person failed 2, fails the person passed 3, no verdict 2

qwen3:4b insanla otuz cevabın 23'ünde, gemma3:4b 21'inde aynı kararı verdi. Ayrıntı toplamdan daha önemli. İki judge'ın da yanlışlıkla geçirdiği iki cevap aynı: pq-05 ve pq-06'nın v2 cevapları. Birincisi "iade süresi 30 gün olsa da" diyor, ikincisi "kalan kitapların fiyatı üzerinden ödeme yapmanız istenecek" diye bir süreç uyduruyor. İkisi de referansla çelişmiyor, ama politikayla çelişiyor; judge yalnız referansı gördüğü için bunu bilemez. gemma3:4b'nin yedi yanlış alarmının dördü qwen3:4b'nin kısa cevaplarında. Referanstaki bir ayrıntıyı tekrar etmeyen kısa ve doğru bir cevabı eksik saydı; 30 cevaplık bir sette bunu kesin bir yanlılık diye okuyamayız, ama izlenmesi gereken bir işaret.

"No verdict" satırları ayrı bir ders. qwen3:4b'nin ilk koşusunda tek bir judge çağrısı 8 dakika 30 saniye sürdü: model JSON'un reasoning alanına durmadan yazdı. Judge çağrısına bir çıktı sınırı koyduk ve sınıra takılıp ayrıştırılamayan cevabı "no verdict" saydık. 1.024 token'lık sınır geçerli cevapları da kesti, 4.096'ya çıkardık; iki çağrı yine sınıra takıldı. İkisi de 20 gün sonraki iade sorusu, yani judge'ı en çok zorlayan vaka. Kararsız judge'ı asla "geçti" saymamak gerekiyor; böyle cevaplar insana gider.

Judge'ın Yanlılıkları: Sıra ve Kendi Cevabı

Judge'ın bilinen iki yanlılığı var: iki cevabı karşılaştırırken sıraya bakması ve kendi ürettiğine benzeyen cevabı kayırması. Sırayı ölçmek kolay: aynı çifti iki sırayla soruyorsun; tutarlı bir judge iki seferde de aynı cevabı seçer. v1 ve v2'nin gemma3:4b cevaplarını qwen3:4b'ye çift çift verdik, prompt'ta "uzunluk ve üslup önemli değil" yazarak:

dotnet run --project src/Bookstore.Prompts.Lab -- policy-qa pairwise qwen3:4b
pq-01: v1 first -> v1, v2 first -> v1
pq-02: v1 first -> v1, v2 first -> v1
pq-03: v1 first -> v1, v2 first -> v1
pq-04: v1 first -> none, v2 first -> none
pq-05: v1 first -> v1, v2 first -> none  (order decided)
pq-06: v1 first -> none, v2 first -> none
pq-07: v1 first -> v2, v2 first -> v2
pq-08: v1 first -> v2, v2 first -> v1  (order decided)
pq-09: v1 first -> v2, v2 first -> v1  (order decided)
pq-10: v1 first -> none, v2 first -> v1  (order decided)
consistent 4/10; answer in slot A won 5 of 20 comparisons

On çiftin yalnız dördünde iki sıra aynı sonucu verdi. pq-08 ve pq-09'da kazanan tamamen sıraya göre değişti: iki seferde de ikinci sıradaki cevap kazandı. Karar verilen 14 karşılaştırmanın 9'unda ikinci cevap seçildi; altı karşılaştırmada ise judge çıktı sınırına takıldı ve hiç karar vermedi. Pairwise karşılaştırma tek bir sırayla yapılırsa, bu sette sonuçların neredeyse yarısı cevapların kendisinden değil sıralarından gelirdi. Çözüm basit ama iki kat pahalı: her çifti iki sırayla sor, yalnız tutarlı kararları say.

Kendi cevabını kayırma eğilimi için bu sette kesin bir kanıt yok. gemma3:4b judge'ı kendi cevaplarında insanla 20 cevabın 15'inde, qwen3:4b'nin cevaplarında 10'un 6'sında uyuştu; ama aradaki fark, kısa cevapları eksik sayma eğiliminden de gelebilir. Pratik kural değişmiyor: mümkünse cevabı üreten modelle judge'ı farklı seç ve judge'ın kararlarını insan etiketleriyle düzenli olarak karşılaştır.

Microsoft.Extensions.AI.Evaluation ile Aynı Ölçüm

Kendi grader'larımızı yazdık; .NET'te bunun standart bir çerçevesi de var. Microsoft.Extensions.AI.Evaluation, evaluator'ları IEvaluator arayüzü arkasında topluyor; LLM kullanan hazır evaluator'lar (RelevanceEvaluator, CompletenessEvaluator, EquivalenceEvaluator, GroundednessEvaluator ve agent odaklı olanlar) Quality paketinde, sonuçları saklayıp rapor üreten kısım Reporting paketinde. API'nin ayrıntısı .NET ile Yapay Zeka serisinin "IChatClient Kullanan Kodu Test Etmek" yazısında; burada yalnız kendi kontrollerimizi bu çerçeveye nasıl taşıdığımıza bakıyoruz. Kural tabanlı kontroller bir IEvaluator olarak BooleanMetric döndürüyor:

public ValueTask<EvaluationResult> EvaluateAsync(
    IEnumerable<ChatMessage> messages,
    ChatResponse modelResponse,
    ChatConfiguration? chatConfiguration = null,
    IEnumerable<EvaluationContext>? additionalContext = null,
    CancellationToken cancellationToken = default)
{
    KeyFactsContext context = additionalContext?.OfType<KeyFactsContext>().Single()
        ?? throw new ArgumentException("KeyFactsContext is required", nameof(additionalContext));
    string question = messages.Last(m => m.Role == ChatRole.User).Text;
    List<string> issues = PolicyChecks.Check(question, modelResponse.Text, context.Expected, context.Policy);

    BooleanMetric metric = new(MetricName, issues.Count == 0, issues.Count == 0 ? "all checks passed" : string.Join("; ", issues));
    metric.Interpretation = new EvaluationMetricInterpretation(
        issues.Count == 0 ? EvaluationRating.Good : EvaluationRating.Unacceptable, failed: issues.Count > 0);
    return ValueTask.FromResult(new EvaluationResult(metric));
}

Hazır EquivalenceEvaluator ile yan yana, aynı kayıtlı cevaplar üzerinde koşuyor; referans cevap EquivalenceEvaluatorContext ile veriliyor:

IEvaluator evaluator = new CompositeEvaluator(new KeyFactsEvaluator(), new EquivalenceEvaluator());
ChatConfiguration judge = new(judgeClient);
...
EvaluationResult result = await evaluator.EvaluateAsync(messages, response, judge,
    [new KeyFactsContext(testCase.Expected, Policy), new EquivalenceEvaluatorContext(testCase.Expected.Reference)]);

Judge olarak önce qwen3:4b'yi verdik. İlk üç cevabın üçünde de evaluator skoru okuyamadı ve metriği Inconclusive işaretledi; model, evaluator'ın beklediği biçimde bir skor yerine boş bir cevap döndürdü. Koşuyu durdurup judge'ı gemma3:4b yaptık. Burada judge, notladığı cevapları üreten modelle aynı; bu bir ödünleşim ve sonuçları okurken akılda tutmak gerekiyor:

dotnet run --project src/Bookstore.Prompts.Lab -- policy-qa meai v2 gemma3:4b
pq-01 key facts False, equivalence 2 (Poor)
pq-02 key facts True, equivalence 3 (Average)
pq-03 key facts True, equivalence 5 (Exceptional)
pq-04 key facts False, equivalence 5 (Exceptional)
pq-05 key facts True, equivalence 2 (Poor)
pq-06 key facts True, equivalence 5 (Exceptional)
pq-07 key facts True, equivalence 3 (Average)
pq-08 key facts True, equivalence 2 (Poor)
pq-09 key facts False, equivalence 2 (Poor)
pq-10 key facts False, equivalence 3 (Average)

Equivalence 1–5 arası bir skor. İnsan etiketiyle karşılaştırınca eşiğin ne kadar önemli olduğu görülüyor: "4 ve üstü geçer" dersen doğru beş cevabın üçü kalıyor; "3 ve üstü" dersen on cevabın dokuzunda insanla aynı karar çıkıyor ve tek fark pq-06, iki judge'ın da kaçırdığı uydurma süreç. Bu, kendi evaluator'ımızdaki "doğru/yanlış" kararından farklı bir bilgi: skoru bir eşikle karara çevirmek de kalibre edilmesi gereken bir adım.

promptfoo ile Prompt × Model Matrisi ve CI

Aynı ölçümün ikinci aracı promptfoo: açık kaynak, Node.js ile çalışan, prompt'ları ve modelleri bir matriste karşılaştıran ve CI'da koşabilen bir eval aracı. Prompt'ları ve test setini elle kopyalamak yerine katalogdan dışa aktarıyoruz; kaynak tek kalıyor:

dotnet run --project src/Bookstore.Prompts.Lab -- policy-qa export-promptfoo eval/promptfoo
exported 2 prompts and 10 tests to eval/promptfoo
# policy-qa için prompt x model matrisi; prompt'lar ve testler katalogdan dışa aktarılır
description: Bookstore policy-qa regression
prompts:
  - id: file://prompts/policy-qa-v1.json
    label: policy-qa v1
  - id: file://prompts/policy-qa-v2.json
    label: policy-qa v2
providers:
  - id: ollama:chat:gemma3:4b
    label: gemma3:4b
    config:
      temperature: 0
      seed: 7
  - id: ollama:chat:qwen3:4b
    label: qwen3:4b
    config:
      temperature: 0
      seed: 7
      think: false
defaultTest:
  options:
    # qwen3:4b, think kapalıyken de akıl yürütmesini </think> öncesine yazıyor
    transform: "output.includes('</think>') ? output.split('</think>').pop().trim() : output.trim()"
  assert:
    - type: javascript
      value: "output.length <= 400"
      metric: length
tests: file://tests.yaml

Her test, zorunlu bilgileri birer javascript assertion'ı olarak taşıyor. promptfoo'yu yerel modelle, paylaşım ve telemetri kapalı (--no-share, PROMPTFOO_DISABLE_TELEMETRY=1) ve Ollama adresi OLLAMA_BASE_URL ile verilerek koştuk:

npx promptfoo@0.125.1 eval -c promptfooconfig.yaml --no-share --no-cache -j 1 -o results.json
Results:
  ✓ 20 passed (50.00%)
  ✗ 20 failed (50.00%)
  0 errors (0%)
Duration: 15m 28s (concurrency: 1)

Sonuç dosyasında hücre başına sayınca: v1 gemma3:4b 5/10, v2 gemma3:4b 6/10, v1 qwen3:4b 2/10, v2 qwen3:4b 7/10. gemma3:4b ve v2 qwen3:4b sütunları .NET tarafındaki kural kontrolleriyle birebir aynı skoru verdi; aynı prompt, aynı seed, aynı kurallar. Matrisin yeni söylediği şey v1 qwen3:4b: kısa cevap kuralı olmayan prompt'la qwen3:4b'nin cevaplarının çoğu 400 karakteri aştı. Prompt ile modelin birlikte değerlendirilmesi gerektiğinin küçük bir örneği: v1 bir modelde iyi, diğerinde kötü.

promptfoo komutu en az bir test başarısız olduğunda 100 koduyla çıktı; CI'da bu doğrudan bir regresyon kapısı. Eşik PROMPTFOO_PASS_RATE_THRESHOLD ile ayarlanabiliyor. Workflow iki iş içeriyor: model çağırmayan testler her pull request'te, model eval'i ise yalnız prompt değişikliği etiketi olan pull request'lerde ve modellerin kurulu olduğu bir runner'da:

  model-eval:
    needs: unit-tests
    runs-on: [self-hosted, ollama]
    if: contains(github.event.pull_request.labels.*.name, 'prompt-change')
    env:
      OLLAMA_BASE_URL: http://127.0.0.1:11434
      PROMPTFOO_DISABLE_TELEMETRY: '1'
      PROMPTFOO_DISABLE_UPDATE: '1'
      PROMPTFOO_PASS_RATE_THRESHOLD: '80'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.x'
      - uses: actions/setup-node@v4
        with:
          node-version: '24'
      - run: dotnet run --project src/Bookstore.Prompts.Lab -- policy-qa export-promptfoo eval/promptfoo
      - run: npx promptfoo@0.125.1 eval -c eval/promptfoo/promptfooconfig.yaml --no-share -j 1 -o eval/promptfoo/results.json

Bu workflow bu seride çalıştırılmadı; başlangıç noktası olarak duruyor. promptfoo'nun resmi GitHub Action'ı (promptfoo/promptfoo-action@v1) önceki ve sonraki prompt'u karşılaştırıp sonucu pull request'e yorum olarak yazabiliyor; dokümana göre Node.js 22.22 veya üstü gerekiyor.

Model Yükseltmesi: Aynı Setle Karar Vermek

Bu yazının ölçümleri bir karar tablosuna dönüşüyor. Production'da v1 ile gemma3:4b çalışıyor olsun. Masada iki aday var: yeni prompt (v2) ve yeni model (qwen3:4b). Her şey aynı on soru, aynı kayıtlı cevaplar ve aynı insan etiketleriyle ölçüldü:

KoşuİnsanKurallarMedyan süreEn uzun cevap
v1, gemma3:4b (production)8/105/102,6 sn812 karakter
v2, gemma3:4b5/106/102,1 sn340 karakter
v2, qwen3:4b10/107/1017,7 sn235 karakter

v2'yi gemma3:4b ile açmak, kısa cevaplar için doğruluğu feda etmek demek; reddedildi. qwen3:4b ile v2 en doğru sonuç, ama bu makinede medyan cevap süresi yedi kat uzun ve bir cevap 54 saniye sürdü. Karar yalnız skorla verilemiyor: iade politikası soruları canlı sohbette mi soruluyor, yoksa e-postaya mı cevap veriliyor? Canlı sohbette bu gecikme kabul edilemez; e-posta taslağında kabul edilebilir. Bir ara yol, 03. yazıdaki gibi iki modeli birlikte kullanmak: hızlı model cevaplar, kurallar ya da judge bir sorun görürse cevap yavaş modele gider. Her durumda karar, tarihli bir test seti sonucuyla pull request'te kayda geçer ve yeni modelin digest'i prompt dosyasına yazılır. OpenAI'ın rehberi de model sürümü değiştiğinde aynı eval setini yeniden koşmayı öneriyor; bulut sağlayıcıda tabloya bir de maliyet sütunu ekleniyor.

Üretimde Dikkat

Ölçüm katmanlarını maliyete göre ayır. Kural tabanlı kontroller ve model çağırmayan unit testler her pull request'te koşar; saniyeler sürer ve ücretsizdir. Judge'lı eval, model çağırdığı için pahalı ve yavaş: bu yazıdaki 30 cevabı qwen3:4b ile notlamak yerelde dakikalar sürdü, bulut sağlayıcıda her koşu token demek. Onu yalnız prompt dosyası, model ya da test seti değiştiğinde, gece koşusunda veya bir etiketle tetiklenen işte koş.

Judge'ı da ölç. Judge'ın insanla uyuşma oranı, test setinin bir parçası olarak saklanmalı. Judge prompt'unu ya da judge modelini değiştirdiğinde ilk iş bu oranı yeniden hesaplamak; aksi hâlde ölçümün kendisi sessizce değişir.

Test setini prompt'a ezberletme. Bu yazıda v2'nin kurallarını cevapları görmeden yazdık. Bir prompt'u bir setin hatalarına bakarak düzeltiyorsan, o setin skoru artık iyimser bir tahmin; kararı, prompt geliştirilirken hiç bakılmamış ayrı bir holdout setinde (prompt'u geliştirirken kullanılmayan, yalnız son ölçüm için ayrılan vakalar) ver. Holdout'a bakıp prompt'u değiştirdiğin an, o set de geliştirme setine dönüşür.

Production'dan öğren. Müşteri şikâyeti, insanın düzelttiği cevaplar ve judge'ın düşük not verdiği cevaplar, altın sete aday vaka. Seti büyütürken referans cevabı ve etiketi yine ekip yazmalı; modelin cevabını referans yapmak, modelin hatasını teste kopyalamak olur.

Sık Düşülen Tuzaklar

Tek bir skora bakmak. Kurallar v2 gemma3:4b'ye 6/10 verdi, insan 5/10; ama aynı cevaplar değil. Vaka bazında hangi katmanın neyi yakaladığını görmeden skor yanıltıcı.

Judge'ı kalibre etmeden eşik koymak. Judge'ın insanla uyuşmadığı cevaplar rastgele değil, belli türlerde toplanıyor; bunu bilmeden "judge yüzde 80 doğru diyorsa merge et" kuralı yanlış bir güven verir.

Judge'a referanstan fazlasını bilmediğini unutmak. "İade süresi 30 gün olsa da" diyen cevap, referansta 30 gün geçmediği için judge'dan geçti. Judge'ın bilmesi gereken bağlamı (politika metni) ona vermek ya da o tür hataları kurallara bırakmak gerekiyor.

Pahalı eval'i her commit'te koşmak. Maliyet ve bekleme, ekibi eval'den kaçırır; katmanları ayır.

Model yükseltmesini yalnız skorla değerlendirmek. Gecikme ve maliyet de kararın parçası; aynı setle, aynı tabloda ölç.

Küçük Bir Deneme

★☆☆ Judge prompt'unun v2'sini yaz: sisteme politika metnini de ver ve v1 ile v2'nin insanla uyuşma oranını aynı otuz cevapta karşılaştır. pq-05'teki "30 gün" cevabını v2 yakalıyor mu?

★★☆ Setin üç sorusunu holdout olarak ayır. Kalan yedi soruya bakarak policy-qa v3'ü yaz ve v1, v2, v3'ü önce yedi soruda, sonra holdout'ta ölç. İki skor arasındaki fark, prompt'u sete ne kadar uydurduğunu gösterir.

★★★ promptfoo config'ine bir llm-rubric assertion'ı ekle ve grading provider'ı yerel qwen3:4b yap. Sonuçlarını bu yazıdaki .NET judge'ının sonuçlarıyla vaka bazında karşılaştır; iki judge aynı rubric'le aynı cevaplara aynı kararı veriyor mu?

Bu yazıyla Prompt Engineering Uygulamada serisi bitiyor. Dokuz yazı boyunca her görevde aynı döngüyü işlettik: görev tanımı, başarı kriteri, küçük bir test seti, prompt, ölçüm ve düzeltme. Extraction'dan agent'a kadar her görevde modelin çıktısını koddaki bir kontrole bağladık; son iki yazıda da prompt'u bir kod parçası gibi versiyonlamayı, review etmeyi ve ölçmeyi kurduk. Buradan devam etmek için en yakın yazılar RAG (Retrieval-Augmented Generation; modelin cevabını dışarıdan getirilen dokümanlara dayandırma yöntemi) serisinin "RAG'i Ölçmek", AI Agent'ları ve MCP (Model Context Protocol; modelleri tool'lara ve veri kaynaklarına bağlayan açık protokol) serisinin "Agent'ları Değerlendirmek" ve LLM Uygulama Güvenliği serisinin CI'da red team regresyonunu anlatan "Guardrail Mimarisi ve Güvenlik Testleri" yazıları.