|
|
|
|
|
import fs from 'fs/promises'; |
|
|
import path from 'path'; |
|
|
|
|
|
|
|
|
async function loadTemplate() { |
|
|
const filePath = path.resolve( |
|
|
path.dirname(new URL(import.meta.url).pathname), |
|
|
'..', '..', 'prompts', 'generator_prompt.txt' |
|
|
); |
|
|
return await fs.readFile(filePath, 'utf8'); |
|
|
} |
|
|
|
|
|
export async function runGenerator(question, contextChunks, provider) { |
|
|
const template = await loadTemplate(); |
|
|
|
|
|
const ctxText = contextChunks |
|
|
.map(c => c.content || c.text || "") |
|
|
.join("\n\n---\n\n"); |
|
|
|
|
|
const prompt = template |
|
|
.replace('{{QUESTION}}', question) |
|
|
.replace('{{CONTEXT}}', ctxText); |
|
|
|
|
|
const response = await provider.generate(prompt, { includeJson: true }); |
|
|
|
|
|
|
|
|
const raw = typeof response === 'string' ? response : response?.response ?? ''; |
|
|
const thinkingObj = typeof response === 'object' && response?.thinking ? response.thinking : null; |
|
|
const rawJson = |
|
|
typeof response === 'object' && response?.fullResponse |
|
|
? (({ context, ...rest }) => rest)(response.fullResponse) |
|
|
: null; |
|
|
|
|
|
|
|
|
let thought = null; |
|
|
let answer = raw?.trim?.() ?? raw; |
|
|
let confidence = null; |
|
|
let evidence = null; |
|
|
let limitations = null; |
|
|
|
|
|
const safeParse = (txt) => { |
|
|
if (!txt || typeof txt !== 'string') return null; |
|
|
try { |
|
|
return JSON.parse(txt); |
|
|
} catch { |
|
|
|
|
|
const start = txt.indexOf('{'); |
|
|
const end = txt.lastIndexOf('}'); |
|
|
if (start !== -1 && end !== -1 && end > start) { |
|
|
try { |
|
|
return JSON.parse(txt.slice(start, end + 1)); |
|
|
} catch { |
|
|
return null; |
|
|
} |
|
|
} |
|
|
return null; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
if (thinkingObj) { |
|
|
thought = thinkingObj; |
|
|
} |
|
|
|
|
|
const extractThoughtBlock = (txt) => { |
|
|
if (!txt || typeof txt !== 'string') return null; |
|
|
const thoughtMatch = txt.match(/<\|thought\|>([\s\S]*?)<\|end_of_thought\|>/i); |
|
|
if (thoughtMatch) return thoughtMatch[1].trim(); |
|
|
|
|
|
const understandingMatch = txt.match(/<understanding>[\s\S]*?(?=<\|answer\||<answer>|$)/i); |
|
|
if (understandingMatch) return understandingMatch[0].trim(); |
|
|
|
|
|
return null; |
|
|
}; |
|
|
|
|
|
|
|
|
const parseAnswerBlock = (txt) => { |
|
|
if (!txt || typeof txt !== 'string') return null; |
|
|
const blockMatch = txt.match(/<\|answer\|>([\s\S]*?)<\|end_of_answer\|>/i); |
|
|
const body = blockMatch ? blockMatch[1] : txt; |
|
|
const lines = body.split('\n').map((l) => l.trim()).filter(Boolean); |
|
|
const result = {}; |
|
|
|
|
|
const answerLine = txt.match(/^answer:\s*(.+)$/im); |
|
|
if (answerLine) result.answer = answerLine[1].trim(); |
|
|
const confLine = txt.match(/^confidence:\s*(.+)$/im); |
|
|
if (confLine) result.confidence = confLine[1].trim(); |
|
|
const evidenceLine = txt.match(/^evidence:\s*(.+)$/im); |
|
|
if (evidenceLine) { |
|
|
const evLine = evidenceLine[1].trim(); |
|
|
let ev = []; |
|
|
const arrMatch = evLine.match(/\[(.*)\]/); |
|
|
if (arrMatch) { |
|
|
ev = arrMatch[1] |
|
|
.split(/,(?=(?:[^'"]|'[^']*'|"[^"]*")*$)/) |
|
|
.map((s) => s.replace(/^["'\s]+|["'\s]+$/g, '')) |
|
|
.filter(Boolean); |
|
|
} else { |
|
|
ev = evLine.split(',').map((s) => s.replace(/^["'\s]+|["'\s]+$/g, '')).filter(Boolean); |
|
|
} |
|
|
result.evidence = ev; |
|
|
} |
|
|
const limLine = txt.match(/^limitations?:\s*(.+)$/im); |
|
|
if (limLine) result.limitations = limLine[1].trim(); |
|
|
|
|
|
for (const line of lines) { |
|
|
if (/^confidence:/i.test(line)) { |
|
|
const val = line.split(':')[1]?.trim(); |
|
|
result.confidence = val || null; |
|
|
} else if (/^answer:/i.test(line)) { |
|
|
result.answer = line.split(':').slice(1).join(':').trim(); |
|
|
} else if (/^evidence:/i.test(line)) { |
|
|
const evLine = line.split(':').slice(1).join(':').trim(); |
|
|
|
|
|
let ev = []; |
|
|
const arrMatch = evLine.match(/\[(.*)\]/); |
|
|
if (arrMatch) { |
|
|
ev = arrMatch[1] |
|
|
.split(/,(?=(?:[^'"]|'[^']*'|"[^"]*")*$)/) |
|
|
.map((s) => s.replace(/^["'\s]+|["'\s]+$/g, '')) |
|
|
.filter(Boolean); |
|
|
} else { |
|
|
ev = evLine.split(',').map((s) => s.trim()).filter(Boolean); |
|
|
} |
|
|
result.evidence = ev; |
|
|
} else if (/^limitations:/i.test(line)) { |
|
|
result.limitations = line.split(':').slice(1).join(':').trim(); |
|
|
} |
|
|
} |
|
|
return result; |
|
|
}; |
|
|
|
|
|
const blockParsed = parseAnswerBlock(raw); |
|
|
if (blockParsed?.answer) { |
|
|
answer = blockParsed.answer; |
|
|
confidence = blockParsed.confidence ?? confidence; |
|
|
evidence = blockParsed.evidence ?? evidence; |
|
|
limitations = blockParsed.limitations ?? limitations; |
|
|
if (!thought) { |
|
|
const t = extractThoughtBlock(raw); |
|
|
if (t) thought = t; |
|
|
} |
|
|
} else { |
|
|
|
|
|
const parsed = safeParse(raw); |
|
|
if (parsed && typeof parsed === 'object') { |
|
|
const reasoning = parsed.reasoning || parsed.REASONING; |
|
|
if (Array.isArray(reasoning) && !thought) { |
|
|
thought = reasoning.join(' '); |
|
|
} |
|
|
|
|
|
const ans = |
|
|
parsed.answer || |
|
|
parsed.ANSWER || |
|
|
parsed.final || |
|
|
parsed.output; |
|
|
if (typeof ans === 'string') { |
|
|
answer = ans.trim(); |
|
|
} else if (Array.isArray(ans)) { |
|
|
answer = ans.join(' ').trim(); |
|
|
} |
|
|
|
|
|
if (parsed.confidence != null) { |
|
|
const num = Number(parsed.confidence); |
|
|
if (Number.isFinite(num)) confidence = num; |
|
|
else if (typeof parsed.confidence === 'string') confidence = parsed.confidence; |
|
|
} |
|
|
|
|
|
if (parsed.evidence) evidence = parsed.evidence; |
|
|
if (parsed.limitations) limitations = parsed.limitations; |
|
|
} else { |
|
|
|
|
|
const tBlock = extractThoughtBlock(raw); |
|
|
if (tBlock) thought = tBlock; |
|
|
|
|
|
const thinkMatch = typeof raw === 'string' |
|
|
? raw.match(/<think>([\s\S]*?)<\/think>/i) |
|
|
: null; |
|
|
thought = thought || (thinkMatch ? thinkMatch[1].trim() : null); |
|
|
if (thinkMatch) { |
|
|
answer = raw.slice(thinkMatch.index + thinkMatch[0].length).trim(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
if (!thought && raw) { |
|
|
thought = raw; |
|
|
} |
|
|
|
|
|
return { |
|
|
raw, |
|
|
thinking: thinkingObj, |
|
|
thought, |
|
|
answer, |
|
|
confidence, |
|
|
evidence, |
|
|
limitations, |
|
|
question, |
|
|
context: contextChunks, |
|
|
rawJson, |
|
|
}; |
|
|
} |
|
|
|
|
|
export default { runGenerator }; |
|
|
|