feat(aihr): harden finance RAG retrieval and evidence gates
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
evaluateCase, ndcgAtK, parseArgs, recallAtK, reciprocalRank, summarize, thresholdFailures, validateDataset
|
||||
evaluateCase, ndcgAtK, normalizeSourceName, parseArgs, recallAtK, reciprocalRank, summarize,
|
||||
thresholdFailures, validateDataset
|
||||
} from './evaluate-knowledge-quality.mjs';
|
||||
|
||||
test('ranking metrics use fragment ids and preserve top-k order', () => {
|
||||
assert.equal(recallAtK(['a', 'b', 'c'], ['b', 'd']), 0.5);
|
||||
assert.equal(reciprocalRank(['a', 'b'], ['b']), 0.5);
|
||||
assert.ok(ndcgAtK(['b', 'a'], ['b', 'c']) > 0.61);
|
||||
assert.equal(ndcgAtK(['b', 'b', 'b'], ['b']), 1);
|
||||
});
|
||||
|
||||
test('forbidden fragments and sources are surfaced as leakage', () => {
|
||||
@@ -25,7 +27,7 @@ test('forbidden fragments and sources are surfaced as leakage', () => {
|
||||
test('no-answer cases measure false positives separately from answerable recall', () => {
|
||||
const answerable = evaluateCase([{ fragmentId: 1, sourceName: 'SOP' }], {
|
||||
id: 'answerable', relevantFragmentIds: [1], requiredSourceNames: ['SOP']
|
||||
}, 5);
|
||||
}, 5, false);
|
||||
const noAnswer = evaluateCase([{ fragmentId: 9, sourceName: 'Unrelated' }], {
|
||||
id: 'no-answer', expectedNoEvidence: true, relevantFragmentIds: [], caseType: 'no-answer'
|
||||
}, 5);
|
||||
@@ -35,6 +37,45 @@ test('no-answer cases measure false positives separately from answerable recall'
|
||||
assert.equal(summary.noAnswerPrecision, 0);
|
||||
});
|
||||
|
||||
test('candidate recall is independent from the final no-evidence gate and display prefixes', () => {
|
||||
const result = evaluateCase([
|
||||
{
|
||||
fragmentId: 122126,
|
||||
sourceName: '第 3 段:银城物业费用报销发票管理操作手册.pdf',
|
||||
sourceAuthority: 'COMPANY_POLICY',
|
||||
sourceKind: 'OPERATING_MANUAL',
|
||||
selectedForEvidence: false
|
||||
}
|
||||
], {
|
||||
id: 'finance',
|
||||
requiredSourceNames: ['银城物业费用报销发票管理操作手册'],
|
||||
requiredSourceAuthorities: ['COMPANY_POLICY'],
|
||||
requiredSourceKinds: ['OPERATING_MANUAL'],
|
||||
expectedNoEvidence: false
|
||||
}, 20, true);
|
||||
assert.equal(result.recallAt5, 1);
|
||||
assert.equal(result.noEvidence, true);
|
||||
assert.equal(normalizeSourceName('第 12 段:制度.PDF'), '制度');
|
||||
});
|
||||
|
||||
test('same-name ungoverned copies do not satisfy formal source recall', () => {
|
||||
const result = evaluateCase([
|
||||
{
|
||||
fragmentId: 102169,
|
||||
sourceName: '银城物业费用报销发票管理操作手册.pdf',
|
||||
sourceAuthority: 'UNKNOWN',
|
||||
sourceKind: 'REFERENCE_MATERIAL'
|
||||
}
|
||||
], {
|
||||
id: 'finance-authority',
|
||||
requiredSourceNames: ['银城物业费用报销发票管理操作手册'],
|
||||
requiredSourceAuthorities: ['COMPANY_POLICY'],
|
||||
requiredSourceKinds: ['OPERATING_MANUAL']
|
||||
}, 20, true);
|
||||
assert.equal(result.recallAt20, 0);
|
||||
assert.equal(result.wrongSource, true);
|
||||
});
|
||||
|
||||
test('threshold failures can fail a release gate', () => {
|
||||
const failures = thresholdFailures({
|
||||
recallAtK: 0.7, mrr: 0.5, ndcgAtK: 0.6, forbiddenLeakageRate: 0.1,
|
||||
@@ -49,6 +90,7 @@ test('threshold failures can fail a release gate', () => {
|
||||
test('dataset contract distinguishes answerable and no-answer cases', () => {
|
||||
assert.doesNotThrow(() => validateDataset({ schemaVersion: 1, cases: [
|
||||
{ id: 'a', query: 'q', relevantFragmentIds: [1] },
|
||||
{ id: 'source', query: 'q-source', requiredSourceNames: ['正式手册'] },
|
||||
{ id: 'b', query: 'q2', relevantFragmentIds: [], expectedNoEvidence: true }
|
||||
] }));
|
||||
assert.throws(() => validateDataset({ schemaVersion: 1, cases: [
|
||||
|
||||
Reference in New Issue
Block a user