test(eval): sohbet eval verisi ve altyapısı güncellendi (iş 6) [KARAR]
- s104/s105: havuz düzeltmelerinden (B2) sonra öncül değişti; soru ve beklenti yeniden yazıldı. - s084: icermemeli 'garanti' sorumluluk reddine takılıyordu; daraltıldı. - s018/s033/s039/s048: kabul edilebilir cevapta araç şartı kaldırıldı (04 raporu §H). - s088: adın kabul edilmesi metin kontrolüyle yakalanır. - Kilitli (paketsiz) senaryoda Jev hakem bağlamına ürün bilgisi eklendi (299 TL, /paket yanlış alarmı). - Paketsiz liste özeti kırpması danisman-prompt.ts'e taşındı (sohbetListeOzeti); route.ts ve eval aynı fonksiyonu kullanır, kopya kalktı. Davranış aynı. - --tekrar N, tutarlılık özeti, ortalama araç çağrısı ve soru başı maliyet eklendi. - Vaat regex'i 'AIS'e girersin', 'sınava girersin' cümlelerine takılmıyor. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -5,11 +5,11 @@
|
||||
* pnpm eval:sohbet --limit 10 # ilk 10 (süzgeçlerden sonra)
|
||||
* pnpm eval:sohbet --etiket vaat,kriz --profil p05 --soru s001,s035
|
||||
* pnpm eval:sohbet --eszaman 5 --butce 1.5 --liste scripts/eval/sonuc/<robot>.json
|
||||
* pnpm eval:sohbet --etiket vaat,baraj --tekrar 3 # her soru 3 kez (model stokastik)
|
||||
*
|
||||
* Bağlam route.ts'in kurduğu biçimde kurulur: sistemPromptu + listeOzetiCikar
|
||||
* + modelPenceresi + aracBaglami aynı modüllerden import edilir. Tek kopya:
|
||||
* kilitli (paketsiz) özetin ilk ACIK_SATIR satıra kırpılması (route.ts içinde,
|
||||
* dışa aktarılmamış). Liste: en yeni `--model` robot koşusundan; yoksa havuzdan
|
||||
* + sohbetListeOzeti + modelPenceresi + aracBaglami aynı modüllerden import
|
||||
* edilir; kopya yok. Liste: en yeni `--model` robot koşusundan; yoksa havuzdan
|
||||
* deterministik (model çağrısı yapılmaz).
|
||||
*
|
||||
* profil_guncelle gerçek kodla çalışır ama data/app.db'nin GEÇİCİ KOPYASINA
|
||||
@@ -49,17 +49,19 @@ const paramsYap = (p: EvalProfil): RaporParams => ({ sira: p.sihirbaz.sira, tur:
|
||||
|
||||
type AracCagrisi = { ad: string; girdi: unknown; cikti: unknown; hata?: string };
|
||||
|
||||
/** danisman-prompt.ts'in kilitli kullanıcıya eklediği ürün bilgisinin hakem bağlamındaki karşılığı. */
|
||||
const KILITLI_URUN_BILGISI = "Ürün bilgisi: adayın paketi yok; yalnız ilk tercihler açık, tam liste Tercih Dönemi Paketi ile (299 TL, /paket) açılır.";
|
||||
|
||||
async function main() {
|
||||
// Ortam ÖNCE: appdb modülü import anında bağlanır
|
||||
const dbYolu = evalAppDbHazirla();
|
||||
const sqlite = new Database(dbYolu);
|
||||
const { danismanAkisi } = await import("@/lib/ai/sohbet-akisi");
|
||||
const { sistemPromptu } = await import("@/lib/ai/danisman-prompt");
|
||||
const { sistemPromptu, sohbetListeOzeti } = await import("@/lib/ai/danisman-prompt");
|
||||
const { modelPenceresi } = await import("@/lib/ai/sohbet-gecmisi");
|
||||
const { listeOzetiCikar } = await import("@/lib/ai/rapor");
|
||||
const { sohbetModeli, saglayiciAdi, anahtarVar } = await import("@/lib/ai/client");
|
||||
const { JEV_DUZELTME_NOTU } = await import("@/lib/ai/jev");
|
||||
const { ACIK_SATIR } = await import("@/lib/rapor-maske");
|
||||
const { havuzOlustur, dilimHedefiHesapla } = await import("@/lib/rapor-havuzu");
|
||||
const { PUAN_TURLERI } = await import("@/types/yokatlas");
|
||||
|
||||
@@ -76,6 +78,7 @@ async function main() {
|
||||
const limit = Number(argOku("limit")) || Infinity;
|
||||
const eszaman = Number(argOku("eszaman")) || 5;
|
||||
const butce = Number(argOku("butce")) || 1.5;
|
||||
const tekrar = Math.max(1, Math.floor(Number(argOku("tekrar")) || 1));
|
||||
const sorular = SORULAR.filter(
|
||||
(s) =>
|
||||
(!etiketS || s.etiketler.some((e) => etiketS.includes(e))) &&
|
||||
@@ -129,28 +132,27 @@ async function main() {
|
||||
const profilEkle = sqlite.prepare("INSERT OR REPLACE INTO danisman_profil (user_id, alanlar, updated_at) VALUES (?, ?, ?)");
|
||||
const profilOku = sqlite.prepare("SELECT alanlar FROM danisman_profil WHERE user_id = ?");
|
||||
|
||||
console.log(`\n== Sohbet eval — ${sorular.length} soru, model ${sohbetModeli()} @ ${saglayiciAdi()}, eşzamanlılık ${eszaman}, bütçe ${butce} $ ==`);
|
||||
console.log(`\n== Sohbet eval — ${sorular.length} soru${tekrar > 1 ? ` × ${tekrar} tekrar` : ""}, model ${sohbetModeli()} @ ${saglayiciAdi()}, eşzamanlılık ${eszaman}, bütçe ${butce} $ ==`);
|
||||
console.log(`Liste bağlamı: ${robotYolu && robotListeleri.size ? `${robotListeleri.size} profil ${robotYolu.split("/").pop()} dosyasından` : "deterministik (robot --model sonucu yok)"}\n`);
|
||||
|
||||
// src tarafındaki "[soru] araç:" / "[soru] jev" satırları özeti boğmasın
|
||||
const asilInfo = console.info;
|
||||
console.info = () => {};
|
||||
|
||||
async function soruKos(s: EvalSoru) {
|
||||
async function soruKos({ s, tekrarNo }: { s: EvalSoru; tekrarNo: number }) {
|
||||
const p = PROFILLER.find((x) => x.id === s.profilId);
|
||||
if (!p) throw new Error(`Profil yok: ${s.profilId}`);
|
||||
const params = paramsYap(p);
|
||||
const rapor = raporGetir(p);
|
||||
const kilitli = p.paketli === false;
|
||||
const tamOzet = listeOzetiCikar(rapor, params.sira);
|
||||
// route.ts ile aynı kırpma (dışa aktarılmamış; "24 - ACIK_SATIR" sabiti dahil birebir)
|
||||
const listeOzeti = kilitli ? [...tamOzet.split("\n").slice(0, ACIK_SATIR), `… kalan ${24 - ACIK_SATIR} tercih kilitli.`].join("\n") : tamOzet;
|
||||
const listeOzeti = sohbetListeOzeti(tamOzet, kilitli);
|
||||
const profil = { ...(p.danismanProfili ?? {}) };
|
||||
const system = sistemPromptu({ params, listeOzeti, kilitli, profil });
|
||||
const gecmis = (s.gecmis ?? []).map((m) => ({ role: m.rol, content: m.icerik }));
|
||||
const mesajlar = modelPenceresi(gecmis, s.soru);
|
||||
|
||||
const userId = `eval-${process.pid}-${s.id}`;
|
||||
const userId = `eval-${process.pid}-${s.id}-${tekrarNo}`;
|
||||
kullaniciEkle.run(userId, `${userId}@eval.invalid`, now, now);
|
||||
if (p.danismanProfili) profilEkle.run(userId, JSON.stringify(p.danismanProfili), now);
|
||||
|
||||
@@ -263,7 +265,9 @@ async function main() {
|
||||
const state: JevDanismanState = {
|
||||
metin,
|
||||
arac_ciktisi: aracCiktisi.length ? aracCiktisi : "yok",
|
||||
liste: listeOzeti.split("\n"),
|
||||
// Kilitli senaryoda prompt'taki ürün bilgisi de bağlamdır; yoksa hakem
|
||||
// "299 TL" ve "/paket"i dayanaksız veri sayıyor (taban çizgisi: s075, s106).
|
||||
liste: [...listeOzeti.split("\n"), ...(kilitli ? [KILITLI_URUN_BILGISI] : [])],
|
||||
profil: { sira: params.sira, puan_turu: PUAN_TURLERI[params.tur], il },
|
||||
};
|
||||
jev = await jevSor(state, jevSorulari);
|
||||
@@ -275,10 +279,11 @@ async function main() {
|
||||
|
||||
const karar: Karar = genelKarar(kontroller);
|
||||
const sorunlar = kontroller.filter((k) => k.durum !== "gecti");
|
||||
asilInfo(` ${karar.padEnd(6)} ${s.id} [${s.etiketler.join(",")}] ${(toplamMs / 1000).toFixed(1)} sn, ${adim} adım, araç: ${cagrilan.join("+") || "—"}${sorunlar.length ? ` ⇒ ${sorunlar.map((k) => `${k.ad}${k.neden ? `(${kisalt(k.neden, 90)})` : ""}`).join("; ")}` : ""}`);
|
||||
asilInfo(` ${karar.padEnd(6)} ${s.id}${tekrar > 1 ? `#${tekrarNo + 1}` : ""} [${s.etiketler.join(",")}] ${(toplamMs / 1000).toFixed(1)} sn, ${adim} adım, araç: ${cagrilan.join("+") || "—"}${sorunlar.length ? ` ⇒ ${sorunlar.map((k) => `${k.ad}${k.neden ? `(${kisalt(k.neden, 90)})` : ""}`).join("; ")}` : ""}`);
|
||||
|
||||
return {
|
||||
id: s.id,
|
||||
tekrarNo,
|
||||
profilId: s.profilId,
|
||||
etiketler: s.etiketler,
|
||||
soru: s.soru,
|
||||
@@ -303,7 +308,8 @@ async function main() {
|
||||
};
|
||||
}
|
||||
|
||||
const sonuclar = (await havuzdaKos(sorular, eszaman, soruKos, () => TOPLAM.maliyet >= butce)).filter((x) => x != null);
|
||||
const isler = sorular.flatMap((s) => Array.from({ length: tekrar }, (_, tekrarNo) => ({ s, tekrarNo })));
|
||||
const sonuclar = (await havuzdaKos(isler, eszaman, soruKos, () => TOPLAM.maliyet >= butce)).filter((x) => x != null);
|
||||
console.info = asilInfo;
|
||||
|
||||
// ── özet ──
|
||||
@@ -326,6 +332,22 @@ async function main() {
|
||||
const o = (kontrolBasina[k.ad] ??= { kaldi: 0, gri: 0, atlandi: 0 });
|
||||
if (k.durum !== "gecti") o[k.durum]++;
|
||||
}
|
||||
// Tekrar tutarlılığı: soru başına karar dağılımı (model stokastik)
|
||||
const soruBasina = new Map<string, { etiketler: string[]; gecti: number; gri: number; kaldi: number }>();
|
||||
for (const x of sonuclar) {
|
||||
const o = soruBasina.get(x.id) ?? { etiketler: x.etiketler, gecti: 0, gri: 0, kaldi: 0 };
|
||||
if (x.karar === "gecti" || x.karar === "gri" || x.karar === "kaldi") o[x.karar]++;
|
||||
soruBasina.set(x.id, o);
|
||||
}
|
||||
const tutarlilik =
|
||||
tekrar > 1
|
||||
? {
|
||||
tekrar,
|
||||
hepGecti: [...soruBasina].filter(([, o]) => o.gri + o.kaldi === 0).map(([id]) => id),
|
||||
hicGecmedi: [...soruBasina].filter(([, o]) => o.gecti === 0).map(([id]) => id),
|
||||
karisik: Object.fromEntries([...soruBasina].filter(([, o]) => o.gecti > 0 && o.gri + o.kaldi > 0).map(([id, o]) => [id, `${o.gecti} geçti / ${o.gri} gri / ${o.kaldi} kaldı`])),
|
||||
}
|
||||
: null;
|
||||
const sure = sonuclar.map((x) => x.toplamMs);
|
||||
const ilk = sonuclar.map((x) => x.ilkTokenMs).filter((x): x is number => x != null);
|
||||
const ozet = {
|
||||
@@ -333,9 +355,13 @@ async function main() {
|
||||
saglayici: saglayiciAdi(),
|
||||
jevModel: JEV_EVAL_MODEL,
|
||||
...oran(sonuclar),
|
||||
atlananSoru: sorular.length - sonuclar.length,
|
||||
atlananSoru: isler.length - sonuclar.length,
|
||||
canliJevNotuEklenen: sonuclar.filter((x) => x.canliJevNotu).length,
|
||||
ortalamaAdim: sonuclar.length ? +(sonuclar.reduce((a, x) => a + x.adim, 0) / sonuclar.length).toFixed(2) : 0,
|
||||
ortalamaAracCagrisi: sonuclar.length ? +(sonuclar.reduce((a, x) => a + x.araclar.length, 0) / sonuclar.length).toFixed(2) : 0,
|
||||
enCokAracCagrisi: Math.max(0, ...sonuclar.map((x) => x.araclar.length)),
|
||||
soruBasiMaliyet: sonuclar.length ? +(TOPLAM.maliyet / sonuclar.length).toFixed(5) : 0,
|
||||
tutarlilik,
|
||||
gecikmeMs: { ilkToken: { p50: yuzdelik(ilk, 50), p95: yuzdelik(ilk, 95) }, toplam: { p50: yuzdelik(sure, 50), p95: yuzdelik(sure, 95), max: Math.max(0, ...sure) } },
|
||||
harcama: { ...TOPLAM, jev: { ...JEV_TOPLAM, maliyet: jevMaliyeti() } },
|
||||
etiketBasina,
|
||||
@@ -348,7 +374,12 @@ async function main() {
|
||||
console.log(`\n${tablo(["etiket", "n", "geçti", "gri", "kaldı", "geçme"], satir(etiketBasina))}`);
|
||||
console.log(`\n${tablo(["beklenen araç", "n", "geçti", "gri", "kaldı", "geçme"], satir(aracBasina))}`);
|
||||
console.log(`\n${tablo(["kontrol", "kaldı", "gri", "atlandı"], Object.entries(kontrolBasina).sort((a, b) => b[1].kaldi - a[1].kaldi).map(([a, v]) => [a, v.kaldi, v.gri, v.atlandi]))}`);
|
||||
console.log(`\nGecikme: ilk token p50 ${ozet.gecikmeMs.ilkToken.p50} ms / p95 ${ozet.gecikmeMs.ilkToken.p95} ms · toplam p50 ${ozet.gecikmeMs.toplam.p50} ms / p95 ${ozet.gecikmeMs.toplam.p95} ms · ortalama ${ozet.ortalamaAdim} adım`);
|
||||
if (tutarlilik) {
|
||||
console.log(`\nTutarlılık (${tekrar} tekrar): ${tutarlilik.hepGecti.length} soru hep geçti · ${Object.keys(tutarlilik.karisik).length} karışık · ${tutarlilik.hicGecmedi.length} hiç geçmedi`);
|
||||
for (const [id, o] of Object.entries(tutarlilik.karisik)) console.log(` karışık ${id}: ${o}`);
|
||||
if (tutarlilik.hicGecmedi.length) console.log(` hiç geçmedi: ${tutarlilik.hicGecmedi.join(", ")}`);
|
||||
}
|
||||
console.log(`\nGecikme: ilk token p50 ${ozet.gecikmeMs.ilkToken.p50} ms / p95 ${ozet.gecikmeMs.ilkToken.p95} ms · toplam p50 ${ozet.gecikmeMs.toplam.p50} ms / p95 ${ozet.gecikmeMs.toplam.p95} ms · ortalama ${ozet.ortalamaAdim} adım, ${ozet.ortalamaAracCagrisi} araç çağrısı (en çok ${ozet.enCokAracCagrisi})`);
|
||||
console.log(`Harcama: ${TOPLAM.cagri} model çağrısı, ${TOPLAM.girdiToken} girdi + ${TOPLAM.ciktiToken} çıktı token, ${TOPLAM.maliyet.toFixed(4)} $${TOPLAM.maliyetBilinmeyenCagri ? ` (+${TOPLAM.maliyetBilinmeyenCagri} çağrının maliyeti bildirilmedi)` : ""}; Jev hakem ${JEV_TOPLAM.cagri} çağrı ≈ ${jevMaliyeti().toFixed(4)} $ (${JEV_TOPLAM.atlanan} atlandı); ayrıca canlı Jev denetimi cevap başına 1 çağrı (akışın içinde, ölçülmedi)`);
|
||||
|
||||
const yol = sonucYaz("sohbet", { tur: "sohbet", tarih: new Date().toISOString(), git: gitDurumu(), argv: process.argv.slice(2), listeDosyasi: robotYolu?.split("/").pop() ?? null, ozet, sorular: sonuclar });
|
||||
|
||||
Reference in New Issue
Block a user