|
@@ -5,7 +5,7 @@ import { config } from "../config/index.js";
|
|
|
import { Atendimento } from "../models/Atendimento.model.js";
|
|
import { Atendimento } from "../models/Atendimento.model.js";
|
|
|
import { AtendimentoMensagem } from "../models/AtendimentoMensagem.model.js";
|
|
import { AtendimentoMensagem } from "../models/AtendimentoMensagem.model.js";
|
|
|
import { AtendimentoRagIndex } from "../models/AtendimentoRagIndex.model.js";
|
|
import { AtendimentoRagIndex } from "../models/AtendimentoRagIndex.model.js";
|
|
|
-import { buildConversaTextoParaRag, calcularUltimaMensagemId } from "../utils/atendimentoFormat.js";
|
|
|
|
|
|
|
+import { buildConversaTextoParaRag, calcularUltimaMensagemId, classificarQualidadeAtendimento, buildBlocoAvaliacaoParaRag } from "../utils/atendimentoFormat.js";
|
|
|
import { ingestDocuments } from "./ingestService.js";
|
|
import { ingestDocuments } from "./ingestService.js";
|
|
|
import { searchDocs } from "./searchService.js";
|
|
import { searchDocs } from "./searchService.js";
|
|
|
import { generateHydeDocument } from "./hydeService.js";
|
|
import { generateHydeDocument } from "./hydeService.js";
|
|
@@ -47,47 +47,79 @@ async function upsertRagIndex(atendimentoId, dados) {
|
|
|
export async function ingestarAtendimento(atendimentoId) {
|
|
export async function ingestarAtendimento(atendimentoId) {
|
|
|
const atendimento = await Atendimento.query()
|
|
const atendimento = await Atendimento.query()
|
|
|
.findById(atendimentoId)
|
|
.findById(atendimentoId)
|
|
|
- .withGraphFetched("[cliente, mensagens]")
|
|
|
|
|
|
|
+ .withGraphFetched("[cliente, mensagens, avaliacao]")
|
|
|
.modifyGraph("mensagens", (q) => q.orderBy("Timestamp", "asc").orderBy("Id", "asc"));
|
|
.modifyGraph("mensagens", (q) => q.orderBy("Timestamp", "asc").orderBy("Id", "asc"));
|
|
|
|
|
|
|
|
if (!atendimento) throw new NotFoundError("atendimento_not_found");
|
|
if (!atendimento) throw new NotFoundError("atendimento_not_found");
|
|
|
|
|
|
|
|
const mensagens = atendimento.mensagens ?? [];
|
|
const mensagens = atendimento.mensagens ?? [];
|
|
|
const ultimaMensagemId = calcularUltimaMensagemId(mensagens);
|
|
const ultimaMensagemId = calcularUltimaMensagemId(mensagens);
|
|
|
|
|
+ const avaliacao = atendimento.avaliacao ?? null;
|
|
|
|
|
+ const avaliacaoUpdatedAt = avaliacao?.UpdatedAt ?? null;
|
|
|
const conversaTexto = buildConversaTextoParaRag(
|
|
const conversaTexto = buildConversaTextoParaRag(
|
|
|
{ atendimento, cliente: atendimento.cliente, mensagens },
|
|
{ atendimento, cliente: atendimento.cliente, mensagens },
|
|
|
{ boilerplateSet }
|
|
{ boilerplateSet }
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
-
|
|
|
|
|
if (!conversaTexto) {
|
|
if (!conversaTexto) {
|
|
|
await upsertRagIndex(atendimento.Id, {
|
|
await upsertRagIndex(atendimento.Id, {
|
|
|
UltimaMensagemId: ultimaMensagemId,
|
|
UltimaMensagemId: ultimaMensagemId,
|
|
|
|
|
+ AvaliacaoUpdatedAt: avaliacaoUpdatedAt,
|
|
|
ChunksCount: 0,
|
|
ChunksCount: 0,
|
|
|
EmbeddingModel: null
|
|
EmbeddingModel: null
|
|
|
});
|
|
});
|
|
|
return { ingestado: false, chunks: 0 };
|
|
return { ingestado: false, chunks: 0 };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const { upserted } = await ingestDocuments(
|
|
|
|
|
- [
|
|
|
|
|
- {
|
|
|
|
|
- id: atendimento.Id,
|
|
|
|
|
- source: `atendimento:${atendimento.Codigo}`,
|
|
|
|
|
- title: `Atendimento ${atendimento.Codigo}`,
|
|
|
|
|
- metadata: { atendimentoId: atendimento.Id, codigo: atendimento.Codigo, setor: atendimento.Setor },
|
|
|
|
|
- text: conversaTexto
|
|
|
|
|
- }
|
|
|
|
|
- ],
|
|
|
|
|
|
|
+ const qualidade = classificarQualidadeAtendimento({
|
|
|
|
|
+ scoreAtendente: avaliacao?.ScoreAtendente ?? null,
|
|
|
|
|
+ resolvido: avaliacao?.Resolvido ?? null,
|
|
|
|
|
+ sentimento: avaliacao?.Sentimento ?? null
|
|
|
|
|
+ });
|
|
|
|
|
+ const blocoAvaliacao = buildBlocoAvaliacaoParaRag(avaliacao);
|
|
|
|
|
+
|
|
|
|
|
+ const metadataBase = {
|
|
|
|
|
+ atendimentoId: atendimento.Id,
|
|
|
|
|
+ codigo: atendimento.Codigo,
|
|
|
|
|
+ setor: atendimento.Setor,
|
|
|
|
|
+ avaliado: Boolean(avaliacao?.Avaliavel),
|
|
|
|
|
+ qualidade,
|
|
|
|
|
+ scoreAtendente: avaliacao?.ScoreAtendente ?? null,
|
|
|
|
|
+ resolvido: avaliacao?.Resolvido ?? null,
|
|
|
|
|
+ sentimento: avaliacao?.Sentimento ?? null,
|
|
|
|
|
+ statusCancelamento: avaliacao?.StatusCancelamento ?? null
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const documentos = [
|
|
|
{
|
|
{
|
|
|
- collectionName: config.atendimentosRag.collection,
|
|
|
|
|
- chunkSize: config.atendimentosRag.chunkSize,
|
|
|
|
|
- chunkOverlap: config.atendimentosRag.chunkOverlap
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ id: `${atendimento.Id}-conversa`,
|
|
|
|
|
+ source: `atendimento:${atendimento.Codigo}`,
|
|
|
|
|
+ title: `Atendimento ${atendimento.Codigo}`,
|
|
|
|
|
+ metadata: { ...metadataBase, tipo: "conversa" },
|
|
|
|
|
+ text: conversaTexto
|
|
|
|
|
+ },
|
|
|
|
|
+ ...(blocoAvaliacao
|
|
|
|
|
+ ? [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: `${atendimento.Id}-avaliacao`,
|
|
|
|
|
+ source: `atendimento:${atendimento.Codigo}`,
|
|
|
|
|
+ title: `Atendimento ${atendimento.Codigo} — Avaliação`,
|
|
|
|
|
+ metadata: { ...metadataBase, tipo: "avaliacao" },
|
|
|
|
|
+ text: blocoAvaliacao
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ : [])
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ const { upserted } = await ingestDocuments(documentos, {
|
|
|
|
|
+ collectionName: config.atendimentosRag.collection,
|
|
|
|
|
+ chunkSize: config.atendimentosRag.chunkSize,
|
|
|
|
|
+ chunkOverlap: config.atendimentosRag.chunkOverlap
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
await upsertRagIndex(atendimento.Id, {
|
|
await upsertRagIndex(atendimento.Id, {
|
|
|
UltimaMensagemId: ultimaMensagemId,
|
|
UltimaMensagemId: ultimaMensagemId,
|
|
|
|
|
+ AvaliacaoUpdatedAt: avaliacaoUpdatedAt,
|
|
|
ChunksCount: upserted,
|
|
ChunksCount: upserted,
|
|
|
EmbeddingModel: config.ollama.embeddingsModel
|
|
EmbeddingModel: config.ollama.embeddingsModel
|
|
|
});
|
|
});
|
|
@@ -109,10 +141,16 @@ function pendentesQuery({ setor } = {}) {
|
|
|
.select("atendimentos.Id")
|
|
.select("atendimentos.Id")
|
|
|
.join(subMax, "m.AtendimentoId", "atendimentos.Id")
|
|
.join(subMax, "m.AtendimentoId", "atendimentos.Id")
|
|
|
.leftJoin("atendimento_rag_index as ri", "ri.AtendimentoId", "atendimentos.Id")
|
|
.leftJoin("atendimento_rag_index as ri", "ri.AtendimentoId", "atendimentos.Id")
|
|
|
|
|
+ .leftJoin("atendimento_avaliacoes as av", "av.AtendimentoId", "atendimentos.Id")
|
|
|
.where((q) => {
|
|
.where((q) => {
|
|
|
q.whereNull("ri.AtendimentoId")
|
|
q.whereNull("ri.AtendimentoId")
|
|
|
.orWhereNull("ri.UltimaMensagemId")
|
|
.orWhereNull("ri.UltimaMensagemId")
|
|
|
- .orWhereRaw("ri.UltimaMensagemId < m.MaxMsgId");
|
|
|
|
|
|
|
+ .orWhereRaw("ri.UltimaMensagemId < m.MaxMsgId")
|
|
|
|
|
+ .orWhere((q2) => {
|
|
|
|
|
+ q2.where("av.Avaliavel", true)
|
|
|
|
|
+ .whereNotNull("av.UpdatedAt")
|
|
|
|
|
+ .andWhere((q3) => q3.whereNull("ri.AvaliacaoUpdatedAt").orWhereRaw("ri.AvaliacaoUpdatedAt < av.UpdatedAt"));
|
|
|
|
|
+ });
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
if (setor) query.where("atendimentos.Setor", setor);
|
|
if (setor) query.where("atendimentos.Setor", setor);
|
|
@@ -161,8 +199,10 @@ export async function buscarAtendimentosSemelhantes({
|
|
|
topK = config.atendimentosRag.topK,
|
|
topK = config.atendimentosRag.topK,
|
|
|
minScore = config.atendimentosRag.minScore,
|
|
minScore = config.atendimentosRag.minScore,
|
|
|
hyde = config.atendimentosRag.hyde,
|
|
hyde = config.atendimentosRag.hyde,
|
|
|
|
|
+ rerank = config.atendimentosRag.rerank,
|
|
|
collectionName = config.atendimentosRag.collection,
|
|
collectionName = config.atendimentosRag.collection,
|
|
|
- embeddingsModel = config.ollama.embeddingsModel
|
|
|
|
|
|
|
+ embeddingsModel = config.ollama.embeddingsModel,
|
|
|
|
|
+ filter
|
|
|
}) {
|
|
}) {
|
|
|
let embeddingQuery = query;
|
|
let embeddingQuery = query;
|
|
|
let embedRole = "query";
|
|
let embedRole = "query";
|
|
@@ -183,11 +223,12 @@ export async function buscarAtendimentosSemelhantes({
|
|
|
minScore,
|
|
minScore,
|
|
|
collectionName,
|
|
collectionName,
|
|
|
embedRole,
|
|
embedRole,
|
|
|
- embeddingsModel
|
|
|
|
|
|
|
+ embeddingsModel,
|
|
|
|
|
+ filter
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
-
|
|
|
|
|
- if (!config.atendimentosRag.rerank || hits.length <= 1) return hits;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (!rerank || hits.length <= 1) return hits;
|
|
|
return rerankHits(query, hits, {
|
|
return rerankHits(query, hits, {
|
|
|
topN: config.atendimentosRag.rerankTopN,
|
|
topN: config.atendimentosRag.rerankTopN,
|
|
|
numCtx: config.llm.atendimentosNumCtx
|
|
numCtx: config.llm.atendimentosNumCtx
|