İçeriğe geç
mcpd
daemon yok
Kaynak

Regresyon testi

Salı günü bozulan çalıştırma, sonraki her sürümün geçmesi gereken vaka olur.

Kapatmak için var olan döngü

Bir ajan gerçek bir soruda başarısız olur. Biri bunu elle yeniden üretir, bir prompt'u değiştirir ve durum düzelmiş görünür. Üç hafta sonra aynı soruda yine başarısız olur, çünkü onu saklayan hiçbir şey yoktur.

Veri kümesi onu saklar. Başarısız çalıştırmayı yakalayın; artık bir anekdot olmaktan çıkar ve bir girdisi, elinizde varsa beklenen bir yanıtı ve geldiği trace'e giden bir işaretçisi olan bir vaka olur. Sonraki her sürüm ona karşı çalışır.

Bir çalıştırmayı yakalamak

Çalıştırmayı arayüzde ya da API üzerinden bulun, sonra trace kimliğini veri kümesine verin.

başarısız bir çalıştırmayı vaka olarak yakala

curl -X POST localhost:4317/v1/api/datasets/$DATASET/capture \ -H 'content-type: application/json' \ -d '{"trace_ids": ["b3d9e71a4c0f428eb6a5d2317f90c684"]}'

Yakalanan vaka source_trace_id alanını saklar; böylece aylar sonra ona sebep olan çalıştırmayı açıp girdiye bakarak tahmin yürütmek yerine gerçekte ne olduğunu okuyabilirsiniz.

Trace b3d9e71a Fault
0ms 2.1s 4.2s 6.3s 8.4s
  1. agent support_agent fail
  2. model chat
  3. tool returns_policy
  4. model chat
  5. tool returns_policy
  6. model chat
  7. tool returns_policy

The model called one tool three times and never answered. That is visible in four seconds here, and invisible in a log file.

Çalıştırmanın kendi yanıtı, beklenen yanıt değildir

use_output_as_expected varsayılan olarak kapalıdır ve önemli olan kısım bu varsayılandır. Yakalamaya değer çalıştırmalar genellikle ters gidenlerdir; yanlış bir yanıtı referans olarak kaydetmek de hatayı kalıcılaştırır: sonraki her sürüm o hatayı yeniden üretme becerisine göre ölçülür.

Zaten beğendiğiniz bir davranışı yerinde tutmak için doğru sonuçlanmış çalıştırmaları yakalarken bunu açın. Bir başarısızlığı yakalarken kapalı bırakın ve beklenen yanıtı kendiniz yazın.

Yerleşik puanlayıcılar

mcpd on üç deterministik puanlayıcıyla gelir: saklanmış bir çalıştırmayı okuyup bir sayı döndüren sıradan kod. Hiçbir maliyetleri yoktur, hiçbir zaman bir model çağırmazlar ve iki kez aynı yanıtı verirler; bir regresyon testinin ihtiyaç duyduğu, bir yargıç modelde ise bulunmayan özellik budur.

PuanlayıcıNe zaman geçer
answeredÇalıştırma boş olmayan bir yanıt üretti.
no_errorsÇalıştırmadaki hiçbir span başarısız olmadı.
exact_matchYanıt beklenenin aynısı.
containsYanıt, istenen dizeyi içeriyor.
json_validYanıt JSON olarak ayrıştırılıyor.
latency_budgetÇalıştırma, milisaniye cinsinden bir bütçenin içinde bitti.
cost_budgetÇalıştırma, belirlenen tutardan fazlaya mal olmadı.
no_tool_loopHiçbir araç aynı girdiyle iki kez çağrılmadı.
tool_efficiencyÇalıştırma, araç çağrısı bütçesinin içinde kaldı.
not_a_refusalYanıt bir ret ya da özür değil.
no_leaked_secretYanıtta hiçbir anahtar, token ya da kimlik bilgisi geçmiyor.
span_presentÇalıştırma, istenen türde ya da adda bir span kaydetti.
tool_succeededİstenen araç çalıştı ve en az bir çağrısı düzgün bitti.

GET /v1/api/scorers aynı listeyi bir needs_config bayrağıyla döndürür. Beklenen bir yanıta ihtiyaç duyan ama kendisine verilmeyen bir puanlayıcı tahmin etmek yerine puan üretmez ve bu durum bildirilir.

Notu bir model verdiğinde

Bazı soruları hiçbir deterministik kod yanıtlayamaz. Bir yanıtın soruyu gerçekten karşılayıp karşılamadığı, bir özetin anlamı koruyup korumadığı, bir üslubun yerinde olup olmadığı: bunlar bir okuyucu ister ve ucuz okuyucu bir modeldir.

Yargıçlı puanlama siz yapılandırana kadar kapalıdır ve yargıçlı bir puanlayıcı siz adını vermedikçe hiç çalışmaz. İki varsayılan da aynı nedenle vardır: not verme, okuduğu her çalıştırma için para harcar ve bunu sessizce yapan bir özellik, varlığını bir faturada öğrendiğiniz özelliktir.

{
  "judge": {
    "endpoint": "http://localhost:11434/v1",
    "model": "qwen2.5:14b",
    "api_key": "",
    "timeout_seconds": 30,
    "criteria": {
      "answer_relevance": "Does the answer address the question that was asked?",
      "no_hedging": "Does the answer commit, rather than hedge?"
    }
  }
}

İstek, OpenAI chat-completions biçimindedir; bu bir sağlayıcı tercihi değildir: Ollama, vLLM, llama.cpp, LM Studio, OpenRouter ve barındırılan sağlayıcıların hepsi bunu kabul eder, yani aynı yapılandırma hem dizüstünüzdeki bir modele hem de üretimdeki bir ağ geçidine ulaşır. Anahtar istemeyen yerel bir model için api_key alanını boş bırakın. Sıcaklık sıfırdır, çünkü her seferinde farklı yanıt veren bir yargıç kendisiyle karşılaştırılamaz, ki puanların tek işi budur.

Ölçütler her istekte gönderilmek yerine yapılandırılır. Ürettiği her puanın üzerinde bir puanlayıcı kimliği saklanır ve sonraki bir karşılaştırma buna göre gruplar; her çağrının hem kimliği hem soruyu kendi uydurmasına izin vermek de kimsenin güvenemeyeceği bir sütun kurardı.

Yapılandırılmış ölçütler GET /v1/api/scorers içinde görünmez. O endpoint yalnızca yerleşik on üç puanlayıcıyı listeler, başka bir şey listelemez; dolayısıyla puanlayıcı menüsünü ondan kuran bir istemci sizin ölçütlerinizi sunmayacaktır. Daemon'un bunları size geri söylediği tek yer, bilinmeyen bir puanlayıcı kimliği için aldığınız hatadır; o hata yerleşik olanlarla yapılandırılmış ölçütleri bir arada listeler.

son çalıştırmada yargıçlı bir puanlayıcı çalıştır

curl -X POST localhost:4317/v1/api/score \ -H 'content-type: application/json' \ -d '{"limit": 1, "scorers": ["answer_relevance"]}'

timeout_seconds, tek bir puanlama çağrısının bağlanmadan yanıtın son baytına kadar ne kadar sürebileceğidir: varsayılan 30, aralık 1 ile 300. Bunu aşan çağrı anahtarı adlandırarak puan üretmez ve mcpd bir yanıtın en fazla 1 MiB'ını okur.

Her yargıçlı puanlayıcı, çalıştırma başına bir model çağrısıdır ve istek --request-timeout-seconds içinde bitmek zorundadır. Bu yüzden bir istek, her çağrı timeout_seconds süresinin tamamını kullansa bile bu süreye sığacak kadar çalıştırmayı puanlar. Varsayılan 30 ve 30 ile tek bir yargıçlı puanlayıcıda bu bir çalıştırmadır. trace_ids ya da limit içinde daha fazlasını isteyen bir istek, hiçbir model çağrılmadan sınırı adlandıran bir 400 ile reddedilir. limit verilmezse istek, sığdığı kadar son çalıştırmayı puanlar. Tek istekte daha fazlasını puanlamak için --request-timeout-seconds değerini yükseltin. Her yargıçlı puan, model yanıt verir vermez saklanır; böylece yarıda kesilen bir istek bitirdiği puanları korur.

Tahmin etmek yerine puan üretmez

Türetmediği bir sayıyı döndüren bir yargıç, hiç yargıç olmamasından kötüdür, çünkü kanıt gibi görünür. Bu yüzden aşağıdakilerin her biri, nedeniyle birlikte skipped içinde bildirilir ve hiçbiri bir puan üretmez.

Ne oldumcpd ne yapar
Çalıştırma hiçbir yanıt kaydetmedi Puan üretmez. Yanıtı olmayan bir çalıştırma ilgililikte düşük puan almış olmaz, hiç ölçülmemiştir.
Model düz metinle yanıtladı Puan üretmez ve yanıtı alıntılar. Kod bloğuna ya da bir cümlenin içine sarılmış JSON yine de okunur.
Model ölçeğin dışında yanıt verdi Puan üretmez. Bir üzerinden verilen bir yedi, onda dokuz demek değildir; sayıyı sınıra çekmek de bir sayı uydurmak olurdu.
Modele ulaşılamadı Puan üretmez, durum kodunu ve gövdeyi verir. Hız sınırı bir nedendir, sıfır değil.
Yanıt 1 MiB'tan büyüktü Gerisini okumadan puan üretmez. Bir karar birkaç yüz bayttır, o boyutta bir yanıt karar değildir.
İsteğin süresi çağrı için zaman bırakmadı Modeli çağırmadan reddeder. Geçiş, 503 yerine süre içinde bitirdiği puanlarla yanıt verir.

Yargıçlı her puan, modeli kendi scorer_version alanına kaydeder. İki farklı yargıçtan gelen iki puan karşılaştırılabilir değildir ve kayıtta bunu söyleyen alan budur.

Puanlama olaydan sonra olur

POST /v1/api/score, puanlayıcıları hâlihazırda saklanmış çalıştırmalar üzerinde çalıştırır. Hiçbir şey satır içinde değerlendirilmez. İstek yolundaki bir değerlendirme, kendi gecikmesini yargıladığı şeyin üzerine ekler ve hata fırlatan bir değerlendirme hiçbir zaman bir kullanıcının isteğini düşürmemelidir.

son yüz çalıştırmayı puanla

curl -X POST localhost:4317/v1/api/score \ -H 'content-type: application/json' \ -d '{"project": "support-desk", "limit": 100, "config": {"max_ms": 4000}}'

Yanıt, yazdığı puanları ve bunlardan ayrı olarak puan üretmeyen her puanlayıcıyı nedeniyle birlikte taşır. Yanıt veremeyen bir puanlayıcı, bir sayı döndürenden daha ilginçtir, o yüzden mcpd bunu asla yutmaz. Projenin alım tavanı da bu kapıya kadar gelir: taramanın hesapladığı, projeyi --quota-per-minute sınırının üstüne çıkaracak bir puan saklanmaz; tavanın ötesine yazılmak ya da sessizce atılmak yerine bunu söyleyerek skipped içinde listelenir.

Hiçbir şey saklamadan bir taramanın ne üreteceğini görmek için "dry_run": true gönderin.

Bir taramanın hangi çalıştırmaları okuyacağına trace_ids, from ve limit karar verir. Tam olarak belirli çalıştırmaları puanlamak için onları trace_ids içinde adlandırın. Boş bırakırsanız tarama, projedeki en yeni limit kadar çalıştırmayı en yeniden başlayarak alır; from da o sayfayı sınırlar: from ile gönderilen bir tarama yalnızca o anda ya da sonrasında başlayan çalıştırmaları okur, ki bir dağıtımdan sonraki her şeyi kimlik listelemeden puanlamanın yolu budur.

İki sürümü karşılaştırmak

Karşılaştırmak istediğiniz her sürüm için bir deney kaydedin, puanlarını o deneyin kimliğiyle etiketleyin ve mcpd'den farkı isteyin.

yeni prompt işe yaradı mı

curl 'localhost:4317/v1/api/experiments/compare?baseline=v3&candidate=v4'

Yanıt, her iki taraftaki örneklem boyutuyla birlikte puanlayıcı başına bir fark ve bir decidable bayrağıdır. Yirmi gözlemin altında mcpd, bir hareket bildirmek yerine karşılaştırmanın karara bağlanamayacağını söyler.

Bu bilerek kaba bir kuraldır, anlamlılık testi değildir. Önlediği hata, yaygın olanıdır: dört çalıştırmada kımıldayan, sayısı yazılmadan basılan, sonuç diye okunup yayına alınan bir ortalama. Sayıları farkın yanına basmak, gürültünün bulgu sanılmasını durduran şeydir.

Bunun çalışacağı yer

Hepsi HTTP API'sidir, yani döngünün yeri CI'dır: başarısızlıkları oluştukça yakalayın, test takımınızın ürettiği çalıştırmaları puanlayın, derlemeyi bir deney olarak kaydedin ve karara bağlanabilir bir karşılaştırmada regressed değeri true dönerse işi başarısız sayın.

Sırada