Model paketsiz cevapta "[[/paket](/paket)](/paket)" yazıyor, ekranda köşeli parantezler ve "Paket (Paket)" kartı çıkıyordu (QA B3). Rehber eşlemesinden önce aynı adresi iki kez saran bağlantı her adres için düzleştirilir; akış tamponu `)`'den sonra `](` gelip gelmediğine bakar (bir karakterlik bekleme), parça parça çıktı tek seferlikle aynı kalır. baglanti-denetim'e iki senaryo: 56/56. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
142 lines
6.2 KiB
TypeScript
142 lines
6.2 KiB
TypeScript
/**
|
||
* 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; iç içe `[[x](y)](y)` bağlantısı (her adres)
|
||
* `[x](y)` olur; 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,
|
||
},
|
||
{
|
||
// QA 8 Eki 2026: paketsiz cevapta modelin ürettiği biçim
|
||
ad: "ic-ice-paket-baglantisi",
|
||
metin: "Tercih Dönemi Paketi (299 TL, [[/paket](/paket)](/paket)) ile açılır; [[a](/x)](/y) kalır.",
|
||
bekle: "Tercih Dönemi Paketi (299 TL, [/paket](/paket)) ile açılır; [[a](/x)](/y) kalır.",
|
||
duzeltme: 0,
|
||
},
|
||
{
|
||
ad: "ic-ice-rehber-baglantisi-sonra-eslesir",
|
||
metin: "Oku: [[Yatay geçiş](/rehber/yatay-gecis-rehberi)](/rehber/yatay-gecis-rehberi) ve [[[OBP](/rehber/obp-nedir)](/rehber/obp-nedir)](/rehber/obp-nedir).",
|
||
bekle: `Oku: [${yatay.baslik}](/rehber/${yatay.slug}) ve [${obp.baslik}](/rehber/${obp.slug}).`,
|
||
duzeltme: 2,
|
||
},
|
||
];
|
||
}
|
||
|
||
/** 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();
|