@mastra/lance 1.4.0 → 1.5.0-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/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/reference-vectors-lance.md +56 -10
- package/dist/index.cjs +153 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +153 -5
- package/dist/index.js.map +1 -1
- package/dist/vector/index.d.ts +33 -1
- package/dist/vector/index.d.ts.map +1 -1
- package/dist/vector/types.d.ts +1 -1
- package/dist/vector/types.d.ts.map +1 -1
- package/package.json +5 -5
package/dist/docs/SKILL.md
CHANGED
|
@@ -49,15 +49,17 @@ const s3Store = await LanceVectorStore.create('s3://bucket/db', {
|
|
|
49
49
|
|
|
50
50
|
#### `LanceIndexConfig`
|
|
51
51
|
|
|
52
|
-
**type** (`'ivfflat' | 'hnsw'`): Index type (Default: `hnsw`)
|
|
52
|
+
**type** (`'ivfflat' | 'ivfpq' | 'hnsw'`): Index type (Default: `hnsw`)
|
|
53
53
|
|
|
54
|
-
**type.ivfflat** (`ivfflat`): Clusters vectors into
|
|
54
|
+
**type.ivfflat** (`ivfflat`): Clusters vectors into partitions and stores full vectors. Searches compute exact distances only within the probed partitions, so closer vectors in other partitions can be missed.
|
|
55
|
+
|
|
56
|
+
**type.ivfpq** (`ivfpq`): IVF index with product quantization. Uses less memory than ivfflat with lower recall.
|
|
55
57
|
|
|
56
58
|
**type.hnsw** (`hnsw`): Graph-based index offering fast search times and high recall.
|
|
57
59
|
|
|
58
60
|
**numPartitions** (`number`): Number of partitions for IVF indexes (Default: `128`)
|
|
59
61
|
|
|
60
|
-
**numSubVectors** (`number`): Number of sub-vectors for product quantization (Default: `16`)
|
|
62
|
+
**numSubVectors** (`number`): Number of sub-vectors for product quantization. Only applies to ivfpq (Default: `16`)
|
|
61
63
|
|
|
62
64
|
**hnsw** (`HNSWConfig`): HNSW configuration
|
|
63
65
|
|
|
@@ -137,16 +139,60 @@ interface IndexStats {
|
|
|
137
139
|
dimension: number
|
|
138
140
|
count: number
|
|
139
141
|
metric: 'cosine' | 'euclidean' | 'dotproduct'
|
|
140
|
-
type: 'ivfflat' | 'hnsw'
|
|
141
|
-
config: {
|
|
142
|
-
m?: number
|
|
143
|
-
efConstruction?: number
|
|
144
|
-
numPartitions?: number
|
|
145
|
-
numSubVectors?: number
|
|
146
|
-
}
|
|
147
142
|
}
|
|
148
143
|
```
|
|
149
144
|
|
|
145
|
+
### `getIndexCoverage()`
|
|
146
|
+
|
|
147
|
+
**tableName** (`string`): Table to inspect. Defaults to indexName when omitted.
|
|
148
|
+
|
|
149
|
+
**indexName** (`string`): Used as the table name when tableName is omitted, matching upsert() and query().
|
|
150
|
+
|
|
151
|
+
Returns one entry per index on the table:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
interface LanceIndexCoverage {
|
|
155
|
+
indexName: string
|
|
156
|
+
indexType: string
|
|
157
|
+
columns: string[]
|
|
158
|
+
numIndexedRows: number
|
|
159
|
+
numUnindexedRows: number
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### `optimize()`
|
|
164
|
+
|
|
165
|
+
Runs LanceDB table maintenance. It compacts data files, adds rows written since the index was built to the existing index, and removes old table versions. Mastra never calls it automatically. Concurrent calls for the same table in one process share a single run.
|
|
166
|
+
|
|
167
|
+
**tableName** (`string`): Table to optimize. Defaults to indexName when omitted.
|
|
168
|
+
|
|
169
|
+
**indexName** (`string`): Used as the table name when tableName is omitted.
|
|
170
|
+
|
|
171
|
+
**cleanupOlderThan** (`Date`): Remove table versions older than this date. When omitted, LanceDB keeps versions from the last 7 days.
|
|
172
|
+
|
|
173
|
+
**deleteUnverified** (`boolean`): Also delete unverified files newer than 7 days. Only set this when no other process writes to the table, or the table can be corrupted. (Default: `false`)
|
|
174
|
+
|
|
175
|
+
Returns LanceDB `OptimizeStats` (compaction and pruning counts).
|
|
176
|
+
|
|
177
|
+
#### When to optimize
|
|
178
|
+
|
|
179
|
+
`upsert()` writes rows outside the existing vector index. `query()` still returns those rows by scanning them without the index, so results stay complete, but queries get slower as unindexed rows grow. LanceDB's `fastSearch()` skips unindexed rows entirely.
|
|
180
|
+
|
|
181
|
+
`optimize()` adds new rows to the existing index without retraining it. If your data distribution has changed a lot, rebuild the index with `deleteIndex()` and `createIndex()` instead.
|
|
182
|
+
|
|
183
|
+
LanceDB suggests optimizing after roughly 100,000 modified rows or 20 write operations. Use `getIndexCoverage()` to build your own policy:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
const [coverage] = await store.getIndexCoverage({ indexName: 'docs' })
|
|
187
|
+
const total = coverage.numIndexedRows + coverage.numUnindexedRows
|
|
188
|
+
|
|
189
|
+
if (coverage.numUnindexedRows >= 1_000 && coverage.numUnindexedRows / total >= 0.1) {
|
|
190
|
+
await store.optimize({ indexName: 'docs' })
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Managed LanceDB Cloud and Enterprise maintain tables automatically, so you don't need to call `optimize()` there.
|
|
195
|
+
|
|
150
196
|
### `deleteIndex()`
|
|
151
197
|
|
|
152
198
|
**indexName** (`string`): Name of the index to delete
|
package/dist/index.cjs
CHANGED
|
@@ -2190,8 +2190,11 @@ var LanceFilterTranslator = class extends _mastra_core_vector_filter.BaseFilterT
|
|
|
2190
2190
|
};
|
|
2191
2191
|
//#endregion
|
|
2192
2192
|
//#region src/vector/index.ts
|
|
2193
|
+
const LANCE_INDEX_FINGERPRINT_KEY = "mastra.index.fingerprint";
|
|
2194
|
+
const LANCE_INDEX_UUID_KEY = "mastra.index.uuid";
|
|
2193
2195
|
var LanceVectorStore = class LanceVectorStore extends _mastra_core_vector.MastraVector {
|
|
2194
2196
|
lanceClient;
|
|
2197
|
+
optimizeInFlight = /* @__PURE__ */ new Map();
|
|
2195
2198
|
/**
|
|
2196
2199
|
* Creates a new instance of LanceVectorStore
|
|
2197
2200
|
* @param uri The URI to connect to LanceDB
|
|
@@ -2576,6 +2579,7 @@ var LanceVectorStore = class LanceVectorStore extends _mastra_core_vector.Mastra
|
|
|
2576
2579
|
}
|
|
2577
2580
|
}, err);
|
|
2578
2581
|
}
|
|
2582
|
+
if (indexConfig.type === "ivfflat" && indexConfig.numSubVectors !== void 0) this.logger.warn(`numSubVectors is ignored for 'ivfflat' indexes. Use type 'ivfpq' for product-quantized IVF indexes.`);
|
|
2579
2583
|
try {
|
|
2580
2584
|
const tables = await this.lanceClient.tableNames();
|
|
2581
2585
|
let table;
|
|
@@ -2601,19 +2605,51 @@ var LanceVectorStore = class LanceVectorStore extends _mastra_core_vector.Mastra
|
|
|
2601
2605
|
this.logger.warn(`Table ${resolvedTableName} has ${rowCount} rows, which is below the 256 row minimum for index creation. Skipping index creation.`);
|
|
2602
2606
|
return;
|
|
2603
2607
|
}
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2608
|
+
const numPartitions = indexConfig.numPartitions || 128;
|
|
2609
|
+
const buildParams = indexConfig.type === "ivfflat" ? { numPartitions } : indexConfig.type === "ivfpq" ? {
|
|
2610
|
+
numPartitions,
|
|
2611
|
+
numSubVectors: indexConfig.numSubVectors || 16
|
|
2612
|
+
} : {
|
|
2613
|
+
m: indexConfig?.hnsw?.m || 16,
|
|
2614
|
+
efConstruction: indexConfig?.hnsw?.efConstruction || 100
|
|
2615
|
+
};
|
|
2616
|
+
const fingerprintType = indexConfig.type === "ivfflat" ? "ivf_flat" : indexConfig.type === "ivfpq" ? "ivf_pq" : "hnsw_pq";
|
|
2617
|
+
const fingerprint = JSON.stringify({
|
|
2618
|
+
type: fingerprintType,
|
|
2619
|
+
metric: metricType,
|
|
2620
|
+
...buildParams
|
|
2621
|
+
});
|
|
2622
|
+
const existingIndex = (await table.listIndices()).find((index) => index.columns.includes(columnToIndex));
|
|
2623
|
+
if (existingIndex?.indexUuid) {
|
|
2624
|
+
const field = (await table.schema()).fields.find((f) => f.name === columnToIndex);
|
|
2625
|
+
if (field?.metadata.get(LANCE_INDEX_FINGERPRINT_KEY) === fingerprint && field.metadata.get(LANCE_INDEX_UUID_KEY) === existingIndex.indexUuid) {
|
|
2626
|
+
this.logger.debug(`Index ${existingIndex.name} on ${resolvedTableName} is unchanged. Skipping rebuild.`);
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
if (indexConfig.type === "ivfflat") await table.createIndex(columnToIndex, { config: _lancedb_lancedb.Index.ivfFlat({
|
|
2631
|
+
numPartitions,
|
|
2632
|
+
distanceType: metricType
|
|
2633
|
+
}) });
|
|
2634
|
+
else if (indexConfig.type === "ivfpq") await table.createIndex(columnToIndex, { config: _lancedb_lancedb.Index.ivfPq({
|
|
2635
|
+
...buildParams,
|
|
2607
2636
|
distanceType: metricType
|
|
2608
2637
|
}) });
|
|
2609
2638
|
else {
|
|
2610
2639
|
this.logger.debug("Creating HNSW PQ index with config:", indexConfig);
|
|
2611
2640
|
await table.createIndex(columnToIndex, { config: _lancedb_lancedb.Index.hnswPq({
|
|
2612
|
-
|
|
2613
|
-
efConstruction: indexConfig?.hnsw?.efConstruction || 100,
|
|
2641
|
+
...buildParams,
|
|
2614
2642
|
distanceType: metricType
|
|
2615
2643
|
}) });
|
|
2616
2644
|
}
|
|
2645
|
+
const builtUuid = (await table.listIndices()).find((index) => index.columns.includes(columnToIndex))?.indexUuid;
|
|
2646
|
+
if (builtUuid && builtUuid !== existingIndex?.indexUuid) await table.updateFieldMetadata([{
|
|
2647
|
+
path: columnToIndex,
|
|
2648
|
+
metadata: {
|
|
2649
|
+
[LANCE_INDEX_FINGERPRINT_KEY]: fingerprint,
|
|
2650
|
+
[LANCE_INDEX_UUID_KEY]: builtUuid
|
|
2651
|
+
}
|
|
2652
|
+
}]);
|
|
2617
2653
|
} catch (error) {
|
|
2618
2654
|
throw new _mastra_core_error.MastraError({
|
|
2619
2655
|
id: (0, _mastra_core_storage.createVectorErrorId)("LANCE", "CREATE_INDEX", "FAILED"),
|
|
@@ -2722,6 +2758,118 @@ var LanceVectorStore = class LanceVectorStore extends _mastra_core_vector.Mastra
|
|
|
2722
2758
|
}, error);
|
|
2723
2759
|
}
|
|
2724
2760
|
}
|
|
2761
|
+
async openExistingTable({ tableName, indexName }, operation) {
|
|
2762
|
+
const resolvedTableName = tableName ?? indexName;
|
|
2763
|
+
let validationError;
|
|
2764
|
+
if (!this.lanceClient) validationError = "LanceDB client not initialized. Use LanceVectorStore.create() to create an instance";
|
|
2765
|
+
else if (!resolvedTableName) validationError = "tableName or indexName is required";
|
|
2766
|
+
if (validationError || !resolvedTableName || !this.lanceClient) throw new _mastra_core_error.MastraError({
|
|
2767
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("LANCE", operation, "INVALID_ARGS"),
|
|
2768
|
+
text: validationError,
|
|
2769
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
2770
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
2771
|
+
details: { tableName: resolvedTableName ?? "" }
|
|
2772
|
+
});
|
|
2773
|
+
let tableExists;
|
|
2774
|
+
try {
|
|
2775
|
+
tableExists = (await this.lanceClient.tableNames()).includes(resolvedTableName);
|
|
2776
|
+
if (tableExists) return {
|
|
2777
|
+
table: await this.lanceClient.openTable(resolvedTableName),
|
|
2778
|
+
resolvedTableName
|
|
2779
|
+
};
|
|
2780
|
+
} catch (error) {
|
|
2781
|
+
throw new _mastra_core_error.MastraError({
|
|
2782
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("LANCE", operation, "FAILED"),
|
|
2783
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
2784
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
2785
|
+
details: { tableName: resolvedTableName }
|
|
2786
|
+
}, error);
|
|
2787
|
+
}
|
|
2788
|
+
throw new _mastra_core_error.MastraError({
|
|
2789
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("LANCE", operation, "INVALID_ARGS"),
|
|
2790
|
+
text: `Table ${resolvedTableName} does not exist`,
|
|
2791
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
2792
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
2793
|
+
details: { tableName: resolvedTableName }
|
|
2794
|
+
});
|
|
2795
|
+
}
|
|
2796
|
+
/**
|
|
2797
|
+
* Runs LanceDB table maintenance: compacts fragments, adds unindexed rows to existing indexes
|
|
2798
|
+
* (without retraining them), and prunes old table versions.
|
|
2799
|
+
*
|
|
2800
|
+
* Never runs implicitly. Concurrent calls for the same table share a single in-flight run.
|
|
2801
|
+
*/
|
|
2802
|
+
async optimize(params) {
|
|
2803
|
+
const key = params.tableName ?? params.indexName;
|
|
2804
|
+
const inFlight = key ? this.optimizeInFlight.get(key) : void 0;
|
|
2805
|
+
if (inFlight) return inFlight;
|
|
2806
|
+
const run = (async () => {
|
|
2807
|
+
try {
|
|
2808
|
+
const { table, resolvedTableName } = await this.openExistingTable(params, "OPTIMIZE");
|
|
2809
|
+
try {
|
|
2810
|
+
const schema = await table.schema();
|
|
2811
|
+
const tracked = (await table.listIndices()).filter((index) => index.columns.some((column) => schema.fields.find((f) => f.name === column)?.metadata.get(LANCE_INDEX_UUID_KEY) === index.indexUuid));
|
|
2812
|
+
const stats = await table.optimize({
|
|
2813
|
+
...params.cleanupOlderThan ? { cleanupOlderThan: params.cleanupOlderThan } : {},
|
|
2814
|
+
deleteUnverified: params.deleteUnverified ?? false
|
|
2815
|
+
});
|
|
2816
|
+
if (tracked.length) {
|
|
2817
|
+
const after = await table.listIndices();
|
|
2818
|
+
const updates = tracked.flatMap((before) => {
|
|
2819
|
+
const current = after.find((index) => index.name === before.name);
|
|
2820
|
+
return current?.indexUuid && current.indexUuid !== before.indexUuid ? before.columns.map((path) => ({
|
|
2821
|
+
path,
|
|
2822
|
+
metadata: { [LANCE_INDEX_UUID_KEY]: current.indexUuid }
|
|
2823
|
+
})) : [];
|
|
2824
|
+
});
|
|
2825
|
+
if (updates.length) await table.updateFieldMetadata(updates);
|
|
2826
|
+
}
|
|
2827
|
+
return stats;
|
|
2828
|
+
} catch (error) {
|
|
2829
|
+
throw new _mastra_core_error.MastraError({
|
|
2830
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("LANCE", "OPTIMIZE", "FAILED"),
|
|
2831
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
2832
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
2833
|
+
details: { tableName: resolvedTableName }
|
|
2834
|
+
}, error);
|
|
2835
|
+
}
|
|
2836
|
+
} finally {
|
|
2837
|
+
if (key) this.optimizeInFlight.delete(key);
|
|
2838
|
+
}
|
|
2839
|
+
})();
|
|
2840
|
+
if (key) this.optimizeInFlight.set(key, run);
|
|
2841
|
+
return run;
|
|
2842
|
+
}
|
|
2843
|
+
/**
|
|
2844
|
+
* Reports how many rows each index on a table covers. Rows written after an index was built
|
|
2845
|
+
* stay unindexed until `optimize()` runs; use this to decide when to optimize.
|
|
2846
|
+
*/
|
|
2847
|
+
async getIndexCoverage(params) {
|
|
2848
|
+
const { table, resolvedTableName } = await this.openExistingTable(params, "GET_INDEX_COVERAGE");
|
|
2849
|
+
try {
|
|
2850
|
+
const indices = await table.listIndices();
|
|
2851
|
+
const coverage = [];
|
|
2852
|
+
for (const index of indices) {
|
|
2853
|
+
const stats = await table.indexStats(index.name);
|
|
2854
|
+
if (!stats) continue;
|
|
2855
|
+
coverage.push({
|
|
2856
|
+
indexName: index.name,
|
|
2857
|
+
indexType: stats.indexType,
|
|
2858
|
+
columns: index.columns,
|
|
2859
|
+
numIndexedRows: stats.numIndexedRows,
|
|
2860
|
+
numUnindexedRows: stats.numUnindexedRows
|
|
2861
|
+
});
|
|
2862
|
+
}
|
|
2863
|
+
return coverage;
|
|
2864
|
+
} catch (error) {
|
|
2865
|
+
throw new _mastra_core_error.MastraError({
|
|
2866
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("LANCE", "GET_INDEX_COVERAGE", "FAILED"),
|
|
2867
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
2868
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
2869
|
+
details: { tableName: resolvedTableName }
|
|
2870
|
+
}, error);
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2725
2873
|
/**
|
|
2726
2874
|
* Deletes all tables in the database
|
|
2727
2875
|
*/
|