60 lines
2.6 KiB
JavaScript
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);
|