MMR de-duplication
# grade this stage (from your course directory)
npx theinference test --stage mmr-select --watchBuild MMR. Given a query vector and candidate chunks (each with a vector), greedily select k chunk ids that balance relevance and diversity by the MMR score.
What to build
Implement mmrSelect
Greedy loop: on each step pick the remaining candidate with the highest lambda*Sim1(d,q) - (1-lambda)*max_{s in S} Sim2(d,s). For the first pick the penalty is 0 (nothing selected yet), so it's the most relevant candidate. Tie-break by ascending chunk id. Return the selected ids in selection order.
The adapter wraps this: it tags each matrix row with its index as a chunk id, runs mmrSelect, and returns the selected indices as numbers.
export function mmrSelect(
queryVec: number[], candidates: { chunkId: string; vec: number[] }[],
lambda: number, k: number,
): string[] {
// repeat k times: argmax of lambda*cos(q,d) - (1-lambda)*max_{s in S} cos(d,s)
// move the winner from remaining -> selected; return selected ids in order
}
// adapter mode: runMmrSelect(query, matrix, lambda, k) -> number[] (selected indices)
// fixture has deliberate near-duplicates; lambda=0.7, k=3
adapter mode. The harness calls runMmrSelect on a candidate set with planted near-duplicates and compares the selected indices to the reference exactly. On the redundant fixture, MMR's pick differs from plain top-k — proving it's wired, not stubbed to relevance-only.
Use the same unit vectors for Sim1 (query-chunk) and Sim2 (chunk-chunk) — both cosine. A common bug is comparing chunk-chunk on un-normalized vectors, which silently changes the diversity penalty and reorders your picks.