@seanhogg/builderforce-memory 2026.9.29 → 2026.9.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridges/AnthropicBridge.d.ts.map +1 -1
- package/dist/bridges/AnthropicBridge.js +24 -65
- package/dist/bridges/AnthropicBridge.js.map +1 -1
- package/dist/bridges/FetchBridge.d.ts +12 -5
- package/dist/bridges/FetchBridge.d.ts.map +1 -1
- package/dist/bridges/FetchBridge.js +6 -4
- package/dist/bridges/FetchBridge.js.map +1 -1
- package/dist/bridges/OpenAIBridge.d.ts +17 -4
- package/dist/bridges/OpenAIBridge.d.ts.map +1 -1
- package/dist/bridges/OpenAIBridge.js +71 -94
- package/dist/bridges/OpenAIBridge.js.map +1 -1
- package/dist/bridges/TransformerBridge.d.ts +6 -0
- package/dist/bridges/TransformerBridge.d.ts.map +1 -1
- package/dist/bridges/VertexAIBridge.d.ts.map +1 -1
- package/dist/bridges/VertexAIBridge.js +3 -34
- package/dist/bridges/VertexAIBridge.js.map +1 -1
- package/dist/distillation/DistillationEngine.d.ts +71 -54
- package/dist/distillation/DistillationEngine.d.ts.map +1 -1
- package/dist/distillation/DistillationEngine.js +112 -95
- package/dist/distillation/DistillationEngine.js.map +1 -1
- package/dist/distillation/index.d.ts +7 -1
- package/dist/distillation/index.d.ts.map +1 -1
- package/dist/distillation/index.js +5 -0
- package/dist/distillation/index.js.map +1 -1
- package/dist/distillation/ports.d.ts +43 -0
- package/dist/distillation/ports.d.ts.map +1 -0
- package/dist/distillation/ports.js +29 -0
- package/dist/distillation/ports.js.map +1 -0
- package/dist/eval/EvalHarness.d.ts +2 -1
- package/dist/eval/EvalHarness.d.ts.map +1 -1
- package/dist/eval/EvalHarness.js +13 -1
- package/dist/eval/EvalHarness.js.map +1 -1
- package/dist/eval/baseline.d.ts +33 -0
- package/dist/eval/baseline.d.ts.map +1 -0
- package/dist/eval/baseline.js +41 -0
- package/dist/eval/baseline.js.map +1 -0
- package/dist/eval/index.d.ts +1 -0
- package/dist/eval/index.d.ts.map +1 -1
- package/dist/eval/index.js +1 -0
- package/dist/eval/index.js.map +1 -1
- package/dist/eval/types.d.ts +7 -0
- package/dist/eval/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/rag/EnterpriseRetriever.d.ts.map +1 -1
- package/dist/rag/EnterpriseRetriever.js +2 -1
- package/dist/rag/EnterpriseRetriever.js.map +1 -1
- package/dist/retrieval/HybridRetriever.d.ts.map +1 -1
- package/dist/retrieval/HybridRetriever.js +2 -1
- package/dist/retrieval/HybridRetriever.js.map +1 -1
- package/dist/retrieval/decay.d.ts +24 -0
- package/dist/retrieval/decay.d.ts.map +1 -0
- package/dist/retrieval/decay.js +29 -0
- package/dist/retrieval/decay.js.map +1 -0
- package/dist/retrieval/fusion.d.ts +13 -19
- package/dist/retrieval/fusion.d.ts.map +1 -1
- package/dist/retrieval/fusion.js +17 -44
- package/dist/retrieval/fusion.js.map +1 -1
- package/dist/retrieval/index.d.ts +13 -6
- package/dist/retrieval/index.d.ts.map +1 -1
- package/dist/retrieval/index.js +10 -5
- package/dist/retrieval/index.js.map +1 -1
- package/dist/retrieval/mmr.d.ts +60 -0
- package/dist/retrieval/mmr.d.ts.map +1 -0
- package/dist/retrieval/mmr.js +94 -0
- package/dist/retrieval/mmr.js.map +1 -0
- package/dist/retrieval/weightedFusion.d.ts +64 -0
- package/dist/retrieval/weightedFusion.d.ts.map +1 -0
- package/dist/retrieval/weightedFusion.js +80 -0
- package/dist/retrieval/weightedFusion.js.map +1 -0
- package/dist/similarity/index.d.ts +6 -4
- package/dist/similarity/index.d.ts.map +1 -1
- package/dist/similarity/index.js +5 -3
- package/dist/similarity/index.js.map +1 -1
- package/dist/wire/chatClient.d.ts +100 -0
- package/dist/wire/chatClient.d.ts.map +1 -0
- package/dist/wire/chatClient.js +123 -0
- package/dist/wire/chatClient.js.map +1 -0
- package/dist/wire/index.d.ts +20 -0
- package/dist/wire/index.d.ts.map +1 -0
- package/dist/wire/index.js +18 -0
- package/dist/wire/index.js.map +1 -0
- package/dist/wire/sse.d.ts +37 -0
- package/dist/wire/sse.d.ts.map +1 -0
- package/dist/wire/sse.js +104 -0
- package/dist/wire/sse.js.map +1 -0
- package/dist/wire/usage.d.ts +46 -0
- package/dist/wire/usage.d.ts.map +1 -0
- package/dist/wire/usage.js +78 -0
- package/dist/wire/usage.js.map +1 -0
- package/package.json +11 -3
- package/src/bridges/AnthropicBridge.ts +24 -68
- package/src/bridges/FetchBridge.ts +15 -6
- package/src/bridges/OpenAIBridge.ts +84 -113
- package/src/bridges/TransformerBridge.ts +6 -0
- package/src/bridges/VertexAIBridge.ts +3 -32
- package/src/distillation/DistillationEngine.ts +176 -141
- package/src/distillation/index.ts +15 -1
- package/src/distillation/ports.ts +55 -0
- package/src/eval/EvalHarness.ts +12 -1
- package/src/eval/baseline.ts +51 -0
- package/src/eval/index.ts +1 -0
- package/src/eval/types.ts +7 -0
- package/src/index.ts +5 -2
- package/src/rag/EnterpriseRetriever.ts +2 -1
- package/src/retrieval/HybridRetriever.ts +2 -1
- package/src/retrieval/decay.ts +29 -0
- package/src/retrieval/fusion.ts +19 -52
- package/src/retrieval/index.ts +24 -6
- package/src/retrieval/mmr.ts +126 -0
- package/src/retrieval/weightedFusion.ts +101 -0
- package/src/similarity/index.ts +6 -4
- package/src/wire/chatClient.ts +196 -0
- package/src/wire/index.ts +20 -0
- package/src/wire/sse.ts +96 -0
- package/src/wire/usage.ts +95 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recency decay — exponential half-life down-weighting of an older result's score.
|
|
3
|
+
*
|
|
4
|
+
* `score · e^(−λ·age)` with `λ = ln2 / halfLife`, so a result `halfLife` days old
|
|
5
|
+
* keeps half its score. The maths is here; deciding how old a result IS (a dated
|
|
6
|
+
* file name, an mtime, a `created_at` column) and which results never decay stays
|
|
7
|
+
* with the caller, because that is a property of its corpus.
|
|
8
|
+
*
|
|
9
|
+
* Pure and zero-dependency.
|
|
10
|
+
*/
|
|
11
|
+
/** The decay rate for a half-life in days; 0 (no decay) for a non-positive or non-finite half-life. */
|
|
12
|
+
export function toDecayLambda(halfLifeDays) {
|
|
13
|
+
if (!Number.isFinite(halfLifeDays) || halfLifeDays <= 0)
|
|
14
|
+
return 0;
|
|
15
|
+
return Math.LN2 / halfLifeDays;
|
|
16
|
+
}
|
|
17
|
+
/** The factor in (0,1] a score is multiplied by at `ageInDays` (negative ages count as 0). */
|
|
18
|
+
export function calculateTemporalDecayMultiplier(params) {
|
|
19
|
+
const lambda = toDecayLambda(params.halfLifeDays);
|
|
20
|
+
const clampedAge = Math.max(0, params.ageInDays);
|
|
21
|
+
if (lambda <= 0 || !Number.isFinite(clampedAge))
|
|
22
|
+
return 1;
|
|
23
|
+
return Math.exp(-lambda * clampedAge);
|
|
24
|
+
}
|
|
25
|
+
/** `score` decayed for its age. */
|
|
26
|
+
export function applyTemporalDecayToScore(params) {
|
|
27
|
+
return params.score * calculateTemporalDecayMultiplier(params);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=decay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decay.js","sourceRoot":"","sources":["../../src/retrieval/decay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,uGAAuG;AACvG,MAAM,UAAU,aAAa,CAAC,YAAoB;IAC9C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,YAAY,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAClE,OAAO,IAAI,CAAC,GAAG,GAAG,YAAY,CAAC;AACnC,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,gCAAgC,CAAC,MAAmD;IAChG,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAClD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;IACjD,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC;QAAE,OAAO,CAAC,CAAC;IAC1D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC;AAC1C,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,yBAAyB,CAAC,MAAkE;IACxG,OAAO,MAAM,CAAC,KAAK,GAAG,gCAAgC,CAAC,MAAM,CAAC,CAAC;AACnE,CAAC"}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Rank
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* parameter-light way to combine hybrid retrieval signals.
|
|
8
|
-
* • Maximal Marginal Relevance (MMR) reranks the fused list to trade off
|
|
9
|
-
* relevance against novelty, so the top-k isn't five near-duplicate chunks.
|
|
2
|
+
* Reciprocal Rank Fusion (RRF) — merges rankings (dense vector, sparse BM25, …) into
|
|
3
|
+
* one list without needing their score scales to be commensurable: it fuses on
|
|
4
|
+
* RANK, not raw score. The standard, parameter-light way to combine hybrid signals.
|
|
5
|
+
* Score-based fusion lives in {@link ./weightedFusion}; diversity reranking in
|
|
6
|
+
* {@link ./mmr}.
|
|
10
7
|
*
|
|
11
8
|
* Pure and zero-dependency.
|
|
12
9
|
*/
|
|
@@ -20,21 +17,18 @@ export interface FusedHit {
|
|
|
20
17
|
id: string;
|
|
21
18
|
score: number;
|
|
22
19
|
}
|
|
20
|
+
/** The canonical TREC damping constant for {@link rrfTerm}. */
|
|
21
|
+
export declare const RRF_DEFAULT_K = 60;
|
|
22
|
+
/**
|
|
23
|
+
* One list's RRF contribution for the item at zero-based `rank`: `1 / (k + rank + 1)`.
|
|
24
|
+
* Exported for a ranker that blends the rank signal into a score of its own rather
|
|
25
|
+
* than fusing whole lists.
|
|
26
|
+
*/
|
|
27
|
+
export declare function rrfTerm(rank: number, k?: number): number;
|
|
23
28
|
/**
|
|
24
29
|
* Reciprocal Rank Fusion over any number of ranked lists.
|
|
25
30
|
* score(d) = Σ_lists weight / (k + rank(d)). `k` (default 60) damps the
|
|
26
31
|
* contribution of low-ranked items; the canonical TREC value.
|
|
27
32
|
*/
|
|
28
33
|
export declare function reciprocalRankFusion(lists: RankedList[], k?: number): FusedHit[];
|
|
29
|
-
export interface MmrCandidate {
|
|
30
|
-
id: string;
|
|
31
|
-
vector: Float32Array;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Maximal Marginal Relevance rerank. Greedily selects up to `topK` candidates,
|
|
35
|
-
* each step maximising `λ·sim(query, d) − (1−λ)·max sim(d, already-selected)`.
|
|
36
|
-
* λ=1 is pure relevance; lower λ injects diversity. Candidates without vectors
|
|
37
|
-
* should be filtered out by the caller (they cannot be MMR-scored).
|
|
38
|
-
*/
|
|
39
|
-
export declare function maximalMarginalRelevance(queryVec: Float32Array, candidates: MmrCandidate[], topK: number, lambda?: number): string[];
|
|
40
34
|
//# sourceMappingURL=fusion.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fusion.d.ts","sourceRoot":"","sources":["../../src/retrieval/fusion.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"fusion.d.ts","sourceRoot":"","sources":["../../src/retrieval/fusion.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,UAAU;IACvB,wCAAwC;IACxC,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,+DAA+D;AAC/D,eAAO,MAAM,aAAa,KAAK,CAAC;AAEhC;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,GAAE,MAAsB,GAAG,MAAM,CAEvE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC,SAAgB,GAAG,QAAQ,EAAE,CAWvF"}
|
package/dist/retrieval/fusion.js
CHANGED
|
@@ -1,64 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Rank
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* parameter-light way to combine hybrid retrieval signals.
|
|
8
|
-
* • Maximal Marginal Relevance (MMR) reranks the fused list to trade off
|
|
9
|
-
* relevance against novelty, so the top-k isn't five near-duplicate chunks.
|
|
2
|
+
* Reciprocal Rank Fusion (RRF) — merges rankings (dense vector, sparse BM25, …) into
|
|
3
|
+
* one list without needing their score scales to be commensurable: it fuses on
|
|
4
|
+
* RANK, not raw score. The standard, parameter-light way to combine hybrid signals.
|
|
5
|
+
* Score-based fusion lives in {@link ./weightedFusion}; diversity reranking in
|
|
6
|
+
* {@link ./mmr}.
|
|
10
7
|
*
|
|
11
8
|
* Pure and zero-dependency.
|
|
12
9
|
*/
|
|
13
|
-
|
|
10
|
+
/** The canonical TREC damping constant for {@link rrfTerm}. */
|
|
11
|
+
export const RRF_DEFAULT_K = 60;
|
|
12
|
+
/**
|
|
13
|
+
* One list's RRF contribution for the item at zero-based `rank`: `1 / (k + rank + 1)`.
|
|
14
|
+
* Exported for a ranker that blends the rank signal into a score of its own rather
|
|
15
|
+
* than fusing whole lists.
|
|
16
|
+
*/
|
|
17
|
+
export function rrfTerm(rank, k = RRF_DEFAULT_K) {
|
|
18
|
+
return 1 / (k + rank + 1);
|
|
19
|
+
}
|
|
14
20
|
/**
|
|
15
21
|
* Reciprocal Rank Fusion over any number of ranked lists.
|
|
16
22
|
* score(d) = Σ_lists weight / (k + rank(d)). `k` (default 60) damps the
|
|
17
23
|
* contribution of low-ranked items; the canonical TREC value.
|
|
18
24
|
*/
|
|
19
|
-
export function reciprocalRankFusion(lists, k =
|
|
25
|
+
export function reciprocalRankFusion(lists, k = RRF_DEFAULT_K) {
|
|
20
26
|
const acc = new Map();
|
|
21
27
|
for (const list of lists) {
|
|
22
28
|
const weight = list.weight ?? 1;
|
|
23
29
|
list.ids.forEach((id, rank) => {
|
|
24
|
-
acc.set(id, (acc.get(id) ?? 0) + weight
|
|
30
|
+
acc.set(id, (acc.get(id) ?? 0) + weight * rrfTerm(rank, k));
|
|
25
31
|
});
|
|
26
32
|
}
|
|
27
33
|
return [...acc.entries()]
|
|
28
34
|
.map(([id, score]) => ({ id, score }))
|
|
29
35
|
.sort((a, b) => b.score - a.score);
|
|
30
36
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Maximal Marginal Relevance rerank. Greedily selects up to `topK` candidates,
|
|
33
|
-
* each step maximising `λ·sim(query, d) − (1−λ)·max sim(d, already-selected)`.
|
|
34
|
-
* λ=1 is pure relevance; lower λ injects diversity. Candidates without vectors
|
|
35
|
-
* should be filtered out by the caller (they cannot be MMR-scored).
|
|
36
|
-
*/
|
|
37
|
-
export function maximalMarginalRelevance(queryVec, candidates, topK, lambda = 0.7) {
|
|
38
|
-
const remaining = [...candidates];
|
|
39
|
-
const selected = [];
|
|
40
|
-
const relevance = new Map();
|
|
41
|
-
for (const c of remaining)
|
|
42
|
-
relevance.set(c.id, cosineSimilarity(queryVec, c.vector));
|
|
43
|
-
while (selected.length < topK && remaining.length > 0) {
|
|
44
|
-
let bestIdx = 0;
|
|
45
|
-
let bestScore = -Infinity;
|
|
46
|
-
for (let i = 0; i < remaining.length; i++) {
|
|
47
|
-
const c = remaining[i];
|
|
48
|
-
let maxSimToSelected = 0;
|
|
49
|
-
for (const s of selected) {
|
|
50
|
-
const sim = cosineSimilarity(c.vector, s.vector);
|
|
51
|
-
if (sim > maxSimToSelected)
|
|
52
|
-
maxSimToSelected = sim;
|
|
53
|
-
}
|
|
54
|
-
const mmr = lambda * relevance.get(c.id) - (1 - lambda) * maxSimToSelected;
|
|
55
|
-
if (mmr > bestScore) {
|
|
56
|
-
bestScore = mmr;
|
|
57
|
-
bestIdx = i;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
selected.push(remaining.splice(bestIdx, 1)[0]);
|
|
61
|
-
}
|
|
62
|
-
return selected.map(s => s.id);
|
|
63
|
-
}
|
|
64
37
|
//# sourceMappingURL=fusion.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fusion.js","sourceRoot":"","sources":["../../src/retrieval/fusion.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"fusion.js","sourceRoot":"","sources":["../../src/retrieval/fusion.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAcH,+DAA+D;AAC/D,MAAM,CAAC,MAAM,aAAa,GAAG,EAAE,CAAC;AAEhC;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,IAAY,aAAa;IAC3D,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC;AAC9B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAmB,EAAE,CAAC,GAAG,aAAa;IACvE,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;QAChC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE;YAC1B,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC,CAAC,CAAC;IACP,CAAC;IACD,OAAO,CAAC,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;SACpB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;SACrC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;AAC3C,CAAC"}
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Retrieval layer — chunking, BM25, rank fusion,
|
|
2
|
+
* Retrieval layer — chunking, BM25, rank and score fusion, MMR, recency decay, the
|
|
3
|
+
* similarity primitives they rest on, and the HybridRetriever.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Zero-dependency and engine-free: this is the `@seanhogg/builderforce-memory/retrieval`
|
|
6
|
+
* subpath, which a Cloudflare Worker, Node and the browser can all import without
|
|
7
|
+
* pulling the WebGPU engine. Every host ranks with THESE functions rather than a copy.
|
|
7
8
|
*/
|
|
9
|
+
export { cosineSimilarity, jaccardSimilarity, tokenize } from '../similarity/index.js';
|
|
8
10
|
export { chunkText } from './chunk.js';
|
|
9
11
|
export type { Chunk, ChunkOptions } from './chunk.js';
|
|
10
12
|
export { bm25Search, bm25Idf, bm25LengthNorm, bm25TermScore, BM25_DEFAULT_K1, BM25_DEFAULT_B } from './bm25.js';
|
|
11
13
|
export type { Bm25Doc, Bm25Hit, Bm25Options, Bm25Tokenizer } from './bm25.js';
|
|
12
|
-
export { reciprocalRankFusion,
|
|
13
|
-
export type { RankedList, FusedHit
|
|
14
|
+
export { reciprocalRankFusion, rrfTerm, RRF_DEFAULT_K } from './fusion.js';
|
|
15
|
+
export type { RankedList, FusedHit } from './fusion.js';
|
|
16
|
+
export { mmrSelect, maximalMarginalRelevance, textMaximalMarginalRelevance } from './mmr.js';
|
|
17
|
+
export type { MmrSelectOptions, MmrCandidate, TextMmrItem } from './mmr.js';
|
|
18
|
+
export { DEFAULT_HYBRID_VECTOR_WEIGHT, DEFAULT_HYBRID_TEXT_WEIGHT, normalizeHybridWeights, hybridScore, bm25RankToScore, fuseHybridArms, } from './weightedFusion.js';
|
|
19
|
+
export type { HybridWeights, HybridArmScores } from './weightedFusion.js';
|
|
20
|
+
export { toDecayLambda, calculateTemporalDecayMultiplier, applyTemporalDecayToScore } from './decay.js';
|
|
14
21
|
export { hybridRetrieve } from './HybridRetriever.js';
|
|
15
22
|
export type { RetrievalCandidate, HybridQuery, HybridRetrieveOptions, HybridHit, } from './HybridRetriever.js';
|
|
16
23
|
export { HnswIndex, denseSearch } from './hnsw.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/retrieval/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/retrieval/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAEvF,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChH,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE9E,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3E,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,EAAE,SAAS,EAAE,wBAAwB,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAC;AAC7F,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5E,OAAO,EACH,4BAA4B,EAC5B,0BAA0B,EAC1B,sBAAsB,EACtB,WAAW,EACX,eAAe,EACf,cAAc,GACjB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE1E,OAAO,EAAE,aAAa,EAAE,gCAAgC,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAExG,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EACR,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,SAAS,GACZ,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACnD,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/retrieval/index.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Retrieval layer — chunking, BM25, rank fusion,
|
|
2
|
+
* Retrieval layer — chunking, BM25, rank and score fusion, MMR, recency decay, the
|
|
3
|
+
* similarity primitives they rest on, and the HybridRetriever.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Zero-dependency and engine-free: this is the `@seanhogg/builderforce-memory/retrieval`
|
|
6
|
+
* subpath, which a Cloudflare Worker, Node and the browser can all import without
|
|
7
|
+
* pulling the WebGPU engine. Every host ranks with THESE functions rather than a copy.
|
|
7
8
|
*/
|
|
9
|
+
export { cosineSimilarity, jaccardSimilarity, tokenize } from '../similarity/index.js';
|
|
8
10
|
export { chunkText } from './chunk.js';
|
|
9
11
|
export { bm25Search, bm25Idf, bm25LengthNorm, bm25TermScore, BM25_DEFAULT_K1, BM25_DEFAULT_B } from './bm25.js';
|
|
10
|
-
export { reciprocalRankFusion,
|
|
12
|
+
export { reciprocalRankFusion, rrfTerm, RRF_DEFAULT_K } from './fusion.js';
|
|
13
|
+
export { mmrSelect, maximalMarginalRelevance, textMaximalMarginalRelevance } from './mmr.js';
|
|
14
|
+
export { DEFAULT_HYBRID_VECTOR_WEIGHT, DEFAULT_HYBRID_TEXT_WEIGHT, normalizeHybridWeights, hybridScore, bm25RankToScore, fuseHybridArms, } from './weightedFusion.js';
|
|
15
|
+
export { toDecayLambda, calculateTemporalDecayMultiplier, applyTemporalDecayToScore } from './decay.js';
|
|
11
16
|
export { hybridRetrieve } from './HybridRetriever.js';
|
|
12
17
|
export { HnswIndex, denseSearch } from './hnsw.js';
|
|
13
18
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/retrieval/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/retrieval/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAEvF,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGhH,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG3E,OAAO,EAAE,SAAS,EAAE,wBAAwB,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAC;AAG7F,OAAO,EACH,4BAA4B,EAC5B,0BAA0B,EAC1B,sBAAsB,EACtB,WAAW,EACX,eAAe,EACf,cAAc,GACjB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,aAAa,EAAE,gCAAgC,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAExG,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAQtD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maximal Marginal Relevance — ONE greedy selection loop, two similarity bases.
|
|
3
|
+
*
|
|
4
|
+
* MMR reranks a relevance-ordered list so the top of it is not five near-duplicates:
|
|
5
|
+
* each step picks the candidate maximising `λ·relevance − (1−λ)·max sim(candidate,
|
|
6
|
+
* already-selected)`. λ=1 is pure relevance; lower λ buys diversity.
|
|
7
|
+
*
|
|
8
|
+
* What differs between callers is only what "similar" means:
|
|
9
|
+
* • {@link maximalMarginalRelevance} — cosine over embedding vectors, relevance =
|
|
10
|
+
* cosine to the query vector. For retrievers that hold vectors;
|
|
11
|
+
* • {@link textMaximalMarginalRelevance} — Jaccard over word tokens, relevance =
|
|
12
|
+
* the candidate's own score min-max normalised onto [0,1]. For retrievers that
|
|
13
|
+
* only have text and a fused score (an FTS5 + sqlite-vec hybrid, say).
|
|
14
|
+
*
|
|
15
|
+
* Both run {@link mmrSelect}, so the selection rule itself exists once.
|
|
16
|
+
*
|
|
17
|
+
* Pure and zero-dependency.
|
|
18
|
+
*/
|
|
19
|
+
export interface MmrSelectOptions<T> {
|
|
20
|
+
/** Relevance of one candidate, on the same scale as {@link similarity}. */
|
|
21
|
+
relevance: (item: T) => number;
|
|
22
|
+
/** Similarity of two candidates. */
|
|
23
|
+
similarity: (a: T, b: T) => number;
|
|
24
|
+
/** 0 = maximum diversity, 1 = maximum relevance. Default 0.7. */
|
|
25
|
+
lambda?: number;
|
|
26
|
+
/** Stop after this many picks. Default: every candidate. */
|
|
27
|
+
topK?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Breaks an exact MMR tie in favour of the higher value. Without it the earlier
|
|
30
|
+
* candidate wins, so input order decides.
|
|
31
|
+
*/
|
|
32
|
+
tieScore?: (item: T) => number;
|
|
33
|
+
}
|
|
34
|
+
/** Greedy MMR selection. Returns the picked candidates in pick order. */
|
|
35
|
+
export declare function mmrSelect<T>(items: readonly T[], opts: MmrSelectOptions<T>): T[];
|
|
36
|
+
export interface MmrCandidate {
|
|
37
|
+
id: string;
|
|
38
|
+
vector: Float32Array;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Vector MMR. Selects up to `topK` candidates by cosine relevance to `queryVec`,
|
|
42
|
+
* penalising cosine similarity to what is already selected. Candidates without
|
|
43
|
+
* vectors should be filtered out by the caller (they cannot be MMR-scored).
|
|
44
|
+
*/
|
|
45
|
+
export declare function maximalMarginalRelevance(queryVec: Float32Array, candidates: MmrCandidate[], topK: number, lambda?: number): string[];
|
|
46
|
+
export interface TextMmrItem {
|
|
47
|
+
id: string;
|
|
48
|
+
/** The candidate's fused relevance score (any scale — it is normalised here). */
|
|
49
|
+
score: number;
|
|
50
|
+
/** The text diversity is judged on. */
|
|
51
|
+
content: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Text MMR. Reorders every item, judging diversity by Jaccard similarity of word
|
|
55
|
+
* tokens, and relevance by the item's `score` min-max normalised onto [0,1] (all
|
|
56
|
+
* equal → 1). An exact tie goes to the higher raw score. `lambda` is clamped to
|
|
57
|
+
* [0,1]; at 1 this is a plain score sort.
|
|
58
|
+
*/
|
|
59
|
+
export declare function textMaximalMarginalRelevance<T extends TextMmrItem>(items: readonly T[], lambda?: number): T[];
|
|
60
|
+
//# sourceMappingURL=mmr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mmr.d.ts","sourceRoot":"","sources":["../../src/retrieval/mmr.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,MAAM,WAAW,gBAAgB,CAAC,CAAC;IAC/B,2EAA2E;IAC3E,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;IAC/B,oCAAoC;IACpC,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,MAAM,CAAC;IACnC,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;CAClC;AAED,yEAAyE;AACzE,wBAAgB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CA4BhF;AAED,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,YAAY,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACpC,QAAQ,EAAE,YAAY,EACtB,UAAU,EAAE,YAAY,EAAE,EAC1B,IAAI,EAAE,MAAM,EACZ,MAAM,SAAM,GACb,MAAM,EAAE,CAOV;AAED,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,MAAM,SAAM,GAAG,CAAC,EAAE,CAmB1G"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maximal Marginal Relevance — ONE greedy selection loop, two similarity bases.
|
|
3
|
+
*
|
|
4
|
+
* MMR reranks a relevance-ordered list so the top of it is not five near-duplicates:
|
|
5
|
+
* each step picks the candidate maximising `λ·relevance − (1−λ)·max sim(candidate,
|
|
6
|
+
* already-selected)`. λ=1 is pure relevance; lower λ buys diversity.
|
|
7
|
+
*
|
|
8
|
+
* What differs between callers is only what "similar" means:
|
|
9
|
+
* • {@link maximalMarginalRelevance} — cosine over embedding vectors, relevance =
|
|
10
|
+
* cosine to the query vector. For retrievers that hold vectors;
|
|
11
|
+
* • {@link textMaximalMarginalRelevance} — Jaccard over word tokens, relevance =
|
|
12
|
+
* the candidate's own score min-max normalised onto [0,1]. For retrievers that
|
|
13
|
+
* only have text and a fused score (an FTS5 + sqlite-vec hybrid, say).
|
|
14
|
+
*
|
|
15
|
+
* Both run {@link mmrSelect}, so the selection rule itself exists once.
|
|
16
|
+
*
|
|
17
|
+
* Pure and zero-dependency.
|
|
18
|
+
*/
|
|
19
|
+
import { cosineSimilarity, jaccardSimilarity, tokenize } from '../similarity/index.js';
|
|
20
|
+
/** Greedy MMR selection. Returns the picked candidates in pick order. */
|
|
21
|
+
export function mmrSelect(items, opts) {
|
|
22
|
+
const lambda = opts.lambda ?? 0.7;
|
|
23
|
+
const limit = Math.min(opts.topK ?? items.length, items.length);
|
|
24
|
+
const remaining = [...items];
|
|
25
|
+
const relevance = new Map();
|
|
26
|
+
for (const item of remaining)
|
|
27
|
+
relevance.set(item, opts.relevance(item));
|
|
28
|
+
const selected = [];
|
|
29
|
+
while (selected.length < limit && remaining.length > 0) {
|
|
30
|
+
let bestIdx = -1;
|
|
31
|
+
let bestScore = -Infinity;
|
|
32
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
33
|
+
const candidate = remaining[i];
|
|
34
|
+
let maxSimToSelected = 0;
|
|
35
|
+
for (const s of selected) {
|
|
36
|
+
const sim = opts.similarity(candidate, s);
|
|
37
|
+
if (sim > maxSimToSelected)
|
|
38
|
+
maxSimToSelected = sim;
|
|
39
|
+
}
|
|
40
|
+
const score = lambda * relevance.get(candidate) - (1 - lambda) * maxSimToSelected;
|
|
41
|
+
const wins = score > bestScore
|
|
42
|
+
|| (score === bestScore && bestIdx >= 0 && opts.tieScore !== undefined
|
|
43
|
+
&& opts.tieScore(candidate) > opts.tieScore(remaining[bestIdx]));
|
|
44
|
+
if (wins) {
|
|
45
|
+
bestScore = score;
|
|
46
|
+
bestIdx = i;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (bestIdx < 0)
|
|
50
|
+
break;
|
|
51
|
+
selected.push(remaining.splice(bestIdx, 1)[0]);
|
|
52
|
+
}
|
|
53
|
+
return selected;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Vector MMR. Selects up to `topK` candidates by cosine relevance to `queryVec`,
|
|
57
|
+
* penalising cosine similarity to what is already selected. Candidates without
|
|
58
|
+
* vectors should be filtered out by the caller (they cannot be MMR-scored).
|
|
59
|
+
*/
|
|
60
|
+
export function maximalMarginalRelevance(queryVec, candidates, topK, lambda = 0.7) {
|
|
61
|
+
return mmrSelect(candidates, {
|
|
62
|
+
relevance: (c) => cosineSimilarity(queryVec, c.vector),
|
|
63
|
+
similarity: (a, b) => cosineSimilarity(a.vector, b.vector),
|
|
64
|
+
lambda,
|
|
65
|
+
topK,
|
|
66
|
+
}).map((c) => c.id);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Text MMR. Reorders every item, judging diversity by Jaccard similarity of word
|
|
70
|
+
* tokens, and relevance by the item's `score` min-max normalised onto [0,1] (all
|
|
71
|
+
* equal → 1). An exact tie goes to the higher raw score. `lambda` is clamped to
|
|
72
|
+
* [0,1]; at 1 this is a plain score sort.
|
|
73
|
+
*/
|
|
74
|
+
export function textMaximalMarginalRelevance(items, lambda = 0.7) {
|
|
75
|
+
if (items.length <= 1)
|
|
76
|
+
return [...items];
|
|
77
|
+
const l = Math.max(0, Math.min(1, lambda));
|
|
78
|
+
if (l === 1)
|
|
79
|
+
return [...items].sort((a, b) => b.score - a.score);
|
|
80
|
+
const tokens = new Map();
|
|
81
|
+
for (const item of items)
|
|
82
|
+
tokens.set(item.id, new Set(tokenize(item.content)));
|
|
83
|
+
const tokensOf = (item) => tokens.get(item.id) ?? new Set(tokenize(item.content));
|
|
84
|
+
const scores = items.map((i) => i.score);
|
|
85
|
+
const min = Math.min(...scores);
|
|
86
|
+
const range = Math.max(...scores) - min;
|
|
87
|
+
return mmrSelect(items, {
|
|
88
|
+
relevance: (item) => (range === 0 ? 1 : (item.score - min) / range),
|
|
89
|
+
similarity: (a, b) => jaccardSimilarity(tokensOf(a), tokensOf(b)),
|
|
90
|
+
lambda: l,
|
|
91
|
+
tieScore: (item) => item.score,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=mmr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mmr.js","sourceRoot":"","sources":["../../src/retrieval/mmr.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weighted-score fusion — the ONE formula for fusing a semantic arm with a lexical
|
|
3
|
+
* arm by SCORE, where {@link ./fusion reciprocalRankFusion} fuses by RANK.
|
|
4
|
+
*
|
|
5
|
+
* Score fusion is the choice when two different engines must rank the same way: an
|
|
6
|
+
* on-prem `sqlite-vec` cosine + FTS5/BM25 pair and a cloud pgvector cosine + ILIKE
|
|
7
|
+
* pair answer the same question, and a weighted sum of scores already on roughly
|
|
8
|
+
* [0,1] makes "recall ranks the same everywhere" a property of one function.
|
|
9
|
+
*
|
|
10
|
+
* Union semantics: a candidate found by only one arm scores 0 on the other rather
|
|
11
|
+
* than being dropped, because "the lexical arm did not match" is evidence about the
|
|
12
|
+
* query's wording, not about the candidate's relevance.
|
|
13
|
+
*
|
|
14
|
+
* Pure and zero-dependency.
|
|
15
|
+
*/
|
|
16
|
+
/** Weight on the semantic arm when a caller states none. */
|
|
17
|
+
export declare const DEFAULT_HYBRID_VECTOR_WEIGHT = 0.7;
|
|
18
|
+
/** Weight on the lexical arm when a caller states none. */
|
|
19
|
+
export declare const DEFAULT_HYBRID_TEXT_WEIGHT = 0.3;
|
|
20
|
+
export interface HybridWeights {
|
|
21
|
+
vectorWeight: number;
|
|
22
|
+
textWeight: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Renormalise a pair of weights so it sums to 1 — a fused score is only comparable
|
|
26
|
+
* across queries if the weights always sum the same. A degenerate pair (both zero
|
|
27
|
+
* or non-finite) falls back to the defaults.
|
|
28
|
+
*
|
|
29
|
+
* The pair is a RATIO, so only the lower bound is clamped: `(3, 1)` means "three
|
|
30
|
+
* parts semantic to one part lexical" and must come back as 0.75/0.25, not be
|
|
31
|
+
* capped into an even split. Normalisation is what bounds the result.
|
|
32
|
+
*/
|
|
33
|
+
export declare function normalizeHybridWeights(vectorWeight?: number, textWeight?: number): HybridWeights;
|
|
34
|
+
/** One candidate's per-arm scores, both already normalised to roughly [0,1]. */
|
|
35
|
+
export interface HybridArmScores {
|
|
36
|
+
vectorScore?: number;
|
|
37
|
+
textScore?: number;
|
|
38
|
+
}
|
|
39
|
+
/** The fused score for one candidate under the given (already normalised) weights. */
|
|
40
|
+
export declare function hybridScore(scores: HybridArmScores, weights: HybridWeights): number;
|
|
41
|
+
/**
|
|
42
|
+
* Map a BM25 rank (lower is better, unbounded — FTS5's `bm25()`) onto a (0,1]
|
|
43
|
+
* score so it can be summed with a cosine similarity. A non-finite rank is treated
|
|
44
|
+
* as very poor rather than as an error.
|
|
45
|
+
*/
|
|
46
|
+
export declare function bm25RankToScore(rank: number): number;
|
|
47
|
+
/**
|
|
48
|
+
* Fuse two arms keyed by a caller-supplied id and return the candidates ordered
|
|
49
|
+
* best-first. `merge` builds the output row from the winning payload of each id;
|
|
50
|
+
* later duplicates of the same id keep the first payload seen (arm order decides).
|
|
51
|
+
*/
|
|
52
|
+
export declare function fuseHybridArms<TVector extends {
|
|
53
|
+
id: string;
|
|
54
|
+
}, TText extends {
|
|
55
|
+
id: string;
|
|
56
|
+
}, TOut>(params: {
|
|
57
|
+
vector: readonly TVector[];
|
|
58
|
+
text: readonly TText[];
|
|
59
|
+
vectorScoreOf: (row: TVector) => number;
|
|
60
|
+
textScoreOf: (row: TText) => number;
|
|
61
|
+
merge: (row: TVector | TText, score: number) => TOut;
|
|
62
|
+
weights?: Partial<HybridWeights>;
|
|
63
|
+
}): TOut[];
|
|
64
|
+
//# sourceMappingURL=weightedFusion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"weightedFusion.d.ts","sourceRoot":"","sources":["../../src/retrieval/weightedFusion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,4DAA4D;AAC5D,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAChD,2DAA2D;AAC3D,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,aAAa,CAUhG;AAED,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,GAAG,MAAM,CAInF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGpD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,IAAI,EAAE,MAAM,EAAE;IACvG,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;IAC3B,IAAI,EAAE,SAAS,KAAK,EAAE,CAAC;IACvB,aAAa,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IACxC,WAAW,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,MAAM,CAAC;IACpC,KAAK,EAAE,CAAC,GAAG,EAAE,OAAO,GAAG,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;CACpC,GAAG,IAAI,EAAE,CAkBT"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weighted-score fusion — the ONE formula for fusing a semantic arm with a lexical
|
|
3
|
+
* arm by SCORE, where {@link ./fusion reciprocalRankFusion} fuses by RANK.
|
|
4
|
+
*
|
|
5
|
+
* Score fusion is the choice when two different engines must rank the same way: an
|
|
6
|
+
* on-prem `sqlite-vec` cosine + FTS5/BM25 pair and a cloud pgvector cosine + ILIKE
|
|
7
|
+
* pair answer the same question, and a weighted sum of scores already on roughly
|
|
8
|
+
* [0,1] makes "recall ranks the same everywhere" a property of one function.
|
|
9
|
+
*
|
|
10
|
+
* Union semantics: a candidate found by only one arm scores 0 on the other rather
|
|
11
|
+
* than being dropped, because "the lexical arm did not match" is evidence about the
|
|
12
|
+
* query's wording, not about the candidate's relevance.
|
|
13
|
+
*
|
|
14
|
+
* Pure and zero-dependency.
|
|
15
|
+
*/
|
|
16
|
+
/** Weight on the semantic arm when a caller states none. */
|
|
17
|
+
export const DEFAULT_HYBRID_VECTOR_WEIGHT = 0.7;
|
|
18
|
+
/** Weight on the lexical arm when a caller states none. */
|
|
19
|
+
export const DEFAULT_HYBRID_TEXT_WEIGHT = 0.3;
|
|
20
|
+
/**
|
|
21
|
+
* Renormalise a pair of weights so it sums to 1 — a fused score is only comparable
|
|
22
|
+
* across queries if the weights always sum the same. A degenerate pair (both zero
|
|
23
|
+
* or non-finite) falls back to the defaults.
|
|
24
|
+
*
|
|
25
|
+
* The pair is a RATIO, so only the lower bound is clamped: `(3, 1)` means "three
|
|
26
|
+
* parts semantic to one part lexical" and must come back as 0.75/0.25, not be
|
|
27
|
+
* capped into an even split. Normalisation is what bounds the result.
|
|
28
|
+
*/
|
|
29
|
+
export function normalizeHybridWeights(vectorWeight, textWeight) {
|
|
30
|
+
const clamp = (n, fallback) => typeof n === 'number' && Number.isFinite(n) ? Math.max(0, n) : fallback;
|
|
31
|
+
const v = clamp(vectorWeight, DEFAULT_HYBRID_VECTOR_WEIGHT);
|
|
32
|
+
const t = clamp(textWeight, DEFAULT_HYBRID_TEXT_WEIGHT);
|
|
33
|
+
const sum = v + t;
|
|
34
|
+
if (sum <= 0) {
|
|
35
|
+
return { vectorWeight: DEFAULT_HYBRID_VECTOR_WEIGHT, textWeight: DEFAULT_HYBRID_TEXT_WEIGHT };
|
|
36
|
+
}
|
|
37
|
+
return { vectorWeight: v / sum, textWeight: t / sum };
|
|
38
|
+
}
|
|
39
|
+
/** The fused score for one candidate under the given (already normalised) weights. */
|
|
40
|
+
export function hybridScore(scores, weights) {
|
|
41
|
+
const v = Number.isFinite(scores.vectorScore) ? scores.vectorScore : 0;
|
|
42
|
+
const t = Number.isFinite(scores.textScore) ? scores.textScore : 0;
|
|
43
|
+
return weights.vectorWeight * v + weights.textWeight * t;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Map a BM25 rank (lower is better, unbounded — FTS5's `bm25()`) onto a (0,1]
|
|
47
|
+
* score so it can be summed with a cosine similarity. A non-finite rank is treated
|
|
48
|
+
* as very poor rather than as an error.
|
|
49
|
+
*/
|
|
50
|
+
export function bm25RankToScore(rank) {
|
|
51
|
+
const normalized = Number.isFinite(rank) ? Math.max(0, rank) : 999;
|
|
52
|
+
return 1 / (1 + normalized);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Fuse two arms keyed by a caller-supplied id and return the candidates ordered
|
|
56
|
+
* best-first. `merge` builds the output row from the winning payload of each id;
|
|
57
|
+
* later duplicates of the same id keep the first payload seen (arm order decides).
|
|
58
|
+
*/
|
|
59
|
+
export function fuseHybridArms(params) {
|
|
60
|
+
const weights = normalizeHybridWeights(params.weights?.vectorWeight, params.weights?.textWeight);
|
|
61
|
+
const byId = new Map();
|
|
62
|
+
for (const row of params.vector) {
|
|
63
|
+
byId.set(row.id, { row, scores: { vectorScore: params.vectorScoreOf(row), textScore: 0 } });
|
|
64
|
+
}
|
|
65
|
+
for (const row of params.text) {
|
|
66
|
+
const existing = byId.get(row.id);
|
|
67
|
+
if (existing)
|
|
68
|
+
existing.scores.textScore = params.textScoreOf(row);
|
|
69
|
+
else
|
|
70
|
+
byId.set(row.id, { row, scores: { vectorScore: 0, textScore: params.textScoreOf(row) } });
|
|
71
|
+
}
|
|
72
|
+
return [...byId.values()]
|
|
73
|
+
.map((entry) => {
|
|
74
|
+
const score = hybridScore(entry.scores, weights);
|
|
75
|
+
return { out: params.merge(entry.row, score), score };
|
|
76
|
+
})
|
|
77
|
+
.sort((a, b) => b.score - a.score)
|
|
78
|
+
.map((entry) => entry.out);
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=weightedFusion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"weightedFusion.js","sourceRoot":"","sources":["../../src/retrieval/weightedFusion.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,10 +10,12 @@ export declare function tokenize(text: string): string[];
|
|
|
10
10
|
/** Jaccard similarity between two token sets: |A ∩ B| / |A ∪ B|. */
|
|
11
11
|
export declare function jaccardSimilarity(a: Set<string>, b: Set<string>): number;
|
|
12
12
|
/**
|
|
13
|
-
* Cosine similarity between two vectors in the range [-1, 1] (0 when either is
|
|
14
|
-
* zero vector). Compares over the shorter length when lengths differ. Vectors
|
|
13
|
+
* Cosine similarity between two vectors in the range [-1, 1] (0 when either is empty
|
|
14
|
+
* or a zero vector). Compares over the shorter length when lengths differ. Vectors
|
|
15
15
|
* from MambaSession.embed() are already L2-normalised — this reduces to a dot
|
|
16
|
-
* product — but we normalise defensively for vectors from other sources.
|
|
16
|
+
* product — but we normalise defensively for vectors from other sources. Takes any
|
|
17
|
+
* numeric array, so a `Float32Array` embedding and a JSON-decoded `number[]` (a
|
|
18
|
+
* pgvector row, a KV entry) go through the same function.
|
|
17
19
|
*/
|
|
18
|
-
export declare function cosineSimilarity(a:
|
|
20
|
+
export declare function cosineSimilarity(a: ArrayLike<number>, b: ArrayLike<number>): number;
|
|
19
21
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/similarity/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,oEAAoE;AACpE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAE/C;AAED,oEAAoE;AACpE,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CASxE;AAED
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/similarity/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,oEAAoE;AACpE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAE/C;AAED,oEAAoE;AACpE,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CASxE;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,MAAM,CAUnF"}
|
package/dist/similarity/index.js
CHANGED
|
@@ -23,10 +23,12 @@ export function jaccardSimilarity(a, b) {
|
|
|
23
23
|
return intersection / union;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
* Cosine similarity between two vectors in the range [-1, 1] (0 when either is
|
|
27
|
-
* zero vector). Compares over the shorter length when lengths differ. Vectors
|
|
26
|
+
* Cosine similarity between two vectors in the range [-1, 1] (0 when either is empty
|
|
27
|
+
* or a zero vector). Compares over the shorter length when lengths differ. Vectors
|
|
28
28
|
* from MambaSession.embed() are already L2-normalised — this reduces to a dot
|
|
29
|
-
* product — but we normalise defensively for vectors from other sources.
|
|
29
|
+
* product — but we normalise defensively for vectors from other sources. Takes any
|
|
30
|
+
* numeric array, so a `Float32Array` embedding and a JSON-decoded `number[]` (a
|
|
31
|
+
* pgvector row, a KV entry) go through the same function.
|
|
30
32
|
*/
|
|
31
33
|
export function cosineSimilarity(a, b) {
|
|
32
34
|
const n = Math.min(a.length, b.length);
|