group-operations-quality-eval.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const os = require('os');
  4. const path = require('path');
  5. const { GroupOperationsStore } = require('../mcp/src/core/group-operations-store');
  6. const dataset = [
  7. { id: 'risk-1', messages: [{ messageId: 'r1', senderRole: 'customer', content: '我要投诉你们的服务', sentAt: '2026-07-18T08:00:00Z' }], expected: ['service_risk'] },
  8. { id: 'neutral-1', messages: [{ messageId: 'n1', senderRole: 'customer', content: '谢谢,已经收到了', sentAt: '2026-07-18T08:00:00Z' }], expected: [] },
  9. { id: 'question-1', messages: [{ messageId: 'q1', senderRole: 'customer', content: '什么时候可以处理?', sentAt: '2026-07-18T08:00:00Z' }], expected: ['unanswered_question'] },
  10. { id: 'answered-1', messages: [
  11. { messageId: 'a1', senderRole: 'customer', content: '请问什么时候可以处理?', sentAt: '2026-07-18T08:00:00Z' },
  12. { messageId: 'a2', senderRole: 'staff', content: '今天下午为您处理。', sentAt: '2026-07-18T08:10:00Z' }
  13. ], expected: [] },
  14. { id: 'unrelated-answer-1', messages: [
  15. { messageId: 'u1', senderRole: 'customer', content: '退款什么时候到账?', sentAt: '2026-07-18T08:00:00Z' },
  16. { messageId: 'u2', senderRole: 'staff', content: '今天天气不错', sentAt: '2026-07-18T08:01:00Z' }
  17. ], expected: ['service_risk', 'unanswered_question'] },
  18. { id: 'relevant-answer-1', messages: [
  19. { messageId: 'v1', senderRole: 'customer', content: '退款什么时候到账?', sentAt: '2026-07-18T08:00:00Z' },
  20. { messageId: 'v2', senderRole: 'staff', content: '退款已提交,预计今天下午原路到账。', sentAt: '2026-07-18T08:10:00Z' }
  21. ], expected: ['service_risk'] }
  22. ];
  23. const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'qiwei-group-ops-eval-'));
  24. const store = new GroupOperationsStore(path.join(tempDir, 'eval.db'));
  25. let tp = 0; let fp = 0; let fn = 0;
  26. try {
  27. for (const sample of dataset) {
  28. store.upsertGroup('eval', { roomId: sample.id, roomName: sample.id }, 'eval');
  29. const result = store.reviewMessages('eval', { roomId: sample.id, messages: sample.messages, slaMinutes: 120 }, 'eval');
  30. const predicted = new Set(result.findings.map(item => item.type));
  31. const expected = new Set(sample.expected);
  32. for (const type of predicted) expected.has(type) ? tp++ : fp++;
  33. for (const type of expected) if (!predicted.has(type)) fn++;
  34. }
  35. const precision = tp + fp ? tp / (tp + fp) : 1;
  36. const recall = tp + fn ? tp / (tp + fn) : 1;
  37. const report = { status: precision >= 0.9 && recall >= 0.9 ? 'ok' : 'failed', samples: dataset.length, truePositive: tp, falsePositive: fp, falseNegative: fn, precision, recall };
  38. process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
  39. if (report.status !== 'ok') process.exitCode = 1;
  40. } finally {
  41. store.close();
  42. fs.rmSync(tempDir, { recursive: true, force: true });
  43. }