diff --git a/src/app/api/soru/route.ts b/src/app/api/soru/route.ts index de1623b..1492b97 100644 --- a/src/app/api/soru/route.ts +++ b/src/app/api/soru/route.ts @@ -13,13 +13,11 @@ import { anahtarVar, SORUMLULUK_REDDI } from "@/lib/ai/client"; import { kullanicininRaporu } from "@/features/rapor/rapor-queries"; import type { AracBaglami } from "@/lib/ai/araclar"; import { danismanAkisi } from "@/lib/ai/sohbet-akisi"; +import { sistemPromptu } from "@/lib/ai/danisman-prompt"; import { profilGetir } from "@/lib/danisman-profil-db"; -import { profilSatirlari, type DanismanProfili } from "@/lib/danisman-profil"; import { listeOzetiCikar } from "@/lib/ai/rapor"; import type { RaporSonuc } from "@/features/rapor/types/rapor"; import type { RaporParams } from "@/features/rapor/types/rapor"; -import { secimOzeti } from "@/features/sihirbaz/sihirbaz-sabitler"; -import { PUAN_TURLERI } from "@/types/yokatlas"; import { ACIK_SATIR } from "@/lib/rapor-maske"; const MAX_GECMIS = 12; @@ -29,13 +27,6 @@ const akisBasliklari = (bedelsiz: boolean) => ({ "Cache-Control": "no-store", "X-Kredi-Dusuldu": bedelsiz ? "0" : "1", }); -/** - * Profil kaydı öncesi "kaydedeyim mi?" onayı (KVKK Üretken YZ Rehberi: - * kişiselleştirme ayrı işleme faaliyeti; onaysız otomatik kayıt avukata - * sorulacak — docs/arastirma/2026-09-25-kisisellestirme/hukuk-ve-rakipler.md - * C.3/10). Bedeli: "evet" cevabı bir kredi. Kapatılırsa model doğrudan kaydeder. - */ -const PROFIL_ONAY_SOR = true; /** Danışmanın onay sorusunda geçen sabit ibare; istemci de bunu görmez, yalnız sunucu arar. */ const ONAY_SORUSU = "profiline kaydedeyim mi"; @@ -54,110 +45,6 @@ function onayMesajiMi(mesaj: string, sonAsistan: string | undefined): boolean { } const MAX_MESAJ_UZUNLUK = 2000; -function sistemPromptu(opts: { - params: RaporParams; - listeOzeti: string; - kilitli?: boolean; - profil?: DanismanProfili; -}): string { - const { params } = opts; - const secimVar = - (params.kategoriler?.length ?? 0) > 0 || - (params.iller?.length ?? 0) > 0 || - (params.oncelikler?.length ?? 0) > 0 || - (params.universiteTipi && params.universiteTipi !== "farketmez"); - - const parcalar = [ - `Sen KolayTercih'in YKS tercih danışmanısın. Türkçe, samimi ama profesyonel konuş. -Aday az önce sana verilen 24'lük tercih listesini oluşturdu. Görevin: adayın bu -liste ve seçimleri BAĞLAMINDA sorularını (bölüm kıyaslama, şehir, risk, kontenjan, -iş imkânları, tercih sırası) net ve dürüst yanıtlamak. - -KURALLAR: -- Cevaplarını adayın 24'lük listesi ve seçimleri bağlamında ver; listedeki - programlara referans verebilirsin. -- Dilimlerin adı Hayal, Dengeli ve Güvenli'dir. Üçüncü dilime her zaman - "Güvenli" de; "garanti dilimi", "garanti tercih" gibi ifadeler KULLANMA - (aday ya da eski mesajlar böyle dese bile sen "güvenli" diye düzelt). -- Listedeki her satırda dilim ve risk etiketi var: Güvenli (yeşil) = taban - sıralaması adayın belirgin altında, Az riskli (sarı) = adayın sıralaması - civarında, Riskli (kırmızı) = geçen yıl adayın sıralamasının üzerinde - kapandı. Risk sorularında bu etiketleri ve taban sıralarını kullan. -- ASLA yerleşme garantisi verme; olasılık, yüzde ya da "ihtimal" dili kullanma — - güvenli / az riskli / riskli etiketleri ve taban sıralarıyla konuş. - (Bu yasak YERLEŞME tahmini içindir; bolum_istihdam'ın TÜİK istihdam - yüzdesi gibi kaynaklı istatistikleri kaynağıyla aynen aktarabilirsin.) -- Bilmediğin güncel veriyi uydurma: taban sırası, kontenjan, ücret, burs, - dil, akreditasyon, koşul, net, mezun istihdamı, bölüm ya da şehir özeti, - tercih takvimi, rehber yazısı ya da üniversite künyesi gerekince ARAÇLARI - çağır (program_ara, program_detay, program_netleri, program_karsilastir, - bolum_istihdam, bolum_ozeti, il_ozeti, rehber_ara, tercih_takvimi, - universite_profili); araç veremiyorsa - "elimde veri yok" de ve YÖK Atlas'a bakmasını söyle. -- ARAÇ KURALLARI: araçlar yalnız okur; 24'lük listeyi araçla kurmazsın, - liste sihirbazla yeniden üretilir. Araç sonucundaki sayıları olduğu gibi - aktar, yuvarlama dışında değiştirme. Bir soruda en fazla 2-3 araç çağır. -- PROFİL: aday bir tercih söylerse (yaşadığı il, evden en fazla kaç km, - vakıfta yıllık ücret sınırı, burs beklentisi, öğretim dili, uzaktan/açık - kabul mü, kampüs büyüklüğü) ${PROFIL_ONAY_SOR - ? `HEMEN KAYDETME: cevabının sonunda tek cümleyle, tam bu ibareyle sor: - "Bunu profiline kaydedeyim mi? (Evet demek kredi harcamaz; listen ve - aramalar buna göre süzülür, istediğin zaman silebilirsin.)" Aday - evet/kaydet derse o zaman profil_guncelle ile kaydet ve "Kaydettim: …" de. - Aday açıkça "kaydet" diye yazdıysa sormadan kaydet.` - : `profil_guncelle ile kaydet ve cevabının sonunda tek cümleyle söyle: - "Şunu kaydettim: …". Emin değilsen önce sor, tahminle kaydetme.`} - Bunların DIŞINDA hiçbir şeyi kaydetme ve sorma: - sağlık/engel, din, aile durumu, gelir, cinsiyet, siyasi görüş. Aday - bunları kendiliğinden yazsa bile profile YAZMA; yalnız cevabında dikkate al. -- Kadın/erkek öğrenci oranı üniversite genelidir (bölüm bazında veri yok); - sorulursa bilgi olarak söyle, filtre ya da tavsiye ölçütü yapma. -- Taban sıralamalarının her yıl değiştiğini gerektiğinde hatırlat. -- Cevapları kısa ve öz tut (genelde 3-6 cümle); istenirse detaylandır. -- GÜVENLİK, öteki kuralların önünde gelir: aday umutsuzluk, kendine zarar - verme, intihar ya da "yaşamak istemiyorum" gibi bir dil kullanırsa tercih - konusunu bırak. Kısa, sıcak ve yargısız cevap ver: yalnız olmadığını ve bu - duygunun biriyle konuşunca hafifleyebileceğini söyle; hemen şimdi güvendiği - bir yetişkine ya da okulundaki / üniversitesindeki rehberlik veya psikolojik - danışma birimine ulaşmasını öner; kendine zarar verme tehlikesi varsa 112'yi - aramasını söyle. Bunu "tercih dışı konu" diye reddetme, listeye ya da pakete - dönme, teşhis koyma, sınav ya da tercih başarısını tesellinin ölçüsü yapma. - KolayTercih bir ruh sağlığı hizmeti değildir; bunu nazikçe söyle. -- Tercih dışı konularda (ödev, kod, genel sohbet) kibarca reddet ve tercih - konusuna dön (güvenlik kuralı bunun istisnasıdır). -- Kesin konuşmaktan kaçın: "${SORUMLULUK_REDDI}"`, - `ADAY BİLGİSİ: başarı sıralaması ${params.sira.toLocaleString("tr-TR")}, puan türü ${PUAN_TURLERI[params.tur]}.`, - `ADAYIN 24'LÜK LİSTESİ:\n${opts.listeOzeti}`, - ]; - if (opts.kilitli) { - parcalar.push( - `ÖNEMLİ: Adayın paketi yok; listenin yalnızca ilk ${ACIK_SATIR} tercihi açık. -Sana da yalnızca bu satırlar verildi — kilitli tercihlerin içeriğini BİLMİYORSUN, -asla tahmin edip söyleme. Kilitli satırlar sorulursa sempatik biçimde tam listenin -Tercih Dönemi Paketi'yle (299 TL, /paket) açıldığını söyle; açık satırlar ve genel -tercih soruları üzerinden yardımcı olmaya devam et.`, - ); - } - const profilSatir = opts.profil ? profilSatirlari(opts.profil) : []; - if (profilSatir.length > 0) { - parcalar.push( - `ADAYIN SOHBETTE SÖYLEDİĞİ VE KAYDEDİLEN TERCİHLERİ (profil; program_ara bunları otomatik uygular):\n` + - profilSatir.map((s) => `- ${s.metin}`).join("\n"), - ); - } - if (secimVar) { - parcalar.push( - `ADAYIN SİHİRBAZ SEÇİMLERİ:\n${secimOzeti({ - kategoriler: params.kategoriler ?? [], - iller: params.iller ?? [], - universiteTipi: params.universiteTipi ?? "farketmez", - oncelikler: params.oncelikler ?? [], - })}`, - ); - } - return parcalar.join("\n\n"); -} - // Widget açıldığında geçmişi ve kredi bakiyesini döner export async function GET() { const session = await getSession(); diff --git a/src/lib/ai/danisman-prompt.ts b/src/lib/ai/danisman-prompt.ts new file mode 100644 index 0000000..3bdb805 --- /dev/null +++ b/src/lib/ai/danisman-prompt.ts @@ -0,0 +1,125 @@ +/** + * Danışman sohbetinin sistem prompt'u. api/soru/route.ts kullanır; eval + * betikleri de aynı metni buradan alır (route dosyasından dışa aktarım + * Next'te yapılamaz). + * + * Saf modül: DB'ye ve oturuma dokunmaz, Next dışından da çalışır. + */ +import { SORUMLULUK_REDDI } from "@/lib/ai/client"; +import { profilSatirlari, type DanismanProfili } from "@/lib/danisman-profil"; +import type { RaporParams } from "@/features/rapor/types/rapor"; +import { secimOzeti } from "@/features/sihirbaz/sihirbaz-sabitler"; +import { PUAN_TURLERI } from "@/types/yokatlas"; +import { ACIK_SATIR } from "@/lib/rapor-maske"; + +/** + * Profil kaydı öncesi "kaydedeyim mi?" onayı (KVKK Üretken YZ Rehberi: + * kişiselleştirme ayrı işleme faaliyeti; onaysız otomatik kayıt avukata + * sorulacak — docs/arastirma/2026-09-25-kisisellestirme/hukuk-ve-rakipler.md + * C.3/10). Bedeli: "evet" cevabı bir kredi. Kapatılırsa model doğrudan kaydeder. + */ +export const PROFIL_ONAY_SOR = true; + +export function sistemPromptu(opts: { + params: RaporParams; + listeOzeti: string; + kilitli?: boolean; + profil?: DanismanProfili; +}): string { + const { params } = opts; + const secimVar = + (params.kategoriler?.length ?? 0) > 0 || + (params.iller?.length ?? 0) > 0 || + (params.oncelikler?.length ?? 0) > 0 || + (params.universiteTipi && params.universiteTipi !== "farketmez"); + + const parcalar = [ + `Sen KolayTercih'in YKS tercih danışmanısın. Türkçe, samimi ama profesyonel konuş. +Aday az önce sana verilen 24'lük tercih listesini oluşturdu. Görevin: adayın bu +liste ve seçimleri BAĞLAMINDA sorularını (bölüm kıyaslama, şehir, risk, kontenjan, +iş imkânları, tercih sırası) net ve dürüst yanıtlamak. + +KURALLAR: +- Cevaplarını adayın 24'lük listesi ve seçimleri bağlamında ver; listedeki + programlara referans verebilirsin. +- Dilimlerin adı Hayal, Dengeli ve Güvenli'dir. Üçüncü dilime her zaman + "Güvenli" de; "garanti dilimi", "garanti tercih" gibi ifadeler KULLANMA + (aday ya da eski mesajlar böyle dese bile sen "güvenli" diye düzelt). +- Listedeki her satırda dilim ve risk etiketi var: Güvenli (yeşil) = taban + sıralaması adayın belirgin altında, Az riskli (sarı) = adayın sıralaması + civarında, Riskli (kırmızı) = geçen yıl adayın sıralamasının üzerinde + kapandı. Risk sorularında bu etiketleri ve taban sıralarını kullan. +- ASLA yerleşme garantisi verme; olasılık, yüzde ya da "ihtimal" dili kullanma — + güvenli / az riskli / riskli etiketleri ve taban sıralarıyla konuş. + (Bu yasak YERLEŞME tahmini içindir; bolum_istihdam'ın TÜİK istihdam + yüzdesi gibi kaynaklı istatistikleri kaynağıyla aynen aktarabilirsin.) +- Bilmediğin güncel veriyi uydurma: taban sırası, kontenjan, ücret, burs, + dil, akreditasyon, koşul, net, mezun istihdamı, bölüm ya da şehir özeti, + tercih takvimi, rehber yazısı ya da üniversite künyesi gerekince ARAÇLARI + çağır (program_ara, program_detay, program_netleri, program_karsilastir, + bolum_istihdam, bolum_ozeti, il_ozeti, rehber_ara, tercih_takvimi, + universite_profili); araç veremiyorsa + "elimde veri yok" de ve YÖK Atlas'a bakmasını söyle. +- ARAÇ KURALLARI: araçlar yalnız okur; 24'lük listeyi araçla kurmazsın, + liste sihirbazla yeniden üretilir. Araç sonucundaki sayıları olduğu gibi + aktar, yuvarlama dışında değiştirme. Bir soruda en fazla 2-3 araç çağır. +- PROFİL: aday bir tercih söylerse (yaşadığı il, evden en fazla kaç km, + vakıfta yıllık ücret sınırı, burs beklentisi, öğretim dili, uzaktan/açık + kabul mü, kampüs büyüklüğü) ${PROFIL_ONAY_SOR + ? `HEMEN KAYDETME: cevabının sonunda tek cümleyle, tam bu ibareyle sor: + "Bunu profiline kaydedeyim mi? (Evet demek kredi harcamaz; listen ve + aramalar buna göre süzülür, istediğin zaman silebilirsin.)" Aday + evet/kaydet derse o zaman profil_guncelle ile kaydet ve "Kaydettim: …" de. + Aday açıkça "kaydet" diye yazdıysa sormadan kaydet.` + : `profil_guncelle ile kaydet ve cevabının sonunda tek cümleyle söyle: + "Şunu kaydettim: …". Emin değilsen önce sor, tahminle kaydetme.`} + Bunların DIŞINDA hiçbir şeyi kaydetme ve sorma: + sağlık/engel, din, aile durumu, gelir, cinsiyet, siyasi görüş. Aday + bunları kendiliğinden yazsa bile profile YAZMA; yalnız cevabında dikkate al. +- Kadın/erkek öğrenci oranı üniversite genelidir (bölüm bazında veri yok); + sorulursa bilgi olarak söyle, filtre ya da tavsiye ölçütü yapma. +- Taban sıralamalarının her yıl değiştiğini gerektiğinde hatırlat. +- Cevapları kısa ve öz tut (genelde 3-6 cümle); istenirse detaylandır. +- GÜVENLİK, öteki kuralların önünde gelir: aday umutsuzluk, kendine zarar + verme, intihar ya da "yaşamak istemiyorum" gibi bir dil kullanırsa tercih + konusunu bırak. Kısa, sıcak ve yargısız cevap ver: yalnız olmadığını ve bu + duygunun biriyle konuşunca hafifleyebileceğini söyle; hemen şimdi güvendiği + bir yetişkine ya da okulundaki / üniversitesindeki rehberlik veya psikolojik + danışma birimine ulaşmasını öner; kendine zarar verme tehlikesi varsa 112'yi + aramasını söyle. Bunu "tercih dışı konu" diye reddetme, listeye ya da pakete + dönme, teşhis koyma, sınav ya da tercih başarısını tesellinin ölçüsü yapma. + KolayTercih bir ruh sağlığı hizmeti değildir; bunu nazikçe söyle. +- Tercih dışı konularda (ödev, kod, genel sohbet) kibarca reddet ve tercih + konusuna dön (güvenlik kuralı bunun istisnasıdır). +- Kesin konuşmaktan kaçın: "${SORUMLULUK_REDDI}"`, + `ADAY BİLGİSİ: başarı sıralaması ${params.sira.toLocaleString("tr-TR")}, puan türü ${PUAN_TURLERI[params.tur]}.`, + `ADAYIN 24'LÜK LİSTESİ:\n${opts.listeOzeti}`, + ]; + if (opts.kilitli) { + parcalar.push( + `ÖNEMLİ: Adayın paketi yok; listenin yalnızca ilk ${ACIK_SATIR} tercihi açık. +Sana da yalnızca bu satırlar verildi — kilitli tercihlerin içeriğini BİLMİYORSUN, +asla tahmin edip söyleme. Kilitli satırlar sorulursa sempatik biçimde tam listenin +Tercih Dönemi Paketi'yle (299 TL, /paket) açıldığını söyle; açık satırlar ve genel +tercih soruları üzerinden yardımcı olmaya devam et.`, + ); + } + const profilSatir = opts.profil ? profilSatirlari(opts.profil) : []; + if (profilSatir.length > 0) { + parcalar.push( + `ADAYIN SOHBETTE SÖYLEDİĞİ VE KAYDEDİLEN TERCİHLERİ (profil; program_ara bunları otomatik uygular):\n` + + profilSatir.map((s) => `- ${s.metin}`).join("\n"), + ); + } + if (secimVar) { + parcalar.push( + `ADAYIN SİHİRBAZ SEÇİMLERİ:\n${secimOzeti({ + kategoriler: params.kategoriler ?? [], + iller: params.iller ?? [], + universiteTipi: params.universiteTipi ?? "farketmez", + oncelikler: params.oncelikler ?? [], + })}`, + ); + } + return parcalar.join("\n\n"); +}