|
|
@@ -1,6 +1,7 @@
|
|
|
import { config } from "../config/index.js";
|
|
|
import { chatCompletion } from "./ollamaClient.js";
|
|
|
import { buildConversaTexto, calcularUltimaMensagemId } from "../utils/atendimentoFormat.js";
|
|
|
+import { buscarContextoIxc } from "./atendimentoIxcContextoService.js";
|
|
|
import { Atendimento } from "../models/Atendimento.model.js";
|
|
|
import { AtendimentoAvaliacao } from "../models/AtendimentoAvaliacao.model.js";
|
|
|
import { AtendimentoMensagem } from "../models/AtendimentoMensagem.model.js";
|
|
|
@@ -17,6 +18,16 @@ const AVALIACAO_SYSTEM_PROMPT = [
|
|
|
"Continue avaliando normalmente a qualidade e o conteúdo da resposta dada.",
|
|
|
"Baseie-se APENAS no que está na conversa; não invente fatos, nomes ou desfechos.",
|
|
|
"",
|
|
|
+ 'Quando a mensagem do usuário incluir um bloco "=== DADO FACTUAL DO ERP (IXC) ===", trate-o',
|
|
|
+ "como registro real do sistema da empresa (não é inferência de IA nem parte da",
|
|
|
+ "conversa) — use-o para checar e ancorar resolvido, status_cancelamento e",
|
|
|
+ "motivo_cancelamento quando a conversa for ambígua ou incompleta sobre o desfecho; a",
|
|
|
+ '"nota de resolução do técnico", quando presente, costuma dizer diretamente o que foi',
|
|
|
+ "feito e se o problema foi resolvido. IMPORTANTE: score_atendente continua avaliando",
|
|
|
+ "apenas a conduta do atendente NA CONVERSA (clareza, agilidade, cordialidade) — não",
|
|
|
+ "julgue nem penalize pelo desfecho técnico registrado no ERP, que pode depender de",
|
|
|
+ "fatores fora do controle do atendente do chat.",
|
|
|
+ "",
|
|
|
"Tempo de resposta NÃO é o critério principal da nota. Intervalos de minutos até cerca de uma hora",
|
|
|
"entre mensagens são comuns na operação e, sozinhos, não devem derrubar o score — priorize sempre se",
|
|
|
"o problema foi entendido, se a solução foi correta e clara, e se o cliente saiu satisfeito. Só deixe",
|
|
|
@@ -147,6 +158,27 @@ const AVALIACAO_JSON_SCHEMA = {
|
|
|
const TRUNCATION_MARKER = "\n[... trecho intermediário da conversa omitido ...]\n";
|
|
|
|
|
|
|
|
|
+function formatarContextoIxc(contexto) {
|
|
|
+ const linhas = ["=== DADO FACTUAL DO ERP (IXC), fora da conversa ==="];
|
|
|
+ if (contexto.clienteNome) linhas.push(`Cliente cadastrado no ERP: ${contexto.clienteNome}`);
|
|
|
+
|
|
|
+ if (!contexto.osList.length) {
|
|
|
+ linhas.push("Nenhuma ordem de serviço técnica registrada para este atendimento no ERP.");
|
|
|
+ } else {
|
|
|
+ contexto.osList.forEach((os, i) => {
|
|
|
+ linhas.push(`OS ${i + 1}${os.assunto ? ` (${os.assunto})` : ""}:`);
|
|
|
+ linhas.push(
|
|
|
+ ` aberta em ${os.dataAbertura ?? "?"}` +
|
|
|
+ (os.dataFechamento ? `, fechada em ${os.dataFechamento}` : ", ainda em aberto")
|
|
|
+ );
|
|
|
+ if (os.tecnico) linhas.push(` técnico responsável: ${os.tecnico}`);
|
|
|
+ if (os.mensagemResposta) linhas.push(` nota de resolução do técnico: "${os.mensagemResposta}"`);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ linhas.push("=== fim do dado do ERP ===");
|
|
|
+ return linhas.join("\n");
|
|
|
+}
|
|
|
+
|
|
|
function truncateConversa(text, maxChars = config.avaliacao.maxChars) {
|
|
|
if (text.length <= maxChars) return text;
|
|
|
const head = Math.floor(maxChars * 0.45);
|
|
|
@@ -286,12 +318,17 @@ export async function avaliarAtendimento(atendimentoId) {
|
|
|
mensagens
|
|
|
});
|
|
|
|
|
|
+ const contextoIxc = await buscarContextoIxc(atendimento);
|
|
|
+ const conteudoUsuario = contextoIxc
|
|
|
+ ? `${formatarContextoIxc(contextoIxc)}\n\n${truncateConversa(conversaTexto)}`
|
|
|
+ : truncateConversa(conversaTexto);
|
|
|
+
|
|
|
const model = config.avaliacao.model || config.ollama.chatModel;
|
|
|
const { content } = await chatCompletion({
|
|
|
model,
|
|
|
messages: [
|
|
|
{ role: "system", content: AVALIACAO_SYSTEM_PROMPT },
|
|
|
- { role: "user", content: truncateConversa(conversaTexto) }
|
|
|
+ { role: "user", content: conteudoUsuario }
|
|
|
],
|
|
|
format: AVALIACAO_JSON_SCHEMA,
|
|
|
options: { temperature: 0.1, num_ctx: 8192, num_predict: 1024 }
|