@seanhogg/builderforce-memory 2026.10.27 → 2026.10.29
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/ingest/IngestionPipeline.d.ts +1 -8
- package/dist/ingest/IngestionPipeline.d.ts.map +1 -1
- package/dist/ingest/IngestionPipeline.js +3 -15
- package/dist/ingest/IngestionPipeline.js.map +1 -1
- package/dist/ingest/docsExtract.d.ts +30 -0
- package/dist/ingest/docsExtract.d.ts.map +1 -0
- package/dist/ingest/docsExtract.js +221 -0
- package/dist/ingest/docsExtract.js.map +1 -0
- package/dist/ingest/hash.d.ts +15 -0
- package/dist/ingest/hash.d.ts.map +1 -0
- package/dist/ingest/hash.js +22 -0
- package/dist/ingest/hash.js.map +1 -0
- package/dist/ingest/parsers.d.ts.map +1 -1
- package/dist/ingest/parsers.js +4 -0
- package/dist/ingest/parsers.js.map +1 -1
- package/dist/ingest/types.d.ts +7 -0
- package/dist/ingest/types.d.ts.map +1 -1
- package/dist/ingest/types.js.map +1 -1
- package/dist/knowledge/build.d.ts +24 -0
- package/dist/knowledge/build.d.ts.map +1 -0
- package/dist/knowledge/build.js +263 -0
- package/dist/knowledge/build.js.map +1 -0
- package/dist/knowledge/cards.d.ts +17 -0
- package/dist/knowledge/cards.d.ts.map +1 -0
- package/dist/knowledge/cards.js +118 -0
- package/dist/knowledge/cards.js.map +1 -0
- package/dist/knowledge/findings.d.ts +24 -0
- package/dist/knowledge/findings.d.ts.map +1 -0
- package/dist/knowledge/findings.js +152 -0
- package/dist/knowledge/findings.js.map +1 -0
- package/dist/knowledge/importance.d.ts +21 -0
- package/dist/knowledge/importance.d.ts.map +1 -0
- package/dist/knowledge/importance.js +70 -0
- package/dist/knowledge/importance.js.map +1 -0
- package/dist/knowledge/index.d.ts +31 -0
- package/dist/knowledge/index.d.ts.map +1 -0
- package/dist/knowledge/index.js +26 -0
- package/dist/knowledge/index.js.map +1 -0
- package/dist/knowledge/judge.d.ts +86 -0
- package/dist/knowledge/judge.d.ts.map +1 -0
- package/dist/knowledge/judge.js +213 -0
- package/dist/knowledge/judge.js.map +1 -0
- package/dist/knowledge/model.d.ts +333 -0
- package/dist/knowledge/model.d.ts.map +1 -0
- package/dist/knowledge/model.js +13 -0
- package/dist/knowledge/model.js.map +1 -0
- package/dist/knowledge/packs/docs.d.ts +17 -0
- package/dist/knowledge/packs/docs.d.ts.map +1 -0
- package/dist/knowledge/packs/docs.js +89 -0
- package/dist/knowledge/packs/docs.js.map +1 -0
- package/dist/knowledge/partitions.d.ts +31 -0
- package/dist/knowledge/partitions.d.ts.map +1 -0
- package/dist/knowledge/partitions.js +135 -0
- package/dist/knowledge/partitions.js.map +1 -0
- package/dist/knowledge/plan.d.ts +33 -0
- package/dist/knowledge/plan.d.ts.map +1 -0
- package/dist/knowledge/plan.js +89 -0
- package/dist/knowledge/plan.js.map +1 -0
- package/dist/knowledge/refs.d.ts +40 -0
- package/dist/knowledge/refs.d.ts.map +1 -0
- package/dist/knowledge/refs.js +102 -0
- package/dist/knowledge/refs.js.map +1 -0
- package/dist/knowledge/resolve.d.ts +35 -0
- package/dist/knowledge/resolve.d.ts.map +1 -0
- package/dist/knowledge/resolve.js +67 -0
- package/dist/knowledge/resolve.js.map +1 -0
- package/dist/knowledge/review.d.ts +23 -0
- package/dist/knowledge/review.d.ts.map +1 -0
- package/dist/knowledge/review.js +233 -0
- package/dist/knowledge/review.js.map +1 -0
- package/dist/knowledge/sources.d.ts +47 -0
- package/dist/knowledge/sources.d.ts.map +1 -0
- package/dist/knowledge/sources.js +136 -0
- package/dist/knowledge/sources.js.map +1 -0
- package/dist/knowledge/stores/localSnapshotStore.d.ts +50 -0
- package/dist/knowledge/stores/localSnapshotStore.d.ts.map +1 -0
- package/dist/knowledge/stores/localSnapshotStore.js +125 -0
- package/dist/knowledge/stores/localSnapshotStore.js.map +1 -0
- package/dist/knowledge/trace.d.ts +32 -0
- package/dist/knowledge/trace.d.ts.map +1 -0
- package/dist/knowledge/trace.js +102 -0
- package/dist/knowledge/trace.js.map +1 -0
- package/dist/knowledge/truncate.d.ts +24 -0
- package/dist/knowledge/truncate.d.ts.map +1 -0
- package/dist/knowledge/truncate.js +128 -0
- package/dist/knowledge/truncate.js.map +1 -0
- package/dist/memory/SemanticRecall.d.ts.map +1 -1
- package/dist/memory/SemanticRecall.js +3 -1
- package/dist/memory/SemanticRecall.js.map +1 -1
- package/dist/retrieval/HybridRetriever.d.ts.map +1 -1
- package/dist/retrieval/HybridRetriever.js +3 -1
- package/dist/retrieval/HybridRetriever.js.map +1 -1
- package/dist/retrieval/bm25.d.ts +3 -2
- package/dist/retrieval/bm25.d.ts.map +1 -1
- package/dist/retrieval/bm25.js +5 -3
- package/dist/retrieval/bm25.js.map +1 -1
- package/dist/retrieval/fusion.d.ts +2 -1
- package/dist/retrieval/fusion.d.ts.map +1 -1
- package/dist/retrieval/fusion.js +4 -2
- package/dist/retrieval/fusion.js.map +1 -1
- package/dist/retrieval/hnsw.d.ts +49 -11
- package/dist/retrieval/hnsw.d.ts.map +1 -1
- package/dist/retrieval/hnsw.js +57 -29
- package/dist/retrieval/hnsw.js.map +1 -1
- package/dist/retrieval/index.d.ts +7 -3
- package/dist/retrieval/index.d.ts.map +1 -1
- package/dist/retrieval/index.js +5 -2
- package/dist/retrieval/index.js.map +1 -1
- package/dist/retrieval/knowledge-index/bytes.d.ts +43 -0
- package/dist/retrieval/knowledge-index/bytes.d.ts.map +1 -0
- package/dist/retrieval/knowledge-index/bytes.js +151 -0
- package/dist/retrieval/knowledge-index/bytes.js.map +1 -0
- package/dist/retrieval/knowledge-index/cascade.d.ts +37 -0
- package/dist/retrieval/knowledge-index/cascade.d.ts.map +1 -0
- package/dist/retrieval/knowledge-index/cascade.js +111 -0
- package/dist/retrieval/knowledge-index/cascade.js.map +1 -0
- package/dist/retrieval/knowledge-index/compact.d.ts +18 -0
- package/dist/retrieval/knowledge-index/compact.d.ts.map +1 -0
- package/dist/retrieval/knowledge-index/compact.js +64 -0
- package/dist/retrieval/knowledge-index/compact.js.map +1 -0
- package/dist/retrieval/knowledge-index/csr.d.ts +41 -0
- package/dist/retrieval/knowledge-index/csr.d.ts.map +1 -0
- package/dist/retrieval/knowledge-index/csr.js +76 -0
- package/dist/retrieval/knowledge-index/csr.js.map +1 -0
- package/dist/retrieval/knowledge-index/digest.d.ts +21 -0
- package/dist/retrieval/knowledge-index/digest.d.ts.map +1 -0
- package/dist/retrieval/knowledge-index/digest.js +50 -0
- package/dist/retrieval/knowledge-index/digest.js.map +1 -0
- package/dist/retrieval/knowledge-index/embeddings.d.ts +32 -0
- package/dist/retrieval/knowledge-index/embeddings.d.ts.map +1 -0
- package/dist/retrieval/knowledge-index/embeddings.js +78 -0
- package/dist/retrieval/knowledge-index/embeddings.js.map +1 -0
- package/dist/retrieval/knowledge-index/expand.d.ts +19 -0
- package/dist/retrieval/knowledge-index/expand.d.ts.map +1 -0
- package/dist/retrieval/knowledge-index/expand.js +49 -0
- package/dist/retrieval/knowledge-index/expand.js.map +1 -0
- package/dist/retrieval/knowledge-index/index.d.ts +40 -0
- package/dist/retrieval/knowledge-index/index.d.ts.map +1 -0
- package/dist/retrieval/knowledge-index/index.js +134 -0
- package/dist/retrieval/knowledge-index/index.js.map +1 -0
- package/dist/retrieval/knowledge-index/lexical.d.ts +26 -0
- package/dist/retrieval/knowledge-index/lexical.d.ts.map +1 -0
- package/dist/retrieval/knowledge-index/lexical.js +76 -0
- package/dist/retrieval/knowledge-index/lexical.js.map +1 -0
- package/dist/retrieval/knowledge-index/pairs.d.ts +22 -0
- package/dist/retrieval/knowledge-index/pairs.d.ts.map +1 -0
- package/dist/retrieval/knowledge-index/pairs.js +51 -0
- package/dist/retrieval/knowledge-index/pairs.js.map +1 -0
- package/dist/retrieval/knowledge-index/prune.d.ts +21 -0
- package/dist/retrieval/knowledge-index/prune.d.ts.map +1 -0
- package/dist/retrieval/knowledge-index/prune.js +43 -0
- package/dist/retrieval/knowledge-index/prune.js.map +1 -0
- package/dist/retrieval/knowledge-index/quantize.d.ts +37 -0
- package/dist/retrieval/knowledge-index/quantize.d.ts.map +1 -0
- package/dist/retrieval/knowledge-index/quantize.js +77 -0
- package/dist/retrieval/knowledge-index/quantize.js.map +1 -0
- package/dist/retrieval/knowledge-index/serialize.d.ts +18 -0
- package/dist/retrieval/knowledge-index/serialize.d.ts.map +1 -0
- package/dist/retrieval/knowledge-index/serialize.js +123 -0
- package/dist/retrieval/knowledge-index/serialize.js.map +1 -0
- package/dist/retrieval/knowledge-index/sha256.d.ts +30 -0
- package/dist/retrieval/knowledge-index/sha256.d.ts.map +1 -0
- package/dist/retrieval/knowledge-index/sha256.js +130 -0
- package/dist/retrieval/knowledge-index/sha256.js.map +1 -0
- package/dist/retrieval/knowledge-index/state.d.ts +55 -0
- package/dist/retrieval/knowledge-index/state.d.ts.map +1 -0
- package/dist/retrieval/knowledge-index/state.js +67 -0
- package/dist/retrieval/knowledge-index/state.js.map +1 -0
- package/dist/retrieval/knowledge-index/tokenize.d.ts +20 -0
- package/dist/retrieval/knowledge-index/tokenize.d.ts.map +1 -0
- package/dist/retrieval/knowledge-index/tokenize.js +42 -0
- package/dist/retrieval/knowledge-index/tokenize.js.map +1 -0
- package/dist/retrieval/knowledge-index/traverse.d.ts +26 -0
- package/dist/retrieval/knowledge-index/traverse.d.ts.map +1 -0
- package/dist/retrieval/knowledge-index/traverse.js +90 -0
- package/dist/retrieval/knowledge-index/traverse.js.map +1 -0
- package/dist/retrieval/knowledge-index/types.d.ts +85 -0
- package/dist/retrieval/knowledge-index/types.d.ts.map +1 -0
- package/dist/retrieval/knowledge-index/types.js +9 -0
- package/dist/retrieval/knowledge-index/types.js.map +1 -0
- package/dist/retrieval/mmr.d.ts +11 -4
- package/dist/retrieval/mmr.d.ts.map +1 -1
- package/dist/retrieval/mmr.js +28 -8
- package/dist/retrieval/mmr.js.map +1 -1
- package/dist/retrieval/order.d.ts +41 -0
- package/dist/retrieval/order.d.ts.map +1 -0
- package/dist/retrieval/order.js +54 -0
- package/dist/retrieval/order.js.map +1 -0
- package/dist/retrieval/weightedFusion.d.ts +2 -1
- package/dist/retrieval/weightedFusion.d.ts.map +1 -1
- package/dist/retrieval/weightedFusion.js +7 -5
- package/dist/retrieval/weightedFusion.js.map +1 -1
- package/dist/vectorstore/MemoryVectorStore.d.ts.map +1 -1
- package/dist/vectorstore/MemoryVectorStore.js +2 -1
- package/dist/vectorstore/MemoryVectorStore.js.map +1 -1
- package/package.json +16 -5
- package/src/ingest/IngestionPipeline.ts +3 -15
- package/src/ingest/docsExtract.ts +219 -0
- package/src/ingest/hash.ts +22 -0
- package/src/ingest/parsers.ts +4 -0
- package/src/ingest/types.ts +7 -0
- package/src/knowledge/build.test.ts +150 -0
- package/src/knowledge/build.ts +266 -0
- package/src/knowledge/cards.ts +130 -0
- package/src/knowledge/docs.test.ts +118 -0
- package/src/knowledge/findings.ts +159 -0
- package/src/knowledge/importance.ts +72 -0
- package/src/knowledge/index.ts +53 -0
- package/src/knowledge/judge.ts +231 -0
- package/src/knowledge/model.ts +312 -0
- package/src/knowledge/packs/docs.ts +91 -0
- package/src/knowledge/partitions.test.ts +98 -0
- package/src/knowledge/partitions.ts +119 -0
- package/src/knowledge/plan.ts +115 -0
- package/src/knowledge/refs.ts +108 -0
- package/src/knowledge/resolve.ts +78 -0
- package/src/knowledge/review.test.ts +180 -0
- package/src/knowledge/review.ts +235 -0
- package/src/knowledge/sources.ts +146 -0
- package/src/knowledge/stores/localSnapshotStore.test.ts +82 -0
- package/src/knowledge/stores/localSnapshotStore.ts +159 -0
- package/src/knowledge/testkit.test-util.ts +92 -0
- package/src/knowledge/trace.test.ts +91 -0
- package/src/knowledge/trace.ts +111 -0
- package/src/knowledge/truncate.ts +118 -0
- package/src/memory/SemanticRecall.ts +3 -1
- package/src/retrieval/HybridRetriever.ts +3 -1
- package/src/retrieval/bm25.ts +5 -3
- package/src/retrieval/fusion.ts +5 -2
- package/src/retrieval/hnsw.ts +82 -35
- package/src/retrieval/index.ts +17 -3
- package/src/retrieval/knowledge-index/bytes.ts +166 -0
- package/src/retrieval/knowledge-index/cascade.ts +125 -0
- package/src/retrieval/knowledge-index/compact.ts +68 -0
- package/src/retrieval/knowledge-index/csr.ts +102 -0
- package/src/retrieval/knowledge-index/digest.ts +54 -0
- package/src/retrieval/knowledge-index/embeddings.ts +85 -0
- package/src/retrieval/knowledge-index/expand.ts +55 -0
- package/src/retrieval/knowledge-index/index.ts +162 -0
- package/src/retrieval/knowledge-index/lexical.ts +78 -0
- package/src/retrieval/knowledge-index/pairs.ts +57 -0
- package/src/retrieval/knowledge-index/prune.ts +48 -0
- package/src/retrieval/knowledge-index/quantize.ts +78 -0
- package/src/retrieval/knowledge-index/serialize.ts +124 -0
- package/src/retrieval/knowledge-index/sha256.ts +122 -0
- package/src/retrieval/knowledge-index/state.ts +108 -0
- package/src/retrieval/knowledge-index/tokenize.ts +41 -0
- package/src/retrieval/knowledge-index/traverse.ts +95 -0
- package/src/retrieval/knowledge-index/types.ts +78 -0
- package/src/retrieval/mmr.ts +35 -10
- package/src/retrieval/order.ts +68 -0
- package/src/retrieval/weightedFusion.ts +8 -5
- package/src/vectorstore/MemoryVectorStore.ts +2 -1
package/src/retrieval/mmr.ts
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { cosineSimilarity, jaccardSimilarity, tokenize } from '../similarity/index.js';
|
|
21
|
+
import { compareHits, compareIds, quantizeScore, DENSE_SCORE_QUANTUM } from './order.js';
|
|
21
22
|
|
|
22
23
|
export interface MmrSelectOptions<T> {
|
|
23
24
|
/** Relevance of one candidate, on the same scale as {@link similarity}. */
|
|
@@ -29,19 +30,44 @@ export interface MmrSelectOptions<T> {
|
|
|
29
30
|
/** Stop after this many picks. Default: every candidate. */
|
|
30
31
|
topK?: number;
|
|
31
32
|
/**
|
|
32
|
-
* Breaks an
|
|
33
|
-
*
|
|
33
|
+
* Breaks an MMR tie in favour of the higher value. Ties are judged on the MMR
|
|
34
|
+
* score quantized to {@link quantum}.
|
|
34
35
|
*/
|
|
35
36
|
tieScore?: (item: T) => number;
|
|
37
|
+
/**
|
|
38
|
+
* The candidate's id — the last tie-break, ascending. Without it the explicit
|
|
39
|
+
* id is the candidate's index in `items`, so the earlier candidate wins.
|
|
40
|
+
*/
|
|
41
|
+
idOf?: (item: T) => string;
|
|
42
|
+
/** Quantum the MMR score is compared at. Default 1e-4 (cosine noise). */
|
|
43
|
+
quantum?: number;
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
/** Greedy MMR selection. Returns the picked candidates in pick order. */
|
|
39
47
|
export function mmrSelect<T>(items: readonly T[], opts: MmrSelectOptions<T>): T[] {
|
|
40
48
|
const lambda = opts.lambda ?? 0.7;
|
|
49
|
+
const quantum = opts.quantum ?? DENSE_SCORE_QUANTUM;
|
|
41
50
|
const limit = Math.min(opts.topK ?? items.length, items.length);
|
|
42
51
|
const remaining = [...items];
|
|
43
52
|
const relevance = new Map<T, number>();
|
|
44
|
-
|
|
53
|
+
const position = new Map<T, number>();
|
|
54
|
+
items.forEach((item, i) => {
|
|
55
|
+
relevance.set(item, opts.relevance(item));
|
|
56
|
+
if (!position.has(item)) position.set(item, i);
|
|
57
|
+
});
|
|
58
|
+
const idOf = (item: T): string | number => (opts.idOf ? opts.idOf(item) : position.get(item)!);
|
|
59
|
+
// Higher quantized MMR score, then higher tieScore, then lower id.
|
|
60
|
+
const beats = (a: T, aScore: number, b: T, bScore: number): boolean => {
|
|
61
|
+
const qa = quantizeScore(aScore, quantum);
|
|
62
|
+
const qb = quantizeScore(bScore, quantum);
|
|
63
|
+
if (qa !== qb) return qa > qb;
|
|
64
|
+
if (opts.tieScore) {
|
|
65
|
+
const ta = opts.tieScore(a);
|
|
66
|
+
const tb = opts.tieScore(b);
|
|
67
|
+
if (ta !== tb) return ta > tb;
|
|
68
|
+
}
|
|
69
|
+
return compareIds(idOf(a), idOf(b)) < 0;
|
|
70
|
+
};
|
|
45
71
|
|
|
46
72
|
const selected: T[] = [];
|
|
47
73
|
while (selected.length < limit && remaining.length > 0) {
|
|
@@ -55,10 +81,7 @@ export function mmrSelect<T>(items: readonly T[], opts: MmrSelectOptions<T>): T[
|
|
|
55
81
|
if (sim > maxSimToSelected) maxSimToSelected = sim;
|
|
56
82
|
}
|
|
57
83
|
const score = lambda * relevance.get(candidate)! - (1 - lambda) * maxSimToSelected;
|
|
58
|
-
|
|
59
|
-
|| (score === bestScore && bestIdx >= 0 && opts.tieScore !== undefined
|
|
60
|
-
&& opts.tieScore(candidate) > opts.tieScore(remaining[bestIdx]!));
|
|
61
|
-
if (wins) { bestScore = score; bestIdx = i; }
|
|
84
|
+
if (bestIdx < 0 || beats(candidate, score, remaining[bestIdx]!, bestScore)) { bestScore = score; bestIdx = i; }
|
|
62
85
|
}
|
|
63
86
|
if (bestIdx < 0) break;
|
|
64
87
|
selected.push(remaining.splice(bestIdx, 1)[0]!);
|
|
@@ -87,6 +110,7 @@ export function maximalMarginalRelevance(
|
|
|
87
110
|
similarity: (a, b) => cosineSimilarity(a.vector, b.vector),
|
|
88
111
|
lambda,
|
|
89
112
|
topK,
|
|
113
|
+
idOf: (c) => c.id,
|
|
90
114
|
}).map((c) => c.id);
|
|
91
115
|
}
|
|
92
116
|
|
|
@@ -101,13 +125,13 @@ export interface TextMmrItem {
|
|
|
101
125
|
/**
|
|
102
126
|
* Text MMR. Reorders every item, judging diversity by Jaccard similarity of word
|
|
103
127
|
* tokens, and relevance by the item's `score` min-max normalised onto [0,1] (all
|
|
104
|
-
* equal → 1).
|
|
105
|
-
* [0,1]; at 1 this is a plain
|
|
128
|
+
* equal → 1). A tie goes to the higher raw score, then the lower id. `lambda` is
|
|
129
|
+
* clamped to [0,1]; at 1 this is a plain {@link compareHits} sort.
|
|
106
130
|
*/
|
|
107
131
|
export function textMaximalMarginalRelevance<T extends TextMmrItem>(items: readonly T[], lambda = 0.7): T[] {
|
|
108
132
|
if (items.length <= 1) return [...items];
|
|
109
133
|
const l = Math.max(0, Math.min(1, lambda));
|
|
110
|
-
if (l === 1) return [...items].sort(
|
|
134
|
+
if (l === 1) return [...items].sort(compareHits);
|
|
111
135
|
|
|
112
136
|
const tokens = new Map<string, Set<string>>();
|
|
113
137
|
for (const item of items) tokens.set(item.id, new Set(tokenize(item.content)));
|
|
@@ -122,5 +146,6 @@ export function textMaximalMarginalRelevance<T extends TextMmrItem>(items: reado
|
|
|
122
146
|
similarity: (a, b) => jaccardSimilarity(tokensOf(a), tokensOf(b)),
|
|
123
147
|
lambda: l,
|
|
124
148
|
tieScore: (item) => item.score,
|
|
149
|
+
idOf: (item) => item.id,
|
|
125
150
|
});
|
|
126
151
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* order.ts — the ONE total order every ranker in this package returns.
|
|
3
|
+
*
|
|
4
|
+
* Score descending, then id ascending. Scores are quantized before they are
|
|
5
|
+
* compared, so float noise in the last bits (a CPU vs a WebGPU embedding, a
|
|
6
|
+
* different summation order) cannot flip a near-tie: two scores in the same
|
|
7
|
+
* quantum ARE tied, and the id decides. Without this, ties fell back to input
|
|
8
|
+
* order, and input order is not part of any index's state.
|
|
9
|
+
*
|
|
10
|
+
* Quanta: dense (cosine) scores to 1e-4, BM25 / RRF and other exact-arithmetic
|
|
11
|
+
* scores to 1e-6. Ids compare by UTF-16 code unit (`<`), never `localeCompare`,
|
|
12
|
+
* which depends on the host's ICU data.
|
|
13
|
+
*
|
|
14
|
+
* Pure and zero-dependency.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Quantum for cosine / dot-product scores — absorbs cross-platform float noise. */
|
|
18
|
+
export const DENSE_SCORE_QUANTUM = 1e-4;
|
|
19
|
+
/** Quantum for BM25, RRF and other scores computed in exact-enough arithmetic. */
|
|
20
|
+
export const LEXICAL_SCORE_QUANTUM = 1e-6;
|
|
21
|
+
|
|
22
|
+
/** The quantized bucket of `score`. NaN sorts last; ±Infinity keep their place. */
|
|
23
|
+
export function quantizeScore(score: number, quantum: number = LEXICAL_SCORE_QUANTUM): number {
|
|
24
|
+
if (Number.isNaN(score)) return -Infinity;
|
|
25
|
+
if (!Number.isFinite(score)) return score;
|
|
26
|
+
return Math.round(score / quantum);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Ascending id order by code unit. Numeric ids (a doc index) compare numerically. */
|
|
30
|
+
export function compareIds(a: string | number, b: string | number): number {
|
|
31
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The primitive behind {@link compareHits}, for callers whose items keep score and
|
|
36
|
+
* id apart (a node index → id lookup inside a graph walk) — no object per compare.
|
|
37
|
+
*/
|
|
38
|
+
export function compareScoreThenId(
|
|
39
|
+
scoreA: number,
|
|
40
|
+
idA: string | number,
|
|
41
|
+
scoreB: number,
|
|
42
|
+
idB: string | number,
|
|
43
|
+
quantum: number = LEXICAL_SCORE_QUANTUM,
|
|
44
|
+
): number {
|
|
45
|
+
const qa = quantizeScore(scoreA, quantum);
|
|
46
|
+
const qb = quantizeScore(scoreB, quantum);
|
|
47
|
+
if (qa !== qb) return qb > qa ? 1 : -1;
|
|
48
|
+
return compareIds(idA, idB);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** A ranked item: anything with an id and a score. */
|
|
52
|
+
export interface ScoredId {
|
|
53
|
+
id: string | number;
|
|
54
|
+
score: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Score descending (quantized), then id ascending. Usable directly as an
|
|
59
|
+
* `Array.prototype.sort` comparator (the quantum then defaults to 1e-6).
|
|
60
|
+
*/
|
|
61
|
+
export function compareHits(a: ScoredId, b: ScoredId, quantum: number = LEXICAL_SCORE_QUANTUM): number {
|
|
62
|
+
return compareScoreThenId(a.score, a.id, b.score, b.id, quantum);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** {@link compareHits} at the dense quantum (1e-4) — for cosine-scored lists. */
|
|
66
|
+
export function compareDenseHits(a: ScoredId, b: ScoredId): number {
|
|
67
|
+
return compareScoreThenId(a.score, a.id, b.score, b.id, DENSE_SCORE_QUANTUM);
|
|
68
|
+
}
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* Pure and zero-dependency.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { compareScoreThenId, DENSE_SCORE_QUANTUM } from './order.js';
|
|
18
|
+
|
|
17
19
|
/** Weight on the semantic arm when a caller states none. */
|
|
18
20
|
export const DEFAULT_HYBRID_VECTOR_WEIGHT = 0.7;
|
|
19
21
|
/** Weight on the lexical arm when a caller states none. */
|
|
@@ -70,7 +72,8 @@ export function bm25RankToScore(rank: number): number {
|
|
|
70
72
|
|
|
71
73
|
/**
|
|
72
74
|
* Fuse two arms keyed by a caller-supplied id and return the candidates ordered
|
|
73
|
-
* best-first
|
|
75
|
+
* best-first: fused score (quantized at the dense quantum — the vector arm carries
|
|
76
|
+
* float noise) descending, then id ascending. `merge` builds the output row from the winning payload of each id;
|
|
74
77
|
* later duplicates of the same id keep the first payload seen (arm order decides).
|
|
75
78
|
*/
|
|
76
79
|
export function fuseHybridArms<TVector extends { id: string }, TText extends { id: string }, TOut>(params: {
|
|
@@ -91,11 +94,11 @@ export function fuseHybridArms<TVector extends { id: string }, TText extends { i
|
|
|
91
94
|
if (existing) existing.scores.textScore = params.textScoreOf(row);
|
|
92
95
|
else byId.set(row.id, { row, scores: { vectorScore: 0, textScore: params.textScoreOf(row) } });
|
|
93
96
|
}
|
|
94
|
-
return [...byId.
|
|
95
|
-
.map((entry) => {
|
|
97
|
+
return [...byId.entries()]
|
|
98
|
+
.map(([id, entry]) => {
|
|
96
99
|
const score = hybridScore(entry.scores, weights);
|
|
97
|
-
return { out: params.merge(entry.row, score), score };
|
|
100
|
+
return { id, out: params.merge(entry.row, score), score };
|
|
98
101
|
})
|
|
99
|
-
.sort((a, b) =>
|
|
102
|
+
.sort((a, b) => compareScoreThenId(a.score, a.id, b.score, b.id, DENSE_SCORE_QUANTUM))
|
|
100
103
|
.map((entry) => entry.out);
|
|
101
104
|
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
import { bm25Search } from '../retrieval/bm25.js';
|
|
22
22
|
import { HnswIndex, type HnswOptions } from '../retrieval/hnsw.js';
|
|
23
|
+
import { compareScoreThenId, DENSE_SCORE_QUANTUM } from '../retrieval/order.js';
|
|
23
24
|
import { cosineSimilarity } from '../similarity/index.js';
|
|
24
25
|
import { combineFilters, matchesFilter } from './filter.js';
|
|
25
26
|
import type {
|
|
@@ -192,7 +193,7 @@ export class MemoryVectorStore implements VectorStore {
|
|
|
192
193
|
const exactScan = () => embedded
|
|
193
194
|
.filter((r) => matchesFilter(r.metadata, filter))
|
|
194
195
|
.map((r) => ({ record: r, score: cosineSimilarity(vector, r.vector as Float32Array) }))
|
|
195
|
-
.sort((a, b) =>
|
|
196
|
+
.sort((a, b) => compareScoreThenId(a.score, a.record.id, b.score, b.record.id, DENSE_SCORE_QUANTUM));
|
|
196
197
|
|
|
197
198
|
if (embedded.length < this._annThreshold) return exactScan();
|
|
198
199
|
|