From 90a9994e5fc1b8631a86d9924d0696bb4d6c81c0 Mon Sep 17 00:00:00 2001 From: bilalgursen Date: Mon, 5 Oct 2026 01:06:34 +0300 Subject: [PATCH] =?UTF-8?q?fix(danisman):=20H2=20(2)=20=E2=80=94=20zaman?= =?UTF-8?q?=20a=C5=9F=C4=B1m=C4=B1nda=20yar=C4=B1m=20cevap=20tamam=20say?= =?UTF-8?q?=C4=B1lmas=C4=B1n,=20kredi=20iade=20edilsin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SDK akış ortası abort'u hata fırlatmadan "abort" parçasıyla bitiriyordu; döngü bunu tamamlanma sayıp yarım metni kaydediyor, krediyi iade etmiyordu. Döngüden sonra sinyale bakılır: kesildiyse onYarim (iade + yarım kayıt) çalışır, istemciye hata parçası gider. Süre zamanAsimiMs ile verilebilir (varsayılan 120 sn). Co-Authored-By: Claude Fable 5.1 --- src/lib/ai/sohbet-akisi.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lib/ai/sohbet-akisi.ts b/src/lib/ai/sohbet-akisi.ts index b391315..b6cbb92 100644 --- a/src/lib/ai/sohbet-akisi.ts +++ b/src/lib/ai/sohbet-akisi.ts @@ -34,6 +34,8 @@ export function danismanAkisi(opts: { onTamam: (metin: string) => Promise; /** Cevap tamamlanamadı; `metin` o ana kadar üretilen kısım (boş olabilir). */ onYarim: (metin: string) => Promise; + /** Sağlayıcı bu sürede bitirmezse cevap yarım sayılır (varsayılan 120 sn). */ + zamanAsimiMs?: number; }): ReadableStream { return createUIMessageStream({ execute: async ({ writer }) => { @@ -41,6 +43,9 @@ export function danismanAkisi(opts: { let metin = ""; // Model/sağlayıcı hatası akışa "error" parçası olarak düşer let akisHatasi: string | null = null; + // Sağlayıcı takılırsa SDK akışı "abort" parçasıyla sessizce bitirir + // (hata fırlatmaz); döngüden sonra sinyale ayrıca bakılır. + const zamanAsimi = AbortSignal.timeout(opts.zamanAsimiMs ?? 120_000); try { const result = streamText({ model: sohbetDilModeli(), @@ -53,7 +58,7 @@ export function danismanAkisi(opts: { prepareStep: ({ stepNumber }) => stepNumber >= ARAC_TUR_SINIRI - 1 ? { toolChoice: "none" } : undefined, maxOutputTokens: 1024, - abortSignal: AbortSignal.timeout(120_000), + abortSignal: zamanAsimi, providerOptions: { openaiCompatible: { reasoningEffort: "none" } }, onError: ({ error }) => { // Hata nesnesinin tamamı istek gövdesini ve cevap başlıklarını @@ -71,6 +76,8 @@ export function danismanAkisi(opts: { for (;;) { const { done, value: parca } = await okuyucu.read(); if (done) break; + // Kesilme istemciye "abort" değil hata olarak gider (aşağıda fırlatılır) + if (parca.type === "abort") continue; if (parca.type === "text-start" && metin && !metin.endsWith("\n")) { // Araç turundan sonra yeni metin parçası: kayıtta yapışmasın metin += "\n\n"; @@ -82,6 +89,8 @@ export function danismanAkisi(opts: { writer.write(parca); } if (akisHatasi != null) throw new Error(akisHatasi); + // Yarım metin "tamamlandı" diye kaydedilmesin: onYarim krediyi iade eder + if (zamanAsimi.aborted) throw new Error("Zaman aşımı: sağlayıcı cevabı bitirmedi"); if (!metin.trim()) throw new Error("Boş cevap"); // Çıktı denetimi (jev.ts): cevap garanti/olasılık dili içeriyorsa