Skip to content

Commit 2372deb

Browse files
committed
fix: preserve doc intent after rerank rebase merge
1 parent 01d4082 commit 2372deb

File tree

3 files changed

+70
-70
lines changed

3 files changed

+70
-70
lines changed

src/indexer/index.ts

Lines changed: 9 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1590,14 +1590,18 @@ export class Indexer {
15901590
return candidates;
15911591
}
15921592

1593-
if (options?.definitionIntent === true || options?.hasIdentifierHints === true) {
1594-
return candidates;
1595-
}
1596-
15971593
const queryTokens = Array.from(tokenizeTextForRanking(query));
15981594
const preferSourcePaths = classifyQueryIntentRaw(query) === "source";
15991595
const docIntent = classifyDocIntent(queryTokens) === "docs";
16001596

1597+
if (options?.definitionIntent === true) {
1598+
return candidates;
1599+
}
1600+
1601+
if (options?.hasIdentifierHints === true && preferSourcePaths && !docIntent) {
1602+
return candidates;
1603+
}
1604+
16011605
const topN = Math.min(reranker.topN, candidates.length);
16021606
const head = candidates.slice(0, topN);
16031607
const tail = candidates.slice(topN);
@@ -2867,70 +2871,7 @@ export class Indexer {
28672871
: baseFiltered
28682872
).slice(0, maxResults);
28692873

2870-
// Apply reranking if enabled and available
2871-
let finalResults = filtered;
2872-
if (this.reranker?.isAvailable() && filtered.length > 1) {
2873-
const rerankStartTime = performance.now();
2874-
2875-
// Read content for reranking
2876-
const documentsForRerank = await Promise.all(
2877-
filtered.map(async (r) => {
2878-
try {
2879-
const fileContent = await fsPromises.readFile(r.metadata.filePath, "utf-8");
2880-
const lines = fileContent.split("\n");
2881-
return lines.slice(r.metadata.startLine - 1, r.metadata.endLine).join("\n");
2882-
} catch {
2883-
return r.metadata.name ?? r.metadata.chunkType;
2884-
}
2885-
})
2886-
);
2887-
2888-
try {
2889-
const rerankResponse = await this.reranker.rerank(
2890-
query,
2891-
documentsForRerank,
2892-
this.config.reranker?.topN ?? filtered.length
2893-
);
2894-
2895-
if (rerankResponse.results.length > 0) {
2896-
// Create a map of original index to rerank score
2897-
const rerankScores = new Map<number, number>();
2898-
for (const result of rerankResponse.results) {
2899-
rerankScores.set(result.index, result.relevanceScore);
2900-
}
2901-
2902-
// Reorder results based on rerank scores
2903-
const rerankedIndices = rerankResponse.results
2904-
.sort((a, b) => b.relevanceScore - a.relevanceScore)
2905-
.map(r => r.index);
2906-
2907-
// Build final results: reranked first, then remaining
2908-
const rerankedSet = new Set(rerankedIndices);
2909-
const reranked = rerankedIndices
2910-
.filter(idx => idx < filtered.length)
2911-
.map(idx => ({
2912-
...filtered[idx],
2913-
score: rerankScores.get(idx) ?? filtered[idx].score,
2914-
}));
2915-
const remaining = filtered
2916-
.filter((_, idx) => !rerankedSet.has(idx));
2917-
2918-
finalResults = [...reranked, ...remaining].slice(0, maxResults);
2919-
}
2920-
2921-
const rerankMs = performance.now() - rerankStartTime;
2922-
this.logger.search("debug", "Reranking complete", {
2923-
documentsReranked: documentsForRerank.length,
2924-
rerankMs: Math.round(rerankMs * 100) / 100,
2925-
tokensUsed: rerankResponse.tokensUsed,
2926-
});
2927-
} catch (error) {
2928-
// Reranking failed, use original results
2929-
this.logger.search("warn", "Reranking failed, using original results", {
2930-
error: error instanceof Error ? error.message : String(error),
2931-
});
2932-
}
2933-
}
2874+
const finalResults = filtered;
29342875

29352876
const totalSearchMs = performance.now() - searchStartTime;
29362877
this.logger.recordSearch(totalSearchMs, {

tests/retrieval-ranking.test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,65 @@ describe("retrieval ranking", () => {
547547
globalThis.fetch = fetchSpy;
548548
});
549549

550+
it("allows external reranker for documentation intent even when identifier hints are present", async () => {
551+
const config = parseConfig({
552+
embeddingProvider: "custom",
553+
customProvider: {
554+
baseUrl: "http://localhost:11434/v1",
555+
model: "mock-embed",
556+
dimensions: 8,
557+
},
558+
reranker: {
559+
enabled: true,
560+
provider: "custom",
561+
model: "mock-reranker",
562+
baseUrl: "https://rerank.example/v1",
563+
topN: 3,
564+
},
565+
});
566+
const indexer = new Indexer("/repo", config);
567+
568+
const fetchSpy = globalThis.fetch;
569+
let rerankCalled = false;
570+
let rerankDocuments: string[] | undefined;
571+
globalThis.fetch = (async (input, init) => {
572+
if (String(input).includes("/rerank")) {
573+
rerankCalled = true;
574+
rerankDocuments = (JSON.parse(String(init?.body ?? "{}")) as { documents?: string[] }).documents;
575+
return new Response(JSON.stringify({
576+
results: [
577+
{ index: 1, relevance_score: 0.99 },
578+
{ index: 0, relevance_score: 0.6 },
579+
],
580+
}), { status: 200 });
581+
}
582+
return new Response(JSON.stringify({ data: [{ embedding: Array.from({ length: 8 }, () => 0.1) }], usage: { total_tokens: 1 } }), { status: 200 });
583+
}) as typeof fetch;
584+
585+
const candidates: Candidate[] = [
586+
{ id: "impl", score: 0.9, metadata: meta({ filePath: "/repo/src/indexer/index.ts", name: "rankHybridResults", chunkType: "function", startLine: 1, endLine: 3 }) },
587+
{ id: "docs-readme", score: 0.89, metadata: meta({ filePath: "/repo/README.md", name: "retrieval documentation", chunkType: "other", startLine: 1, endLine: 3 }) },
588+
{ id: "docs-guide", score: 0.88, metadata: meta({ filePath: "/repo/docs/guide.md", name: "rankHybridResults guide", chunkType: "other", startLine: 1, endLine: 3 }) },
589+
];
590+
591+
const reranked = await (indexer as unknown as {
592+
rerankCandidatesWithApi(
593+
query: string,
594+
items: Candidate[],
595+
options?: { definitionIntent?: boolean; hasIdentifierHints?: boolean }
596+
): Promise<Candidate[]>;
597+
}).rerankCandidatesWithApi("rankHybridResults documentation guide", candidates, {
598+
hasIdentifierHints: true,
599+
});
600+
601+
expect(rerankCalled).toBe(true);
602+
expect(reranked.map((candidate) => candidate.id)).toEqual(["docs-guide", "docs-readme", "impl"]);
603+
expect(rerankDocuments?.length).toBe(2);
604+
expect(rerankDocuments?.[0]).toContain("path: /repo/README.md");
605+
expect(rerankDocuments?.[1]).toContain("path: /repo/docs/guide.md");
606+
globalThis.fetch = fetchSpy;
607+
});
608+
550609
it("diversifies external reranker output for exploratory queries", async () => {
551610
const config = parseConfig({
552611
embeddingProvider: "custom",

tests/search-integration.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export function rerankResults(query: string) { return rankHybridResults(query);
133133
const indexer = new Indexer(tempDir, config);
134134
await indexer.index();
135135

136-
const results = await indexer.search("where is rankHybridResults documentation", 5, {
136+
const results = await indexer.search("rankHybridResults documentation guide", 5, {
137137
metadataOnly: true,
138138
filterByBranch: false,
139139
});
@@ -338,7 +338,7 @@ export function rerankResults(query: string) { return rankHybridResults(query);
338338
const indexer = new Indexer(tempDir, config);
339339
await indexer.index();
340340

341-
const results = await indexer.search("where is rankHybridResults documentation", 5, {
341+
const results = await indexer.search("rankHybridResults documentation guide", 5, {
342342
metadataOnly: true,
343343
filterByBranch: false,
344344
});

0 commit comments

Comments
 (0)