Merge branch 'worktree-agent-a3eecaccfe3c3822d' into v2
This commit is contained in:
@@ -7,16 +7,24 @@
|
||||
* araç çağrılarını aynen döner. Gerçek danismanAkisi + gerçek araçlar koşar
|
||||
* (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ı. Model davranışını ÖLÇMEZ; onu eval:sohbet ölçer.
|
||||
* 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.
|
||||
* 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 };
|
||||
type Senaryo = { ad: string; beklenen: Beklenen; adimlar: ({ metin: string } | { araclar: { name: string; arguments: string }[] })[] };
|
||||
const B = (vaatNotu: boolean, kayitNotu: boolean, reddedilenArac = 0): Beklenen => ({ vaatNotu, kayitNotu, reddedilenArac });
|
||||
type Beklenen = { vaatNotu: boolean; kayitNotu: boolean; reddedilenArac: number; baglantiDuzeltme: number };
|
||||
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[] };
|
||||
};
|
||||
const B = (vaatNotu: boolean, kayitNotu: boolean, reddedilenArac = 0, baglantiDuzeltme = 0): Beklenen => ({ vaatNotu, kayitNotu, reddedilenArac, baglantiDuzeltme });
|
||||
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." }] },
|
||||
@@ -25,6 +33,19 @@ const SENARYOLAR: Senaryo[] = [
|
||||
{ ad: "vaat", beklenen: B(true, false), adimlar: [{ metin: "Bu programa yerleşme olasılığın düşük ama yine de yazabilirsin." }] },
|
||||
{ ad: "temiz", beklenen: B(false, false), adimlar: [{ metin: "Bu program son yerleştirmede 178.278. sırada kapandı; etiketi Riskli. Garanti verilemez." }] },
|
||||
{ ad: "kota", beklenen: B(false, false, 2), adimlar: [{ araclar: Array.from({ length: 6 }, (_, i) => ({ name: "il_ozeti", arguments: JSON.stringify({ il: ["İzmir", "Ankara", "Bursa", "Konya", "Adana", "Samsun"][i] }) })) }, { metin: "İllere baktım." }] },
|
||||
// Model araç çağırmadan ezberden iki yanlış rehber adresi yazar; ikisi de akışta gerçek slug'a döner
|
||||
{
|
||||
ad: "rehber-uydurma",
|
||||
beklenen: B(false, false, 0, 2),
|
||||
adimlar: [{ metin: "Şu yazılara bak: [OBP](/rehber/obp-nedir) ve [Yatay geçiş](/rehber/yatay-gecis-rehberi). İkisi de sana göre." }],
|
||||
metinde: { var: ["/rehber/obp-nedir-nasil-hesaplanir", "/rehber/yatay-gecis-cap-yandal"], yok: ["/rehber/obp-nedir)", "/rehber/yatay-gecis-rehberi"] },
|
||||
},
|
||||
{
|
||||
ad: "rehber-gercek",
|
||||
beklenen: B(false, false, 0, 0),
|
||||
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"] },
|
||||
},
|
||||
];
|
||||
let aktif: Senaryo = SENARYOLAR[0];
|
||||
let adimNo = 0;
|
||||
@@ -61,6 +82,9 @@ async function main() {
|
||||
const Database = (await import("better-sqlite3")).default;
|
||||
const sqlite = new Database(db);
|
||||
const { danismanAkisi } = await import("@/lib/ai/sohbet-akisi");
|
||||
// Gerçek content/rehber listesi: bağlantı doğrulama tamponu aktif
|
||||
const { getRehberAramaKayitlari } = await import("@/lib/rehber");
|
||||
const rehberler = getRehberAramaKayitlari();
|
||||
const info = console.info;
|
||||
console.info = () => {};
|
||||
let hata = 0;
|
||||
@@ -76,6 +100,7 @@ async function main() {
|
||||
system: "test",
|
||||
mesajlar: [{ role: "user", content: "test" }],
|
||||
aracBaglami: { userId, sira: 100000, tur: "say", profil: {} },
|
||||
rehberler,
|
||||
onTamam: async (m) => {
|
||||
tam = m;
|
||||
},
|
||||
@@ -91,7 +116,9 @@ async function main() {
|
||||
if (v.type === "tool-output-available") araclar.push(JSON.stringify(v.output).slice(0, 70));
|
||||
}
|
||||
const d = denetim as Beklenen | null;
|
||||
const tuttu = d != null && d.vaatNotu === s.beklenen.vaatNotu && d.kayitNotu === s.beklenen.kayitNotu && d.reddedilenArac === s.beklenen.reddedilenArac;
|
||||
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;
|
||||
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, " ⏎ ")}`);
|
||||
}
|
||||
|
||||
127
scripts/eval/baglanti-denetim.ts
Normal file
127
scripts/eval/baglanti-denetim.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Rehber bağlantısı doğrulamasının MEKANİK testi — parasız, anahtarsız, DB'siz.
|
||||
*
|
||||
* pnpm tsx --conditions=react-server scripts/eval/baglanti-denetim.ts
|
||||
*
|
||||
* lib/ai/baglanti-denetimi.ts gerçek content/rehber listesiyle sınanır:
|
||||
* kayıtlı slug dokunulmaz, yakın slug yeniden yazılır (başlık kayıttan),
|
||||
* uydurma slug düz metne iner; akış tamponunun parça parça çıktısı tek
|
||||
* seferlik sonuçla birebir aynı olmalı. Beklenti tutmazsa çıkış kodu 1.
|
||||
*/
|
||||
import { BaglantiTamponu, rehberBaglantilariniDuzelt, type RehberKaydi } from "@/lib/ai/baglanti-denetimi";
|
||||
import { getRehberAramaKayitlari } from "@/lib/rehber";
|
||||
|
||||
type Senaryo = {
|
||||
ad: string;
|
||||
metin: string;
|
||||
/** Beklenen tam çıktı (string) ya da çıktıyı denetleyen fonksiyon. */
|
||||
bekle: string | ((cikti: string) => boolean);
|
||||
duzeltme: number;
|
||||
};
|
||||
|
||||
function senaryolar(rehberler: readonly RehberKaydi[]): Senaryo[] {
|
||||
const kayit = (slug: string) => {
|
||||
const r = rehberler.find((x) => x.slug === slug);
|
||||
if (!r) throw new Error(`content/rehber içinde yok: ${slug}`);
|
||||
return r;
|
||||
};
|
||||
const yatay = kayit("yatay-gecis-cap-yandal");
|
||||
const obp = kayit("obp-nedir-nasil-hesaplanir");
|
||||
const gercek = kayit("devlet-mi-vakif-mi");
|
||||
return [
|
||||
{
|
||||
ad: "gercek-slug-aynen",
|
||||
metin: `Şu yazıya bak: [${gercek.baslik}](/rehber/${gercek.slug}) — orada ayrıntı var.`,
|
||||
bekle: `Şu yazıya bak: [${gercek.baslik}](/rehber/${gercek.slug}) — orada ayrıntı var.`,
|
||||
duzeltme: 0,
|
||||
},
|
||||
{
|
||||
ad: "yakin-eslesme-markdown",
|
||||
metin: "Detay için [Yatay geçiş](/rehber/yatay-gecis-rehberi) yazısına bak.",
|
||||
bekle: `Detay için [${yatay.baslik}](/rehber/${yatay.slug}) yazısına bak.`,
|
||||
duzeltme: 1,
|
||||
},
|
||||
{
|
||||
ad: "uydurma-duz-metin",
|
||||
metin: "Bunu [Tamamen uydurma yazı](/rehber/tamamen-uydurma-seyler) anlatıyor.",
|
||||
bekle: "Bunu Tamamen uydurma yazı anlatıyor.",
|
||||
duzeltme: 1,
|
||||
},
|
||||
{
|
||||
ad: "ters-tirnak-ve-ciplak",
|
||||
metin: "Bkz. `/rehber/obp-nedir` ve ayrıca /rehber/yatay-gecis-rehberi. Uydurma: /rehber/tamamen-uydurma-seyler bitti.",
|
||||
bekle: `Bkz. [${obp.baslik}](/rehber/${obp.slug}) ve ayrıca [${yatay.baslik}](/rehber/${yatay.slug}). Uydurma: bitti.`,
|
||||
duzeltme: 3,
|
||||
},
|
||||
{
|
||||
ad: "koseli-parantez-kayipsiz",
|
||||
metin: "[2024] verisi ve [not] şöyle; `kod` de olduğu gibi kalır.",
|
||||
bekle: "[2024] verisi ve [not] şöyle; `kod` de olduğu gibi kalır.",
|
||||
duzeltme: 0,
|
||||
},
|
||||
{
|
||||
ad: "baska-yol-dokunulmaz",
|
||||
metin: "[Ankara Üniversitesi](/universite/ankara-universitesi) ve [Tıp](/bolum/tip) kalır.",
|
||||
bekle: "[Ankara Üniversitesi](/universite/ankara-universitesi) ve [Tıp](/bolum/tip) kalır.",
|
||||
duzeltme: 0,
|
||||
},
|
||||
{
|
||||
ad: "site-koku-ile-markdown",
|
||||
metin: "Oku: [OBP](https://kolaytercih.com/rehber/obp-nedir).",
|
||||
bekle: `Oku: [${obp.baslik}](/rehber/${obp.slug}).`,
|
||||
duzeltme: 1,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** Metni verilen parça sayısına rastgele olmayan, sabit noktalardan böler. */
|
||||
function parcala(metin: string, adet: number): string[] {
|
||||
const boy = Math.max(1, Math.ceil(metin.length / adet));
|
||||
const parcalar: string[] = [];
|
||||
for (let i = 0; i < metin.length; i += boy) parcalar.push(metin.slice(i, i + boy));
|
||||
return parcalar;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const rehberler = getRehberAramaKayitlari();
|
||||
if (rehberler.length < 10) throw new Error(`rehber listesi şüpheli derecede kısa: ${rehberler.length}`);
|
||||
let hata = 0;
|
||||
const kontrol = (ad: string, tuttu: boolean, detay: string) => {
|
||||
if (!tuttu) hata++;
|
||||
console.log(`${tuttu ? "geçti" : "KALDI"} [${ad}]${tuttu ? "" : `\n ${detay}`}`);
|
||||
};
|
||||
for (const s of senaryolar(rehberler)) {
|
||||
const tek = rehberBaglantilariniDuzelt(s.metin, rehberler);
|
||||
const beklendi = typeof s.bekle === "string" ? tek.metin === s.bekle : s.bekle(tek.metin);
|
||||
kontrol(s.ad, beklendi && tek.duzeltmeler.length === s.duzeltme, `çıktı: ${tek.metin}\n düzeltme: ${JSON.stringify(tek.duzeltmeler)}`);
|
||||
// Tampon: metin 1, 3, 4, 7 ve karakter karakter parçalanınca da aynı sonuç
|
||||
for (const adet of [1, 3, 4, 7, s.metin.length]) {
|
||||
const tampon = new BaglantiTamponu(rehberler);
|
||||
let akis = "";
|
||||
for (const p of parcala(s.metin, adet)) akis += tampon.besle(p);
|
||||
akis += tampon.bitir();
|
||||
kontrol(
|
||||
`${s.ad} · tampon ${adet} parça`,
|
||||
akis === tek.metin && tampon.duzeltmeler.length === tek.duzeltmeler.length,
|
||||
`tampon: ${akis}\n tek seferlik: ${tek.metin}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Tampon bir bağlantı adayını kapanmadan bırakmamalı (yanlış hap ekrana gitmesin)
|
||||
{
|
||||
const tampon = new BaglantiTamponu(rehberler);
|
||||
const ilk = tampon.besle("Bak: [Yatay geçiş](/rehber/yatay-gecis");
|
||||
const son = tampon.besle("-rehberi) iyi.") + tampon.bitir();
|
||||
kontrol("tampon-adayi-tutuyor", ilk === "Bak: " && !ilk.includes("/rehber/") && son.includes("/rehber/yatay-gecis-cap-yandal"), `ilk: ${ilk}\n son: ${son}`);
|
||||
}
|
||||
{
|
||||
const tampon = new BaglantiTamponu(rehberler);
|
||||
const ilk = tampon.besle("Ayrıntı /reh");
|
||||
const son = tampon.besle("ber/obp-nedir yazısında.") + tampon.bitir();
|
||||
kontrol("tampon-yarim-onek", ilk === "Ayrıntı " && son.includes("/rehber/obp-nedir-nasil-hesaplanir"), `ilk: ${ilk}\n son: ${son}`);
|
||||
}
|
||||
const toplam = senaryolar(rehberler).length * 6 + 2;
|
||||
console.log(`\n${toplam - hata}/${toplam} senaryo geçti (mekanik test; gerçek model davranışını ölçmez)`);
|
||||
process.exit(hata ? 1 : 0);
|
||||
}
|
||||
main();
|
||||
Reference in New Issue
Block a user