chatChain.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import { config } from "../src/config/index.js";
  2. import { chatCompletion, chatCompletionStream } from "../src/services/ollamaClient.js";
  3. import { getRecentMessages } from "../src/services/conversationsService.js";
  4. import { searchDocs } from "../chat/searchChat.js";
  5. const SYSTEM_PROMPT = [
  6. "Você é um assistente de atendimento interno da empresa Star.",
  7. "Seu papel é responder perguntas dos colaboradores com base nos documentos internos fornecidos no CONTEXTO.",
  8. "",
  9. "Regras:",
  10. "- Responda sempre em português brasileiro.",
  11. "- Baseie-se exclusivamente no CONTEXTO quando ele estiver disponível.",
  12. '- Se a informação não estiver no CONTEXTO, diga claramente: "Não encontrei essa informação na base de conhecimento."',
  13. "- Nunca invente informações ou suponha dados que não estejam no CONTEXTO.",
  14. "- Seja direto, objetivo e use bullet points quando a resposta tiver múltiplos itens.",
  15. "- Se a pergunta for vaga, peça esclarecimento antes de responder.",
  16. "- Ao citar informações, mencione a fonte (nome do documento) quando disponível."
  17. ].join("\n");
  18. function buildContextBlock(hits) {
  19. const lines = [];
  20. hits.forEach((h, i) => {
  21. const header = `# Fonte ${i + 1}${h.source ? ` (${h.source})` : ""}`;
  22. lines.push(header);
  23. lines.push(h.text);
  24. lines.push("");
  25. });
  26. return lines.join("\n").trim();
  27. }
  28. function buildMessages(message, context, history = []) {
  29. return [
  30. { role: "system", content: SYSTEM_PROMPT },
  31. ...(context ? [{ role: "system", content: `CONTEXTO:\n${context}` }] : []),
  32. ...history.map((m) => ({ role: m.Role.toLowerCase(), content: m.Content })),
  33. { role: "user", content: message }
  34. ];
  35. }
  36. function hitsToSources(hits) {
  37. return hits.map((h) => ({
  38. id: h.id,
  39. source: h.source,
  40. score: h.score,
  41. chunkIndex: h.chunkIndex,
  42. metadata: h.metadata
  43. }));
  44. }
  45. async function rewriteQuery(query) {
  46. try {
  47. const { content } = await chatCompletion({
  48. messages: [
  49. {
  50. role: "system",
  51. content:
  52. "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."
  53. },
  54. { role: "user", content: query }
  55. ]
  56. });
  57. return content?.trim() || query;
  58. } catch {
  59. return query;
  60. }
  61. }
  62. async function loadHistory(conversationId) {
  63. if (!conversationId) return [];
  64. try {
  65. return await getRecentMessages(conversationId, 12);
  66. } catch {
  67. return [];
  68. }
  69. }
  70. export async function answerWithContext({ message, conversationId, options }) {
  71. const searchQuery = config.rag.queryRewrite ? await rewriteQuery(message) : message;
  72. const hits = await searchDocs({ query: searchQuery, topK: config.rag.topK });
  73. const context = buildContextBlock(hits);
  74. const history = await loadHistory(conversationId);
  75. const messages = buildMessages(message, context, history);
  76. const completion = await chatCompletion({ messages, options });
  77. return {
  78. answer: completion.content,
  79. sources: hitsToSources(hits)
  80. };
  81. }
  82. export async function answerWithContextStream({ message, conversationId, options, onChunk, signal }) {
  83. const searchQuery = config.rag.queryRewrite ? await rewriteQuery(message) : message;
  84. const hits = await searchDocs({ query: searchQuery, topK: config.rag.topK });
  85. const context = buildContextBlock(hits);
  86. const history = await loadHistory(conversationId);
  87. const messages = buildMessages(message, context, history);
  88. const sources = hitsToSources(hits);
  89. const completion = await chatCompletionStream({ messages, onChunk, signal, options });
  90. return {
  91. answer: completion.content,
  92. sources
  93. };
  94. }