fix(danisman): 'Kaydettim' deyip kaydetmeme — sunucu tarafı denetim + prompt kuralı (iş 4; QA H4) [KARAR]

- sohbet-akisi.ts: cevapta kayıt beyanı (kaydettim / profiline ekledim / not aldım…) var ama o turda
  başarılı profil_guncelle ('kaydedildi') yoksa cevabın sonuna düzeltme notu eklenir. Kayıt kararı
  modelin sözüne bırakılmaz; gerçek araç sonucuyla karşılaştırılır. Hata dönen çağrı sayılmaz.
- Kredi: bu cevabın kredisi değişmez (iade yok, ek model çağrısı yok). Not 'profiline kaydedeyim mi'
  ibaresini taşır; adayın 'evet'i mevcut bedelsiz onay yolundan geçer — sistemin hatasını düzeltmek
  adaya kredi harcatmaz.
- Prompt: KAYIT DÜRÜSTLÜĞÜ kuralı (araç 'kaydedildi' dönmediyse 'kaydettim' deme).
- scripts/eval/akis-denetim.ts: sahte yerel model ucuyla parasız mekanik test (6 senaryo: aracsız
  'kaydettim', araçlı, araç hatası, vaat regex notu, temiz, araç kotası) — 6/6 geçti.
  Gerçek modelle bu yol henüz tetiklenmedi; model davranışı eval:sohbet ile ölçülür.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-10-05 08:37:33 +03:00
parent ebc4aa4205
commit f4a5e23c51
5 changed files with 167 additions and 5 deletions

View File

@@ -24,7 +24,7 @@ import {
} from "ai";
import { aracSeti, type AracBaglami } from "./araclar";
import { sohbetDilModeli } from "./client";
import { vaatDiliBul } from "./cikti-denetimi";
import { KAYIT_DUZELTME_NOTU, kayitBeyaniVar, vaatDiliBul } from "./cikti-denetimi";
import { cevapIhlalOlasiligi, JEV_DUZELTME_NOTU, JEV_IHLAL_ESIGI } from "./jev";
/** Bir cevapta en fazla model adımı (araç turları + son cevap). */
@@ -49,6 +49,8 @@ export type CiktiDenetimi = {
vaatNotu: boolean;
/** Sınır aşıldığı için çalıştırılmayan araç çağrısı sayısı. */
reddedilenArac: number;
/** "Kaydettim" dendi ama başarılı profil_guncelle yoktu; düzeltme notu eklendi. */
kayitNotu: boolean;
};
export function danismanAkisi(opts: {
@@ -76,6 +78,7 @@ export function danismanAkisi(opts: {
let adimdakiArac = 0;
let toplamArac = 0;
let reddedilenArac = 0;
let basariliKayit = 0;
try {
const result = streamText({
model: sohbetDilModeli(),
@@ -91,6 +94,12 @@ export function danismanAkisi(opts: {
reddedilenArac++;
return `Araç çağrısı sınırı aşıldı (bir adımda en fazla ${ADIM_BASINA_ARAC}, bir cevapta en fazla ${CEVAP_BASINA_ARAC}). Bu çağrı ÇALIŞTIRILMADI. Yeni araç çağırma; elindeki sonuçlarla cevap ver, eksik kalan kısmı adaya açıkça söyle ve sorusunu daraltmasını iste. Çalışmayan çağrı için sayı uydurma.`;
},
(ad, cikti) => {
// Başarılı kayıt = profil_guncelle "kaydedildi" döndü (hata dönerse sayılmaz)
if (ad === "profil_guncelle" && cikti != null && typeof cikti === "object" && "kaydedildi" in cikti) {
basariliKayit++;
}
},
),
// En fazla ARAC_TUR_SINIRI adım; son adımda araç kapalı (cevap
// vermek zorunda).
@@ -152,8 +161,22 @@ export function danismanAkisi(opts: {
writer.write({ type: "text-delta", id, delta: JEV_DUZELTME_NOTU });
writer.write({ type: "text-end", id });
}
// Kayıt beyanı denetimi (QA H4): cevap "kaydettim" diyor ama bu turda
// başarılı profil_guncelle yok → düzeltme notu. Kayıt kararı modelin
// sözüne bırakılmaz; gerçek araç sonucuyla karşılaştırılır. Kredi:
// bu cevap için değişmez; notun sorduğu "kaydedeyim mi?"ye verilen
// "evet" bedelsizdir.
const kayitNotu = basariliKayit === 0 && kayitBeyaniVar(metin);
if (kayitNotu) {
console.info("[soru] kayıt beyanı var, başarılı profil_guncelle yok → not eklendi");
const id = crypto.randomUUID();
metin += `\n\n${KAYIT_DUZELTME_NOTU}`;
writer.write({ type: "text-start", id });
writer.write({ type: "text-delta", id, delta: KAYIT_DUZELTME_NOTU });
writer.write({ type: "text-end", id });
}
if (reddedilenArac) console.info("[soru] araç sınırı:", reddedilenArac, "çağrı çalıştırılmadı");
opts.onDenetim?.({ jevP, vaatRegex, vaatNotu, reddedilenArac });
opts.onDenetim?.({ jevP, vaatRegex, vaatNotu, reddedilenArac, kayitNotu });
await opts.onTamam(metin);
tamamlandi = true;
writer.write({ type: "finish" });