Skip to content

Commit 708c8e7

Browse files
committed
feat: add bounded feedback ranking signal
1 parent 751ae42 commit 708c8e7

6 files changed

Lines changed: 32 additions & 2 deletions

File tree

src/core/ranker.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ export interface ContextRegistryRankOptions {
147147
source?: string;
148148
tags?: string[];
149149
negativeExamples?: string[];
150+
localQualitySignals?: Record<string, number>;
150151
}
151152

152153
export interface RankedContextEntry {
@@ -181,11 +182,12 @@ export function rankContextEntry(task: string, terms: string[], entry: ContextEn
181182
const dependency = fieldScore(terms, (entry.dependencyChain ?? []).join(" "), 10);
182183
const source = sourceWeight(entry.trustLevel);
183184
const quality = Math.min(10, entry.qualityScore ?? qualityScore(entry));
185+
const localFeedback = Math.max(-3, Math.min(3, options.localQualitySignals?.[feedbackKey(entry)] ?? 0));
184186
const regression = regressionWeight(terms, entry);
185187
const negativePenalty = negativePenaltyForEntry(entry, options.negativeExamples ?? []);
186188
const exactBoost = exactId ? 1000 : 0;
187189
const taskType = taskTypeWeight(options.taskType, entry.kind);
188-
const score = exactBoost + lexical + symbol + dependency + source + quality + regression + taskType - negativePenalty;
190+
const score = exactBoost + lexical + symbol + dependency + source + quality + localFeedback + regression + taskType - negativePenalty;
189191
const scoreBreakdown: RetrievalScoreBreakdown = {
190192
lexical,
191193
path: 0,
@@ -200,13 +202,22 @@ export function rankContextEntry(task: string, terms: string[], entry: ContextEn
200202
dependency,
201203
source,
202204
quality,
205+
localFeedback,
203206
regression,
204207
exactId: exactBoost,
205208
total: score
206209
};
207210
return { entry, score, scoreBreakdown, exactId };
208211
}
209212

213+
export function contextEntryFeedbackKey(entry: Pick<ContextEntry, "sourceName" | "id">): string {
214+
return `${entry.sourceName}\0${entry.id}`;
215+
}
216+
217+
function feedbackKey(entry: ContextEntry): string {
218+
return contextEntryFeedbackKey(entry);
219+
}
220+
210221
export function matchesEntryFilters(entry: ContextEntry, options: ContextRegistryRankOptions): boolean {
211222
if (options.packageVersion && entry.packageVersion !== options.packageVersion) return false;
212223
if (options.language && entry.language !== options.language) return false;

src/retrievers/context-registry.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ export class ContextRegistryRetriever implements ContextRetriever {
1919
language: options.language,
2020
source: options.source,
2121
tags: options.tags,
22-
negativeExamples: options.negativeExamples
22+
negativeExamples: options.negativeExamples,
23+
localQualitySignals: options.localQualitySignals
2324
});
2425
return ranked.slice(0, Math.max(1, options.topK)).map(({ entry, score, scoreBreakdown, exactId }) => {
2526
const relatedFiles = entry.files.map((file) => `context://${entry.sourceName}/${file.path}`).sort();

src/retrievers/ripgrep.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ function hitFor(file: IndexedFile, source: "ripgrep"): ContextHit {
9696
dependency: 0,
9797
source: 0,
9898
quality: 0,
99+
localFeedback: 0,
99100
regression: 0,
100101
exactId: 0,
101102
total: 0

src/retrievers/static.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export class StaticContextRetriever implements ContextRetriever {
6060
dependency: chainBoost,
6161
source: 0,
6262
quality: 0,
63+
localFeedback: 0,
6364
regression: regressionBoost,
6465
exactId: 0,
6566
total: score
@@ -207,6 +208,7 @@ export function mergeScoreBreakdowns(left: RetrievalScoreBreakdown | undefined,
207208
dependency,
208209
source: value("source"),
209210
quality: value("quality"),
211+
localFeedback: value("localFeedback"),
210212
regression,
211213
exactId: value("exactId"),
212214
total:
@@ -220,6 +222,7 @@ export function mergeScoreBreakdowns(left: RetrievalScoreBreakdown | undefined,
220222
regression +
221223
value("source") +
222224
value("quality") +
225+
value("localFeedback") +
223226
value("exactId") -
224227
negativePenalty
225228
};

src/retrievers/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface RetrievalScoreBreakdown {
1616
dependency: number;
1717
source: number;
1818
quality: number;
19+
localFeedback: number;
1920
regression: number;
2021
exactId: number;
2122
total: number;
@@ -32,6 +33,7 @@ export interface ContextRetrieverOptions {
3233
language?: string;
3334
source?: string;
3435
tags?: string[];
36+
localQualitySignals?: Record<string, number>;
3537
}
3638

3739
export function adaptiveTopK(taskType: RetrievalTaskType = "auto", requested?: number): number {

test/retrievers.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,18 @@ test("registry retrieval is stable for equal scores and applies negative penalty
205205
assert.equal(penalized.at(-1)?.metadata.scoreBreakdown?.negativePenalty, 40);
206206
});
207207

208+
test("local feedback quality is opt-in, bounded, and cannot override exact ID", async () => {
209+
const retriever = new ContextRegistryRetriever([registryEntry("community", "alpha", { qualityScore: 0 }), registryEntry("community", "zeta", { qualityScore: 0 })]);
210+
const baseline = await retriever.search("", { topK: 10 });
211+
assert.deepEqual(baseline.map((hit) => hit.id), ["community/alpha", "community/zeta"]);
212+
assert.equal(baseline[0]?.metadata.scoreBreakdown?.localFeedback, 0);
213+
const weighted = await retriever.search("", { topK: 10, localQualitySignals: { "community\0community/zeta": 50 } });
214+
assert.equal(weighted[0]?.id, "community/zeta");
215+
assert.equal(weighted[0]?.metadata.scoreBreakdown?.localFeedback, 3);
216+
const exact = await retriever.search("community/alpha", { topK: 10, localQualitySignals: { "community\0community/zeta": 50, "community\0community/alpha": -50 } });
217+
assert.equal(exact[0]?.id, "community/alpha");
218+
});
219+
208220
test("registry retrieval reports useful precision and recall at top K", async () => {
209221
const retriever = new ContextRegistryRetriever([
210222
registryEntry("official", "session-timeout"),

0 commit comments

Comments
 (0)