@mastra/lance 1.1.0 → 1.1.1-alpha.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/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # @mastra/lance
2
2
 
3
+ ## 1.1.1-alpha.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Fixed `LanceVectorStore.query()` returning a raw LanceDB distance in the `score` field, which inverted ranking compared to every other Mastra vector store. ([#18104](https://github.com/mastra-ai/mastra/pull/18104))
8
+
9
+ LanceDB's `_distance` is a distance (lower = more similar), while Mastra's `score` is a similarity (higher = more similar). Returning the distance unchanged meant the closest match got the _lowest_ score, silently breaking `Memory` semantic recall, `rerank()` vector weighting, and any `minScore`/threshold filtering written against other stores (pg, Chroma, S3 Vectors, Pinecone, …).
10
+
11
+ `query()` now converts `_distance` into a similarity score consistent with the other stores and sets the search distance type to match the detected index metric, or an explicit query metric when no physical Lance index exists:
12
+ - cosine → `1 - distance` (cosine similarity)
13
+ - dot product → `1 - distance` (recovers the dot product, matching `@mastra/pg`)
14
+ - euclidean → `1 / (1 + sqrt(distance))` (Lance `l2` returns squared L2, so this maps to Mastra's L2 similarity semantics)
15
+
16
+ The metric defaults to the table's vector index metric when one exists, otherwise `cosine` (matching `createIndex`'s default). For small/unindexed tables where LanceDB has no physical index metadata to inspect, pass `metric` to `query()` when using a non-cosine metric. If a query metric conflicts with an existing Lance index metric, the index metric is used because Lance requires indexed searches to use the index's distance type:
17
+
18
+ ```ts
19
+ // Before: `exact` got score 0, `far` got score 2 — ranking inverted.
20
+ // After: `exact` gets the highest score and ranks first.
21
+ const results = await store.query({
22
+ indexName: 'docs',
23
+ queryVector: [1, 0, 0],
24
+ topK: 2,
25
+ metric: 'cosine', // optional; resolved from the index by default
26
+ });
27
+ ```
28
+
29
+ - Updated dependencies [[`6a1428a`](https://github.com/mastra-ai/mastra/commit/6a1428a23133fc070fc6c1caa08d28f3ba4fe5ff), [`7f51548`](https://github.com/mastra-ai/mastra/commit/7f515481213780be7047cef00640b9d35f3d545c)]:
30
+ - @mastra/core@1.46.0-alpha.2
31
+
3
32
  ## 1.1.0
4
33
 
5
34
  ### Minor Changes
@@ -3,7 +3,7 @@ name: mastra-lance
3
3
  description: Documentation for @mastra/lance. Use when working with @mastra/lance APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/lance"
6
- version: "1.1.0"
6
+ version: "1.1.1-alpha.0"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.0",
2
+ "version": "1.1.1-alpha.0",
3
3
  "package": "@mastra/lance",
4
4
  "exports": {},
5
5
  "modules": {}
package/dist/index.cjs CHANGED
@@ -2647,7 +2647,8 @@ var LanceVectorStore = class _LanceVectorStore extends vector.MastraVector {
2647
2647
  includeVector = false,
2648
2648
  topK = 10,
2649
2649
  columns = [],
2650
- includeAllColumns = false
2650
+ includeAllColumns = false,
2651
+ metric
2651
2652
  }) {
2652
2653
  const resolvedTableName = tableName ?? indexName;
2653
2654
  if (!queryVector) {
@@ -2685,7 +2686,11 @@ var LanceVectorStore = class _LanceVectorStore extends vector.MastraVector {
2685
2686
  return [];
2686
2687
  }
2687
2688
  const table = await this.lanceClient.openTable(resolvedTableName);
2688
- let query = table.search(queryVector);
2689
+ const resolvedMetric = await this.resolveQueryMetric(table, {
2690
+ columnName: tableName ? indexName : "vector",
2691
+ explicitMetric: metric
2692
+ });
2693
+ let query = table.vectorSearch(queryVector).distanceType(this.mastraMetricToLance(resolvedMetric));
2689
2694
  if (filter && Object.keys(filter).length > 0) {
2690
2695
  const whereClause = this.filterTranslator(filter);
2691
2696
  this.logger.debug(`Where clause generated: ${whereClause}`);
@@ -2706,6 +2711,9 @@ var LanceVectorStore = class _LanceVectorStore extends vector.MastraVector {
2706
2711
  if (!selectColumns.includes("id")) {
2707
2712
  selectColumns.push("id");
2708
2713
  }
2714
+ if (!selectColumns.includes("_distance")) {
2715
+ selectColumns.push("_distance");
2716
+ }
2709
2717
  query = query.select(selectColumns);
2710
2718
  }
2711
2719
  query = query.limit(topK);
@@ -2726,7 +2734,7 @@ var LanceVectorStore = class _LanceVectorStore extends vector.MastraVector {
2726
2734
  metadata,
2727
2735
  vector: includeVector && result.vector ? Array.isArray(result.vector) ? result.vector : Array.from(result.vector) : void 0,
2728
2736
  document: result.document,
2729
- score: result._distance
2737
+ score: this.distanceToScore(result._distance, resolvedMetric)
2730
2738
  };
2731
2739
  });
2732
2740
  } catch (error$1) {
@@ -2741,6 +2749,85 @@ var LanceVectorStore = class _LanceVectorStore extends vector.MastraVector {
2741
2749
  );
2742
2750
  }
2743
2751
  }
2752
+ /**
2753
+ * Maps a Mastra distance metric to the LanceDB distance type.
2754
+ */
2755
+ mastraMetricToLance(metric) {
2756
+ switch (metric) {
2757
+ case "euclidean":
2758
+ return "l2";
2759
+ case "dotproduct":
2760
+ return "dot";
2761
+ case "cosine":
2762
+ default:
2763
+ return "cosine";
2764
+ }
2765
+ }
2766
+ /**
2767
+ * Determines the distance metric to use for a query.
2768
+ *
2769
+ * Precedence: the table's vector index metric wins when an index exists because
2770
+ * LanceDB requires indexed searches to use the same distance type the index was
2771
+ * trained with; otherwise an explicitly provided metric is used; otherwise it
2772
+ * defaults to 'cosine'.
2773
+ */
2774
+ async resolveQueryMetric(table, {
2775
+ columnName,
2776
+ explicitMetric
2777
+ }) {
2778
+ try {
2779
+ const indices = await table.listIndices();
2780
+ const matchingIndices = indices.filter((index) => index.columns?.includes(columnName));
2781
+ for (const index of matchingIndices.length > 0 ? matchingIndices : indices) {
2782
+ const stats = await table.indexStats(index.name);
2783
+ const resolved = this.lanceMetricToMastra(stats?.distanceType);
2784
+ if (resolved) {
2785
+ if (explicitMetric && explicitMetric !== resolved) {
2786
+ this.logger.warn(
2787
+ `Ignoring query metric "${explicitMetric}" because Lance index "${index.name}" uses "${resolved}".`
2788
+ );
2789
+ }
2790
+ return resolved;
2791
+ }
2792
+ }
2793
+ } catch (error) {
2794
+ this.logger.debug("Failed to resolve metric from Lance index stats.", error);
2795
+ }
2796
+ return explicitMetric ?? "cosine";
2797
+ }
2798
+ lanceMetricToMastra(metric) {
2799
+ switch (metric) {
2800
+ case "l2":
2801
+ return "euclidean";
2802
+ case "dot":
2803
+ return "dotproduct";
2804
+ case "cosine":
2805
+ return "cosine";
2806
+ default:
2807
+ return void 0;
2808
+ }
2809
+ }
2810
+ /**
2811
+ * Converts a LanceDB `_distance` (smaller = more similar) into a Mastra similarity
2812
+ * `score` (larger = more similar), consistent with every other Mastra vector store.
2813
+ *
2814
+ * - cosine: LanceDB returns cosine distance (`1 - cosine_similarity`), so `1 - distance`
2815
+ * recovers the cosine similarity.
2816
+ * - dotproduct: LanceDB's dot distance is `1 - dot_product`, so `1 - distance` recovers
2817
+ * the dot product (matching `@mastra/pg`).
2818
+ * - euclidean: LanceDB returns squared L2 distance for `l2`, so use its square root
2819
+ * before mapping into (0, 1] (matching `@mastra/pg`).
2820
+ */
2821
+ distanceToScore(distance, metric) {
2822
+ switch (metric) {
2823
+ case "euclidean":
2824
+ return 1 / (1 + Math.sqrt(distance));
2825
+ case "dotproduct":
2826
+ case "cosine":
2827
+ default:
2828
+ return 1 - distance;
2829
+ }
2830
+ }
2744
2831
  filterTranslator(filter) {
2745
2832
  const processFilterKeys = (filterObj) => {
2746
2833
  const result = {};
@@ -3033,14 +3120,7 @@ var LanceVectorStore = class _LanceVectorStore extends vector.MastraVector {
3033
3120
  } else {
3034
3121
  table = await this.lanceClient.openTable(resolvedTableName);
3035
3122
  }
3036
- let metricType;
3037
- if (metric === "euclidean") {
3038
- metricType = "l2";
3039
- } else if (metric === "dotproduct") {
3040
- metricType = "dot";
3041
- } else if (metric === "cosine") {
3042
- metricType = "cosine";
3043
- }
3123
+ const metricType = this.mastraMetricToLance(metric);
3044
3124
  const rowCount = await table.countRows();
3045
3125
  if (rowCount < 256) {
3046
3126
  this.logger.warn(
@@ -3145,7 +3225,7 @@ var LanceVectorStore = class _LanceVectorStore extends vector.MastraVector {
3145
3225
  const dimension = vectorField?.type?.["listSize"] || 0;
3146
3226
  return {
3147
3227
  dimension,
3148
- metric: stats.distanceType,
3228
+ metric: this.lanceMetricToMastra(stats.distanceType),
3149
3229
  count: stats.numIndexedRows
3150
3230
  };
3151
3231
  }