Yapay Zeka Ajanları İçin Self-Hosted Eval Rehberi
Gerçek trace’lerden golden case’ler oluşturun, önce deterministik kontrolleri çalıştırın, ardından LLM judge kullanın ve commit başına pass rate değerini SQLite’ta izleyin.
Yapay zeka ajanları için self-hosted eval nedir
Yapay zeka ajanları için self-hosted eval dört bileşenden oluşur: kayıtlı vakaları içeren bir dosya, ajanı bu vakalar üzerinde çalıştıran bir betik, her yanıtı puanlayan kontroller ve sorgulanabilen sonuç tablosu. Bu bileşenlerin hiçbiri bir vendor gerektirmez. Tüm döngü birkaç yüz satır Python kodu ve bir SQLite dosyasından oluşur.
Ajan demoda çalıştı çünkü beş girdiyi kendiniz seçtiniz. İkinci haftada bozuldu çünkü bir prompt satırı, model veya tool açıklaması değişti ve bunların hiçbirini kapsayan bir ölçüm yoktu. Eval döngüsü, "şimdi daha kötü hissettiriyor" ifadesini "pass rate, 4f1c9ab commit'inde 60 üzerinden 58 iken 51'e düştü" şeklinde ölçülebilir bir sonuca dönüştürür.
Döngü dört adımdan oluşur ve bu kılavuzda her adım için bir bölüm bulunur: gerçek trace'leri toplamak, ilgi çekici olanları case olarak kaydetmek, her değişiklikte her case'i puanlamak ve pass rate değerini bunu üreten commit'in yanında saklamak. Bu döngü, ajanı ne üzerinde çalıştırdığınızdan bağımsız olarak kullanılabilir. Çalıştırılmaya değer self-hosted ajan framework'leri arasındaki temel fark, trace'in ne kadarını hazır olarak sunduklarıdır.
İkinci haftada agent neden bozulur
Bir agent; prompt, model, araç tanımları ve çalışma zamanında alınan bağlamdan oluşur. Bu dört bileşenin tamamı, uygulama kodunuz değişmeden güncellenebilir. Bu nedenle standart bir kod incelemesinde sorun fark edilmeyebilir.
En yaygın neden prompt düzenlemesidir. Kaba bir yanıtı engellemek için bir cümle eklersiniz. Bu cümle, yeniden test edilmeyen girdilerde davranışı değiştirir. Trace kayıtları bunu açıkça gösterir: geçen hafta aynı soru için alınan trace bir create_refund tool call içerirken bu haftaki trace hiçbir tool call içermez ve bunun yerine yanıt olarak kibar bir özür verilir. Herhangi bir hata oluşmadığı için alarm da tetiklenmez.
İkinci neden modeldir. Her çalıştırmada gönderdiğiniz model dizesini tam olarak kaydedin; zihninizde tuttuğunuz kısa ad yerine claude-haiku-4-5-20251001 kullanın. Modelleri değiştirdiğiniz gün başarı oranı düşerse, model satırda yer almadığı sürece bunun nedeni belirlenemez.
Üçüncü neden araçlardır. Bir aracın açıklamasını yeniden ifade etmek, modelin aracı çağırmaya ne zaman karar verdiğini değiştirir. Araçlarınız VPS üzerinde çalışan MCP sunucularından geliyorsa şema başka bir süreçte bulunur. Bu nedenle repository'nizde hiçbir diff oluşmadan değişebilir. Dördüncü neden retrieval işlemidir: aynı soru, gece yeniden oluşturulan bir index'e ulaşır ve yanıt yeni belgeye göre şekillenir.
İzlerden topladığınız verilerle golden set oluşturma
Eval senaryolarını kendiniz uydurmayın. Bunları gerçek ağ trafiğinden alın. Ajanınız için self-hosted Langfuse tracing kullanıyorsanız her istek, girdisi, tool çağrıları ve çıktısıyla birlikte kaydedilir. Bu veriler, bir test senaryosu için gereken ham materyali sağlar.
Public API üzerinden belirli bir zaman aralığındaki root gözlemlerini dışa aktarın. API basic authentication kullanır. Kullanıcı adı olarak public key, parola olarak secret key kullanılır.
export LF_HOST="https://langfuse.example.com"
curl -sS -u "$LF_PUBLIC_KEY:$LF_SECRET_KEY" \
"$LF_HOST/api/public/v2/observations?limit=50&isRootObservation=true&fromStartTime=2026-07-01T00:00:00Z" \
| jq '.data[0]'Herhangi bir ayrıştırma kodu yazmadan önce bir kaydı okuyun. Satırlar data altında döner. Ancak soruyu ve yanıtı içeren alan adları, ajanınızın span'leri nasıl enstrümante ettiğine bağlıdır. Bu nedenle beklediğiniz alan adlarını değil, gerçekten gördüğünüz alanları eşleyin. Ardından senaryoları elle, satır başına bir JSON nesnesi olacak şekilde evals/cases.jsonl içine yazın:
{"id": "refund-double-charge", "tags": ["smoke"], "input": "I was charged twice for order 41822.", "must_call": ["lookup_order", "create_refund"], "must_not_include": ["I cannot help"], "rubric": "The reply confirms exactly one refund for order 41822 and states the amount."}Beş kural, bu setin düzenli olarak çalıştırılmaya değer kalmasını sağlar:
- Başlangıç için 40 ile 80 senaryo yeterlidir. 20'nin altında tek bir kararsız senaryo başarı oranını 5 puan değiştirebilir. Nedensiz dalgalanan bir sayı dikkate alınmaz.
- Düzelttiğiniz her production hatası, düzelttiğiniz gün bir senaryoya dönüşür. Setin doğru yönde büyümesini sağlayan alışkanlık budur.
- Her senaryoda tek bir davranış test edin. İade tutarını ve üslubu birlikte kontrol eden bir senaryo başarısız olduğunda, sorunun kaynağı anlaşılmaz.
idhiçbir zaman değişmez. Çünkü karşılaştırmalar bu id üzerinden yapılır.- Commit etmeden önce verileri redakte edin. Bu dosya git içine alınır. Bu nedenle müşteri adlarını ve size ait olmayan sipariş numaralarını çıkarın.
Önce deterministik kontrolleri değerlendirin; bunlar ücretsizdir
Doğru yanıtı belirli olan her şey için basit bir doğrulama kullanılır. Model çağrısı yapılmaz. Maliyet oluşmaz. Belirsizlik bulunmaz. Deterministik kontroller yapısal regresyonları yakalar. Sistemde agent etrafında çalışan bileşenleri bozan sorunlar genellikle bunlardır: JSON ayrıştırılamaz, araç hiç çağrılmaz, yasak ifade yeniden ortaya çıkar veya yanıtta kaynak belirtilmez.
Agent'ınız hakkında bilgi sahibi olan tek bir işlev bulunur. Harness içindeki diğer her şey geneldir.
import json, os, urllib.request
def run_agent(case):
req = urllib.request.Request(
os.environ["AGENT_URL"],
data=json.dumps({"input": case["input"]}).encode(),
headers={"content-type": "application/json"},
)
with urllib.request.urlopen(req, timeout=120) as resp:
return json.load(resp)
def deterministic(case, result):
text = result.get("output", "")
called = [c["name"] for c in result.get("tool_calls", [])]
failures = []
for tool in case.get("must_call", []):
if tool not in called:
failures.append(f"tool not called: {tool}")
for phrase in case.get("must_not_include", []):
if phrase.lower() in text.lower():
failures.append(f"forbidden phrase: {phrase}")
if len(called) > case.get("max_tool_calls", 12):
failures.append(f"too many tool calls: {len(called)}")
return failuresAraç bütçesini de bu listeye ekleyin. Bir agent bugün bir vakayı 3 çağrıda, yarın ise 11 çağrıda çözerse son yanıt doğru olsa bile regresyon oluşmuştur; çünkü yaptığı her çağrı için ödeme yaparsınız.
LLM judge ve dört hata biçimi
Assertions kontrollerinden geçen yanıtlar, yanıtı okuyacak bir değerlendirici gerektirir. LLM judge, ikinci bir model çağrısıdır: soruyu, agent yanıtını ve tek bir kriteri alır, ardından bir karar döndürür. “Yanıt, kullanıcının sorduğu şeyi cevaplıyor mu?” sorusunu değerlendirmek için pratikte kullanılabilecek tek yöntem budur.
Bir judge'ın kullanılabilir olması için dört kural uygulanmalıdır:
- İkili karar kullanılmalıdır; hiçbir zaman 1 ile 10 arasında puan verilmemelidir. Ölçek kullanıldığında neredeyse her şeye 7 veya 8 verilir. Bu nedenle sayı değişmez ve bu sayıdan hiçbir şey öğrenilemez.
- Her çağrıda tek kriter kullanılmalıdır. Geri ödeme tutarı veya üslup hakkında soru sorulmalıdır; ikisi aynı anda sorulmamalıdır.
- Vakanın beklenen bir yanıtı varsa bu yanıt judge'a verilmelidir. Bir referansa göre değerlendirme yapmak, soyut biçimde değerlendirme yapmaktan çok daha kolaydır.
- Çıktı biçimi zorunlu tutulmalı ve çıktı katı biçimde ayrıştırılmalıdır.
from anthropic import Anthropic
client = Anthropic() # reads ANTHROPIC_API_KEY from the environment
def judge_prompt(case, output):
return (
"You grade one answer against one criterion.\n"
"Reply with JSON only, in this exact shape:\n"
'{"verdict": "pass", "confidence": "high", "reason": "one short sentence"}\n'
f"Criterion: {case['rubric']}\n"
f"Question: {case['input']}\n"
f"Answer: {output}\n"
"Length is not a criterion. Judge only the criterion above."
)
def judge(case, output, model):
msg = client.messages.create(
model=model,
max_tokens=200,
messages=[{"role": "user", "content": judge_prompt(case, output)}],
)
return json.loads(msg.content[0].text)Şimdi hata biçimlerine bakılmalıdır. Her biri için bugün uygulanabilecek bir test vardır. Bu testlerin uygulanması önemlidir; çünkü denetlenmeyen bir judge kesin görünen ancak hiçbir anlam taşımayan sayılar üretir.
Uzunluk yanlılığı. Daha uzun yanıtlar daha sık başarılı olur. Test etmek için judge'ın başarısız bulduğu on yanıt alınmalı ve her birine yeni bir bilgi eklemeyen, kendinden emin ifadeler içeren iki paragraf eklenmelidir. Ardından yanıtlar yeniden değerlendirilmelidir. Karar başarısızdan başarılıya dönüyorsa bu uzunluk yanlılığıdır ve düzeltilmesi gereken şey rubric'tir.
Kendi çıktısını tercih etme. Bir judge, kendi model ailesinin ürettiği çıktıları başka bir model ailesinin çıktılarından daha olumlu değerlendirebilir. Test etmek için aynı 30 yanıt, iki farklı model ailesinden judge'lar kullanılarak değerlendirilmelidir. Kararlar vaka bazında karşılaştırılmalıdır. Kararların farklı olduğu vakalar elle incelenmelidir.
Konum yanlılığı. Judge iki yanıtı, A ve B'yi karşılaştırmak için kullanılıyorsa sıralama değiştirilerek test yeniden çalıştırılmalıdır. Sıralama değiştirildiğinde karar değişiyorsa bu rubric için ikili karşılaştırma henüz güvenli değildir.
Rubric kayması. Belirsiz kriterler, her şeyi uygun bulan judge'lar üretir. “Yanıt faydalı mı?” neredeyse her şeyi başarılı sayar. “Yanıt geri ödeme tutarını dolar cinsinden belirtiyor mu?” yalnızca amaçlanan yanıtları başarılı sayar. Kontrol edilen olguyu açıkça adlandırana kadar her kriter yeniden yazılmalıdır.
Bu dört durumun tamamını tek bir koruma mekanizması kapsar. Elle etiketlenmiş 30 vaka saklanmalı ve judge modeli veya judge prompt'u her değiştirildiğinde judge, bu etiketlere göre puanlanmalıdır. Judge, vakaların onda birinden fazlasında sizin kararınızla uyuşmuyorsa ürettiği herhangi bir başarı oranına güvenmeden önce rubric düzeltilmelidir. Judge bir kod parçasıdır; bu nedenle kod gibi sürümlenmeli ve incelenmelidir.
Düşük maliyetli modelle başlayın, frontier modele yükseltin
Her commit işleminde her durumu en pahalı modelle değerlendirmek, değerlendirme maliyetinin test ettiği agent maliyetini aşmasına neden olur. Değerlendiricileri fiyatlarına göre sıralayın ve yanıt netleştiğinde işlemi durdurun.
The data behind this chart
[
{
"label": "Haiku 4.5, Batch API",
"usd_per_1000_judge_calls": "0.90"
},
{
"label": "Haiku 4.5",
"usd_per_1000_judge_calls": "1.80"
},
{
"label": "Sonnet 5",
"usd_per_1000_judge_calls": "3.60"
},
{
"label": "Opus 5",
"usd_per_1000_judge_calls": "9.00"
}
]Bu rakamlar, her değerlendirici çağrısında yaklaşık 1,200 giriş token'ı ve 120 çıkış token'ı varsayar. Bu boyut, bir soru, bir yanıt ve bir kriter için gerçekçidir. 1,000 durumu değerlendirmek Claude Haiku 4.5 üzerinde 1.80 US doları, Claude Opus 5 üzerinde ise 9.00 tutar. Fark ilk bakışta önemsiz görünür. Ancak bu maliyetler hızla birikir. Her commit işleminde değerlendirilen 60 durumluk bir küme, haftada 40 commit ile nightly job çalıştırılmadan önce haftada 2,400 değerlendirici çağrısı oluşturur.
Eval çalışmaları için iki indirim doğrudan uygulanabilir ve birlikte kullanılabilir. Eval çalışmaları etkileşimli değildir. Bu nedenle Batch API, asenkron teslim karşılığında hem giriş hem de çıkış fiyatlarını yarıya indirir. Bu, grafikteki ilk satırdır. Rubric ve talimatlar her çağrıda byte düzeyinde aynıdır. Bu nedenle prompt caching uygundur: cache okuma işlemi temel giriş fiyatının onda birine mal olur. Beş dakikalık cache yazma işlemi ise temel giriş fiyatının 1.25 katıdır. Bu nedenle cache, tek bir isabetten sonra maliyetini karşılar. Bunlar August 2026 itibarıyla Anthropic liste fiyatlarıdır. Sonnet 5 için introductory pricing 31 August 2026 tarihine kadar geçerlidir. Bu nedenle bu tarihten sonra üçüncü çubuk yükselir.
Sıralı değerlendirme katmanları:
- Her durum için deterministic checks. Hiçbir API maliyeti oluşmaz.
- Bu kontrolleri geçen durumlarda small model judge kullanılır.
- Yalnızca small judge başarısız sonucu verdiğinde veya düşük güvenle başarılı sonucu verdiğinde frontier judge kullanılır.
- Haftada bir kez küçük bir örneklem için human review yapılır.
CHEAP = "claude-haiku-4-5-20251001"
STRICT = "claude-opus-5"
def grade(case, result):
hard = deterministic(case, result)
if hard:
return False, "deterministic", "; ".join(hard)
first = judge(case, result["output"], CHEAP)
if first["verdict"] == "pass" and first["confidence"] == "high":
return True, CHEAP, first["reason"]
second = judge(case, result["output"], STRICT)
return second["verdict"] == "pass", STRICT, second["reason"]Bu yöntem, maliyet karşılığında değerlendirme doğruluğunu bir miktar düşürür. Bu nedenle varsayım yapmak yerine bu değiş tokuş ölçülmelidir. Ayda bir kez tüm kümeyi strict judge ile de değerlendirin ve iki sütunu karşılaştırın. Sonuçlar birkaç durumdan fazlasında farklıysa rubric, small model için yeterince açık değildir. Düzeltilmesi gereken unsur rubric'tir. Agent'ın kendi harcamalarını denetlemek ayrı bir görevdir. Bu konu VPS üzerinde bir AI agent için maliyet denetimi bölümünde ele alınır.
Bir sistemin sahibi olarak geçiş oranını zaman içinde izleme
Bir commit ile ilişkilendiremeyeceğiniz geçiş oranı yalnızca bir izlenimdir. Her çalıştırmada her vaka için bir satır saklayın. Commit ve model bilgilerini aynı satıra yazın.
CREATE TABLE IF NOT EXISTS results (
run_id TEXT NOT NULL,
ran_at TEXT NOT NULL,
git_sha TEXT NOT NULL,
agent_model TEXT NOT NULL,
case_id TEXT NOT NULL,
passed INTEGER NOT NULL,
graded_by TEXT NOT NULL,
reason TEXT
);SELECT run_id, git_sha, agent_model,
count(*) AS cases,
round(100.0 * sum(passed) / count(*), 1) AS pass_pct
FROM results
GROUP BY run_id
ORDER BY ran_at DESC
LIMIT 10;Şemayı sqlite3 evals/results.db < evals/schema.sql ile yükleyin. Ardından eğilimi sqlite3 -box evals/results.db < evals/passrate.sql ile okuyun. 60 vakada bir yıl boyunca her gün yapılan çalıştırmalar yaklaşık 22,000 satır oluşturur. Bu nedenle veri deposu başlı başına bir projeye dönüşmez. Bir VPS üzerinde SQLite'ı production ortamında çalıştırma, bu dosya makineler arasında paylaşılmaya başlarsa önem kazanan ayarları açıklar.
Çalıştırıcı aynı bilgileri kullanıcıya da yazdırır:
run 2026-08-05T09:14:22Z sha 4f1c9ab model claude-sonnet-5 58/60 pass (96.7%)
FAIL refund-double-charge deterministic: tool not called: create_refund
FAIL pto-policy-question judge(opus): reply gives no dollar amountTest paketini bir agent'ı bozabilecek değişiklikler üzerinde çalıştırın. Buna prompt düzenlemeleri, model değişiklikleri ve tool değişiklikleri dahildir. Depodaki her commit için çalıştırmak gerekmez. Bir pre-push hook'u hızlı alt kümeyi kapsar:
cat > .git/hooks/pre-push <<'EOF'
#!/bin/sh
python3 evals/run.py --set smoke || exit 1
EOF
chmod +x .git/hooks/pre-pushTam çalıştırmalar daha yavaştır ve zamanlanmalıdır. VPS üzerindeki her gece çalışan bir systemd service ve timer, tüm seti dağıtılmış prompt'a karşı çalıştırır. Bu işlem, repository dışından gelen değişiklikleri de yakalar. Örneğin hosted bir tool'un davranışı değişebilir.
İnsan incelemesi, kapsamlı inceleme yerine örneklem üzerinden
Değerlendirici insan etiketlerine göre kalibre edilir. Bu nedenle bu etiketlerin oluşturulması gerekir. Her hafta bir örneklem incelenmelidir: değerlendiricinin başarısız olduğu tüm vakalar ve rastgele seçilen on başarılı vaka. Rastgele seçilen başarılı vakalar kritik öneme sahiptir. Çünkü hatalı yanıtları sessizce kabul etmeye başlayan bir değerlendirici, kendi kararlarından oluşturulan herhangi bir dashboard üzerinde kusursuz görünebilir.
Haftada vaka başına üç dakika olmak üzere on beş vaka 45 dakika sürer. Bu çalışma, sizinle değerlendiricinin farklılaştığı durumlarda rubric için düzeltmeler ve daha önce öngörülmemiş hata türleri için yeni vakalar sağlar. İnsan kararını graded_by değeri human olarak ayarlanmış şekilde aynı tabloya yazın. Böylece değerlendirici ile insan arasındaki uyum, belleğe bağlı bir bilgi olmaktan çıkar ve sorgulanabilir hale gelir.
Değerlendirme harness'inin kendisinde ne bozulur
İlk tam çalıştırmada anthropic.RateLimitError. Aynı anda dağıtılan 60 vaka, katmanınız için istek veya token sınırını aşar. Eşzamanlılığı dört worker ile sınırlandırın ve gece çalıştırmasını Batch API'ye taşıyın.
Judge tarafından json.JSONDecodeError: Expecting value: line 1 column 1 (char 0). Model, açıklama metni döndürmüş veya JSON çıktısını bir code fence içine almış olabilir. Bir kez yeniden deneyin, ardından vakayı hata olarak kaydedin. Ayrıştırma hatasının başarılı sonuç olarak sayılmasına hiçbir zaman izin vermeyin. Hataları başarıya dönüştüren bir test paketi, agent kötüleşirken %100'e yaklaşır.
Kararsız vakalar. Agent çıktısını örneklediği için aynı girdi bir çalıştırmada başarılı, sonraki çalıştırmada başarısız olur. Kararsız vakayı üç kez çalıştırın ve vakayı silmek yerine oranı kaydedin. Üç çalıştırmanın ikisinde başarılı olan bir vaka, gerçek bir sağlamlık hatasıdır ve bir müşteri bunu bulacaktır.
Golden set'in eskimesi. Birisi test paketini yeşil duruma getirmek için beklenen yanıtı düzenler. evals/cases.jsonl dosyasındaki diff'leri agent diff'leri kadar dikkatle inceleyin. Çünkü bu dosya, doğru davranışın yazılı tanımıdır.
Hiç başarısız olmayan bir test paketi. Bir ay boyunca %100'de kalan başarı oranı, kümenin ürün davranışını izlemeyi bıraktığı anlamına gelir. Son 10 trace'i alın, agent'in kötü ele aldığı vakaları bulun ve bunları kümeye ekleyin.
FAQ
Bir AI agent eval seti kaç vaka içermelidir?
40 ila 80 vakayla başlanmalı ve set gerçek hatalardan yararlanılarak büyütülmelidir. Yaklaşık 20 vakanın altında, tek bir kararsız sonuç geçme oranını 5 puan değiştirir; bu nedenle sayı anlamlı bilgi taşımamaya başlar. Birkaç yüz vakanın üzerine çıkıldığında her çalıştırma gerçek maliyet ve zaman gerektirir, eklenen her vakanın kapsama katkısı ise azalır. Önemli olan vaka sayısı değildir. Önemli olan, üretimde bilinen hata türlerinin en az bir kez sette yer alma oranıdır.
Agent'ımı puanlamak için bir LLM judge'a güvenebilir miyim?
Yalnızca judge'ı kendi etiketlerinizle karşılaştırarak ölçtükten sonra güvenilmelidir. Elle puanladığınız 30 vakayı saklayın. Judge modelini veya judge prompt'unu her değiştirdiğinizde judge'ı bu vakalara göre değerlendirin. Judge'larda uzunluk yanlılığı görülür; gereksiz şekilde uzatılmış yanıtlar daha sık başarılı kabul edilir. Ayrıca öz-tercih yanlılığı görülür; kendi model ailesinden gelen çıktılar daha hoşgörülü puanlanır. Her ikisi de test edilebilir: başarısız bir yanıtı uzatıp yeniden puanlayın veya aynı yanıtları başka bir model ailesinden judge ile puanlayın. Judge, vakaların 10'da 1'inden fazlasında etiketlerinizle uyuşmuyorsa rubric kullanılmayacak kadar belirsizdir.
Eval'leri hangi model puanlamalıdır?
Ucuz olanla başlayın ve gerektiğinde daha güçlü modele geçin. Deterministik assertions hiçbir maliyet oluşturmaz; bu nedenle her vakada ilk olarak çalıştırılır. Küçük bir model, açıkça başarılı olan vakaları ele alır. Yalnızca başarısız vakalar ve düşük güvenli verdict'ler frontier modele gönderilir. Ağustos 2026 liste fiyatlarına göre 1.000 vakayı puanlamanın maliyeti Claude Haiku 4.5 ile yaklaşık 1.80 US dollars, Claude Opus 5 ile ise yaklaşık 9.00 olur. Eval çalıştırmaları asynchronous olduğundan Batch API her iki tutarı da yarıya indirir.
Eval'ler production monitoring'in yerini alır mı?
Hayır. Çünkü farklı soruları yanıtlarlar. Bir eval suite, yayınlamaya hazırlandığınız bir değişikliğin sabit bir vaka kümesini iyileştirip iyileştirmediğini gösterir. Tracing ve monitoring ise hiçbir vakanın kapsamadığı girdiler dahil olmak üzere gerçek kullanıcıların o anda neyle karşılaştığını gösterir. Bu süreçler birbirini besler: trace kayıtları yeni vakalar sağlar, eval suite ise düzeltmenizin gerçekten işe yarayıp yaramadığını belirler.