|
@@ -1,7 +1,22 @@
|
|
|
import { config } from "../src/config/index.js";
|
|
import { config } from "../src/config/index.js";
|
|
|
import { chatCompletion, chatCompletionStream } from "../src/services/ollamaClient.js";
|
|
import { chatCompletion, chatCompletionStream } from "../src/services/ollamaClient.js";
|
|
|
|
|
+import { getRecentMessages } from "../src/services/conversationsService.js";
|
|
|
import { searchDocs } from "../chat/searchChat.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) {
|
|
function buildContextBlock(hits) {
|
|
|
const lines = [];
|
|
const lines = [];
|
|
|
hits.forEach((h, i) => {
|
|
hits.forEach((h, i) => {
|
|
@@ -13,18 +28,11 @@ function buildContextBlock(hits) {
|
|
|
return lines.join("\n").trim();
|
|
return lines.join("\n").trim();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function buildMessages(message, context, sessionId) {
|
|
|
|
|
- const system = [
|
|
|
|
|
- "Você é um assistente de atendimento interno de uma empresa.",
|
|
|
|
|
- "Responda apenas com base no CONTEXTO quando ele estiver disponível.",
|
|
|
|
|
- "Se o CONTEXTO não tiver a informação, diga que não encontrou na base da empresa e sugira o que solicitar ao time responsável.",
|
|
|
|
|
- "Seja direto e em português."
|
|
|
|
|
- ].join("\n");
|
|
|
|
|
-
|
|
|
|
|
|
|
+function buildMessages(message, context, history = []) {
|
|
|
return [
|
|
return [
|
|
|
- { role: "system", content: system },
|
|
|
|
|
|
|
+ { role: "system", content: SYSTEM_PROMPT },
|
|
|
...(context ? [{ role: "system", content: `CONTEXTO:\n${context}` }] : []),
|
|
...(context ? [{ role: "system", content: `CONTEXTO:\n${context}` }] : []),
|
|
|
- ...(sessionId ? [{ role: "system", content: `session_id:${sessionId}` }] : []),
|
|
|
|
|
|
|
+ ...history.map((m) => ({ role: m.Role.toLowerCase(), content: m.Content })),
|
|
|
{ role: "user", content: message }
|
|
{ role: "user", content: message }
|
|
|
];
|
|
];
|
|
|
}
|
|
}
|
|
@@ -39,11 +47,40 @@ function hitsToSources(hits) {
|
|
|
}));
|
|
}));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-export async function answerWithContext({ message, sessionId }) {
|
|
|
|
|
- const hits = await searchDocs({ query: message, topK: config.rag.topK });
|
|
|
|
|
|
|
+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 context = buildContextBlock(hits);
|
|
|
- const messages = buildMessages(message, context, sessionId);
|
|
|
|
|
- const completion = await chatCompletion({ messages });
|
|
|
|
|
|
|
+ const history = await loadHistory(conversationId);
|
|
|
|
|
+ const messages = buildMessages(message, context, history);
|
|
|
|
|
+ const completion = await chatCompletion({ messages, options });
|
|
|
|
|
|
|
|
return {
|
|
return {
|
|
|
answer: completion.content,
|
|
answer: completion.content,
|
|
@@ -51,13 +88,15 @@ export async function answerWithContext({ message, sessionId }) {
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-export async function answerWithContextStream({ message, sessionId, onChunk, signal }) {
|
|
|
|
|
- const hits = await searchDocs({ query: message, topK: config.rag.topK });
|
|
|
|
|
|
|
+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 context = buildContextBlock(hits);
|
|
|
- const messages = buildMessages(message, context, sessionId);
|
|
|
|
|
|
|
+ const history = await loadHistory(conversationId);
|
|
|
|
|
+ const messages = buildMessages(message, context, history);
|
|
|
const sources = hitsToSources(hits);
|
|
const sources = hitsToSources(hits);
|
|
|
|
|
|
|
|
- const completion = await chatCompletionStream({ messages, onChunk, signal });
|
|
|
|
|
|
|
+ const completion = await chatCompletionStream({ messages, onChunk, signal, options });
|
|
|
|
|
|
|
|
return {
|
|
return {
|
|
|
answer: completion.content,
|
|
answer: completion.content,
|