test(eval): revizyon evali koda bağlı yola göre — niyet/kural/dal/şablon ölçümü; akis-denetim'e 4 koddan senaryo
- eval:revizyon: regex kapısı + yapılandırılmış çıkarım + koddan işlem (sahte çekirdek) ölçülür; beklenen kural alt kümesi, sahte beyan, ad eşleşme, niyet gecikmesi/maliyeti ayrı. Ham sonuçlar: 22-57 (duman), 22-59 (model anlatımı, 2/8 yanlış sayı), 23-02 (doğal kayıt), 23-07 (şablon; r04 niyet zaman aşımı → güvenlik ağı), 23-08 (r04 tekrar). - akis-denetim (13/13): koddan revizyon (modelsiz şablon), niyet=diger + sahte beyan → not + iade, koddan form (model çağrısı 0), koddan revizyon hatası. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -8,23 +8,39 @@
|
||||
* (profil_guncelle data/app.db'nin geçici kopyasına yazar). Doğrulananlar:
|
||||
* vaat regex'i notu (Jev anahtarı yokken), "kaydettim" deyip kaydetmeme notu,
|
||||
* araç çağrısı kotası, rehber bağlantısı doğrulama (uydurma slug akışta
|
||||
* gerçek slug'a döner, kayıt da düzeltilmiş metni alır). Model davranışını
|
||||
* ÖLÇMEZ; onu eval:sohbet ölçer.
|
||||
* gerçek slug'a döner, kayıt da düzeltilmiş metni alır), koda bağlı revizyon
|
||||
* (9 Eki 2026: koddan işlem ekrana tool-liste_revize parçası ve modele
|
||||
* "sunucu işlemi" bölümü olarak gider, "yeniden kurdum" o zaman beyan
|
||||
* sayılmaz; işlem yokken beyan → not + kredi iadesi; koddan form → model
|
||||
* araçsız; liste_revize modelin araç listesinde yok). Model davranışını
|
||||
* ÖLÇMEZ; onu eval:sohbet ve eval:revizyon ölçer.
|
||||
* Beklenti tutmazsa çıkış kodu 1.
|
||||
*/
|
||||
import { createServer } from "node:http";
|
||||
import { copyFileSync } from "node:fs";
|
||||
import { execFileSync } from "node:child_process";
|
||||
|
||||
type Beklenen = { vaatNotu: boolean; kayitNotu: boolean; reddedilenArac: number; baglantiDuzeltme: number };
|
||||
type Beklenen = { vaatNotu: boolean; kayitNotu: boolean; reddedilenArac: number; baglantiDuzeltme: number; revizeNotu?: boolean };
|
||||
type Senaryo = {
|
||||
ad: string;
|
||||
beklenen: Beklenen;
|
||||
adimlar: ({ metin: string } | { araclar: { name: string; arguments: string }[] })[];
|
||||
/** Kaydedilen metinde olması / olmaması gerekenler (rehber bağlantısı senaryoları). */
|
||||
metinde?: { var?: string[]; yok?: string[] };
|
||||
/** Koddan işlem (route'un revizyon-niyeti sonucuna göre verdiği). */
|
||||
islem?: { ad: "liste_revize" | "soru_sor"; cikti: unknown; anlatim?: { ozet: string[]; ilkN: boolean } };
|
||||
/** Sahte uca giden İLK isteğin denetimi: sistem metninde olan, araç listesi, tool_choice. */
|
||||
istek?: { sistemde?: string[]; sistemdeYok?: string[]; aracYok?: string[]; toolChoice?: string };
|
||||
/** Ekrana giden araç parçaları (tool-input-available toolName:durum). */
|
||||
parcalar?: string[];
|
||||
/** onRevizeBeyani çağrıldı mı (kredi iadesi yolu). */
|
||||
iadeIstendi?: boolean;
|
||||
/** Sahte uca giden istek sayısı (koddan cevapta 0). */
|
||||
modelCagrisi?: number;
|
||||
};
|
||||
const B = (vaatNotu: boolean, kayitNotu: boolean, reddedilenArac = 0, baglantiDuzeltme = 0): Beklenen => ({ vaatNotu, kayitNotu, reddedilenArac, baglantiDuzeltme });
|
||||
const B = (vaatNotu: boolean, kayitNotu: boolean, reddedilenArac = 0, baglantiDuzeltme = 0, revizeNotu = false): Beklenen => ({ vaatNotu, kayitNotu, reddedilenArac, baglantiDuzeltme, revizeNotu });
|
||||
const REVIZYON_OK = { ok: true, toplam: 24, dagilim: { hayal: 5, dengeli: 13, garanti: 6 }, ilkTercihler: ["Bilgisayar Mühendisliği — EGE ÜNİVERSİTESİ (İZMİR)"], not: "Liste ekranda yenilendi." };
|
||||
const FORM = { soruldu: true, anket: { id: "f1", sorular: [{ ad: "il", soru: "Hangi il?", secenekler: [{ deger: "izmir", etiket: "İzmir" }, { deger: "ankara", etiket: "Ankara" }] }] }, not: "Form adaya gösterildi." };
|
||||
const SENARYOLAR: Senaryo[] = [
|
||||
{ ad: "kaydettim-aracsiz", beklenen: B(false, true), adimlar: [{ metin: "Kaydettim: Vakıfta yıllık ücret üst sınırı 400.000 TL. Aramalar buna göre süzülecek." }] },
|
||||
{ ad: "kaydettim-aracli", beklenen: B(false, false), adimlar: [{ araclar: [{ name: "profil_guncelle", arguments: JSON.stringify({ alan: "ucretUst", deger: 400000 }) }] }, { metin: "Kaydettim: yıllık ücret en fazla 400.000 TL." }] },
|
||||
@@ -46,13 +62,64 @@ const SENARYOLAR: Senaryo[] = [
|
||||
adimlar: [{ metin: "Bak: [Devlet mi vakıf mı](/rehber/devlet-mi-vakif-mi) yazısı bunu anlatıyor." }],
|
||||
metinde: { var: ["/rehber/devlet-mi-vakif-mi"] },
|
||||
},
|
||||
// Koda bağlı revizyon (QA B1): niyet var + kural geçerli → işlem ve cevap
|
||||
// koddan (model çağrılmaz); ekranda tool-liste_revize parçası; sayılar ve
|
||||
// adlar sunucu sonucundan şablonla.
|
||||
{
|
||||
ad: "koddan-revizyon",
|
||||
beklenen: B(false, false),
|
||||
islem: { ad: "liste_revize", cikti: REVIZYON_OK, anlatim: { ozet: ["ilk 5 tercih: il İZMİR"], ilkN: true } },
|
||||
modelCagrisi: 0,
|
||||
parcalar: ["liste_revize:output-available"],
|
||||
iadeIstendi: false,
|
||||
adimlar: [{ metin: "(model çağrılmamalı)" }],
|
||||
metinde: { var: ["Listeni yeniden kurdum — ilk 5 tercih: il İzmir. 24 tercih: 5 hayal / 13 dengeli / 6 güvenli.", "1. Bilgisayar Mühendisliği — Ege Üniversitesi (İzmir)", "dilim sırası bilerek bozulur"], yok: ["YENİDEN KURULMADI", "model çağrılmamalı"] },
|
||||
},
|
||||
// Niyet regex'i eşleşti ama çıkarım "diger" dedi → işlem yok, normal sohbet;
|
||||
// model yine de "yeniden kurdum" derse not + kredi iadesi (güvenlik ağı).
|
||||
{
|
||||
ad: "niyet-diger-sahte-beyan",
|
||||
beklenen: B(false, false, 0, 0, true),
|
||||
istek: { sistemdeYok: ["BU TURDA SUNUCU İŞLEMİ"], aracYok: ["liste_revize"] },
|
||||
parcalar: [],
|
||||
iadeIstendi: true,
|
||||
adimlar: [{ metin: "Listeni yeniden kurdum, artık ilk 5 tercihin İzmir'den." }],
|
||||
metinde: { var: ["YENİDEN KURULMADI", "kredisi iade edildi"] },
|
||||
},
|
||||
// Niyet var, kural eksik → form koddan; cevap da koddan (model çağrısı yok)
|
||||
{
|
||||
ad: "koddan-form",
|
||||
beklenen: B(false, false),
|
||||
islem: { ad: "soru_sor", cikti: FORM },
|
||||
modelCagrisi: 0,
|
||||
parcalar: ["soru_sor:output-available"],
|
||||
iadeIstendi: false,
|
||||
adimlar: [{ metin: "(model çağrılmamalı)" }],
|
||||
metinde: { var: ["Birkaç kısa soru hazırladım"], yok: ["model çağrılmamalı"] },
|
||||
},
|
||||
// Koddan revizyon başarısız: ekrana hata parçası, cevap koddan nedeniyle (model yok)
|
||||
{
|
||||
ad: "koddan-revizyon-hata",
|
||||
beklenen: B(false, false),
|
||||
islem: { ad: "liste_revize", cikti: { hata: "Liste şu anda zaten yeniden kuruluyor.", not: "'yeniden kurdum' deme." } },
|
||||
modelCagrisi: 0,
|
||||
parcalar: ["liste_revize:output-error"],
|
||||
iadeIstendi: false,
|
||||
adimlar: [{ metin: "(model çağrılmamalı)" }],
|
||||
metinde: { var: ["Listeni yeniden kuramadım: Liste şu anda zaten yeniden kuruluyor."], yok: ["model çağrılmamalı"] },
|
||||
},
|
||||
];
|
||||
const istekler: { system: string; araclar: string[]; toolChoice: unknown }[] = [];
|
||||
let aktif: Senaryo = SENARYOLAR[0];
|
||||
let adimNo = 0;
|
||||
const sunucu = createServer((req, res) => {
|
||||
let govde = "";
|
||||
req.on("data", (c) => (govde += c));
|
||||
req.on("end", () => {
|
||||
try {
|
||||
const j = JSON.parse(govde) as { messages?: { role: string; content: string }[]; tools?: { function?: { name: string } }[]; tool_choice?: unknown };
|
||||
istekler.push({ system: j.messages?.find((m) => m.role === "system")?.content ?? "", araclar: (j.tools ?? []).map((t) => t.function?.name ?? "?"), toolChoice: j.tool_choice ?? null });
|
||||
} catch {}
|
||||
const adim = aktif.adimlar[Math.min(adimNo++, aktif.adimlar.length - 1)];
|
||||
res.writeHead(200, { "Content-Type": "text/event-stream" });
|
||||
const yaz = (delta: unknown, finish: string | null = null) =>
|
||||
@@ -95,12 +162,21 @@ async function main() {
|
||||
sqlite.prepare("INSERT OR REPLACE INTO user (id, name, email, email_verified, credit_balance, has_paket, created_at, updated_at) VALUES (?, 'Dene', ?, 1, 0, 1, 1, 1)").run(userId, `${userId}@eval.invalid`);
|
||||
let tam: string | null = null;
|
||||
let denetim: Beklenen | null = null;
|
||||
let iadeIstendi = false;
|
||||
const araclar: string[] = [];
|
||||
const parcalar: string[] = [];
|
||||
const parcaAdi = new Map<string, string>();
|
||||
istekler.length = 0;
|
||||
const okuyucu = danismanAkisi({
|
||||
system: "test",
|
||||
mesajlar: [{ role: "user", content: "test" }],
|
||||
aracBaglami: { userId, sira: 100000, tur: "say", profil: {} },
|
||||
rehberler,
|
||||
islem: s.islem ? { ad: s.islem.ad, girdi: {}, calistir: async () => s.islem!.cikti, anlatim: s.islem.anlatim } : undefined,
|
||||
onRevizeBeyani: async () => {
|
||||
iadeIstendi = true;
|
||||
return true;
|
||||
},
|
||||
onTamam: async (m) => {
|
||||
tam = m;
|
||||
},
|
||||
@@ -112,15 +188,28 @@ async function main() {
|
||||
for (;;) {
|
||||
const { done, value } = await okuyucu.read();
|
||||
if (done) break;
|
||||
const v = value as { type: string; output?: unknown };
|
||||
const v = value as { type: string; output?: unknown; toolCallId?: string; toolName?: string };
|
||||
if (v.type === "tool-input-available" && v.toolCallId) parcaAdi.set(v.toolCallId, v.toolName ?? "?");
|
||||
if (v.type === "tool-output-available") araclar.push(JSON.stringify(v.output).slice(0, 70));
|
||||
if ((v.type === "tool-output-available" || v.type === "tool-output-error") && v.toolCallId?.startsWith("kod-")) parcalar.push(`${parcaAdi.get(v.toolCallId)}:${v.type.slice("tool-".length)}`);
|
||||
}
|
||||
const d = denetim as Beklenen | null;
|
||||
const kayit = String(tam);
|
||||
const metinTuttu = (s.metinde?.var ?? []).every((m) => kayit.includes(m)) && (s.metinde?.yok ?? []).every((m) => !kayit.includes(m));
|
||||
const tuttu = d != null && d.vaatNotu === s.beklenen.vaatNotu && d.kayitNotu === s.beklenen.kayitNotu && d.reddedilenArac === s.beklenen.reddedilenArac && d.baglantiDuzeltme === s.beklenen.baglantiDuzeltme && metinTuttu;
|
||||
const ilk = istekler[0];
|
||||
const istekTuttu =
|
||||
!s.istek ||
|
||||
(ilk != null &&
|
||||
(s.istek.sistemde ?? []).every((m) => ilk.system.includes(m)) &&
|
||||
(s.istek.sistemdeYok ?? []).every((m) => !ilk.system.includes(m)) &&
|
||||
(s.istek.aracYok ?? []).every((a) => !ilk.araclar.includes(a)) &&
|
||||
(s.istek.toolChoice === undefined || ilk.toolChoice === s.istek.toolChoice));
|
||||
const parcaTuttu = !s.parcalar || JSON.stringify(parcalar) === JSON.stringify(s.parcalar);
|
||||
const iadeTuttu = s.iadeIstendi === undefined || iadeIstendi === s.iadeIstendi;
|
||||
const cagriTuttu = s.modelCagrisi === undefined || istekler.length === s.modelCagrisi;
|
||||
const tuttu = d != null && d.vaatNotu === s.beklenen.vaatNotu && d.kayitNotu === s.beklenen.kayitNotu && d.reddedilenArac === s.beklenen.reddedilenArac && d.baglantiDuzeltme === s.beklenen.baglantiDuzeltme && d.revizeNotu === s.beklenen.revizeNotu && metinTuttu && istekTuttu && parcaTuttu && iadeTuttu && cagriTuttu;
|
||||
if (!tuttu) hata++;
|
||||
info(`\n${tuttu ? "geçti" : "KALDI"} [${s.ad}]\n denetim: ${JSON.stringify(denetim)}\n araç çıktıları: ${araclar.length} → ${araclar.join(" | ")}\n son metin: ${String(tam).replace(/\n+/g, " ⏎ ")}`);
|
||||
info(`\n${tuttu ? "geçti" : "KALDI"} [${s.ad}]\n denetim: ${JSON.stringify(denetim)}${s.istek ? `\n istek: ${istekTuttu ? "ok" : "BEKLENENDEN FARKLI"} (araçlar ${ilk?.araclar.length ?? "?"}, tool_choice ${JSON.stringify(ilk?.toolChoice)})` : ""}${s.parcalar ? `\n koddan parçalar: ${parcalar.join(", ") || "—"}${parcaTuttu ? "" : " (BEKLENENDEN FARKLI)"}` : ""}${s.iadeIstendi !== undefined ? `\n iade istendi: ${iadeIstendi}` : ""}${s.modelCagrisi !== undefined ? `\n model çağrısı: ${istekler.length}` : ""}\n araç çıktıları: ${araclar.length} → ${araclar.join(" | ")}\n son metin: ${String(tam).replace(/\n+/g, " ⏎ ")}`);
|
||||
}
|
||||
sqlite.close();
|
||||
sunucu.close();
|
||||
|
||||
Reference in New Issue
Block a user