Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/state/reranker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,27 @@ export async function rerank(
for (const pair of pairs) {
try {
const output = await reranker(pair.text);
const score = Array.isArray(output) ? output[0]?.score ?? 0 : 0;
const rawScore = Array.isArray(output) ? output[0]?.score : undefined;
const score = Number.isFinite(rawScore) ? Number(rawScore) : pair.result.combinedScore;
scores.push({ result: pair.result, rerankScore: score });
} catch {
scores.push({ result: pair.result, rerankScore: pair.result.combinedScore });
}
}

const distinctScores = new Set(scores.map((s) => s.rerankScore));
if (distinctScores.size <= 1) {
return candidates.map((result, i) => ({
...result,
rerankPosition: i + 1,
}));
}

scores.sort((a, b) => b.rerankScore - a.rerankScore);

return scores.map((s, i) => ({
...s.result,
combinedScore: s.rerankScore,
rerankScore: s.rerankScore,
rerankPosition: i + 1,
}));
}
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ export interface HybridSearchResult {
vectorScore: number;
graphScore: number;
combinedScore: number;
rerankScore?: number;
sessionId: string;
graphContext?: string;
}
Expand Down
85 changes: 78 additions & 7 deletions test/reranker.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import { describe, it, expect, vi } from "vitest";

vi.mock("@xenova/transformers", () => {
throw new Error("not installed");
return {
pipeline: vi.fn(async () => vi.fn(async (text: string) => {
if (text.includes("same-score")) return [{ label: "LABEL_0", score: 1 }];
if (text.includes("strong-match")) return [{ label: "LABEL_0", score: 0.9 }];
return [{ label: "LABEL_0", score: 0.1 }];
})),
};
});

import { rerank, isRerankerAvailable } from "../src/state/reranker.js";

describe("reranker", () => {
it("returns results unchanged when @xenova/transformers is unavailable", async () => {
it("keeps retrieval scores when the reranker returns constant scores", async () => {
const results = [
{
observation: {
id: "o1",
title: "First",
narrative: "First result",
narrative: "same-score first result",
},
bm25Score: 0.5,
vectorScore: 0.6,
Expand All @@ -25,7 +31,7 @@ describe("reranker", () => {
observation: {
id: "o2",
title: "Second",
narrative: "Second result",
narrative: "same-score second result",
},
bm25Score: 0.3,
vectorScore: 0.4,
Expand All @@ -36,11 +42,76 @@ describe("reranker", () => {
] as any;

const reranked = await rerank("test query", results);
expect(reranked).toEqual(results);
expect(reranked.map((r) => r.observation.id)).toEqual(["o1", "o2"]);
expect(reranked.map((r) => r.combinedScore)).toEqual([0.8, 0.5]);
expect(reranked.map((r) => r.rerankPosition)).toEqual([1, 2]);
});

it("isRerankerAvailable returns false when not loaded", () => {
expect(isRerankerAvailable()).toBe(false);
it("uses discriminative reranker scores for ordering without overwriting retrieval score", async () => {
const results = [
{
observation: {
id: "o1",
title: "First",
narrative: "weak-match first result",
},
bm25Score: 0.5,
vectorScore: 0.6,
graphScore: 0,
combinedScore: 0.8,
sessionId: "s1",
},
{
observation: {
id: "o2",
title: "Second",
narrative: "strong-match second result",
},
bm25Score: 0.3,
vectorScore: 0.4,
graphScore: 0,
combinedScore: 0.5,
sessionId: "s1",
},
] as any;

const reranked = await rerank("test query", results);
expect(reranked.map((r) => r.observation.id)).toEqual(["o2", "o1"]);
expect(reranked[0].combinedScore).toBe(0.5);
expect(reranked[0].rerankScore).toBe(0.9);
expect(reranked[0].rerankPosition).toBe(1);
});

it("isRerankerAvailable reflects the loaded pipeline", async () => {
const results = [
{
observation: {
id: "o1",
title: "Availability",
narrative: "strong-match availability result",
},
bm25Score: 0.5,
vectorScore: 0.6,
graphScore: 0,
combinedScore: 0.8,
sessionId: "s1",
},
{
observation: {
id: "o2",
title: "Second",
narrative: "weak-match second result",
},
bm25Score: 0.3,
vectorScore: 0.4,
graphScore: 0,
combinedScore: 0.5,
sessionId: "s1",
},
] as any;

await rerank("test query", results);
expect(isRerankerAvailable()).toBe(true);
});

it("handles single result gracefully", async () => {
Expand Down