Files
kolaytercih/scripts/eval/baglanti-denetim.ts
bilalgursen f955a76c1e fix(sohbet): iç içe markdown bağlantısı düzleştiriliyor ([[x](y)](y) → [x](y))
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>
2026-10-08 23:59:16 +03:00

142 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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();