@mmnto/totem 1.124.0 → 2.1.0
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/artifacts/grounding.d.ts +27 -7
- package/dist/artifacts/grounding.d.ts.map +1 -1
- package/dist/artifacts/grounding.js +23 -5
- package/dist/artifacts/grounding.js.map +1 -1
- package/dist/artifacts/grounding.test.js +28 -0
- package/dist/artifacts/grounding.test.js.map +1 -1
- package/dist/artifacts/schema.d.ts +63 -18
- package/dist/artifacts/schema.d.ts.map +1 -1
- package/dist/artifacts/schema.js +13 -4
- package/dist/artifacts/schema.js.map +1 -1
- package/dist/compiler.d.ts +12 -0
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +17 -3
- package/dist/compiler.js.map +1 -1
- package/dist/config-schema.d.ts +95 -15
- package/dist/config-schema.d.ts.map +1 -1
- package/dist/config-schema.js +99 -14
- package/dist/config-schema.js.map +1 -1
- package/dist/config-schema.test.js +110 -1
- package/dist/config-schema.test.js.map +1 -1
- package/dist/describe.d.ts +19 -0
- package/dist/describe.d.ts.map +1 -1
- package/dist/describe.js +83 -12
- package/dist/describe.js.map +1 -1
- package/dist/describe.test.js +69 -2
- package/dist/describe.test.js.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -6
- package/dist/index.js.map +1 -1
- package/dist/ingest/pipeline.d.ts +12 -0
- package/dist/ingest/pipeline.d.ts.map +1 -1
- package/dist/ingest/pipeline.js +5 -0
- package/dist/ingest/pipeline.js.map +1 -1
- package/dist/ingest/pipeline.test.js +9 -0
- package/dist/ingest/pipeline.test.js.map +1 -1
- package/dist/selection-manifest.d.ts +4 -4
- package/dist/selection-manifest.d.ts.map +1 -1
- package/dist/selection-manifest.js +2 -0
- package/dist/selection-manifest.js.map +1 -1
- package/dist/store/lance-search.d.ts +1 -1
- package/dist/store/lance-search.d.ts.map +1 -1
- package/dist/store/lance-search.js +89 -18
- package/dist/store/lance-search.js.map +1 -1
- package/dist/store/lance-search.test.js +207 -26
- package/dist/store/lance-search.test.js.map +1 -1
- package/dist/store/lance-store.d.ts.map +1 -1
- package/dist/store/lance-store.js +1 -1
- package/dist/store/lance-store.js.map +1 -1
- package/dist/store/relevance.d.ts +70 -0
- package/dist/store/relevance.d.ts.map +1 -0
- package/dist/store/relevance.js +115 -0
- package/dist/store/relevance.js.map +1 -0
- package/dist/store/relevance.sdk.test.d.ts +20 -0
- package/dist/store/relevance.sdk.test.d.ts.map +1 -0
- package/dist/store/relevance.sdk.test.js +145 -0
- package/dist/store/relevance.sdk.test.js.map +1 -0
- package/dist/store/relevance.test.d.ts +2 -0
- package/dist/store/relevance.test.d.ts.map +1 -0
- package/dist/store/relevance.test.js +192 -0
- package/dist/store/relevance.test.js.map +1 -0
- package/dist/types.d.ts +4 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metric-bound relevance (mmnto-ai/totem#2738).
|
|
3
|
+
*
|
|
4
|
+
* A distance is only interpretable against the metric that produced it. The
|
|
5
|
+
* pre-#2738 code hard-coded `1 / (1 + _distance)` at the row-mapping site with
|
|
6
|
+
* no record of which metric LanceDB had actually used, so a metric change
|
|
7
|
+
* anywhere would have silently re-scaled every relevance number in the system.
|
|
8
|
+
* This module makes the metric a named fact and the normalization a map keyed
|
|
9
|
+
* by it: one truth, stated once, quoted in the doc comments below from the
|
|
10
|
+
* LanceDB SDK's own definitions.
|
|
11
|
+
*/
|
|
12
|
+
/** The distance metrics the LanceDB TypeScript SDK can be asked for (`VectorQuery.distanceType`). */
|
|
13
|
+
export type DistanceMetric = 'l2' | 'cosine' | 'dot';
|
|
14
|
+
/**
|
|
15
|
+
* Every spelling the SDK accepts — the allow-list `assertDistanceMetric` gates on.
|
|
16
|
+
* Frozen at runtime as well as `readonly` in the type: a JS caller that pushed a
|
|
17
|
+
* spelling onto it would otherwise pass the gate and reach an undefined map entry
|
|
18
|
+
* (CodeRabbit on mmnto-ai/totem#2761).
|
|
19
|
+
*/
|
|
20
|
+
export declare const DISTANCE_METRICS: readonly DistanceMetric[];
|
|
21
|
+
/**
|
|
22
|
+
* The ONE metric every Totem vector query is issued with (mmnto-ai/totem#2738).
|
|
23
|
+
*
|
|
24
|
+
* Both query sites in `lance-search.ts` chain `.distanceType(VECTOR_DISTANCE_METRIC)`
|
|
25
|
+
* explicitly, so the metric is a recorded fact in the query rather than the SDK
|
|
26
|
+
* default it happens to coincide with. `runSync` also records it in
|
|
27
|
+
* `.totem/index-manifest.json` as `vectorDistanceMetric`.
|
|
28
|
+
*/
|
|
29
|
+
export declare const VECTOR_DISTANCE_METRIC: DistanceMetric;
|
|
30
|
+
/**
|
|
31
|
+
* Why a computed relevance can leave [0, 1] — the cause is METRIC-SPECIFIC
|
|
32
|
+
* (mmnto-ai/totem#2738 falsification round, F1), so the warning must not name a
|
|
33
|
+
* cause the metric cannot have.
|
|
34
|
+
*
|
|
35
|
+
* Under `l2` the SDK returns a squared distance, which is `≥ 0` by
|
|
36
|
+
* construction, so `1 / (1 + d) ∈ (0, 1]` for every value the SDK can legally
|
|
37
|
+
* return: a breach there is NOT a non-unit-norm embedder, it is a fault. And it
|
|
38
|
+
* is always a NEGATIVE, FINITE `_distance` — the search layer discards a
|
|
39
|
+
* non-finite `_distance` BEFORE the map runs (both the row-mapping site and the
|
|
40
|
+
* pre-fusion tally require `Number.isFinite`), so a non-finite value produces no
|
|
41
|
+
* relevance at all and can never reach this warning. The string names only what
|
|
42
|
+
* the warning can actually report (fold 2, F1).
|
|
43
|
+
*
|
|
44
|
+
* Under `cosine` / `dot` the mapping DOES leave [0, 1] for vectors that are not
|
|
45
|
+
* unit-norm, which is the real embedder-profile signal.
|
|
46
|
+
*/
|
|
47
|
+
export declare const OUT_OF_RANGE_CAUSE: Record<DistanceMetric, string>;
|
|
48
|
+
/**
|
|
49
|
+
* Narrow an untrusted value (a manifest field, a config key, a record read off
|
|
50
|
+
* disk) to a `DistanceMetric`, or throw loudly naming the value and every
|
|
51
|
+
* allowed spelling. The type makes an unmapped metric unreachable in code; this
|
|
52
|
+
* is the runtime gate for values that did not come through the type.
|
|
53
|
+
*/
|
|
54
|
+
export declare function assertDistanceMetric(value: unknown): DistanceMetric;
|
|
55
|
+
/**
|
|
56
|
+
* Normalize a LanceDB `_distance` into a relevance under the given metric.
|
|
57
|
+
*
|
|
58
|
+
* A pure map with one job: it never warns, never throws ON RANGE, and never
|
|
59
|
+
* clamps. The caller judges the result with {@link isRelevanceInRange} and
|
|
60
|
+
* decides what a range breach means at that call site.
|
|
61
|
+
*
|
|
62
|
+
* The METRIC is gated: `assertDistanceMetric` runs first (mmnto-ai/totem#2738
|
|
63
|
+
* falsification round, F5), so a spelling that reached here past the type — an
|
|
64
|
+
* `any`, a value read off a record, a JS caller — raises the named
|
|
65
|
+
* `TotemError` instead of a bare `TypeError` from an undefined map entry.
|
|
66
|
+
*/
|
|
67
|
+
export declare function relevanceFromDistance(metric: DistanceMetric, distance: number): number;
|
|
68
|
+
/** Whether a relevance is a finite number inside the closed interval [0, 1]. */
|
|
69
|
+
export declare function isRelevanceInRange(relevance: number): boolean;
|
|
70
|
+
//# sourceMappingURL=relevance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relevance.d.ts","sourceRoot":"","sources":["../../src/store/relevance.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AAEH,qGAAqG;AACrG,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,QAAQ,GAAG,KAAK,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,cAAc,EAI3C,CAAC;AAEZ;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,cAAqB,CAAC;AAE3D;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAI7D,CAAC;AA0CF;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAUnE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtF;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE7D"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { TotemError } from '../errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Every spelling the SDK accepts — the allow-list `assertDistanceMetric` gates on.
|
|
4
|
+
* Frozen at runtime as well as `readonly` in the type: a JS caller that pushed a
|
|
5
|
+
* spelling onto it would otherwise pass the gate and reach an undefined map entry
|
|
6
|
+
* (CodeRabbit on mmnto-ai/totem#2761).
|
|
7
|
+
*/
|
|
8
|
+
export const DISTANCE_METRICS = Object.freeze([
|
|
9
|
+
'l2',
|
|
10
|
+
'cosine',
|
|
11
|
+
'dot',
|
|
12
|
+
]);
|
|
13
|
+
/**
|
|
14
|
+
* The ONE metric every Totem vector query is issued with (mmnto-ai/totem#2738).
|
|
15
|
+
*
|
|
16
|
+
* Both query sites in `lance-search.ts` chain `.distanceType(VECTOR_DISTANCE_METRIC)`
|
|
17
|
+
* explicitly, so the metric is a recorded fact in the query rather than the SDK
|
|
18
|
+
* default it happens to coincide with. `runSync` also records it in
|
|
19
|
+
* `.totem/index-manifest.json` as `vectorDistanceMetric`.
|
|
20
|
+
*/
|
|
21
|
+
export const VECTOR_DISTANCE_METRIC = 'l2';
|
|
22
|
+
/**
|
|
23
|
+
* Why a computed relevance can leave [0, 1] — the cause is METRIC-SPECIFIC
|
|
24
|
+
* (mmnto-ai/totem#2738 falsification round, F1), so the warning must not name a
|
|
25
|
+
* cause the metric cannot have.
|
|
26
|
+
*
|
|
27
|
+
* Under `l2` the SDK returns a squared distance, which is `≥ 0` by
|
|
28
|
+
* construction, so `1 / (1 + d) ∈ (0, 1]` for every value the SDK can legally
|
|
29
|
+
* return: a breach there is NOT a non-unit-norm embedder, it is a fault. And it
|
|
30
|
+
* is always a NEGATIVE, FINITE `_distance` — the search layer discards a
|
|
31
|
+
* non-finite `_distance` BEFORE the map runs (both the row-mapping site and the
|
|
32
|
+
* pre-fusion tally require `Number.isFinite`), so a non-finite value produces no
|
|
33
|
+
* relevance at all and can never reach this warning. The string names only what
|
|
34
|
+
* the warning can actually report (fold 2, F1).
|
|
35
|
+
*
|
|
36
|
+
* Under `cosine` / `dot` the mapping DOES leave [0, 1] for vectors that are not
|
|
37
|
+
* unit-norm, which is the real embedder-profile signal.
|
|
38
|
+
*/
|
|
39
|
+
export const OUT_OF_RANGE_CAUSE = {
|
|
40
|
+
l2: 'a negative _distance, which squared L2 cannot produce: an SDK or data fault',
|
|
41
|
+
cosine: "the embedder's vectors may not be unit-norm",
|
|
42
|
+
dot: "the embedder's vectors may not be unit-norm",
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* distance → relevance, one entry per SDK metric.
|
|
46
|
+
*
|
|
47
|
+
* Private on purpose: the map is an implementation of `relevanceFromDistance`,
|
|
48
|
+
* not a second surface callers can reach around it to index.
|
|
49
|
+
*/
|
|
50
|
+
const RELEVANCE_FROM_DISTANCE = {
|
|
51
|
+
/**
|
|
52
|
+
* SDK: `"l2"` — Euclidean distance, "range of [0, ∞)". MEASURED (R1,
|
|
53
|
+
* mmnto-ai/totem#2738, 3/3 trials, |diff| < 1e-5): what the SDK actually
|
|
54
|
+
* returns in `_distance` for `l2` is the SQUARED Euclidean distance
|
|
55
|
+
* (`lance-linalg` sums `diff * diff` with no sqrt) — a self-hit returns 0,
|
|
56
|
+
* and a pair at cosine 0.845 returned 0.3096 = |a-b|², not |a-b| = 0.5564.
|
|
57
|
+
*
|
|
58
|
+
* On unit-norm vectors |a-b|² = 2 - 2·cos ∈ [0, 4], so relevance ∈ [0.2, 1]
|
|
59
|
+
* (minimum observed: 0.5022 over 3,795 hits on this repo's index).
|
|
60
|
+
*
|
|
61
|
+
* LangChain's euclidean constant presumes a NON-squared distance and is
|
|
62
|
+
* deliberately NOT borrowed — applied to a squared distance it would be
|
|
63
|
+
* wrong. The doctrine (bind the normalization to a named metric) is
|
|
64
|
+
* borrowed; the constant is not.
|
|
65
|
+
*/
|
|
66
|
+
l2: (distance) => 1 / (1 + distance),
|
|
67
|
+
/**
|
|
68
|
+
* SDK: `"cosine"` — "Cosine distance ... has a range of [0, 2]", i.e.
|
|
69
|
+
* `1 - cos(a, b)`, unaffected by vector magnitude. `1 - distance / 2` maps
|
|
70
|
+
* [0, 2] onto relevance [0, 1] linearly in the cosine.
|
|
71
|
+
*/
|
|
72
|
+
cosine: (distance) => 1 - distance / 2,
|
|
73
|
+
/**
|
|
74
|
+
* SDK: `"dot"` — "Dot distance has a range of (-∞, ∞). If the vectors are
|
|
75
|
+
* normalized (i.e. their l2 norm is 1), then dot distance is equivalent to
|
|
76
|
+
* the cosine distance" — so LanceDB's dot distance is the `1 - a·b` form and
|
|
77
|
+
* takes the SAME map as `cosine`. On NON-unit vectors it can leave [0, 2] and
|
|
78
|
+
* the relevance then leaves [0, 1]: this is the case the out-of-range warning
|
|
79
|
+
* at the search call sites exists for.
|
|
80
|
+
*/
|
|
81
|
+
dot: (distance) => 1 - distance / 2,
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Narrow an untrusted value (a manifest field, a config key, a record read off
|
|
85
|
+
* disk) to a `DistanceMetric`, or throw loudly naming the value and every
|
|
86
|
+
* allowed spelling. The type makes an unmapped metric unreachable in code; this
|
|
87
|
+
* is the runtime gate for values that did not come through the type.
|
|
88
|
+
*/
|
|
89
|
+
export function assertDistanceMetric(value) {
|
|
90
|
+
if (typeof value === 'string' && DISTANCE_METRICS.includes(value)) {
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
const allowed = DISTANCE_METRICS.map((m) => `"${m}"`).join(', ');
|
|
94
|
+
throw new TotemError('CONFIG_INVALID', `Unknown vector distance metric ${JSON.stringify(value)} — the LanceDB SDK accepts only ${allowed}.`, `Use one of ${allowed}. These are the SDK's own spellings; "ip"/"inner_product"/"euclidean" are not among them.`);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Normalize a LanceDB `_distance` into a relevance under the given metric.
|
|
98
|
+
*
|
|
99
|
+
* A pure map with one job: it never warns, never throws ON RANGE, and never
|
|
100
|
+
* clamps. The caller judges the result with {@link isRelevanceInRange} and
|
|
101
|
+
* decides what a range breach means at that call site.
|
|
102
|
+
*
|
|
103
|
+
* The METRIC is gated: `assertDistanceMetric` runs first (mmnto-ai/totem#2738
|
|
104
|
+
* falsification round, F5), so a spelling that reached here past the type — an
|
|
105
|
+
* `any`, a value read off a record, a JS caller — raises the named
|
|
106
|
+
* `TotemError` instead of a bare `TypeError` from an undefined map entry.
|
|
107
|
+
*/
|
|
108
|
+
export function relevanceFromDistance(metric, distance) {
|
|
109
|
+
return RELEVANCE_FROM_DISTANCE[assertDistanceMetric(metric)](distance);
|
|
110
|
+
}
|
|
111
|
+
/** Whether a relevance is a finite number inside the closed interval [0, 1]. */
|
|
112
|
+
export function isRelevanceInRange(relevance) {
|
|
113
|
+
return Number.isFinite(relevance) && relevance >= 0 && relevance <= 1;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=relevance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relevance.js","sourceRoot":"","sources":["../../src/store/relevance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAiB1C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAA8B,MAAM,CAAC,MAAM,CAAC;IACvE,IAAI;IACJ,QAAQ;IACR,KAAK;CACG,CAAC,CAAC;AAEZ;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAmB,IAAI,CAAC;AAE3D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAmC;IAChE,EAAE,EAAE,6EAA6E;IACjF,MAAM,EAAE,6CAA6C;IACrD,GAAG,EAAE,6CAA6C;CACnD,CAAC;AAEF;;;;;GAKG;AACH,MAAM,uBAAuB,GAAyD;IACpF;;;;;;;;;;;;;;OAcG;IACH,EAAE,EAAE,CAAC,QAAgB,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC;IAC5C;;;;OAIG;IACH,MAAM,EAAE,CAAC,QAAgB,EAAE,EAAE,CAAC,CAAC,GAAG,QAAQ,GAAG,CAAC;IAC9C;;;;;;;OAOG;IACH,GAAG,EAAE,CAAC,QAAgB,EAAE,EAAE,CAAC,CAAC,GAAG,QAAQ,GAAG,CAAC;CAC5C,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAK,gBAAsC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACzF,OAAO,KAAuB,CAAC;IACjC,CAAC;IACD,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjE,MAAM,IAAI,UAAU,CAClB,gBAAgB,EAChB,kCAAkC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,mCAAmC,OAAO,GAAG,EACpG,cAAc,OAAO,2FAA2F,CACjH,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAsB,EAAE,QAAgB;IAC5E,OAAO,uBAAuB,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;AACzE,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,kBAAkB,CAAC,SAAiB;IAClD,OAAO,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,CAAC,IAAI,SAAS,IAAI,CAAC,CAAC;AACxE,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK-MEASURED metric pinning (mmnto-ai/totem#2738 falsification round, F7).
|
|
3
|
+
*
|
|
4
|
+
* The rest of the relevance suite pins the MAP; it cannot tell you whether the
|
|
5
|
+
* map matches what LanceDB actually returns. The `dot` entry in particular was
|
|
6
|
+
* derived from the SDK's DOC COMMENT ("if the vectors are normalized … dot
|
|
7
|
+
* distance is equivalent to the cosine distance") and never measured — a doc
|
|
8
|
+
* comment is a claim, not a measurement, and it is silent about what happens
|
|
9
|
+
* off the unit sphere. This file is the falsifier that gap wanted: it builds a
|
|
10
|
+
* throwaway LanceDB table with hand-chosen vectors, queries it under each of
|
|
11
|
+
* the SDK's three metrics, and asserts the RETURNED `_distance` before feeding
|
|
12
|
+
* each measured value through the map.
|
|
13
|
+
*
|
|
14
|
+
* If a future SDK bump changes a distance convention — l2 stops being squared,
|
|
15
|
+
* dot flips sign, the default stops being l2 — this file goes red at the exact
|
|
16
|
+
* number that moved, instead of every relevance in the system quietly
|
|
17
|
+
* re-scaling behind a green suite.
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=relevance.sdk.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relevance.sdk.test.d.ts","sourceRoot":"","sources":["../../src/store/relevance.sdk.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK-MEASURED metric pinning (mmnto-ai/totem#2738 falsification round, F7).
|
|
3
|
+
*
|
|
4
|
+
* The rest of the relevance suite pins the MAP; it cannot tell you whether the
|
|
5
|
+
* map matches what LanceDB actually returns. The `dot` entry in particular was
|
|
6
|
+
* derived from the SDK's DOC COMMENT ("if the vectors are normalized … dot
|
|
7
|
+
* distance is equivalent to the cosine distance") and never measured — a doc
|
|
8
|
+
* comment is a claim, not a measurement, and it is silent about what happens
|
|
9
|
+
* off the unit sphere. This file is the falsifier that gap wanted: it builds a
|
|
10
|
+
* throwaway LanceDB table with hand-chosen vectors, queries it under each of
|
|
11
|
+
* the SDK's three metrics, and asserts the RETURNED `_distance` before feeding
|
|
12
|
+
* each measured value through the map.
|
|
13
|
+
*
|
|
14
|
+
* If a future SDK bump changes a distance convention — l2 stops being squared,
|
|
15
|
+
* dot flips sign, the default stops being l2 — this file goes red at the exact
|
|
16
|
+
* number that moved, instead of every relevance in the system quietly
|
|
17
|
+
* re-scaling behind a green suite.
|
|
18
|
+
*/
|
|
19
|
+
import * as fs from 'node:fs';
|
|
20
|
+
import * as os from 'node:os';
|
|
21
|
+
import * as path from 'node:path';
|
|
22
|
+
import * as lancedb from '@lancedb/lancedb';
|
|
23
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
24
|
+
import { cleanTmpDir } from '../test-utils.js';
|
|
25
|
+
import { isRelevanceInRange, relevanceFromDistance, VECTOR_DISTANCE_METRIC } from './relevance.js';
|
|
26
|
+
/** The query vector every measurement below is taken against. */
|
|
27
|
+
const QUERY = [1, 0, 0];
|
|
28
|
+
/**
|
|
29
|
+
* Hand-chosen probes. Four are unit-norm (where all three metrics are supposed
|
|
30
|
+
* to agree up to their own scale); `scaled-2x` is deliberately NOT, because
|
|
31
|
+
* that is the only place `cosine` and `dot` come apart and the only way to
|
|
32
|
+
* observe a `dot` distance outside [0, 2].
|
|
33
|
+
*/
|
|
34
|
+
const ROWS = [
|
|
35
|
+
{ id: 'identical', vector: [1, 0, 0] },
|
|
36
|
+
{ id: 'orthogonal', vector: [0, 1, 0] },
|
|
37
|
+
{ id: 'opposite', vector: [-1, 0, 0] },
|
|
38
|
+
{ id: 'unit-0.6', vector: [0.6, 0.8, 0] },
|
|
39
|
+
{ id: 'scaled-2x', vector: [2, 0, 0] },
|
|
40
|
+
];
|
|
41
|
+
/** MEASURED `_distance` per metric, asserted to 1e-6 against the live SDK. */
|
|
42
|
+
const EXPECTED_DISTANCE = {
|
|
43
|
+
// Squared Euclidean: |q - v|². identical 0; orthogonal 2; opposite 4;
|
|
44
|
+
// unit-0.6 0.4² + 0.8² = 0.8; scaled-2x |(-1,0,0)|² = 1.
|
|
45
|
+
l2: { identical: 0, orthogonal: 2, opposite: 4, 'unit-0.6': 0.8, 'scaled-2x': 1 },
|
|
46
|
+
// 1 - cos: magnitude-independent, so scaled-2x collapses onto identical.
|
|
47
|
+
cosine: { identical: 0, orthogonal: 1, opposite: 2, 'unit-0.6': 0.4, 'scaled-2x': 0 },
|
|
48
|
+
// 1 - q·v: identical to cosine on unit vectors, and NOT on scaled-2x, where
|
|
49
|
+
// 1 - 2 = -1 leaves the [0, 2] interval entirely.
|
|
50
|
+
dot: { identical: 0, orthogonal: 1, opposite: 2, 'unit-0.6': 0.4, 'scaled-2x': -1 },
|
|
51
|
+
};
|
|
52
|
+
/** The relevance the map must produce for each MEASURED distance above. */
|
|
53
|
+
const EXPECTED_RELEVANCE = {
|
|
54
|
+
l2: { identical: 1, orthogonal: 1 / 3, opposite: 0.2, 'unit-0.6': 5 / 9, 'scaled-2x': 0.5 },
|
|
55
|
+
cosine: { identical: 1, orthogonal: 0.5, opposite: 0, 'unit-0.6': 0.8, 'scaled-2x': 1 },
|
|
56
|
+
dot: { identical: 1, orthogonal: 0.5, opposite: 0, 'unit-0.6': 0.8, 'scaled-2x': 1.5 },
|
|
57
|
+
};
|
|
58
|
+
describe('LanceDB SDK distance metrics, measured (mmnto-ai/totem#2738, F7)', () => {
|
|
59
|
+
let tmpDir;
|
|
60
|
+
let table;
|
|
61
|
+
beforeEach(async () => {
|
|
62
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lance-metric-'));
|
|
63
|
+
const db = await lancedb.connect(tmpDir);
|
|
64
|
+
table = await db.createTable('probes', ROWS);
|
|
65
|
+
});
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
cleanTmpDir(tmpDir);
|
|
68
|
+
});
|
|
69
|
+
/** Query under `metric`, or under the SDK DEFAULT when none is given. */
|
|
70
|
+
async function measure(metric) {
|
|
71
|
+
let q = table.vectorSearch(QUERY).limit(ROWS.length);
|
|
72
|
+
if (metric !== undefined)
|
|
73
|
+
q = q.distanceType(metric);
|
|
74
|
+
const rows = await q.toArray();
|
|
75
|
+
const byId = new Map();
|
|
76
|
+
for (const row of rows)
|
|
77
|
+
byId.set(String(row['id']), Number(row['_distance']));
|
|
78
|
+
return byId;
|
|
79
|
+
}
|
|
80
|
+
it('the SDK DEFAULT is l2, and l2 is the SQUARED Euclidean distance', async () => {
|
|
81
|
+
const byDefault = await measure();
|
|
82
|
+
const byL2 = await measure('l2');
|
|
83
|
+
for (const { id } of ROWS) {
|
|
84
|
+
const expected = EXPECTED_DISTANCE.l2[id];
|
|
85
|
+
expect(byDefault.get(id)).toBeCloseTo(expected, 6);
|
|
86
|
+
expect(byL2.get(id)).toBeCloseTo(expected, 6);
|
|
87
|
+
}
|
|
88
|
+
// `opposite` is the discriminator: |q - v| = 2 but |q - v|² = 4. A
|
|
89
|
+
// non-squared l2 would return 2 here and the whole [0.2, 1] bound would be
|
|
90
|
+
// wrong. This is what the store relies on, hence the constant.
|
|
91
|
+
expect(byL2.get('opposite')).toBeCloseTo(4, 6);
|
|
92
|
+
expect(VECTOR_DISTANCE_METRIC).toBe('l2');
|
|
93
|
+
});
|
|
94
|
+
it('cosine returns 1 - cos over [0, 2] and ignores magnitude', async () => {
|
|
95
|
+
const byCosine = await measure('cosine');
|
|
96
|
+
for (const { id } of ROWS) {
|
|
97
|
+
expect(byCosine.get(id)).toBeCloseTo(EXPECTED_DISTANCE.cosine[id], 6);
|
|
98
|
+
}
|
|
99
|
+
// Magnitude-independence, measured: a 2x-scaled copy of the query is at
|
|
100
|
+
// distance 0, exactly where the query itself is.
|
|
101
|
+
expect(byCosine.get('scaled-2x')).toBeCloseTo(byCosine.get('identical'), 6);
|
|
102
|
+
});
|
|
103
|
+
it('dot returns 1 - a·b: equal to cosine on unit vectors, and NEGATIVE off them', async () => {
|
|
104
|
+
const byDot = await measure('dot');
|
|
105
|
+
const byCosine = await measure('cosine');
|
|
106
|
+
for (const { id } of ROWS) {
|
|
107
|
+
expect(byDot.get(id)).toBeCloseTo(EXPECTED_DISTANCE.dot[id], 6);
|
|
108
|
+
}
|
|
109
|
+
// The SDK's documented equivalence, now MEASURED rather than quoted — on
|
|
110
|
+
// the four unit-norm probes only.
|
|
111
|
+
for (const id of ['identical', 'orthogonal', 'opposite', 'unit-0.6']) {
|
|
112
|
+
expect(byDot.get(id)).toBeCloseTo(byCosine.get(id), 6);
|
|
113
|
+
}
|
|
114
|
+
// And where the doc comment is silent: off the unit sphere the two diverge
|
|
115
|
+
// and dot leaves [0, 2] altogether.
|
|
116
|
+
expect(byDot.get('scaled-2x')).toBeCloseTo(-1, 6);
|
|
117
|
+
expect(byDot.get('scaled-2x')).not.toBeCloseTo(byCosine.get('scaled-2x'), 6);
|
|
118
|
+
});
|
|
119
|
+
it('the map turns every MEASURED distance into the pinned relevance', async () => {
|
|
120
|
+
for (const metric of ['l2', 'cosine', 'dot']) {
|
|
121
|
+
const measured = await measure(metric);
|
|
122
|
+
for (const { id } of ROWS) {
|
|
123
|
+
const relevance = relevanceFromDistance(metric, measured.get(id));
|
|
124
|
+
expect(relevance).toBeCloseTo(EXPECTED_RELEVANCE[metric][id], 6);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
it('every measured l2 relevance lands in [0.2, 1]; only dot off the unit sphere breaches', async () => {
|
|
129
|
+
const byL2 = await measure('l2');
|
|
130
|
+
for (const { id } of ROWS) {
|
|
131
|
+
const relevance = relevanceFromDistance('l2', byL2.get(id));
|
|
132
|
+
expect(relevance).toBeGreaterThanOrEqual(0.2);
|
|
133
|
+
expect(relevance).toBeLessThanOrEqual(1);
|
|
134
|
+
expect(isRelevanceInRange(relevance)).toBe(true);
|
|
135
|
+
}
|
|
136
|
+
// The single measured breach in the whole matrix — the case the
|
|
137
|
+
// out-of-range warning exists for, and the reason its cause text is
|
|
138
|
+
// metric-specific (F1).
|
|
139
|
+
const byDot = await measure('dot');
|
|
140
|
+
const breach = relevanceFromDistance('dot', byDot.get('scaled-2x'));
|
|
141
|
+
expect(breach).toBeCloseTo(1.5, 6);
|
|
142
|
+
expect(isRelevanceInRange(breach)).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
//# sourceMappingURL=relevance.sdk.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relevance.sdk.test.js","sourceRoot":"","sources":["../../src/store/relevance.sdk.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relevance.test.d.ts","sourceRoot":"","sources":["../../src/store/relevance.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { TotemError } from '../errors.js';
|
|
3
|
+
import { assertDistanceMetric, DISTANCE_METRICS, isRelevanceInRange, OUT_OF_RANGE_CAUSE, relevanceFromDistance, VECTOR_DISTANCE_METRIC, } from './relevance.js';
|
|
4
|
+
// ─── The pinned map (mmnto-ai/totem#2738) ────────────────
|
|
5
|
+
//
|
|
6
|
+
// These numbers are the contract, not an implementation echo. `l2` is pinned to
|
|
7
|
+
// the MEASURED fact that LanceDB's `_distance` for `l2` is the SQUARED
|
|
8
|
+
// Euclidean distance (R1, 3/3 trials): a self-hit is 0, and the measured pair
|
|
9
|
+
// at cosine 0.845 came back as 0.3096 = |a-b|². On unit-norm vectors that puts
|
|
10
|
+
// `_distance` in [0, 4] and therefore relevance in [0.2, 1].
|
|
11
|
+
describe('relevanceFromDistance — l2', () => {
|
|
12
|
+
it('maps a self-hit (d = 0) to relevance 1', () => {
|
|
13
|
+
expect(relevanceFromDistance('l2', 0)).toBe(1);
|
|
14
|
+
});
|
|
15
|
+
it('maps the antipodal unit-vector bound (d = 4) to relevance 0.2', () => {
|
|
16
|
+
expect(relevanceFromDistance('l2', 4)).toBeCloseTo(0.2, 10);
|
|
17
|
+
});
|
|
18
|
+
it('maps the R1-measured pair (d = 0.3096, cosine 0.845) to ~0.7636', () => {
|
|
19
|
+
expect(relevanceFromDistance('l2', 0.3096)).toBeCloseTo(0.7636, 4);
|
|
20
|
+
});
|
|
21
|
+
it('is monotone decreasing in distance', () => {
|
|
22
|
+
const distances = [0, 0.25, 0.3096, 0.5, 1, 2, 3, 4, 10];
|
|
23
|
+
const relevances = distances.map((d) => relevanceFromDistance('l2', d));
|
|
24
|
+
for (let i = 1; i < relevances.length; i += 1) {
|
|
25
|
+
expect(relevances[i]).toBeLessThan(relevances[i - 1]);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
it('INVARIANT: unit-norm rows produce relevance in [0.2, 1]', () => {
|
|
29
|
+
// On unit vectors |a-b|² = 2 - 2·cos ∈ [0, 4]; the sweep walks that interval.
|
|
30
|
+
for (const d of [0, 0.5, 1, 2, 3, 4]) {
|
|
31
|
+
const relevance = relevanceFromDistance('l2', d);
|
|
32
|
+
expect(relevance).toBeGreaterThanOrEqual(0.2);
|
|
33
|
+
expect(relevance).toBeLessThanOrEqual(1);
|
|
34
|
+
expect(isRelevanceInRange(relevance)).toBe(true);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe('relevanceFromDistance — cosine', () => {
|
|
39
|
+
it('maps d = 0 to 1', () => {
|
|
40
|
+
expect(relevanceFromDistance('cosine', 0)).toBe(1);
|
|
41
|
+
});
|
|
42
|
+
it('maps the far bound d = 2 to 0', () => {
|
|
43
|
+
expect(relevanceFromDistance('cosine', 2)).toBe(0);
|
|
44
|
+
});
|
|
45
|
+
it('maps the orthogonal midpoint d = 1 to 0.5', () => {
|
|
46
|
+
expect(relevanceFromDistance('cosine', 1)).toBe(0.5);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe('relevanceFromDistance — dot', () => {
|
|
50
|
+
// The SDK: "If the vectors are normalized (i.e. their l2 norm is 1), then dot
|
|
51
|
+
// distance is equivalent to the cosine distance" — so `dot` takes the same map.
|
|
52
|
+
it('maps d = 0 to 1', () => {
|
|
53
|
+
expect(relevanceFromDistance('dot', 0)).toBe(1);
|
|
54
|
+
});
|
|
55
|
+
it('maps d = 2 to 0', () => {
|
|
56
|
+
expect(relevanceFromDistance('dot', 2)).toBe(0);
|
|
57
|
+
});
|
|
58
|
+
it('maps d = 1 to 0.5', () => {
|
|
59
|
+
expect(relevanceFromDistance('dot', 1)).toBe(0.5);
|
|
60
|
+
});
|
|
61
|
+
it('agrees with cosine at every pinned point (the SDK equivalence on unit vectors)', () => {
|
|
62
|
+
for (const d of [0, 0.5, 1, 1.5, 2]) {
|
|
63
|
+
expect(relevanceFromDistance('dot', d)).toBe(relevanceFromDistance('cosine', d));
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
// ─── Out-of-range detection ─────────────────────────────
|
|
68
|
+
//
|
|
69
|
+
// `relevanceFromDistance` computes; it never warns, throws or clamps. The range
|
|
70
|
+
// judgment is `isRelevanceInRange`, exercised at the search call sites.
|
|
71
|
+
describe('isRelevanceInRange', () => {
|
|
72
|
+
it('is false for an l2 relevance above 1 (a negative _distance)', () => {
|
|
73
|
+
const relevance = relevanceFromDistance('l2', -0.5);
|
|
74
|
+
expect(relevance).toBeCloseTo(2, 10);
|
|
75
|
+
expect(isRelevanceInRange(relevance)).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
it('is false for a cosine relevance below 0 (a distance past the [0, 2] bound)', () => {
|
|
78
|
+
const relevance = relevanceFromDistance('cosine', 3);
|
|
79
|
+
expect(relevance).toBeCloseTo(-0.5, 10);
|
|
80
|
+
expect(isRelevanceInRange(relevance)).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
it('is false for NaN and Infinity', () => {
|
|
83
|
+
expect(isRelevanceInRange(Number.NaN)).toBe(false);
|
|
84
|
+
expect(isRelevanceInRange(Number.POSITIVE_INFINITY)).toBe(false);
|
|
85
|
+
expect(isRelevanceInRange(Number.NEGATIVE_INFINITY)).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
it('is true at both closed bounds', () => {
|
|
88
|
+
expect(isRelevanceInRange(0)).toBe(true);
|
|
89
|
+
expect(isRelevanceInRange(1)).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
// ─── The loud gate ──────────────────────────────────────
|
|
93
|
+
describe('assertDistanceMetric', () => {
|
|
94
|
+
it('throws a TotemError naming the value and the three allowed spellings', () => {
|
|
95
|
+
// 'ip' is a metric name from OTHER vector databases; the LanceDB SDK has no
|
|
96
|
+
// such spelling, and guessing it silently would be exactly the drift #2738
|
|
97
|
+
// exists to stop.
|
|
98
|
+
expect(() => assertDistanceMetric('ip')).toThrow(TotemError);
|
|
99
|
+
let thrown;
|
|
100
|
+
try {
|
|
101
|
+
assertDistanceMetric('ip');
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
thrown = err;
|
|
105
|
+
}
|
|
106
|
+
const thrownError = thrown;
|
|
107
|
+
expect(thrownError.code).toBe('CONFIG_INVALID');
|
|
108
|
+
expect(thrownError.message).toContain('"ip"');
|
|
109
|
+
expect(thrownError.message).toContain('"l2"');
|
|
110
|
+
expect(thrownError.message).toContain('"cosine"');
|
|
111
|
+
expect(thrownError.message).toContain('"dot"');
|
|
112
|
+
});
|
|
113
|
+
it('returns the metric unchanged for a valid spelling', () => {
|
|
114
|
+
expect(assertDistanceMetric('cosine')).toBe('cosine');
|
|
115
|
+
});
|
|
116
|
+
it('accepts every listed metric and rejects non-strings', () => {
|
|
117
|
+
for (const metric of DISTANCE_METRICS) {
|
|
118
|
+
expect(assertDistanceMetric(metric)).toBe(metric);
|
|
119
|
+
}
|
|
120
|
+
expect(() => assertDistanceMetric(undefined)).toThrow(TotemError);
|
|
121
|
+
expect(() => assertDistanceMetric(null)).toThrow(TotemError);
|
|
122
|
+
expect(() => assertDistanceMetric(2)).toThrow(TotemError);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
describe('VECTOR_DISTANCE_METRIC', () => {
|
|
126
|
+
it('is the l2 metric the store queries with', () => {
|
|
127
|
+
expect(VECTOR_DISTANCE_METRIC).toBe('l2');
|
|
128
|
+
expect(DISTANCE_METRICS).toContain(VECTOR_DISTANCE_METRIC);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
// ─── the falsification round's folds (mmnto-ai/totem#2738) ───
|
|
132
|
+
describe('relevanceFromDistance gates its metric (F5)', () => {
|
|
133
|
+
it('raises the named TotemError for a spelling that reached it past the type', () => {
|
|
134
|
+
// A value read off a record, an `any`, or a JS caller can carry 'ip' here.
|
|
135
|
+
// Indexing the map with it would have thrown a bare TypeError ("is not a
|
|
136
|
+
// function"); the named error says what is wrong and what is allowed.
|
|
137
|
+
const badMetric = 'ip';
|
|
138
|
+
expect(() => relevanceFromDistance(badMetric, 0.5)).toThrow(TotemError);
|
|
139
|
+
let thrown;
|
|
140
|
+
try {
|
|
141
|
+
relevanceFromDistance(badMetric, 0.5);
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
thrown = err;
|
|
145
|
+
}
|
|
146
|
+
expect(thrown.code).toBe('CONFIG_INVALID');
|
|
147
|
+
expect(thrown.message).toContain('"ip"');
|
|
148
|
+
});
|
|
149
|
+
it('still computes for every valid metric', () => {
|
|
150
|
+
for (const metric of DISTANCE_METRICS) {
|
|
151
|
+
expect(Number.isFinite(relevanceFromDistance(metric, 0.5))).toBe(true);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
describe('OUT_OF_RANGE_CAUSE is metric-specific (F1)', () => {
|
|
156
|
+
it('names a FAULT for l2, never a non-unit-norm embedder', () => {
|
|
157
|
+
// Squared L2 is >= 0 by construction, so 1/(1+d) is in (0, 1] for every
|
|
158
|
+
// value the SDK can legally return: no vector norm can push it out.
|
|
159
|
+
expect(OUT_OF_RANGE_CAUSE.l2).toContain('which squared L2 cannot produce');
|
|
160
|
+
expect(OUT_OF_RANGE_CAUSE.l2).not.toContain('unit-norm');
|
|
161
|
+
});
|
|
162
|
+
it('names ONLY the negative-distance case for l2 — a non-finite one never reaches it', () => {
|
|
163
|
+
// The search layer requires `Number.isFinite` before the map runs at BOTH
|
|
164
|
+
// sites, so a non-finite `_distance` yields no relevance and cannot produce
|
|
165
|
+
// a breach. Naming it would describe a report this warning cannot make.
|
|
166
|
+
expect(OUT_OF_RANGE_CAUSE.l2).toContain('a negative _distance');
|
|
167
|
+
expect(OUT_OF_RANGE_CAUSE.l2).not.toContain('non-finite');
|
|
168
|
+
});
|
|
169
|
+
it('names the unit-norm cause for cosine and dot, where the mapping really can leave [0, 1]', () => {
|
|
170
|
+
expect(OUT_OF_RANGE_CAUSE.cosine).toContain('unit-norm');
|
|
171
|
+
expect(OUT_OF_RANGE_CAUSE.dot).toContain('unit-norm');
|
|
172
|
+
// `dot` on a non-unit vector is the live case: q·[2,0,0] = 2 → d = -1 → 1.5.
|
|
173
|
+
expect(isRelevanceInRange(relevanceFromDistance('dot', -1))).toBe(false);
|
|
174
|
+
});
|
|
175
|
+
it('has an entry for every metric', () => {
|
|
176
|
+
for (const metric of DISTANCE_METRICS) {
|
|
177
|
+
expect(OUT_OF_RANGE_CAUSE[metric].length).toBeGreaterThan(0);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
describe('DISTANCE_METRICS', () => {
|
|
182
|
+
// The allow-list `assertDistanceMetric` gates on is frozen at runtime, not only
|
|
183
|
+
// `readonly` in the type: a JS caller that pushed a spelling onto it would
|
|
184
|
+
// otherwise pass the gate and reach an undefined map entry (CodeRabbit on
|
|
185
|
+
// mmnto-ai/totem#2761). Pinned so a refactor cannot drop the freeze unnoticed.
|
|
186
|
+
it('is frozen at runtime', () => {
|
|
187
|
+
expect(Object.isFrozen(DISTANCE_METRICS)).toBe(true);
|
|
188
|
+
expect(() => DISTANCE_METRICS.push('euclidean')).toThrow();
|
|
189
|
+
expect(DISTANCE_METRICS).toEqual(['l2', 'cosine', 'dot']);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
//# sourceMappingURL=relevance.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relevance.test.js","sourceRoot":"","sources":["../../src/store/relevance.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AAExB,4DAA4D;AAC5D,EAAE;AACF,gFAAgF;AAChF,uEAAuE;AACvE,8EAA8E;AAC9E,+EAA+E;AAC/E,6DAA6D;AAE7D,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC1C,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,qBAAqB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,CAAC,qBAAqB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,MAAM,CAAC,qBAAqB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,SAAS,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACzD,MAAM,UAAU,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,qBAAqB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACxE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9C,MAAM,CAAC,UAAU,CAAC,CAAC,CAAE,CAAC,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,8EAA8E;QAC9E,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YACrC,MAAM,SAAS,GAAG,qBAAqB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YACjD,MAAM,CAAC,SAAS,CAAC,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC;YAC9C,MAAM,CAAC,SAAS,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;YACzC,MAAM,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnD,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC9C,EAAE,CAAC,iBAAiB,EAAE,GAAG,EAAE;QACzB,MAAM,CAAC,qBAAqB,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,CAAC,qBAAqB,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,qBAAqB,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,6BAA6B,EAAE,GAAG,EAAE;IAC3C,8EAA8E;IAC9E,gFAAgF;IAChF,EAAE,CAAC,iBAAiB,EAAE,GAAG,EAAE;QACzB,MAAM,CAAC,qBAAqB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iBAAiB,EAAE,GAAG,EAAE;QACzB,MAAM,CAAC,qBAAqB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE;QAC3B,MAAM,CAAC,qBAAqB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gFAAgF,EAAE,GAAG,EAAE;QACxF,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC;YACpC,MAAM,CAAC,qBAAqB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC;QACnF,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,2DAA2D;AAC3D,EAAE;AACF,gFAAgF;AAChF,wEAAwE;AAExE,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,SAAS,GAAG,qBAAqB,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;QACpD,MAAM,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACrC,MAAM,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4EAA4E,EAAE,GAAG,EAAE;QACpF,MAAM,SAAS,GAAG,qBAAqB,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QACrD,MAAM,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACxC,MAAM,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,CAAC,kBAAkB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnD,MAAM,CAAC,kBAAkB,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjE,MAAM,CAAC,kBAAkB,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzC,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,2DAA2D;AAE3D,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,sEAAsE,EAAE,GAAG,EAAE;QAC9E,4EAA4E;QAC5E,2EAA2E;QAC3E,kBAAkB;QAClB,MAAM,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC7D,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,oBAAoB,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,CAAC;QACf,CAAC;QACD,MAAM,WAAW,GAAG,MAAoB,CAAC;QACzC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAChD,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC9C,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC9C,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAClD,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,KAAK,MAAM,MAAM,IAAI,gBAAgB,EAAE,CAAC;YACtC,MAAM,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpD,CAAC;QACD,MAAM,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAClE,MAAM,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC7D,MAAM,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,sBAAsB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,CAAC,gBAAgB,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,gEAAgE;AAEhE,QAAQ,CAAC,6CAA6C,EAAE,GAAG,EAAE;IAC3D,EAAE,CAAC,0EAA0E,EAAE,GAAG,EAAE;QAClF,2EAA2E;QAC3E,yEAAyE;QACzE,sEAAsE;QACtE,MAAM,SAAS,GAAG,IAAiC,CAAC;QACpD,MAAM,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACxE,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,qBAAqB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QACxC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,CAAC;QACf,CAAC;QACD,MAAM,CAAE,MAAqB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC3D,MAAM,CAAE,MAAqB,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,KAAK,MAAM,MAAM,IAAI,gBAAgB,EAAE,CAAC;YACtC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzE,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,4CAA4C,EAAE,GAAG,EAAE;IAC1D,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,wEAAwE;QACxE,oEAAoE;QACpE,MAAM,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,iCAAiC,CAAC,CAAC;QAC3E,MAAM,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kFAAkF,EAAE,GAAG,EAAE;QAC1F,0EAA0E;QAC1E,4EAA4E;QAC5E,wEAAwE;QACxE,MAAM,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;QAChE,MAAM,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yFAAyF,EAAE,GAAG,EAAE;QACjG,MAAM,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACzD,MAAM,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACtD,6EAA6E;QAC7E,MAAM,CAAC,kBAAkB,CAAC,qBAAqB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,KAAK,MAAM,MAAM,IAAI,gBAAgB,EAAE,CAAC;YACtC,MAAM,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,gFAAgF;IAChF,2EAA2E;IAC3E,0EAA0E;IAC1E,+EAA+E;IAC/E,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC9B,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrD,MAAM,CAAC,GAAG,EAAE,CAAE,gBAAwC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QACpF,MAAM,CAAC,gBAAgB,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -102,8 +102,10 @@ export interface SearchResult {
|
|
|
102
102
|
label: string;
|
|
103
103
|
score: number;
|
|
104
104
|
/**
|
|
105
|
-
* True per-hit relevance signal
|
|
106
|
-
*
|
|
105
|
+
* True per-hit relevance signal — the vector-leg relevance from
|
|
106
|
+
* `relevanceFromDistance(VECTOR_DISTANCE_METRIC, _distance)`; on unit-norm
|
|
107
|
+
* vectors under `l2` it lies in [0.2, 1]; absent on FTS-only rows
|
|
108
|
+
* (mmnto-ai/totem#2463, metric-bound in mmnto-ai/totem#2738). Distinct from
|
|
107
109
|
* `score`, which in hybrid/federated modes is an RRF rank artifact
|
|
108
110
|
* (`1/(60+rank)` ≈ 0.016) that carries ordering but destroys the relevance
|
|
109
111
|
* magnitude. Populated by `rowToSearchResult` for vector-derived rows and
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAEpD;;;GAGG;AACH,MAAM,WAAW,KAAK;IACpB,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAC;IAEhB,mDAAmD;IACnD,aAAa,EAAE,MAAM,CAAC;IAEtB,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAC;IAEjB,0CAA0C;IAC1C,IAAI,EAAE,WAAW,CAAC;IAElB,iDAAiD;IACjD,QAAQ,EAAE,aAAa,CAAC;IAExB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IAEd,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAElB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAEhB,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;OAUG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|