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
217 lines
7.4 KiB
TypeScript
217 lines
7.4 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { calculateGleuScore, handleGleuScore } from '../../src/assertions/gleu';
|
|
|
|
import type { AssertionParams } from '../../src/types/index';
|
|
|
|
describe('GLEU score calculation', () => {
|
|
it('identical sentences should have GLEU score of 1', () => {
|
|
const references = ['The cat sat on the mat'];
|
|
const candidate = 'The cat sat on the mat';
|
|
|
|
const score = calculateGleuScore(candidate, references);
|
|
expect(score).toBe(1);
|
|
});
|
|
|
|
it('should handle period after words', () => {
|
|
const references = ['The cat sat on the mat'];
|
|
const candidate = 'The cat sat on the mat.';
|
|
|
|
const score = calculateGleuScore(candidate, references);
|
|
expect(score).toBeGreaterThan(0.95);
|
|
});
|
|
|
|
it('should handle the infamous "the the the … " example', () => {
|
|
const references = ['The cat sat on the mat'];
|
|
const candidate = 'the the the the the the the';
|
|
|
|
const score = calculateGleuScore(candidate, references);
|
|
// Due to how n-grams are counted, this will be approximately 0.09
|
|
expect(score).toBeCloseTo(0.09, 2);
|
|
});
|
|
|
|
it('should evaluate normal machine translation outputs correctly', () => {
|
|
const references = [
|
|
'It is a guide to action that ensures that the military will forever heed Party commands',
|
|
];
|
|
const candidate =
|
|
'It is a guide to action which ensures that the military always obeys the commands of the party';
|
|
|
|
const score = calculateGleuScore(candidate, references);
|
|
expect(score).toBeGreaterThan(0.35);
|
|
expect(score).toBeLessThanOrEqual(0.46);
|
|
});
|
|
|
|
it('should calculate the minimum of precision and recall correctly', () => {
|
|
// This test specifically checks the min(precision, recall) calculation
|
|
const references = ['One two three four five'];
|
|
const candidate = 'One two three';
|
|
|
|
const score = calculateGleuScore(candidate, references);
|
|
|
|
// Due to how n-grams are counted, the result is approximately 0.429
|
|
expect(score).toBeCloseTo(0.429, 1);
|
|
});
|
|
|
|
it('should calculate correctly when candidate is longer', () => {
|
|
const references = ['One two three'];
|
|
const candidate = 'One two three four five';
|
|
|
|
const score = calculateGleuScore(candidate, references);
|
|
|
|
// Due to how n-grams are counted, the result is approximately 0.429
|
|
expect(score).toBeCloseTo(0.429, 1);
|
|
});
|
|
|
|
it('should handle empty or single word sentences', () => {
|
|
const references = ['cat'];
|
|
const candidate = 'cat';
|
|
|
|
const score = calculateGleuScore(candidate, references);
|
|
expect(score).toBe(1);
|
|
});
|
|
|
|
it('should handle sentences with different lengths', () => {
|
|
const references = ['The cat sat on the mat.'];
|
|
const candidate = 'The cat sat.';
|
|
|
|
const score = calculateGleuScore(candidate, references);
|
|
// Due to how n-grams are counted, the result is approximately 0.33
|
|
expect(score).toBeCloseTo(0.33, 2);
|
|
});
|
|
|
|
it('should handle multiple references and take best matching score', () => {
|
|
const references = [
|
|
'The cat sat on the mat.',
|
|
'There is a cat on the mat.',
|
|
'A cat is sitting on the mat.',
|
|
];
|
|
const candidate = 'The cat was sitting on the mat.';
|
|
|
|
const score = calculateGleuScore(candidate, references);
|
|
expect(score).toBeGreaterThanOrEqual(0.5);
|
|
});
|
|
|
|
it('should throw error for empty reference array', () => {
|
|
expect(() => {
|
|
calculateGleuScore('test', []);
|
|
}).toThrow('Invalid inputs');
|
|
});
|
|
|
|
it('should handle multiple references with varying lengths', () => {
|
|
const references = ['The small cat sat.', 'A cat was sitting.', 'The cat is on the mat.'];
|
|
const candidate = 'The small cat sat.';
|
|
|
|
const score = calculateGleuScore(candidate, references);
|
|
expect(score).toBe(1);
|
|
});
|
|
|
|
it('should handle different minN values', () => {
|
|
const references = ['The cat sat on the mat.'];
|
|
const candidate = 'the the the the the the.';
|
|
|
|
const score = calculateGleuScore(candidate, references, 2);
|
|
expect(score).toBe(0); // This is 0 because there are no 2-grams in common
|
|
});
|
|
|
|
it('should handle different maxN values', () => {
|
|
const references = ['The cat sat on the mat.'];
|
|
const candidate = 'the the the the the the the.';
|
|
|
|
// Only using unigrams (n=1) here
|
|
const score = calculateGleuScore(candidate, references, 1, 1);
|
|
// Due to how n-grams are counted, the result is approximately 0.286
|
|
expect(score).toBeCloseTo(0.286, 1);
|
|
});
|
|
|
|
it('should aggregate n-gram matches across different n values', () => {
|
|
const references = ['The cat sat on the mat'];
|
|
const candidate = 'The cat on the mat';
|
|
|
|
// Using n-grams from 1 to 2
|
|
const score = calculateGleuScore(candidate, references, 1, 2);
|
|
|
|
// Due to how n-grams are counted, the result is approximately 0.727
|
|
expect(score).toBeCloseTo(0.727, 1);
|
|
});
|
|
|
|
describe('handleGleuScore', () => {
|
|
it('should handle string reference with passing score', () => {
|
|
const params = {
|
|
assertion: { type: 'gleu', value: 'The cat sat on the mat.' },
|
|
renderedValue: 'The cat sat on the mat.',
|
|
outputString: 'The cat sat on the mat.',
|
|
inverse: false,
|
|
} as AssertionParams;
|
|
expect(handleGleuScore(params)).toEqual({
|
|
pass: true,
|
|
score: expect.any(Number),
|
|
reason: 'Assertion passed',
|
|
assertion: expect.any(Object),
|
|
});
|
|
});
|
|
|
|
it('should handle array of references', () => {
|
|
const params = {
|
|
assertion: {
|
|
type: 'gleu',
|
|
value: ['The cat sat on mat.', 'The cat is sitting on mat.'],
|
|
},
|
|
renderedValue: ['The cat sat on mat.', 'The cat is sitting on mat.'],
|
|
outputString: 'The cat sat on mat.',
|
|
inverse: false,
|
|
} as AssertionParams;
|
|
expect(handleGleuScore(params)).toEqual({
|
|
pass: true,
|
|
score: expect.any(Number),
|
|
reason: 'Assertion passed',
|
|
assertion: expect.any(Object),
|
|
});
|
|
});
|
|
|
|
it('should handle custom threshold', () => {
|
|
const params = {
|
|
assertion: { type: 'gleu', value: 'The cat sat on the mat.', threshold: 0.8 },
|
|
renderedValue: 'The cat sat on the mat.',
|
|
outputString: 'The dog sat on the mat.',
|
|
inverse: false,
|
|
} as AssertionParams;
|
|
expect(handleGleuScore(params)).toEqual({
|
|
pass: false,
|
|
score: expect.any(Number),
|
|
reason: expect.stringMatching(/GLEU score \d+\.\d+ is less than threshold 0\.8/),
|
|
assertion: expect.any(Object),
|
|
});
|
|
});
|
|
|
|
it('should handle inverse assertion', () => {
|
|
const params = {
|
|
assertion: { type: 'gleu', value: 'The cat sat on the mat.', threshold: 0.8 },
|
|
renderedValue: 'The cat sat on the mat.',
|
|
outputString: 'The dog ran in the park.',
|
|
inverse: true,
|
|
} as AssertionParams;
|
|
expect(handleGleuScore(params)).toEqual({
|
|
pass: true,
|
|
score: expect.any(Number),
|
|
reason: 'Assertion passed',
|
|
assertion: expect.any(Object),
|
|
});
|
|
});
|
|
|
|
it('should use default threshold of 0.5', () => {
|
|
const params = {
|
|
assertion: { type: 'gleu', value: 'The cat sat on the mat.' },
|
|
renderedValue: 'The cat sat on the mat.',
|
|
outputString: 'The dog ran in the park.',
|
|
inverse: false,
|
|
} as AssertionParams;
|
|
expect(handleGleuScore(params)).toEqual({
|
|
pass: false,
|
|
score: expect.any(Number),
|
|
reason: expect.stringMatching(/GLEU score \d+\.\d+ is less than threshold 0\.5/),
|
|
assertion: expect.any(Object),
|
|
});
|
|
});
|
|
});
|
|
});
|