import { config } from "../src/config/index.js"; import { chatCompletion, chatCompletionStream } from "../src/services/ollamaClient.js"; import { getRecentMessages } from "../src/services/conversationsService.js"; import { searchDocs } from "../chat/searchChat.js"; const SYSTEM_PROMPT = [ "Você é um assistente de atendimento interno da empresa Star.", "Seu papel é responder perguntas dos colaboradores com base nos documentos internos fornecidos no CONTEXTO.", "", "Regras:", "- Responda sempre em português brasileiro.", "- Baseie-se exclusivamente no CONTEXTO quando ele estiver disponível.", '- Se a informação não estiver no CONTEXTO, diga claramente: "Não encontrei essa informação na base de conhecimento."', "- Nunca invente informações ou suponha dados que não estejam no CONTEXTO.", "- Seja direto, objetivo e use bullet points quando a resposta tiver múltiplos itens.", "- Se a pergunta for vaga, peça esclarecimento antes de responder.", "- Ao citar informações, mencione a fonte (nome do documento) quando disponível." ].join("\n"); function buildContextBlock(hits) { const lines = []; hits.forEach((h, i) => { const header = `# Fonte ${i + 1}${h.source ? ` (${h.source})` : ""}`; lines.push(header); lines.push(h.text); lines.push(""); }); return lines.join("\n").trim(); } function buildMessages(message, context, history = []) { return [ { role: "system", content: SYSTEM_PROMPT }, ...(context ? [{ role: "system", content: `CONTEXTO:\n${context}` }] : []), ...history.map((m) => ({ role: m.Role.toLowerCase(), content: m.Content })), { role: "user", content: message } ]; } function hitsToSources(hits) { return hits.map((h) => ({ id: h.id, source: h.source, score: h.score, chunkIndex: h.chunkIndex, metadata: h.metadata })); } async function rewriteQuery(query) { try { const { content } = await chatCompletion({ messages: [ { role: "system", content: "Você é um motor de busca interno. Reescreva a pergunta abaixo de forma mais específica e completa para busca em documentos internos de empresa. Responda APENAS com a query reescrita, sem explicações ou formatação extra." }, { role: "user", content: query } ] }); return content?.trim() || query; } catch { return query; } } async function loadHistory(conversationId) { if (!conversationId) return []; try { return await getRecentMessages(conversationId, 12); } catch { return []; } } export async function answerWithContext({ message, conversationId, options }) { const searchQuery = config.rag.queryRewrite ? await rewriteQuery(message) : message; const hits = await searchDocs({ query: searchQuery, topK: config.rag.topK }); const context = buildContextBlock(hits); const history = await loadHistory(conversationId); const messages = buildMessages(message, context, history); const completion = await chatCompletion({ messages, options }); return { answer: completion.content, sources: hitsToSources(hits) }; } export async function answerWithContextStream({ message, conversationId, options, onChunk, signal }) { const searchQuery = config.rag.queryRewrite ? await rewriteQuery(message) : message; const hits = await searchDocs({ query: searchQuery, topK: config.rag.topK }); const context = buildContextBlock(hits); const history = await loadHistory(conversationId); const messages = buildMessages(message, context, history); const sources = hitsToSources(hits); const completion = await chatCompletionStream({ messages, onChunk, signal, options }); return { answer: completion.content, sources }; }