@claude-flow/embeddings
Version:
V3 Embedding Service - OpenAI, Transformers.js, Agentic-Flow (ONNX), Mock providers with hyperbolic embeddings, normalization, and chunking
182 lines (162 loc) • 7.39 kB
text/typescript
/**
* ADR-121 Phase 10 — MMR rerank tests.
*
* Coverage:
* - Identity case (λ=1) reduces to plain top-k by relevance
* - Pure diversity (λ=0) ignores query relevance
* - Balanced (λ=0.5) demonstrably diversifies vs plain top-k on a
* corpus with near-duplicate clusters
* - k clamping (k > candidates.length, k === 0)
* - Order: pickOrder reflects iteration index
* - averagePairwiseSimilarity: MMR result is more diverse than the
* plain top-k from the same candidate set
*/
import { describe, it, expect } from 'vitest';
import {
mmrRerank,
mmrIds,
averagePairwiseSimilarity,
type MmrCandidate,
} from '../mmr.js';
const DIM = 8;
function vec(values: number[]): Float32Array {
if (values.length !== DIM) throw new Error('test bug');
return new Float32Array(values);
}
describe('mmrRerank — basic contract', () => {
it('returns empty array when k=0', () => {
const out = mmrRerank(
[{ id: 'a', vector: vec([1, 0, 0, 0, 0, 0, 0, 0]) }],
vec([1, 0, 0, 0, 0, 0, 0, 0]),
{ k: 0 },
);
expect(out).toEqual([]);
});
it('returns empty array when candidates is empty', () => {
const out = mmrRerank([], vec([1, 0, 0, 0, 0, 0, 0, 0]), { k: 5 });
expect(out).toEqual([]);
});
it('clamps k to candidates.length', () => {
const cands: MmrCandidate[] = [
{ id: 'a', vector: vec([1, 0, 0, 0, 0, 0, 0, 0]) },
{ id: 'b', vector: vec([0, 1, 0, 0, 0, 0, 0, 0]) },
];
const out = mmrRerank(cands, vec([1, 0, 0, 0, 0, 0, 0, 0]), { k: 100 });
expect(out.length).toBe(2);
});
it('preserves caller-supplied payload through the rerank', () => {
const cands: MmrCandidate[] = [
{ id: 'a', vector: vec([1, 0, 0, 0, 0, 0, 0, 0]), payload: { source: 'docs' } },
];
const out = mmrRerank(cands, vec([1, 0, 0, 0, 0, 0, 0, 0]), { k: 1 });
expect(out[0]!.payload).toEqual({ source: 'docs' });
});
it('pickOrder matches iteration index', () => {
const cands: MmrCandidate[] = [
{ id: 'a', vector: vec([1, 0, 0, 0, 0, 0, 0, 0]) },
{ id: 'b', vector: vec([0, 1, 0, 0, 0, 0, 0, 0]) },
{ id: 'c', vector: vec([0, 0, 1, 0, 0, 0, 0, 0]) },
];
const out = mmrRerank(cands, vec([1, 0, 0, 0, 0, 0, 0, 0]), { k: 3 });
expect(out.map(p => p.pickOrder)).toEqual([0, 1, 2]);
});
});
describe('mmrRerank — lambda extremes', () => {
// Candidates with three near-duplicates of "x" and two orthogonals.
// Plain top-k (λ=1) returns all 3 x-duplicates first.
// Pure diversity (λ=0) starts wherever and then picks orthogonals.
const candidates: MmrCandidate[] = [
{ id: 'x1', vector: vec([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) },
{ id: 'x2', vector: vec([0.99, 0.01, 0.01, 0.0, 0.0, 0.0, 0.0, 0.0]) },
{ id: 'x3', vector: vec([0.98, 0.02, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) },
{ id: 'y', vector: vec([0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) },
{ id: 'z', vector: vec([0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]) },
];
const query = vec([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
it('λ=1 (pure relevance) returns the 3 x-duplicates first', () => {
const out = mmrRerank(candidates, query, { k: 3, lambda: 1.0 });
const ids = out.map(p => p.id).sort();
expect(ids).toEqual(['x1', 'x2', 'x3']);
});
it('λ=0.3 (diversity-leaning) returns at least one orthogonal in top-3', () => {
// λ=0.5 produces tied scores on the carefully-constructed
// duplicate set (relevance ≈ redundancy = 0.99 for the
// x-duplicates, so the tradeoff cancels). λ=0.3 weights
// diversity more strongly and produces a deterministic spread.
const out = mmrRerank(candidates, query, { k: 3, lambda: 0.3 });
const ids = out.map(p => p.id);
// Top-1 should still be x1 (highest relevance).
expect(ids[0]).toBe('x1');
// Top-3 should include at least one orthogonal (diversity wins
// over redundancy at this lambda).
expect(ids.some(id => id === 'y' || id === 'z')).toBe(true);
});
it('λ=0 (pure diversity) ignores relevance — still picks first by relevance, then diverges', () => {
const out = mmrRerank(candidates, query, { k: 3, lambda: 0.0 });
// First pick: with λ=0, score = 0 * relevance − 1 * 0 = 0 for all
// candidates. The first non-deterministic pick lands on whoever
// comes first in iteration; subsequent picks favor diversity.
expect(out.length).toBe(3);
// The result must be a valid permutation of candidates.
const ids = new Set(out.map(p => p.id));
expect(ids.size).toBe(3);
});
it('λ ∈ [0,1] is clamped (out-of-range still produces a valid result)', () => {
const high = mmrRerank(candidates, query, { k: 3, lambda: 2.0 });
const low = mmrRerank(candidates, query, { k: 3, lambda: -0.5 });
expect(high.length).toBe(3);
expect(low.length).toBe(3);
});
});
describe('mmrRerank — diversification is measurable', () => {
// The MMR result on a duplicate-heavy candidate set should have
// a lower averagePairwiseSimilarity than the plain top-k from the
// same set. That's the whole point.
it('λ=0.3 result is more diverse than λ=1 result on duplicate-heavy candidates', () => {
const candidates: MmrCandidate[] = [
{ id: 'a1', vector: vec([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) },
{ id: 'a2', vector: vec([0.99, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.01]) },
{ id: 'a3', vector: vec([0.98, 0.0, 0.0, 0.0, 0.0, 0.0, 0.02, 0.0]) },
{ id: 'b', vector: vec([0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) },
{ id: 'c', vector: vec([0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]) },
{ id: 'd', vector: vec([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]) },
];
const query = vec([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
const plain = mmrRerank(candidates, query, { k: 3, lambda: 1.0 });
const diverse = mmrRerank(candidates, query, { k: 3, lambda: 0.3 });
const plainAvg = averagePairwiseSimilarity(plain);
const diverseAvg = averagePairwiseSimilarity(diverse);
// Diverse pick should have strictly lower average pairwise sim.
expect(diverseAvg).toBeLessThan(plainAvg);
});
it('redundancy field reflects max sim to picked so far', () => {
const cands: MmrCandidate[] = [
{ id: 'a', vector: vec([1, 0, 0, 0, 0, 0, 0, 0]) },
{ id: 'b', vector: vec([0.99, 0.01, 0, 0, 0, 0, 0, 0]) },
];
const out = mmrRerank(cands, vec([1, 0, 0, 0, 0, 0, 0, 0]), { k: 2, lambda: 0.5 });
// First pick: redundancy is 0 (nothing picked yet).
expect(out[0]!.redundancy).toBe(0);
// Second pick: redundancy ≈ sim(first, second) — close to 1.
expect(out[1]!.redundancy).toBeGreaterThan(0.9);
});
});
describe('mmrIds helper', () => {
it('returns just the picked ids in pick order', () => {
const cands: MmrCandidate[] = [
{ id: 'a', vector: vec([1, 0, 0, 0, 0, 0, 0, 0]) },
{ id: 'b', vector: vec([0, 1, 0, 0, 0, 0, 0, 0]) },
];
const ids = mmrIds(cands, vec([1, 0, 0, 0, 0, 0, 0, 0]), { k: 2 });
expect(ids).toEqual(['a', 'b']);
});
});
describe('averagePairwiseSimilarity', () => {
it('returns 0 for sets smaller than 2', () => {
expect(averagePairwiseSimilarity([])).toBe(0);
expect(averagePairwiseSimilarity([{
id: 'a', vector: vec([1, 0, 0, 0, 0, 0, 0, 0]),
mmrScore: 1, relevance: 1, redundancy: 0, pickOrder: 0,
}])).toBe(0);
});
});