import { config } from "#config/index.js"; import { buscarAtendimentosSemelhantes } from "#services/atendimentoRagService.js"; function parseArgs(argv) { const flags = { hyde: null, minScore: 0, topK: 5, label: "" }; for (const arg of argv) { if (arg === "--hyde") flags.hyde = true; else if (arg === "--no-hyde") flags.hyde = false; else if (arg.startsWith("--min-score=")) flags.minScore = Number(arg.split("=")[1]); else if (arg.startsWith("--top-k=")) flags.topK = Number(arg.split("=")[1]); else if (arg.startsWith("--label=")) flags.label = arg.split("=").slice(1).join("="); } return flags; } const CASES = [ { query: "cliente pedindo segunda via de boleto", type: "positive" }, { query: "problema de internet lenta", type: "positive" }, { query: "cancelamento de contrato", type: "positive" }, { query: "cliente reclamando que a internet caiu de novo", type: "positive" }, { query: "solicitação de mudança de endereço de instalação", type: "positive" }, { query: "cliente questionando cobrança duplicada na fatura", type: "positive" }, { query: "pedido de upgrade de plano de internet", type: "positive" }, { query: "previsão do tempo para amanhã em Paris", type: "negative" }, { query: "receita de bolo de chocolate", type: "negative" }, { query: "quem ganhou o jogo de futebol ontem", type: "negative" }, { query: "qual a capital da Austrália", type: "negative" }, { query: "como treinar para uma maratona", type: "negative" }, { query: "melhores filmes de ficção científica dos anos 80", type: "negative" } ]; async function runCase(testCase, flags) { const hits = await buscarAtendimentosSemelhantes({ query: testCase.query, topK: flags.topK, minScore: flags.minScore, ...(flags.hyde !== null ? { hyde: flags.hyde } : {}) }); const top1 = hits[0] ?? null; return { query: testCase.query, type: testCase.type, top1_score: top1 ? top1.score.toFixed(4) : "-", top1_source: top1?.source ?? "-" }; } async function main() { const flags = parseArgs(process.argv.slice(2)); const hydeEfetivo = flags.hyde !== null ? flags.hyde : config.atendimentosRag.hyde; console.log(`\n=== evalAtendimentosRetrieval ${flags.label ? `[${flags.label}] ` : ""}===`); console.log( `modelo=${config.ollama.embeddingsModel} minScore=${flags.minScore} topK=${flags.topK} ` + `hyde=${hydeEfetivo} colecao=${config.atendimentosRag.collection}\n` ); const rows = []; for (const testCase of CASES) { rows.push(await runCase(testCase, flags)); } console.table(rows); const positives = rows.filter((r) => r.type === "positive"); const negatives = rows.filter((r) => r.type === "negative"); const positiveScores = positives.map((r) => Number(r.top1_score)).filter((n) => !Number.isNaN(n)); const negativeScores = negatives.map((r) => Number(r.top1_score)).filter((n) => !Number.isNaN(n)); const avg = (arr) => (arr.length ? arr.reduce((a, b) => a + b, 0) / arr.length : NaN); const positivoMin = positiveScores.length ? Math.min(...positiveScores) : NaN; const negativoMax = negativeScores.length ? Math.max(...negativeScores) : NaN; const gap = positivoMin - negativoMax; console.log("\n--- resumo ---"); console.log(`positivos: score médio top-1 = ${avg(positiveScores).toFixed(4)}`); console.log(`positivos: score mínimo top-1 (piso de segurança) = ${positiveScores.length ? positivoMin.toFixed(4) : "-"}`); console.log(`negativos: score médio top-1 = ${avg(negativeScores).toFixed(4)}`); console.log(`negativos: score máximo top-1 (teto de ruído) = ${negativeScores.length ? negativoMax.toFixed(4) : "-"}`); console.log(`gap (piso positivos - teto negativos) = ${Number.isNaN(gap) ? "-" : gap.toFixed(4)} ${!Number.isNaN(gap) ? (gap > 0 ? "-> HÁ separação viável" : "-> SEM separação") : ""}`); console.log(""); } main() .then(() => process.exit(0)) .catch((err) => { console.error("[evalAtendimentosRetrieval] erro:", err); process.exit(1); });