Bläddra i källkod

ajustes atendimentos chat

leonardo 2 månader sedan
förälder
incheckning
50489aff25
4 ändrade filer med 57 tillägg och 20 borttagningar
  1. 3 16
      src/chat/chatChain.js
  2. 2 1
      src/config/index.js
  3. 35 3
      src/services/atendimentoRagService.js
  4. 17 0
      src/services/hydeService.js

+ 3 - 16
src/chat/chatChain.js

@@ -3,6 +3,7 @@ import { chatCompletion, chatCompletionStream } from "../services/ollamaClient.j
 import { getRecentMessages } from "../services/conversationsService.js";
 import { searchDocs } from "../services/searchService.js";
 import { resolverContextoAtendimentos } from "../services/atendimentosQueryService.js";
+import { generateHydeDocument } from "../services/hydeService.js";
 
 const SYSTEM_PROMPT = [
   "Você é o Oráculo, assistente interno da Star.",
@@ -192,21 +193,7 @@ const HYDE_SYSTEM_PROMPT = [
   "Responda em português brasileiro."
 ].join("\n");
 
-export async function generateHydeDocument(query) {
-  try {
-    const { content } = await chatCompletion({
-      messages: [
-        { role: "system", content: HYDE_SYSTEM_PROMPT },
-        { role: "user", content: query }
-      ],
-      options: { temperature: 0.2 }
-    });
-    return content?.trim() || null;
-  } catch (err) {
-    console.warn("[chatChain] generateHydeDocument falhou, usando query normal para embedding:", err.message);
-    return null;
-  }
-}
+export { generateHydeDocument };
 
 async function loadHistory(conversationId, userId) {
   if (!conversationId) return [];
@@ -250,7 +237,7 @@ async function resolveEmbeddingQuery(searchQuery, originalMessage) {
   if (shouldSkipHyde(originalMessage, searchQuery)) {
     return { embeddingQuery: searchQuery, embedRole: "query", hydeSkipped: true };
   }
-  const hydeText = await generateHydeDocument(searchQuery);
+  const hydeText = await generateHydeDocument(searchQuery, HYDE_SYSTEM_PROMPT);
   return hydeText
     ? { embeddingQuery: hydeText, embedRole: "passage", hydeSkipped: false }
     : { embeddingQuery: searchQuery, embedRole: "query", hydeSkipped: false };

+ 2 - 1
src/config/index.js

@@ -94,7 +94,8 @@ export const config = {
     chunkOverlap: Number(process.env.ATENDIMENTOS_RAG_CHUNK_OVERLAP ?? 200),
     topK: Number(process.env.ATENDIMENTOS_RAG_TOP_K ?? 20),
     minScore: Number(process.env.ATENDIMENTOS_RAG_MIN_SCORE ?? 0.5),
-    searchEnabled: process.env.ATENDIMENTOS_RAG_SEARCH_ENABLED === "true"
+    searchEnabled: process.env.ATENDIMENTOS_RAG_SEARCH_ENABLED === "true",
+    hyde: process.env.ATENDIMENTOS_RAG_HYDE === "true"
   }
 };
 

+ 35 - 3
src/services/atendimentoRagService.js

@@ -5,8 +5,27 @@ import { AtendimentoRagIndex } from "../models/AtendimentoRagIndex.model.js";
 import { buildConversaTexto, calcularUltimaMensagemId } from "../utils/atendimentoFormat.js";
 import { ingestDocuments } from "./ingestService.js";
 import { searchDocs } from "./searchService.js";
+import { generateHydeDocument } from "./hydeService.js";
 import { NotFoundError } from "../shared/errors/index.js";
 
+const ATENDIMENTO_HYDE_SYSTEM_PROMPT = [
+  "Você é um atendente experiente de suporte ao cliente. Dada a pergunta ou descrição",
+  "abaixo, escreva um parágrafo curto (3 a 6 frases) no estilo de um trecho real de",
+  "uma conversa de atendimento ao cliente (chat de suporte) que trate exatamente desse",
+  "assunto — mensagens típicas de cliente e de atendente, termos usados nesse tipo de",
+  "conversa (ex.: protocolo, boleto, fatura, cancelamento, setor, prazo), quando fizer",
+  "sentido. Não inclua a pergunta original, saudações genéricas ou ressalvas de",
+  "incerteza. Se não tiver certeza do conteúdo exato, escreva de forma plausível no",
+  "mesmo estilo, pois o texto será usado apenas para busca por similaridade, nunca",
+  "mostrado ao usuário. Responda em português brasileiro."
+].join("\n");
+
+const ATENDIMENTO_HYDE_MIN_QUERY_LENGTH = 15;
+
+function shouldSkipAtendimentoHyde(query) {
+  return query.trim().length < ATENDIMENTO_HYDE_MIN_QUERY_LENGTH;
+}
+
 async function upsertRagIndex(atendimentoId, dados) {
   const now = new Date();
   const row = { AtendimentoId: atendimentoId, ...dados, UpdatedAt: now };
@@ -128,13 +147,26 @@ export async function ingestarPendentes({ limite = config.atendimentosRag.batchS
 export async function buscarAtendimentosSemelhantes({
   query,
   topK = config.atendimentosRag.topK,
-  minScore = config.atendimentosRag.minScore
+  minScore = config.atendimentosRag.minScore,
+  hyde = config.atendimentosRag.hyde
 }) {
+  if (!hyde || shouldSkipAtendimentoHyde(query)) {
+    return searchDocs({
+      query,
+      topK,
+      minScore,
+      collectionName: config.atendimentosRag.collection,
+      embedRole: "query"
+    });
+  }
+
+  const hydeText = await generateHydeDocument(query, ATENDIMENTO_HYDE_SYSTEM_PROMPT);
+
   return searchDocs({
-    query,
+    query: hydeText || query,
     topK,
     minScore,
     collectionName: config.atendimentosRag.collection,
-    embedRole: "query"
+    embedRole: hydeText ? "passage" : "query"
   });
 }

+ 17 - 0
src/services/hydeService.js

@@ -0,0 +1,17 @@
+import { chatCompletion } from "./ollamaClient.js";
+
+export async function generateHydeDocument(query, systemPrompt) {
+  try {
+    const { content } = await chatCompletion({
+      messages: [
+        { role: "system", content: systemPrompt },
+        { role: "user", content: query }
+      ],
+      options: { temperature: 0.2 }
+    });
+    return content?.trim() || null;
+  } catch (err) {
+    console.warn("[hydeService] generateHydeDocument falhou, usando query normal para embedding:", err.message);
+    return null;
+  }
+}