Add comprehensive chatbot improvements: better LLM prompts, caching, confidence labels, smarter follow-ups
This commit is contained in:
@@ -89,6 +89,34 @@ let lastVisitorStatsWrite = { ok: true, at: null, error: null }
|
|||||||
let lastHitStatsWrite = { ok: true, at: null, error: null }
|
let lastHitStatsWrite = { ok: true, at: null, error: null }
|
||||||
let lastBackupStatus = { ok: true, at: null, error: null, file: null }
|
let lastBackupStatus = { ok: true, at: null, error: null, file: null }
|
||||||
const adminSessions = new Map()
|
const adminSessions = new Map()
|
||||||
|
const llmResponseCache = new Map()
|
||||||
|
const MAX_CACHE_SIZE = 500
|
||||||
|
const CACHE_TTL_MS = 60 * 60 * 1000
|
||||||
|
|
||||||
|
function getCacheKey(question, draftAnswer) {
|
||||||
|
return sha256(`${question}||${draftAnswer}`).slice(0, 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCachedResponse(cacheKey) {
|
||||||
|
const cached = llmResponseCache.get(cacheKey)
|
||||||
|
if (cached && Date.now() - cached.at < CACHE_TTL_MS) {
|
||||||
|
return cached.text
|
||||||
|
}
|
||||||
|
if (cached) {
|
||||||
|
llmResponseCache.delete(cacheKey)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCachedResponse(cacheKey, text) {
|
||||||
|
if (llmResponseCache.size >= MAX_CACHE_SIZE) {
|
||||||
|
const firstKey = llmResponseCache.keys().next().value
|
||||||
|
if (firstKey) llmResponseCache.delete(firstKey)
|
||||||
|
}
|
||||||
|
llmResponseCache.set(cacheKey, { text, at: Date.now() })
|
||||||
|
}
|
||||||
|
|
||||||
|
function sha256(value)
|
||||||
|
|
||||||
function sha256(value) {
|
function sha256(value) {
|
||||||
return createHash('sha256').update(value).digest('hex')
|
return createHash('sha256').update(value).digest('hex')
|
||||||
@@ -829,13 +857,20 @@ function buildGroundedRewritePrompt({ question, draftAnswer, sources, contextChu
|
|||||||
: 'No context excerpts were provided.'
|
: 'No context excerpts were provided.'
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'You are rewriting a Bible-study chatbot answer.',
|
'You are a Bible study assistant helping rewrite responses to be clearer and more pastoral.',
|
||||||
'Hard rules:',
|
'',
|
||||||
'1) Use ONLY facts in DRAFT ANSWER and CONTEXT EXCERPTS.',
|
'HARD RULES:',
|
||||||
'2) If DRAFT ANSWER is uncertain or says content is missing, keep that uncertainty and do not fill gaps.',
|
'1) Use ONLY facts from DRAFT ANSWER and CONTEXT EXCERPTS. Never add new information.',
|
||||||
'3) Do not add verses, names, history, or claims not present in the provided text.',
|
'2) If the draft is uncertain or incomplete, preserve that. Do not fill gaps or speculate.',
|
||||||
'4) Keep a warm pastoral tone but concise.',
|
'3) Do not invent verses, names, historical details, or theological claims.',
|
||||||
'5) Return plain text only. No markdown headers, no bullets unless needed for clarity.',
|
'4) Maintain a warm, encouraging pastoral tone—like Nate teaching directly.',
|
||||||
|
'5) Keep sentences clear and direct. Avoid jargon unless biblical.',
|
||||||
|
'6) Return plain text only. Use paragraph breaks but no markdown formatting.',
|
||||||
|
'',
|
||||||
|
'GOALS:',
|
||||||
|
'- Help the reader understand Scripture better',
|
||||||
|
'- Stay faithful to Nate\'s teaching and tone',
|
||||||
|
'- Be encouraging but honest about limitations',
|
||||||
'',
|
'',
|
||||||
`QUESTION:\n${question}`,
|
`QUESTION:\n${question}`,
|
||||||
'',
|
'',
|
||||||
@@ -850,6 +885,12 @@ function buildGroundedRewritePrompt({ question, draftAnswer, sources, contextChu
|
|||||||
async function rewriteWithChatbotLlm({ question, draftAnswer, sources, contextChunks }) {
|
async function rewriteWithChatbotLlm({ question, draftAnswer, sources, contextChunks }) {
|
||||||
const controller = new AbortController()
|
const controller = new AbortController()
|
||||||
const timeout = setTimeout(() => controller.abort(), CHATBOT_LLM_TIMEOUT_MS)
|
const timeout = setTimeout(() => controller.abort(), CHATBOT_LLM_TIMEOUT_MS)
|
||||||
|
const cacheKey = getCacheKey(question, draftAnswer)
|
||||||
|
const cached = getCachedResponse(cacheKey)
|
||||||
|
if (cached) {
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const prompt = buildGroundedRewritePrompt({
|
const prompt = buildGroundedRewritePrompt({
|
||||||
@@ -897,7 +938,9 @@ async function rewriteWithChatbotLlm({ question, draftAnswer, sources, contextCh
|
|||||||
throw new Error('LLM returned an empty response')
|
throw new Error('LLM returned an empty response')
|
||||||
}
|
}
|
||||||
|
|
||||||
return rewritten.slice(0, 3500)
|
const result = rewritten.slice(0, 3500)
|
||||||
|
setCachedResponse(cacheKey, result)
|
||||||
|
return result
|
||||||
} finally {
|
} finally {
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
}
|
}
|
||||||
|
|||||||
+29
@@ -1062,6 +1062,22 @@ function buildSmartFollowUpPrompts(chunks: SearchChunk[], analysis: IntentAnalys
|
|||||||
.slice(0, 3)
|
.slice(0, 3)
|
||||||
.map(prompt => ({ label: prompt, prompt }))
|
.map(prompt => ({ label: prompt, prompt }))
|
||||||
|
|
||||||
|
// Add context-specific follow-ups based on what was discussed
|
||||||
|
const subjectContext = analysis.subject || extractIdentitySubject(analysis.rawQuery.toLowerCase())
|
||||||
|
if (subjectContext && !analysis.rawQuery.toLowerCase().includes('apply')) {
|
||||||
|
promptSuggestions.unshift({
|
||||||
|
label: `How does this apply to my life?`,
|
||||||
|
prompt: `Based on what you just said about ${subjectContext}, how should I apply this today?`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (primaryVerse && analysis.type === 'identity') {
|
||||||
|
promptSuggestions.unshift({
|
||||||
|
label: `What's the historical context?`,
|
||||||
|
prompt: `What was the historical and cultural context of ${formatVerseRef(primaryVerse)}?`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const detailedNotesReply = buildDetailedNotesReplyFromChunks(chunks)
|
const detailedNotesReply = buildDetailedNotesReplyFromChunks(chunks)
|
||||||
const canOfferDetailedNotes = hasMeaningfulExtraDetail(shortAnswer, detailedNotesReply)
|
const canOfferDetailedNotes = hasMeaningfulExtraDetail(shortAnswer, detailedNotesReply)
|
||||||
|
|
||||||
@@ -1152,6 +1168,18 @@ function synthesizeSmartReply(scored: ScoredChunk[], analysis: IntentAnalysis, c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addConfidenceLabel(response: ChatResponse, confidence: 'high' | 'medium' | 'low'): ChatResponse {
|
||||||
|
const label = confidence === 'high'
|
||||||
|
? '✓ High confidence'
|
||||||
|
: confidence === 'medium'
|
||||||
|
? '~ Medium confidence'
|
||||||
|
: '? Low confidence'
|
||||||
|
return {
|
||||||
|
...response,
|
||||||
|
text: `${response.text}\n\n[${label}]`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function buildNextChatContext(query: string, analysis: IntentAnalysis, scored: ScoredChunk[], previousContext: ChatContextState): ChatContextState {
|
function buildNextChatContext(query: string, analysis: IntentAnalysis, scored: ScoredChunk[], previousContext: ChatContextState): ChatContextState {
|
||||||
const topChunks = getTopDistinctChunks(scored, 3)
|
const topChunks = getTopDistinctChunks(scored, 3)
|
||||||
if (topChunks.length === 0) return EMPTY_CHAT_CONTEXT
|
if (topChunks.length === 0) return EMPTY_CHAT_CONTEXT
|
||||||
@@ -1449,6 +1477,7 @@ function ChatBot({ mode = 'embedded' }: { mode?: 'embedded' | 'standalone' }) {
|
|||||||
if (confidence !== 'low') {
|
if (confidence !== 'low') {
|
||||||
shouldTryLlmRewrite = true
|
shouldTryLlmRewrite = true
|
||||||
chatContextRef.current = buildNextChatContext(contextualQuery, analysis, scored, activeContext)
|
chatContextRef.current = buildNextChatContext(contextualQuery, analysis, scored, activeContext)
|
||||||
|
botReply = addConfidenceLabel(botReply, confidence)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
botReply = buildSmartFallbackReply(analyzeIntent(contextualQuery, activeContext), activeContext)
|
botReply = buildSmartFallbackReply(analyzeIntent(contextualQuery, activeContext), activeContext)
|
||||||
|
|||||||
Reference in New Issue
Block a user