Files
wehub-resource-sync 0d3cb498a3
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Waiting to run
Test and Publish Multi-arch Docker Image / test (push) Waiting to run
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Blocked by required conditions
Validate Renovate Config / Validate Renovate Configuration (push) Waiting to run
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:24:08 +08:00

117 lines
4.5 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { handlePerplexity, handlePerplexityScore } from '../../src/assertions/perplexity';
import type { AssertionParams } from '../../src/types';
const params = (overrides: Partial<AssertionParams>): AssertionParams =>
({
assertion: { type: 'perplexity', threshold: 5 },
baseType: 'perplexity',
assertionValueContext: {} as any,
inverse: false,
output: '',
outputString: '',
providerResponse: { output: '' },
test: {},
...overrides,
}) as AssertionParams;
// logProbs [0, 0] => perplexity = exp(0) = 1 (within threshold 5)
// logProbs [-2] => perplexity = exp(2) ≈ 7.39 (exceeds threshold 5)
describe('handlePerplexity', () => {
it('passes when perplexity is within threshold', () => {
expect(handlePerplexity(params({ logProbs: [0, 0] })).pass).toBe(true);
});
it('fails when perplexity exceeds threshold', () => {
expect(handlePerplexity(params({ logProbs: [-2] })).pass).toBe(false);
});
it('passes when no threshold is set', () => {
expect(
handlePerplexity(params({ assertion: { type: 'perplexity' }, logProbs: [-2] })).pass,
).toBe(true);
});
it('throws when logProbs are not provided', () => {
expect(() => handlePerplexity(params({ logProbs: undefined }))).toThrow(
'does not support providers that do not return logProbs',
);
});
describe('inverse (not-perplexity)', () => {
it('fails when perplexity is within threshold', () => {
const result = handlePerplexity(params({ logProbs: [0, 0], inverse: true }));
expect(result.pass).toBe(false);
expect(result.reason).toContain('less than or equal to');
});
it('passes when perplexity exceeds threshold', () => {
expect(handlePerplexity(params({ logProbs: [-2], inverse: true })).pass).toBe(true);
});
it('fails at the threshold boundary (perplexity === threshold is "within")', () => {
// logProbs [-ln(5)] => perplexity = exp(ln(5)) = 5, exactly the threshold
const result = handlePerplexity(params({ logProbs: [-Math.log(5)], inverse: true }));
expect(result.pass).toBe(false);
});
});
});
describe('handlePerplexityScore inverse (not-perplexity-score)', () => {
// Use an asymmetric input so the score inversion is actually exercised: logProbs [-2] =>
// perplexity = exp(2) ≈ 7.39 => perplexityNorm = 1 / (1 + 7.39) ≈ 0.1192, and 1 - 0.1192 ≈
// 0.8808. A symmetric input like logProbs [0, 0] (norm 0.5, its own complement) would yield the
// same value whether or not the score is inverted, so it cannot catch a regression here.
it('inverts the normalized score under not- (1 - perplexityNorm)', () => {
const base = handlePerplexityScore(
params({ assertion: { type: 'perplexity-score', threshold: 0.5 }, logProbs: [-2] }),
);
expect(base.score).toBeCloseTo(0.1192, 4);
const inverted = handlePerplexityScore(
params({
assertion: { type: 'perplexity-score', threshold: 0.5 },
logProbs: [-2],
inverse: true,
}),
);
// Inverting the graded score keeps perplexity-score aggregate-friendly ("higher is better")
// under negation: high perplexity (low norm) yields a high score for not-perplexity-score.
// This matters because assertionsResult overrides pass/fail with the aggregate score when a
// test/assertion-set threshold is configured.
expect(inverted.score).toBeCloseTo(0.8808, 4);
});
it('passes and contributes a high score when perplexity exceeds the normalized threshold', () => {
// norm ≈ 0.1192 < threshold 0.5 => base fails, so not- passes; inverted score ≈ 0.8808
const inverted = handlePerplexityScore(
params({
assertion: { type: 'perplexity-score', threshold: 0.5 },
logProbs: [-2],
inverse: true,
}),
);
expect(inverted.pass).toBe(true);
expect(inverted.score).toBeCloseTo(0.8808, 4);
});
it('fails when score is below threshold', () => {
// perplexityNorm = 0.5, threshold 0.9 => below => fail
const result = handlePerplexityScore(
params({ assertion: { type: 'perplexity-score', threshold: 0.9 }, logProbs: [0, 0] }),
);
expect(result.pass).toBe(false);
expect(result.reason).toContain('less than');
});
it('throws when logProbs are not provided', () => {
expect(() =>
handlePerplexityScore(
params({ assertion: { type: 'perplexity-score' }, logProbs: undefined }),
),
).toThrow('does not support providers that do not return logProbs');
});
});