Files
kolaytercih/scripts/eval/baglanti-denetim.ts
bilalgursen 15f4c4f4e1 feat(sohbet): rehber bağlantıları akışta gerçek slug'larla doğrulanıyor
Model araç çağırmadan ezberden /rehber/<slug> yazabiliyordu; istemci her
adresi "Rehber yazısı" hapı yapıp 404'e gönderiyordu. Yeni saf modül
lib/ai/baglanti-denetimi.ts: kayıtlı slug dokunulmaz, yakın slug (Dice ≥ 0,5
ve açık ara önde) kayıttaki başlıkla yeniden yazılır, uydurma slug düz metne
iner / çıplak adres silinir. BaglantiTamponu akış sırasında çalışır (cevap
bittikten sonra ekrandaki hap geri alınamaz); parça parça çıktı tek seferlik
sonuçla birebir aynı. danismanAkisi `rehberler` seçeneği alır (verilmezse
pasif); route gerçek content/rehber listesini verir; CiktiDenetimi'ne
baglantiDuzeltme eklendi. Prompt BAĞLANTI maddesi rehber_ara'yı teşvik eder.

Aynı dosyalardaki revizyon-hak metinleri (prompt revizyonBolumu, araç
açıklaması/HAK dalı, route'taki kalanHak/kalanRevizyon) de bu commit'te:
sınırın kaldırılması bir sonraki commit'te tamamlanır.

Test: scripts/eval/baglanti-denetim.ts (44/44) ve akis-denetim.ts'e
rehber-uydurma / rehber-gercek senaryoları (9/9).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 23:16:34 +03:00

128 lines
5.5 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; 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();