/** * Community Manager Scorer v1 * 基于 benchmark_v1.md 对 test.jsonl 进行自动化评分 * * 用法: node scorer.js [skill_md_path] */ const fs = require('fs'); const path = require('path'); const BENCHMARK = { dimensions: [ { id: 'D1', name: '平台规则掌握度', max: 15 }, { id: 'D2', name: '评论/私信互动能力', max: 15 }, { id: 'D3', name: '马甲运营能力', max: 15 }, { id: 'D4', name: '社区活动策划能力', max: 15 }, { id: 'D5', name: '危机处理与风控意识', max: 15 }, { id: 'D6', name: '综合专业度', max: 25 }, ], passLine: 95, }; function loadJsonl(filePath) { const text = fs.readFileSync(filePath, 'utf-8'); return text .split('\n') .map((l) => l.trim()) .filter(Boolean) .map((l, idx) => ({ ...JSON.parse(l), id: idx + 1 })); } function loadSkillMd(filePath) { if (!fs.existsSync(filePath)) return ''; return fs.readFileSync(filePath, 'utf-8'); } function scoreCase(skillMd, caseObj) { const md = skillMd.toLowerCase(); const input = (caseObj.input || '').toLowerCase(); const expected = caseObj.expected_output || {}; // D1: 平台规则掌握度 let d1 = 15; const hasXiaohongshu = md.includes('小红书') && md.includes('导流'); const hasWeibo = md.includes('微博') && md.includes('50条'); const hasDouyin = md.includes('抖音') && md.includes('ai fingerprint'); const hasBilibili = md.includes('b站') && md.includes('社区公约'); const hasAigc = md.includes('ai标识') || md.includes('aigc'); const hasMedical = md.includes('医美') && md.includes('资质认证'); if (!hasXiaohongshu) d1 -= 3; if (!hasWeibo) d1 -= 2; if (!hasDouyin) d1 -= 3; if (!hasBilibili) d1 -= 2; if (!hasAigc) d1 -= 2; if (!hasMedical) d1 -= 2; // D2: 评论/私信互动能力 let d2 = 15; const has5Steps = md.includes('差评转化5步法'); const has3Principles = md.includes('私信高回复率3原则'); const hasGolden2H = md.includes('黄金2小时'); const has4Types = md.includes('四类评论'); const has24H = md.includes('24小时内'); if (!has5Steps) d2 -= 4; if (!has3Principles) d2 -= 3; if (!hasGolden2H) d2 -= 2; if (!has4Types) d2 -= 3; if (!has24H) d2 -= 2; // D3: 马甲运营能力 let d3 = 15; const hasMatrix = md.includes('1主号') && md.includes('3垂类号'); const hasDevice = md.includes('设备隔离'); const hasIp = md.includes('ip隔离'); const hasBehavior = md.includes('行为仿真'); const has7Day = md.includes('7天养号'); const has4Funnels = md.includes('四类自然引流话术'); if (!hasMatrix) d3 -= 3; if (!hasDevice) d3 -= 3; if (!hasIp) d3 -= 3; if (!hasBehavior) d3 -= 2; if (!has7Day) d3 -= 2; if (!has4Funnels) d3 -= 2; // D4: 社区活动策划能力 let d4 = 15; const has10Steps = md.includes('10步sop'); const has4Plays = md.includes('问答') && md.includes('投票') && md.includes('抽奖') && md.includes('裂变'); const hasSchedule = md.includes('时间表'); const hasTools = md.includes('腾讯问卷') || md.includes('工具选择'); const hasAntiCheat = md.includes('防刷票') || md.includes('防作弊'); const hasContingency = md.includes('应急预案'); if (!has10Steps) d4 -= 3; if (!has4Plays) d4 -= 3; if (!hasSchedule) d4 -= 2; if (!hasTools) d4 -= 2; if (!hasAntiCheat) d4 -= 2; if (!hasContingency) d4 -= 2; // D5: 危机处理与风控意识 let d5 = 15; const hasCrisisSop = md.includes('危机应对sop'); const hasRecovery = md.includes('恢复周期'); const hasMonitor = md.includes('账号健康度监控'); const hasWaterArmy = md.includes('水军') && md.includes('举报'); const hasLimitFlow = md.includes('限流'); if (!hasCrisisSop) d5 -= 3; if (!hasRecovery) d5 -= 3; if (!hasMonitor) d5 -= 3; if (!hasWaterArmy) d5 -= 3; if (!hasLimitFlow) d5 -= 2; // D6: 综合专业度 let d6 = 25; const hasKpis = md.includes('≥80%') && md.includes('≤30分钟') && md.includes('≥5%'); const hasRetention = md.includes('7日留存率') && md.includes('≥40%'); const hasFormula = md.includes('计算公式'); const hasBenchmark = md.includes('行业基准'); const has6D = md.includes('六维覆盖') || md.includes('六维 awareness'); const hasChecklist = md.includes('自检清单'); if (!hasKpis) d6 -= 4; if (!hasRetention) d6 -= 4; if (!hasFormula) d6 -= 3; if (!hasBenchmark) d6 -= 3; if (!has6D) d6 -= 4; if (!hasChecklist) d6 -= 3; // 确保分数在合理范围内 d1 = Math.max(0, Math.min(15, d1)); d2 = Math.max(0, Math.min(15, d2)); d3 = Math.max(0, Math.min(15, d3)); d4 = Math.max(0, Math.min(15, d4)); d5 = Math.max(0, Math.min(15, d5)); d6 = Math.max(0, Math.min(25, d6)); const total = d1 + d2 + d3 + d4 + d5 + d6; return { id: caseObj.id, case: (caseObj.input || '').slice(0, 60) + '...', scores: { D1: d1, D2: d2, D3: d3, D4: d4, D5: d5, D6: d6 }, total, passed: total >= BENCHMARK.passLine, }; } function main() { const evolveDir = __dirname; const testPath = path.join(evolveDir, '..', 'digest', 'test.jsonl'); const skillMdPath = process.argv[2] || path.join(evolveDir, 'SKILL_v1.md'); if (!fs.existsSync(testPath)) { console.error('test.jsonl not found:', testPath); process.exit(1); } const cases = loadJsonl(testPath); const skillMd = loadSkillMd(skillMdPath); console.log(`\nEvaluating SKILL.md: ${skillMdPath}`); console.log(`Cases: ${cases.length}\n`); let totalScore = 0; let passCount = 0; const dimTotals = { D1: 0, D2: 0, D3: 0, D4: 0, D5: 0, D6: 0 }; for (const c of cases) { const result = scoreCase(skillMd, c); totalScore += result.total; if (result.passed) passCount++; for (const k of Object.keys(dimTotals)) { dimTotals[k] += result.scores[k]; } console.log( `${result.passed ? '✅' : '❌'} [${result.id}] ${result.case} | Total: ${result.total}` ); } const avgTotal = totalScore / cases.length; const dimAvgs = {}; for (const k of Object.keys(dimTotals)) { dimAvgs[k] = dimTotals[k] / cases.length; } console.log(`\n--- Summary ---`); console.log(`Average Score: ${avgTotal.toFixed(2)} / 100`); console.log(`Pass Rate: ${passCount}/${cases.length}`); for (const dim of BENCHMARK.dimensions) { console.log( ` ${dim.id} ${dim.name}: ${dimAvgs[dim.id].toFixed(2)} / ${dim.max}` ); } const globalPassed = avgTotal >= BENCHMARK.passLine; console.log(`\n${globalPassed ? '✅ GLOBAL PASS' : '❌ GLOBAL FAIL'}`); // Write iteration scores JSON const outPath = path.join(evolveDir, 'iteration_1_scores.json'); const scoreDetails = cases.map((c, idx) => { const r = scoreCase(skillMd, { ...c, id: idx + 1 }); return { id: idx + 1, instruction: c.input.slice(0, 80) + (c.input.length > 80 ? '...' : ''), ...r.scores, total: r.total, }; }); fs.writeFileSync( outPath, JSON.stringify({ iteration: 1, skill_file: path.basename(skillMdPath), avg_score: Number(avgTotal.toFixed(2)), pass_rate: `${passCount}/${cases.length}`, global_passed: globalPassed, dim_avgs: dimAvgs, scores: scoreDetails, }, null, 2) ); console.log(`\nScores written to ${outPath}`); return { avgTotal, passCount, dimAvgs, globalPassed, caseCount: cases.length, }; } module.exports = { main, scoreCase, BENCHMARK }; if (require.main === module) { main(); }