evalAtendimentosRetrieval.mjs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import { config } from "#config/index.js";
  2. import { buscarAtendimentosSemelhantes } from "#services/atendimentoRagService.js";
  3. function parseArgs(argv) {
  4. const flags = { hyde: null, minScore: 0, topK: 5, label: "" };
  5. for (const arg of argv) {
  6. if (arg === "--hyde") flags.hyde = true;
  7. else if (arg === "--no-hyde") flags.hyde = false;
  8. else if (arg.startsWith("--min-score=")) flags.minScore = Number(arg.split("=")[1]);
  9. else if (arg.startsWith("--top-k=")) flags.topK = Number(arg.split("=")[1]);
  10. else if (arg.startsWith("--label=")) flags.label = arg.split("=").slice(1).join("=");
  11. }
  12. return flags;
  13. }
  14. const CASES = [
  15. { query: "cliente pedindo segunda via de boleto", type: "positive" },
  16. { query: "problema de internet lenta", type: "positive" },
  17. { query: "cancelamento de contrato", type: "positive" },
  18. { query: "cliente reclamando que a internet caiu de novo", type: "positive" },
  19. { query: "solicitação de mudança de endereço de instalação", type: "positive" },
  20. { query: "cliente questionando cobrança duplicada na fatura", type: "positive" },
  21. { query: "pedido de upgrade de plano de internet", type: "positive" },
  22. { query: "previsão do tempo para amanhã em Paris", type: "negative" },
  23. { query: "receita de bolo de chocolate", type: "negative" },
  24. { query: "quem ganhou o jogo de futebol ontem", type: "negative" },
  25. { query: "qual a capital da Austrália", type: "negative" },
  26. { query: "como treinar para uma maratona", type: "negative" },
  27. { query: "melhores filmes de ficção científica dos anos 80", type: "negative" }
  28. ];
  29. async function runCase(testCase, flags) {
  30. const hits = await buscarAtendimentosSemelhantes({
  31. query: testCase.query,
  32. topK: flags.topK,
  33. minScore: flags.minScore,
  34. ...(flags.hyde !== null ? { hyde: flags.hyde } : {})
  35. });
  36. const top1 = hits[0] ?? null;
  37. return {
  38. query: testCase.query,
  39. type: testCase.type,
  40. top1_score: top1 ? top1.score.toFixed(4) : "-",
  41. top1_source: top1?.source ?? "-"
  42. };
  43. }
  44. async function main() {
  45. const flags = parseArgs(process.argv.slice(2));
  46. const hydeEfetivo = flags.hyde !== null ? flags.hyde : config.atendimentosRag.hyde;
  47. console.log(`\n=== evalAtendimentosRetrieval ${flags.label ? `[${flags.label}] ` : ""}===`);
  48. console.log(
  49. `modelo=${config.ollama.embeddingsModel} minScore=${flags.minScore} topK=${flags.topK} ` +
  50. `hyde=${hydeEfetivo} colecao=${config.atendimentosRag.collection}\n`
  51. );
  52. const rows = [];
  53. for (const testCase of CASES) {
  54. rows.push(await runCase(testCase, flags));
  55. }
  56. console.table(rows);
  57. const positives = rows.filter((r) => r.type === "positive");
  58. const negatives = rows.filter((r) => r.type === "negative");
  59. const positiveScores = positives.map((r) => Number(r.top1_score)).filter((n) => !Number.isNaN(n));
  60. const negativeScores = negatives.map((r) => Number(r.top1_score)).filter((n) => !Number.isNaN(n));
  61. const avg = (arr) => (arr.length ? arr.reduce((a, b) => a + b, 0) / arr.length : NaN);
  62. const positivoMin = positiveScores.length ? Math.min(...positiveScores) : NaN;
  63. const negativoMax = negativeScores.length ? Math.max(...negativeScores) : NaN;
  64. const gap = positivoMin - negativoMax;
  65. console.log("\n--- resumo ---");
  66. console.log(`positivos: score médio top-1 = ${avg(positiveScores).toFixed(4)}`);
  67. console.log(`positivos: score mínimo top-1 (piso de segurança) = ${positiveScores.length ? positivoMin.toFixed(4) : "-"}`);
  68. console.log(`negativos: score médio top-1 = ${avg(negativeScores).toFixed(4)}`);
  69. console.log(`negativos: score máximo top-1 (teto de ruído) = ${negativeScores.length ? negativoMax.toFixed(4) : "-"}`);
  70. 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") : ""}`);
  71. console.log("");
  72. }
  73. main()
  74. .then(() => process.exit(0))
  75. .catch((err) => {
  76. console.error("[evalAtendimentosRetrieval] erro:", err);
  77. process.exit(1);
  78. });