Files
2026-10-23 10:35:28 +08:00

60 lines
2.6 KiB
JavaScript

#!/usr/bin/env node
'use strict';
// 离线回归评测:不调用 LLM,覆盖意图/注入/商品词/lead/格式/引用审计等确定性逻辑。
const path = require('path');
const fs = require('fs');
const { quickClassify } = require('../bridge/src/intent');
const { detectInjection } = require('../bridge/src/security');
const { translateProductQuery } = require('../bridge/src/product-query');
const { hasBuyingIntent, missingLeadContacts } = require('../bridge/src/lead-intent');
const { markdownToHtml } = require('../bridge/src/markdown');
const { auditAnswer } = require('../bridge/src/audit');
const ENTRIES = [
{ title: 'Rings', category: 'jewelry/rings', url: 'https://www.yehwang.com/jewelry/rings', aliases: ['戒指', 'ringen'] },
{ title: 'Earrings', category: 'jewelry/earrings', url: 'https://www.yehwang.com/jewelry/earrings', aliases: ['耳环'] },
{ title: 'Necklaces', category: 'jewelry/necklaces', url: 'https://www.yehwang.com/jewelry/necklaces', aliases: ['项链'] },
];
const cases = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../bridge/eval/golden.json'), 'utf8'));
function run(c) {
if (c.type === 'intent') return quickClassify(c.message);
if (c.type === 'injection') return detectInjection(c.message).level;
if (c.type === 'product_query') return translateProductQuery(c.message, ENTRIES).query;
if (c.type === 'lead') {
const missing = missingLeadContacts(c.slots || {});
return missing.join(',');
}
if (c.type === 'markdown') return markdownToHtml(c.message);
if (c.type === 'audit') {
const audit = auditAnswer({
finalContent: c.message,
toolResults: [{ results: [{ source_urls: c.tool_urls || [] }] }],
toolCalls: [],
});
return String(audit.unverified_urls.length);
}
throw new Error(`unknown case type: ${c.type}`);
}
function check(c, actual) {
if (c.type === 'lead') return actual === (c.expect_missing || []).join(',');
if (c.type === 'markdown') {
return (c.expect_contains || []).every((s) => actual.includes(s))
&& (c.expect_not_contains || []).every((s) => !actual.includes(s));
}
if (c.type === 'audit') return actual === String(c.expect_unverified);
return actual === c.expect;
}
let failed = 0;
for (const c of cases) {
const actual = run(c);
const ok = check(c, actual);
if (!ok) failed += 1;
console.log(`${ok ? 'PASS' : 'FAIL'} ${c.id}${ok ? '' : ` expected=${JSON.stringify(c.expect || c.expect_missing || c.expect_contains)} actual=${JSON.stringify(actual)}`}`);
}
console.log(`\n${cases.length - failed}/${cases.length} passed`);
process.exit(failed ? 1 : 0);