Danışman araçları, TÜİK mezun istihdamı ve Jev çıktı denetimi
- Danışman: program_netleri, program_karsilastir ve bolum_istihdam araçları; program_ara'ya devlet/vakıf, öğretim türü ve sıralama parametreleri; program_detay'a trend cümlesi; bozuk araç argümanı modele hata döner. - TÜİK Yükseköğretim İstihdam Göstergeleri 2025: bölüm bazında istihdam oranı, ilk işi bulma süresi ve kazanç grubu (src/lib/bolum-istihdam.ts, üretici scripts/tuik-istihdam-uret.py, kaynak Excel'ler data/kaynak/). - Bölüm sayfasında "Mezun istihdamı" ayrıntı satırı ve alt sayfası; AkademikAyrinti başlık/ikon alır. - Jev (TypeSafe AI) ile danışman cevabında garanti/olasılık dili denetimi; ihlalde cevabın sonuna düzeltme notu. Anahtar yoksa denetim atlanır. - /gizlilik: TypeSafe AI (ABD) sağlayıcı satırı. KARARLAR.md: 5 Ekim kaydı. - Üç katalog dosyasındaki bg-white → bg-card değişiklikleri de bu commit'te. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
dilimAraligi,
|
||||
EFEKTIF_SIRA,
|
||||
getDb,
|
||||
netlerGetir,
|
||||
programGetir,
|
||||
programSatiri,
|
||||
SELECT_COLS,
|
||||
@@ -29,10 +30,18 @@ import {
|
||||
import { getAllUniversiteler, getUniversiteBySlug } from "@/lib/katalog";
|
||||
import { kadinYuzdesi, ogrenciBasinaUye, YOK_ISTATISTIK_DONEM } from "@/lib/yok-istatistik";
|
||||
import { URAP_DONEM, URAP_TOPLAM } from "@/lib/urap-siralama";
|
||||
import { turkishSlugify } from "@/lib/slug";
|
||||
import { bolumSayfaSlug, turkishSlugify } from "@/lib/slug";
|
||||
import {
|
||||
BOLUM_ISTIHDAM,
|
||||
BOLUM_ISTIHDAM_KAYNAK,
|
||||
ISTIHDAM_ORTALAMA,
|
||||
type IstihdamDuzeyi,
|
||||
} from "@/lib/bolum-istihdam";
|
||||
import { DILIM_ETIKET } from "@/lib/risk";
|
||||
import { trendOzetiMetni } from "@/lib/trend";
|
||||
import { TUR_SAYFALARI } from "@/lib/unitur";
|
||||
import { YILLAR } from "@/lib/veri-yillari";
|
||||
import { PUAN_TURLERI, type Program, type PuanTuruKey } from "@/types/yokatlas";
|
||||
import { PUAN_TURLERI, type Program, type ProgramNetSatiri, type PuanTuruKey } from "@/types/yokatlas";
|
||||
import {
|
||||
PROFIL_ALAN_ACIKLAMA,
|
||||
PROFIL_ALANLARI,
|
||||
@@ -55,6 +64,8 @@ export type AracBaglami = {
|
||||
export type AracTanimi = OpenAI.Chat.Completions.ChatCompletionTool;
|
||||
|
||||
const ARAMA_LIMIT = 8;
|
||||
const KARSILASTIRMA_UST = 4;
|
||||
const NET_YIL_SAYISI = 2;
|
||||
|
||||
export const ARACLAR: AracTanimi[] = [
|
||||
{
|
||||
@@ -62,14 +73,20 @@ export const ARACLAR: AracTanimi[] = [
|
||||
function: {
|
||||
name: "program_ara",
|
||||
description:
|
||||
"Adayın puan türündeki programları veriden arar (YÖK Atlas, son yerleştirme). Bölüm adı, üniversite, il, öğretim dili, burs ve yıllık ücret üst sınırına göre süzer; adayın sırasına en yakın programları döner. Sayı uydurmak yerine bunu çağır.",
|
||||
"Adayın puan türündeki programları veriden arar (YÖK Atlas, son yerleştirme). Bölüm adı, üniversite, il, devlet/vakıf, öğretim dili ve türü, burs ve yıllık ücret üst sınırına göre süzer; varsayılan olarak adayın sırasına en yakın programları döner. Sayı uydurmak yerine bunu çağır.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
arama: { type: "string", description: "Bölüm adı parçası, ör. 'bilgisayar mühendisliği', 'hukuk'" },
|
||||
universite: { type: "string", description: "Üniversite adı parçası" },
|
||||
il: { type: "string", description: "İl adı, ör. 'İzmir'" },
|
||||
tur: { type: "string", enum: ["devlet", "vakif"], description: "Yalnız devlet ya da yalnız vakıf üniversiteleri" },
|
||||
dil: { type: "string", enum: ["Türkçe", "İngilizce"], description: "Öğretim dili" },
|
||||
ogretim_turu: {
|
||||
type: "string",
|
||||
enum: ["Örgün Öğretim", "Uzaktan Öğretim", "Açık Öğretim"],
|
||||
description: "Öğretim türü",
|
||||
},
|
||||
burs: {
|
||||
type: "string",
|
||||
enum: ["Burslu", "%50 İndirimli", "%25 İndirimli", "Ücretli"],
|
||||
@@ -81,6 +98,12 @@ export const ARACLAR: AracTanimi[] = [
|
||||
enum: ["hayal", "dengeli", "guvenli"],
|
||||
description: "Yalnız bu dilimdekiler: hayal = adayın sırasının üstünde kapananlar, dengeli = sırasının 1,4 katına kadar, guvenli = daha altı",
|
||||
},
|
||||
sirala: {
|
||||
type: "string",
|
||||
enum: ["yakinlik", "taban", "ucret"],
|
||||
description:
|
||||
"yakinlik (varsayılan) = adayın sırasına en yakın; taban = en iyi taban sırası önce ('en iyi/en yüksek' sorularında); ucret = en ucuz önce (devlet ve tam burslu 0 sayılır)",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
@@ -102,6 +125,65 @@ export const ARACLAR: AracTanimi[] = [
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "program_netleri",
|
||||
description:
|
||||
"Programa yerleşen SON kişinin ders bazında netleri (TYT/AYT/YDT), taban puanı ve OBP'si; son iki yıl. 'Kaç net lazım' sorularında bunu çağır, net uydurma.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
program_id: { type: "string", description: "program_ara sonucundaki id (kılavuz kodu)" },
|
||||
},
|
||||
required: ["program_id"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "program_karsilastir",
|
||||
description: `2-${KARSILASTIRMA_UST} programı yan yana getirir: taban sırası, trend, kontenjan, ücret/burs, dil, akreditasyon, kadro, evden uzaklık. 'A mı B mi' sorularında program_detay'ı tek tek çağırmak yerine bunu kullan.`,
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
program_idler: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
minItems: 2,
|
||||
maxItems: KARSILASTIRMA_UST,
|
||||
description: "program_ara sonucundaki id'ler",
|
||||
},
|
||||
},
|
||||
required: ["program_idler"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "bolum_istihdam",
|
||||
description:
|
||||
"Bölüm mezunlarının iş durumu (TÜİK, ülke geneli): kayıtlı istihdam oranı, ilk işi bulma süresi (ay) ve kazanç grubu; ülke ortalamasıyla birlikte. 'Mezun olunca iş bulur muyum', 'hangisinin işi daha iyi' sorularında bunu çağır. Üniversiteye göre veri YOK; aynı bölüm her üniversitede aynı sayıyı verir.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
bolumler: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
minItems: 1,
|
||||
maxItems: KARSILASTIRMA_UST,
|
||||
description: "Bölüm adları, ör. ['Hukuk', 'Psikoloji']",
|
||||
},
|
||||
},
|
||||
required: ["bolumler"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
@@ -195,12 +277,19 @@ type ProgramAraArg = {
|
||||
arama?: string;
|
||||
universite?: string;
|
||||
il?: string;
|
||||
tur?: "devlet" | "vakif";
|
||||
dil?: string;
|
||||
ogretim_turu?: string;
|
||||
burs?: string;
|
||||
ucret_ust?: number;
|
||||
dilim?: "hayal" | "dengeli" | "guvenli";
|
||||
sirala?: "yakinlik" | "taban" | "ucret";
|
||||
};
|
||||
|
||||
/** Ücret sıralaması: devlet (ücret yok) ve tam burslu 0 sayılır. */
|
||||
const UCRET_SIRA_SQL = `COALESCE((SELECT CASE WHEN o.burs_orani = 'Burslu' THEN 0 ELSE o.ucret END
|
||||
FROM program_ozellik o WHERE o.id = programs.id), 0)`;
|
||||
|
||||
function programAra(b: AracBaglami, a: ProgramAraArg, profilUygula = true): ProgramAraSonuc {
|
||||
const db = getDb();
|
||||
const profil: DanismanProfili = profilUygula ? b.profil : {};
|
||||
@@ -237,6 +326,12 @@ function programAra(b: AracBaglami, a: ProgramAraArg, profilUygula = true): Prog
|
||||
uygulanan.push(`profil: ${profil.evIl} iline en fazla ${profil.maxKm} km (${iller.length} il)`);
|
||||
}
|
||||
}
|
||||
if (a.tur && a.tur in TUR_SAYFALARI) {
|
||||
const uniturler = TUR_SAYFALARI[a.tur].uniturler;
|
||||
kosullar.push(`unitur IN (${uniturler.map(() => "?").join(",")})`);
|
||||
args.push(...uniturler);
|
||||
uygulanan.push(`tür = ${a.tur === "devlet" ? "devlet" : "vakıf"}`);
|
||||
}
|
||||
// Özellik süzgeçleri alt sorguda (program_ozellik.id çakışmasın)
|
||||
const ozKosul: string[] = [];
|
||||
const ozArgs: (string | number)[] = [];
|
||||
@@ -260,7 +355,11 @@ function programAra(b: AracBaglami, a: ProgramAraArg, profilUygula = true): Prog
|
||||
ozArgs.push(ucretUst);
|
||||
uygulanan.push(`yıllık ücret ≤ ${ucretUst.toLocaleString("tr-TR")} TL${a.ucret_ust ? "" : " (profil)"}`);
|
||||
}
|
||||
if (profil.ogretimTuru === "yalniz-orgun") {
|
||||
if (a.ogretim_turu) {
|
||||
ozKosul.push("ogrenim_turu = ?");
|
||||
ozArgs.push(a.ogretim_turu);
|
||||
uygulanan.push(`öğretim türü = ${a.ogretim_turu}`);
|
||||
} else if (profil.ogretimTuru === "yalniz-orgun") {
|
||||
ozKosul.push("(ogrenim_turu IS NULL OR ogrenim_turu = 'Örgün Öğretim')");
|
||||
uygulanan.push("profil: yalnız örgün");
|
||||
}
|
||||
@@ -277,10 +376,20 @@ function programAra(b: AracBaglami, a: ProgramAraArg, profilUygula = true): Prog
|
||||
uygulanan.push(`dilim = ${DILIM_ETIKET[anahtar]}`);
|
||||
}
|
||||
|
||||
// İkincil anahtar hep sıraya yakınlık: eşit ücretlilerde (ör. tüm devletler) anlamlı kalsın
|
||||
const yakinlik = `ABS(${EFEKTIF_SIRA} - ?) ASC`;
|
||||
const siralama =
|
||||
a.sirala === "taban"
|
||||
? `${EFEKTIF_SIRA} ASC, ${yakinlik}`
|
||||
: a.sirala === "ucret"
|
||||
? `${UCRET_SIRA_SQL} ASC, ${yakinlik}`
|
||||
: yakinlik;
|
||||
if (a.sirala === "taban") uygulanan.push("sıralama: en iyi taban sırası önce");
|
||||
if (a.sirala === "ucret") uygulanan.push("sıralama: en ucuz önce");
|
||||
const satirlar = db
|
||||
.prepare(
|
||||
`SELECT ${SELECT_COLS} FROM programs WHERE ${kosullar.join(" AND ")}
|
||||
ORDER BY ABS(${EFEKTIF_SIRA} - ?) ASC LIMIT ?`,
|
||||
ORDER BY ${siralama} LIMIT ?`,
|
||||
)
|
||||
.all(...args, b.sira, ARAMA_LIMIT) as ProgramHamSatiri[];
|
||||
const programlar = satirlar.map(programSatiri);
|
||||
@@ -328,6 +437,7 @@ function programDetay(b: AracBaglami, a: { program_id?: string }) {
|
||||
fakulte: p.fakulte,
|
||||
sure: p.sure,
|
||||
tabanSiraYillara: Object.fromEntries(YILLAR.map((y, i) => [y, p.siraGecmisi[i]])),
|
||||
trend: trendOzetiMetni(p.siraGecmisi),
|
||||
puanSon: p.puanSon,
|
||||
yerlesenSon: p.yerlesenSon,
|
||||
ucretYillik: o?.ucret ?? null,
|
||||
@@ -345,6 +455,100 @@ function programDetay(b: AracBaglami, a: { program_id?: string }) {
|
||||
};
|
||||
}
|
||||
|
||||
const NET_ETIKET: Partial<Record<keyof ProgramNetSatiri, string>> = {
|
||||
tytTrkNet: "TYT Türkçe",
|
||||
tytSosNet: "TYT Sosyal",
|
||||
tytMatNet: "TYT Matematik",
|
||||
tytFenNet: "TYT Fen",
|
||||
aytMatNet: "AYT Matematik",
|
||||
aytFizNet: "AYT Fizik",
|
||||
aytKimNet: "AYT Kimya",
|
||||
aytBioNet: "AYT Biyoloji",
|
||||
aytTdeNet: "AYT Edebiyat",
|
||||
aytTrh1Net: "AYT Tarih-1",
|
||||
aytCog1Net: "AYT Coğrafya-1",
|
||||
aytTrh2Net: "AYT Tarih-2",
|
||||
aytCog2Net: "AYT Coğrafya-2",
|
||||
aytDinNet: "AYT Din Kültürü",
|
||||
aytFelNet: "AYT Felsefe Grubu",
|
||||
ydtYdilNet: "YDT Yabancı Dil",
|
||||
};
|
||||
|
||||
function programNetleri(_b: AracBaglami, a: { program_id?: string }) {
|
||||
const id = String(a.program_id ?? "").trim();
|
||||
const p = id ? programGetir(id) : null;
|
||||
if (!p) return { hata: "Program bulunamadı; önce program_ara ile id al." };
|
||||
const satirlar = netlerGetir(id).slice(0, NET_YIL_SAYISI);
|
||||
if (!satirlar.length) {
|
||||
return { program: p.isim, universite: p.universite, hata: "Bu program için net verisi yok; uydurma." };
|
||||
}
|
||||
return {
|
||||
program: p.isim,
|
||||
universite: p.universite,
|
||||
yillar: satirlar.map((s) => ({
|
||||
yil: s.yil,
|
||||
tabanPuan: s.tabanPuan,
|
||||
obp: s.obp,
|
||||
netler: Object.fromEntries(
|
||||
(Object.keys(NET_ETIKET) as (keyof ProgramNetSatiri)[])
|
||||
.filter((k) => s[k] != null)
|
||||
.map((k) => [NET_ETIKET[k], s[k]]),
|
||||
),
|
||||
})),
|
||||
not: "Netler programa yerleşen SON kişinin netleridir (YÖK Atlas); aynı puana farklı net dağılımıyla da ulaşılır, OBP sonucu değiştirir. Hedef değil, yaklaşık düzey olarak aktar.",
|
||||
};
|
||||
}
|
||||
|
||||
function programKarsilastir(b: AracBaglami, a: { program_idler?: unknown }) {
|
||||
const idler = Array.isArray(a.program_idler)
|
||||
? Array.from(new Set(a.program_idler.map((x) => String(x).trim()).filter(Boolean)))
|
||||
: [];
|
||||
if (idler.length < 2) return { hata: "En az 2 program id'si gerekli; önce program_ara ile id al." };
|
||||
return {
|
||||
programlar: idler.slice(0, KARSILASTIRMA_UST).map((id) => {
|
||||
const d = programDetay(b, { program_id: id });
|
||||
if ("hata" in d) return { id, hata: "Program bulunamadı." };
|
||||
// Koşul metinleri uzun; karşılaştırmada yalnız sayısı, ayrıntı program_detay'da
|
||||
const { kosullar, ...ozet } = d;
|
||||
return { ...ozet, kosulSayisi: kosullar.length };
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function bolumIstihdam(b: AracBaglami, a: { bolumler?: unknown }) {
|
||||
const adlar = Array.isArray(a.bolumler)
|
||||
? a.bolumler.map((x) => String(x).trim()).filter(Boolean).slice(0, KARSILASTIRMA_UST)
|
||||
: [];
|
||||
if (!adlar.length) return { hata: "En az bir bölüm adı gerekli." };
|
||||
const duzey: IstihdamDuzeyi = b.tur === "tyt" ? "onlisans" : "lisans";
|
||||
const tablo = BOLUM_ISTIHDAM[duzey];
|
||||
const anahtarlar = Object.keys(tablo);
|
||||
return {
|
||||
duzey: duzey === "lisans" ? "lisans mezunları" : "ön lisans mezunları",
|
||||
ulkeOrtalamasi: ISTIHDAM_ORTALAMA[duzey],
|
||||
bolumler: adlar.map((ad) => {
|
||||
const slug = bolumSayfaSlug(ad);
|
||||
// Birebir > başı tutan > içinde geçen ("bilgisayar müh" → bilgisayar-muhendisligi)
|
||||
const anahtar =
|
||||
(slug in tablo ? slug : undefined) ??
|
||||
anahtarlar.find((k) => k.startsWith(slug)) ??
|
||||
anahtarlar.find((k) => k.includes(slug));
|
||||
if (!anahtar) {
|
||||
return { aranan: ad, hata: "Bu bölüm TÜİK tablosunda yok (yeni bölüm ya da mezun sayısı az); istihdam verisi uydurma." };
|
||||
}
|
||||
const v = tablo[anahtar];
|
||||
return {
|
||||
aranan: ad,
|
||||
bolumSayfasi: `/bolum/${anahtar}`,
|
||||
kayitliIstihdamYuzde: v.istihdam,
|
||||
ilkIsBulmaAy: v.isBulmaAy,
|
||||
kazancGrubu: v.kazanc,
|
||||
};
|
||||
}),
|
||||
not: `Kaynak: ${BOLUM_ISTIHDAM_KAYNAK}. Ülke geneli, tüm üniversitelerin mezunları birlikte; üniversiteye göre değişir ama o veri yok. Kayıtlı istihdam = SGK kaydıyla çalışma oranı. Kazanç TL değil, bölümler arası göreli grup. Bu sayılar geçmiş mezunları anlatır; yerleşme ya da iş bulma garantisi değildir, cevabında kaynağı söyle.`,
|
||||
};
|
||||
}
|
||||
|
||||
function universiteProfili(_b: AracBaglami, a: { universite?: string }) {
|
||||
const q = String(a.universite ?? "").trim();
|
||||
if (!q) return { hata: "Üniversite adı gerekli." };
|
||||
@@ -443,6 +647,12 @@ export function aracCalistirici(b: AracBaglami) {
|
||||
return programAra(b, a as ProgramAraArg);
|
||||
case "program_detay":
|
||||
return programDetay(b, a);
|
||||
case "program_netleri":
|
||||
return programNetleri(b, a);
|
||||
case "program_karsilastir":
|
||||
return programKarsilastir(b, a);
|
||||
case "bolum_istihdam":
|
||||
return bolumIstihdam(b, a);
|
||||
case "universite_profili":
|
||||
return universiteProfili(b, a);
|
||||
case "profil_guncelle":
|
||||
|
||||
@@ -255,14 +255,19 @@ export async function* sohbetAkisiAraclarla(opts: {
|
||||
for (const [i, c] of liste.entries()) {
|
||||
opts.onArac?.(c.name);
|
||||
let arg: unknown = {};
|
||||
let argBozuk = false;
|
||||
try {
|
||||
arg = c.arguments ? JSON.parse(c.arguments) : {};
|
||||
} catch {
|
||||
arg = {};
|
||||
argBozuk = true;
|
||||
}
|
||||
let sonuc: unknown;
|
||||
try {
|
||||
sonuc = await opts.calistir(c.name, arg);
|
||||
// Bozuk argümanla çalıştırma: boş argüman süzgeçsiz sonuç döndürür,
|
||||
// model onu sorunun cevabı sanar. Hatayı söyle, yeniden denesin.
|
||||
sonuc = argBozuk
|
||||
? { hata: "Araç argümanları geçerli JSON değil; aracı düzgün argümanlarla yeniden çağır." }
|
||||
: await opts.calistir(c.name, arg);
|
||||
} catch (hata) {
|
||||
console.error("[cagri] araç hatası:", c.name, hata);
|
||||
sonuc = { hata: "Araç çalışmadı; veriye ulaşılamadı, uydurma." };
|
||||
|
||||
75
src/lib/ai/jev.ts
Normal file
75
src/lib/ai/jev.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Danışman çıktı denetimi — Jev (TypeSafe "System One" modeli).
|
||||
*
|
||||
* Danışman cevabı bittikten sonra tek soru sorulur: cevap yerleşme garantisi
|
||||
* veriyor ya da yerleşmeyi olasılık/yüzde/ihtimal diliyle tahmin ediyor mu
|
||||
* (sistem prompt'undaki yasak; api/soru/route.ts). Jev metin üretmez, 0-1
|
||||
* arası bir olasılık döner. Yalnız danışmanın CEVABI gider; kullanıcının
|
||||
* mesajı, adı ya da e-postası gitmez. Sağlayıcı ABD'de — /gizlilik "Hizmet
|
||||
* sağlayıcılar" bölümünde yazılı; değişirse aynı commit'te güncellenir.
|
||||
*
|
||||
* Güvenli tarafa düşer: anahtar yoksa, zaman aşımında ya da hata olursa
|
||||
* denetim atlanır (null), cevap olduğu gibi kalır. Saf modül: Next dışından
|
||||
* (scripts/kesif-jev.ts) da import edilir.
|
||||
*/
|
||||
|
||||
const UC = "https://api.typesafe.ai/v1/systemone";
|
||||
const MODEL = "jev-latest";
|
||||
/** 5 Eki 2026 keşfi: temiz cevaplar ≤0,37, ihlaller ≥0,74 (36 örnek). */
|
||||
export const JEV_IHLAL_ESIGI = 0.5;
|
||||
/** Akış sonunda bekletir; ölçülen p95 ~335 ms. */
|
||||
const ZAMAN_ASIMI_MS = 2000;
|
||||
|
||||
export const JEV_IHLAL_SORUSU = {
|
||||
type: "noul",
|
||||
instructions:
|
||||
"Bu metin bir üniversite tercih danışmanının öğrenciye verdiği Türkçe cevaptır. Cevap, öğrenciye bir programa yerleşeceğine dair garanti/söz/kesinlik veriyor mu YA DA yerleşmeyi olasılık, yüzde, ihtimal ya da şans diliyle tahmin ediyor mu? " +
|
||||
"Şunlar ihlal DEĞİLDİR: 'Güvenli / Az riskli / Riskli' etiketleri; 'garanti verilemez' gibi reddeden cümleler; burs indirimi, doluluk, öğrenci oranı ya da mezunların istihdam oranı gibi yerleşme tahmini olmayan yüzdeler; geçmiş taban sırası ve kontenjan sayıları.",
|
||||
} as const;
|
||||
|
||||
type JevYanit = {
|
||||
answers?: Record<string, { noul?: number }>;
|
||||
usage?: { input_tokens?: number };
|
||||
};
|
||||
|
||||
export function jevAnahtari(): string | undefined {
|
||||
return process.env.TYPESAFE_API_KEY?.trim() || undefined;
|
||||
}
|
||||
|
||||
/** Ham çağrı: ihlal olasılığı + girdi token'ı. Hata fırlatır (keşif betiği görsün). */
|
||||
export async function jevIhlalSor(
|
||||
anahtar: string,
|
||||
cevap: string,
|
||||
zamanAsimiMs = 30_000,
|
||||
): Promise<{ p: number; girdiToken: number }> {
|
||||
const r = await fetch(UC, {
|
||||
method: "POST",
|
||||
headers: { Authorization: `Bearer ${anahtar}`, "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ state: cevap, model: MODEL, questions: { ihlal: JEV_IHLAL_SORUSU } }),
|
||||
signal: AbortSignal.timeout(zamanAsimiMs),
|
||||
});
|
||||
if (!r.ok) throw new Error(`Jev ${r.status}: ${(await r.text()).slice(0, 300)}`);
|
||||
const y = (await r.json()) as JevYanit;
|
||||
const p = y.answers?.ihlal?.noul;
|
||||
if (typeof p !== "number") throw new Error(`Jev beklenmeyen yanıt: ${JSON.stringify(y).slice(0, 300)}`);
|
||||
return { p, girdiToken: y.usage?.input_tokens ?? 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* Danışman cevabının ihlal olasılığı; denetim yapılamadıysa null. Asla
|
||||
* fırlatmaz — denetim cevabı düşürmemeli.
|
||||
*/
|
||||
export async function cevapIhlalOlasiligi(cevap: string): Promise<number | null> {
|
||||
const anahtar = jevAnahtari();
|
||||
if (!anahtar || !cevap.trim()) return null;
|
||||
try {
|
||||
return (await jevIhlalSor(anahtar, cevap, ZAMAN_ASIMI_MS)).p;
|
||||
} catch (hata) {
|
||||
console.warn("[jev] denetim atlandı:", hata instanceof Error ? hata.message : hata);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** İhlal yakalanınca cevabın sonuna eklenen düzeltme notu. */
|
||||
export const JEV_DUZELTME_NOTU =
|
||||
"Not: Yukarıdaki cevap bir yerleşme garantisi ya da olasılık tahmini değildir. Taban sıraları her yıl değişir; karar verirken Güvenli / Az riskli / Riskli etiketlerine ve geçmiş taban sıralarına bak.";
|
||||
Reference in New Issue
Block a user