Skip to content
Merged
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
38 changes: 24 additions & 14 deletions src/lib/rag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -564,12 +564,17 @@ function resultsHaveReleaseRankScore(results: SearchResult[]) {
*
* App-layer rank scores remain available to answer evidence ranking and telemetry, but the
* live corpus gate has not validated using them as the final retrieval order. Resolve duplicate
* chunks to their strongest released-hybrid copy. Distinct results keep the clinical selection's
* existing order unless `preferSecondStageScore` is enabled and the current set carries bounded
* second-stage release scores.
* chunks to their strongest released-hybrid copy. When second-stage ordering was requested but the
* final set has no valid release scores, distinct results keep the clinical selection's existing
* order. Otherwise they use the live-proven raw-hybrid or bounded second-stage order.
*/
export function stabilizeReleasedSearchOrder(results: SearchResult[], preferSecondStageScore = false) {
export function stabilizeReleasedSearchOrder(
results: SearchResult[],
preferSecondStageScore = false,
preserveIncomingOrder = false,
) {
const useSecondStageReleaseOrder = preferSecondStageScore && resultsHaveReleaseRankScore(results);
const preserveCurrentOrder = preserveIncomingOrder || (preferSecondStageScore && !useSecondStageReleaseOrder);
const compareReleasedHybridStrength = (left: SearchResult, right: SearchResult) => {
const leftHybrid = left.hybrid_score ?? left.similarity ?? 0;
const rightHybrid = right.hybrid_score ?? right.similarity ?? 0;
Expand All @@ -582,12 +587,14 @@ export function stabilizeReleasedSearchOrder(results: SearchResult[], preferSeco
return left.id.localeCompare(right.id);
};
const compareReleasedSearchOrder = (left: SearchResult, right: SearchResult) => {
const leftReleaseScore = isBoundedReleaseRankScore(left.score_explanation?.releaseRankScore)
? left.score_explanation.releaseRankScore
: (left.hybrid_score ?? left.similarity ?? 0);
const rightReleaseScore = isBoundedReleaseRankScore(right.score_explanation?.releaseRankScore)
? right.score_explanation.releaseRankScore
: (right.hybrid_score ?? right.similarity ?? 0);
const leftReleaseScore =
useSecondStageReleaseOrder && isBoundedReleaseRankScore(left.score_explanation?.releaseRankScore)
? left.score_explanation.releaseRankScore
: (left.hybrid_score ?? left.similarity ?? 0);
const rightReleaseScore =
useSecondStageReleaseOrder && isBoundedReleaseRankScore(right.score_explanation?.releaseRankScore)
? right.score_explanation.releaseRankScore
: (right.hybrid_score ?? right.similarity ?? 0);
if (rightReleaseScore !== leftReleaseScore) return rightReleaseScore - leftReleaseScore;
const leftSimilarity = left.similarity ?? 0;
const rightSimilarity = right.similarity ?? 0;
Expand All @@ -602,9 +609,11 @@ export function stabilizeReleasedSearchOrder(results: SearchResult[], preferSeco
if (!current || compareReleasedHybridStrength(result, current) < 0) strongestById.set(result.id, result);
}
const distinctResults = [...strongestById.values()];
const releasedResults = useSecondStageReleaseOrder
? distinctResults.sort(compareReleasedSearchOrder)
: distinctResults;
const releasedResults = preserveCurrentOrder
? distinctResults
: useSecondStageReleaseOrder
? distinctResults.sort(compareReleasedSearchOrder)
: distinctResults.sort(compareReleasedHybridStrength);
const deduped = releasedResults.map((result, index) =>
result.score_explanation
? { ...result, score_explanation: { ...result.score_explanation, finalRank: index + 1 } }
Expand Down Expand Up @@ -633,7 +642,8 @@ function recordSearchScoreTelemetry(telemetry: SearchTelemetry, results: SearchR

const useSecondStageReleaseOrder = resultsHaveReleaseRankScore(results);
telemetry.second_stage_rerank_used = useSecondStageReleaseOrder;
stabilizeReleasedSearchOrder(results, useSecondStageReleaseOrder);
const preserveSemanticRerankOrder = telemetry.semantic_rerank_outcome === "reordered" && !useSecondStageReleaseOrder;
stabilizeReleasedSearchOrder(results, useSecondStageReleaseOrder, preserveSemanticRerankOrder);
const coverageScores = results
.map((result) => Math.max(0, result.hybrid_score ?? result.similarity ?? 0))
.sort((left, right) => right - left);
Expand Down
28 changes: 26 additions & 2 deletions tests/rag-second-stage-ranking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ describe("second-stage rank score", () => {
expect(stabilized.map((item) => item.score_explanation?.finalRank)).toEqual([1, 2]);
});

it("ignores sticky second-stage preference when the final result set has no releaseRankScore", () => {
it("preserves sticky second-stage order while retaining raw-hybrid fallback when release scores are absent", () => {
const clinicallyOrdered = [
result({
id: "lithium-monitoring",
Expand All @@ -255,6 +255,10 @@ describe("second-stage rank score", () => {
expect(stabilized.map((item) => item.id)).toEqual(["lithium-monitoring", "cardiac-dose"]);
expect(stabilized[0].content).toBe("Stronger released-hybrid copy of the lithium evidence.");
expect(stabilized.map((item) => item.score_explanation?.finalRank)).toEqual([1, 2]);

const hybridFallback = stabilizeReleasedSearchOrder([...clinicallyOrdered], false);
expect(hybridFallback.map((item) => item.id)).toEqual(["cardiac-dose", "lithium-monitoring"]);
expect(hybridFallback[1].content).toBe("Stronger released-hybrid copy of the lithium evidence.");
});

it("only applies bounded release ordering when the current result set carries releaseRankScore", () => {
Expand All @@ -274,10 +278,30 @@ describe("second-stage rank score", () => {
"hybrid-leading",
]);
expect(stabilizeReleasedSearchOrder([withReleaseScore, withoutReleaseScore], false).map((item) => item.id)).toEqual(
["release-ranked", "hybrid-leading"],
["hybrid-leading", "release-ranked"],
);
});

it("preserves semantic rerank order when release scores are absent", () => {
const semanticallyOrdered = [
result({
id: "semantic-first",
hybrid_score: 0.7,
score_explanation: explanation(0.7),
}),
result({
id: "semantic-second",
hybrid_score: 0.9,
score_explanation: explanation(0.9),
}),
];

expect(stabilizeReleasedSearchOrder([...semanticallyOrdered], false, true).map((item) => item.id)).toEqual([
"semantic-first",
"semantic-second",
]);
});

it("preserves clinical order when releaseRankScore values are not finite", () => {
const clinicallyOrdered = [
result({
Expand Down