Preserve question source chunks and reject fabricated citations

This commit is contained in:
2026-09-06 10:10:26 +03:00
parent 0b3bb8223f
commit a78aaef7e5
5 changed files with 81 additions and 18 deletions
+33
View File
@@ -0,0 +1,33 @@
/** Lossless bounded chunks, including a short last clause and long unbroken lines. */
export function questionChunks(value, target = 1200) {
if (!Number.isInteger(target) || target < 32 || target > 1400) throw new Error('Geçersiz kaynak parçası boyutu.');
const text = String(value ?? '');
const chunks = [];
let offset = 0;
while (offset < text.length) {
let end = Math.min(offset + target, text.length);
if (end < text.length) {
const segment = text.slice(offset, end);
const boundary = Math.max(segment.lastIndexOf('\n'), segment.lastIndexOf(' '));
if (boundary >= target / 2) end = offset + boundary + 1;
// A Unicode surrogate pair is one character, never two source fragments.
if (/[\uD800-\uDBFF]/.test(text[end - 1]) && /[\uDC00-\uDFFF]/.test(text[end])) end--;
}
chunks.push(text.slice(offset, end));
offset = end;
}
return chunks;
}
/** Reject fabricated references rather than attaching unrelated fallback sources. */
export function qualifyQuestionAnswer(result, selected) {
if (typeof result?.cevap !== 'string' || !result.cevap.trim())
throw new Error('Model geçerli bir yanıt üretmedi. Yeniden deneyin.');
const cevap = result.cevap.trim();
const cited = [...new Set([...cevap.matchAll(/\[(\d+)\]/g)].map(match => Number(match[1])))];
if (!cited.length) throw new Error('Yanıt kaynak parçasına atıf içermiyor. Yeniden deneyin.');
const available = new Map(selected.map(part => [part.n, part]));
if (cited.some(id => !available.has(id)))
throw new Error('Yanıttaki kaynak numarası incelenen parçalarla uyuşmuyor. Yeniden deneyin.');
return {cevap, kaynaklar:cited.map(n => ({n, alinti:available.get(n).icerik}))};
}