libsql-search 0.5.0 → 0.6.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/README.md +6 -8
- package/dist/index.cjs +73 -15
- package/dist/index.d.ts +4 -0
- package/dist/index.esm.js +73 -15
- package/docs/API.md +9 -2
- package/docs/INDEXING.md +6 -2
- package/docs/INTEGRATIONS.md +10 -8
- package/docs/PROVIDERS.md +26 -12
- package/docs/TROUBLESHOOTING-SHARP.md +7 -6
- package/docs/TROUBLESHOOTING.md +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -20,8 +20,8 @@ Use it when you want:
|
|
|
20
20
|
|
|
21
21
|
- Markdown indexing from local directories with frontmatter via `gray-matter`
|
|
22
22
|
- libSQL/Turso storage and vector search
|
|
23
|
-
- Embedding providers: local `Xenova/all-MiniLM-L6-v2`,
|
|
24
|
-
`@cf/baai/bge-m3`, Mistral `mistral-embed`, Google Gemini
|
|
23
|
+
- Embedding providers: local Hugging Face `Xenova/all-MiniLM-L6-v2`,
|
|
24
|
+
Cloudflare Workers AI `@cf/baai/bge-m3`, Mistral `mistral-embed`, Google Gemini
|
|
25
25
|
`gemini-embedding-2`, and OpenAI `text-embedding-3-small` /
|
|
26
26
|
`text-embedding-3-large`
|
|
27
27
|
- npm distribution plus JSR publishing
|
|
@@ -65,14 +65,13 @@ const client = createClient({
|
|
|
65
65
|
authToken: "your-auth-token",
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
await createTable(client
|
|
68
|
+
await createTable(client);
|
|
69
69
|
|
|
70
70
|
await indexContent({
|
|
71
71
|
client,
|
|
72
72
|
contentPath: "./content",
|
|
73
73
|
embeddingOptions: {
|
|
74
74
|
provider: "local",
|
|
75
|
-
dimensions: 768,
|
|
76
75
|
},
|
|
77
76
|
});
|
|
78
77
|
|
|
@@ -82,7 +81,6 @@ const results = await search({
|
|
|
82
81
|
limit: 5,
|
|
83
82
|
embeddingOptions: {
|
|
84
83
|
provider: "local",
|
|
85
|
-
dimensions: 768,
|
|
86
84
|
},
|
|
87
85
|
});
|
|
88
86
|
|
|
@@ -98,9 +96,9 @@ Important behavior:
|
|
|
98
96
|
- Call `createTable()` before indexing or searching.
|
|
99
97
|
- Keep dimensions aligned across table creation, indexing, and search queries.
|
|
100
98
|
- `indexContent()` clears existing rows before rebuilding the index.
|
|
101
|
-
- `local` is the default offline provider
|
|
102
|
-
option. Cloudflare and Mistral use 1024 dimensions.
|
|
103
|
-
dimensions and supports 128-3072.
|
|
99
|
+
- `local` is the default offline provider and uses 384 dimensions. Cloudflare is
|
|
100
|
+
the recommended hosted option. Cloudflare and Mistral use 1024 dimensions.
|
|
101
|
+
Gemini defaults to 3072 dimensions and supports 128-3072.
|
|
104
102
|
|
|
105
103
|
## Core API
|
|
106
104
|
|
package/dist/index.cjs
CHANGED
|
@@ -4,7 +4,8 @@ var promises = require('fs/promises');
|
|
|
4
4
|
var path = require('path');
|
|
5
5
|
var matter = require('gray-matter');
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const OPENAI_DEFAULT_DIMENSIONS = 768;
|
|
8
|
+
const LOCAL_DIMENSIONS = 384;
|
|
8
9
|
const DEFAULT_MAX_LENGTH = 8e3;
|
|
9
10
|
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
10
11
|
const LOCAL_MODEL = "Xenova/all-MiniLM-L6-v2";
|
|
@@ -33,17 +34,66 @@ function getEnvironmentVariable(name) {
|
|
|
33
34
|
return void 0;
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
|
-
|
|
37
|
+
function deletePendingLocalModelCache(modelName, entry) {
|
|
38
|
+
if (!entry.settled && entry.waiters === 0 && localModelCacheByModel.get(modelName) === entry) {
|
|
39
|
+
localModelCacheByModel.delete(modelName);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function getLocalEmbeddingModel(modelName, signal) {
|
|
37
43
|
const cached = localModelCacheByModel.get(modelName);
|
|
38
|
-
if (cached
|
|
39
|
-
|
|
44
|
+
if (cached) {
|
|
45
|
+
cached.waiters++;
|
|
46
|
+
try {
|
|
47
|
+
return await waitForLocalEmbeddingModel(modelName, cached, signal);
|
|
48
|
+
} finally {
|
|
49
|
+
cached.waiters--;
|
|
50
|
+
deletePendingLocalModelCache(modelName, cached);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const modelPromise = (async () => {
|
|
54
|
+
console.log(`Loading local embedding model (${modelName})...`);
|
|
55
|
+
const { pipeline } = await import('@huggingface/transformers');
|
|
56
|
+
const model = await pipeline("feature-extraction", modelName);
|
|
57
|
+
console.log("Local model loaded successfully");
|
|
58
|
+
return model;
|
|
59
|
+
})();
|
|
60
|
+
const entry = {
|
|
61
|
+
promise: modelPromise,
|
|
62
|
+
settled: false,
|
|
63
|
+
waiters: 1
|
|
64
|
+
};
|
|
65
|
+
localModelCacheByModel.set(modelName, entry);
|
|
66
|
+
modelPromise.then(() => {
|
|
67
|
+
entry.settled = true;
|
|
68
|
+
}).catch(() => {
|
|
69
|
+
localModelCacheByModel.delete(modelName);
|
|
70
|
+
});
|
|
71
|
+
try {
|
|
72
|
+
return await waitForLocalEmbeddingModel(modelName, entry, signal);
|
|
73
|
+
} finally {
|
|
74
|
+
entry.waiters--;
|
|
75
|
+
deletePendingLocalModelCache(modelName, entry);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function waitForLocalEmbeddingModel(modelName, entry, signal) {
|
|
79
|
+
if (signal.aborted) {
|
|
80
|
+
deletePendingLocalModelCache(modelName, entry);
|
|
81
|
+
throw providerError("local", "model inference was aborted");
|
|
82
|
+
}
|
|
83
|
+
let rejectAbort = () => {
|
|
84
|
+
};
|
|
85
|
+
const abortPromise = new Promise((_resolve, reject) => {
|
|
86
|
+
rejectAbort = reject;
|
|
87
|
+
});
|
|
88
|
+
const onAbort = () => {
|
|
89
|
+
rejectAbort(providerError("local", "model inference was aborted"));
|
|
90
|
+
};
|
|
91
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
92
|
+
try {
|
|
93
|
+
return await Promise.race([entry.promise, abortPromise]);
|
|
94
|
+
} finally {
|
|
95
|
+
signal.removeEventListener("abort", onAbort);
|
|
40
96
|
}
|
|
41
|
-
console.log(`Loading local embedding model (${modelName})...`);
|
|
42
|
-
const { pipeline } = await import('@xenova/transformers');
|
|
43
|
-
const model = await pipeline("feature-extraction", modelName);
|
|
44
|
-
localModelCacheByModel.set(modelName, { model });
|
|
45
|
-
console.log("Local model loaded successfully");
|
|
46
|
-
return model;
|
|
47
97
|
}
|
|
48
98
|
function getPositiveInteger(value, optionName) {
|
|
49
99
|
if (!Number.isInteger(value) || value <= 0) {
|
|
@@ -285,6 +335,15 @@ function createProviderMetadata(provider, dimensions) {
|
|
|
285
335
|
}
|
|
286
336
|
}
|
|
287
337
|
function getEffectiveDimensions(provider, dimensions) {
|
|
338
|
+
if (provider === "local") {
|
|
339
|
+
if (dimensions !== void 0 && dimensions !== LOCAL_DIMENSIONS) {
|
|
340
|
+
throw providerError(
|
|
341
|
+
"local",
|
|
342
|
+
`${LOCAL_MODEL} returns ${LOCAL_DIMENSIONS} dimensions; received dimensions ${String(dimensions)}`
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
return LOCAL_DIMENSIONS;
|
|
346
|
+
}
|
|
288
347
|
if (provider === "gemini") {
|
|
289
348
|
const effectiveDimensions = dimensions ?? GEMINI_DIMENSIONS;
|
|
290
349
|
if (!Number.isInteger(effectiveDimensions) || effectiveDimensions < GEMINI_MIN_DIMENSIONS || effectiveDimensions > GEMINI_MAX_DIMENSIONS) {
|
|
@@ -313,7 +372,7 @@ function getEffectiveDimensions(provider, dimensions) {
|
|
|
313
372
|
}
|
|
314
373
|
return CLOUDFLARE_DIMENSIONS;
|
|
315
374
|
}
|
|
316
|
-
return getPositiveInteger(dimensions ??
|
|
375
|
+
return getPositiveInteger(dimensions ?? OPENAI_DEFAULT_DIMENSIONS, "dimensions");
|
|
317
376
|
}
|
|
318
377
|
function assertBatchSize(metadata, count) {
|
|
319
378
|
if (metadata.batch.maxSize !== void 0 && count > metadata.batch.maxSize) {
|
|
@@ -350,14 +409,13 @@ class LocalEmbeddingProvider {
|
|
|
350
409
|
"model inference",
|
|
351
410
|
this.#timeoutMs,
|
|
352
411
|
async (signal) => {
|
|
353
|
-
const model = await getLocalEmbeddingModel(this.metadata.model);
|
|
412
|
+
const model = await getLocalEmbeddingModel(this.metadata.model, signal);
|
|
354
413
|
return embedSequentially("local", "model inference", texts, signal, async (text) => {
|
|
355
414
|
const output = await model(text, {
|
|
356
415
|
pooling: "mean",
|
|
357
416
|
normalize: true
|
|
358
417
|
});
|
|
359
|
-
|
|
360
|
-
return padEmbedding(embedding, this.metadata.dimensions);
|
|
418
|
+
return Array.from(output.data);
|
|
361
419
|
});
|
|
362
420
|
},
|
|
363
421
|
options.signal
|
|
@@ -824,7 +882,7 @@ async function insertDocument(client, document, quotedTableName) {
|
|
|
824
882
|
]
|
|
825
883
|
});
|
|
826
884
|
}
|
|
827
|
-
async function createTable(client, tableName = "articles", dimensions =
|
|
885
|
+
async function createTable(client, tableName = "articles", dimensions = 384) {
|
|
828
886
|
const quotedTableName = quoteSqlIdentifier(tableName, "tableName");
|
|
829
887
|
const vectorDimensions = normalizeVectorDimensions(dimensions);
|
|
830
888
|
const quotedEmbeddingIndexName = quoteSqlIdentifier(`${tableName}_embedding_idx`, "embedding index name");
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { Client } from '@libsql/client';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Multi-provider embedding generation
|
|
5
|
+
* Supports local Hugging Face Transformers, Gemini, OpenAI, Mistral, and Cloudflare Workers AI
|
|
6
|
+
*/
|
|
3
7
|
type EmbeddingProvider = 'local' | 'gemini' | 'openai' | 'mistral' | 'cloudflare';
|
|
4
8
|
type EmbeddingIntent = 'document' | 'query';
|
|
5
9
|
type EmbeddingBatchMode = 'native' | 'sequential';
|
package/dist/index.esm.js
CHANGED
|
@@ -2,7 +2,8 @@ import { readdir, readFile } from 'fs/promises';
|
|
|
2
2
|
import { join, extname, relative, dirname } from 'path';
|
|
3
3
|
import matter from 'gray-matter';
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const OPENAI_DEFAULT_DIMENSIONS = 768;
|
|
6
|
+
const LOCAL_DIMENSIONS = 384;
|
|
6
7
|
const DEFAULT_MAX_LENGTH = 8e3;
|
|
7
8
|
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
8
9
|
const LOCAL_MODEL = "Xenova/all-MiniLM-L6-v2";
|
|
@@ -31,17 +32,66 @@ function getEnvironmentVariable(name) {
|
|
|
31
32
|
return void 0;
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
+
function deletePendingLocalModelCache(modelName, entry) {
|
|
36
|
+
if (!entry.settled && entry.waiters === 0 && localModelCacheByModel.get(modelName) === entry) {
|
|
37
|
+
localModelCacheByModel.delete(modelName);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function getLocalEmbeddingModel(modelName, signal) {
|
|
35
41
|
const cached = localModelCacheByModel.get(modelName);
|
|
36
|
-
if (cached
|
|
37
|
-
|
|
42
|
+
if (cached) {
|
|
43
|
+
cached.waiters++;
|
|
44
|
+
try {
|
|
45
|
+
return await waitForLocalEmbeddingModel(modelName, cached, signal);
|
|
46
|
+
} finally {
|
|
47
|
+
cached.waiters--;
|
|
48
|
+
deletePendingLocalModelCache(modelName, cached);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const modelPromise = (async () => {
|
|
52
|
+
console.log(`Loading local embedding model (${modelName})...`);
|
|
53
|
+
const { pipeline } = await import('@huggingface/transformers');
|
|
54
|
+
const model = await pipeline("feature-extraction", modelName);
|
|
55
|
+
console.log("Local model loaded successfully");
|
|
56
|
+
return model;
|
|
57
|
+
})();
|
|
58
|
+
const entry = {
|
|
59
|
+
promise: modelPromise,
|
|
60
|
+
settled: false,
|
|
61
|
+
waiters: 1
|
|
62
|
+
};
|
|
63
|
+
localModelCacheByModel.set(modelName, entry);
|
|
64
|
+
modelPromise.then(() => {
|
|
65
|
+
entry.settled = true;
|
|
66
|
+
}).catch(() => {
|
|
67
|
+
localModelCacheByModel.delete(modelName);
|
|
68
|
+
});
|
|
69
|
+
try {
|
|
70
|
+
return await waitForLocalEmbeddingModel(modelName, entry, signal);
|
|
71
|
+
} finally {
|
|
72
|
+
entry.waiters--;
|
|
73
|
+
deletePendingLocalModelCache(modelName, entry);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function waitForLocalEmbeddingModel(modelName, entry, signal) {
|
|
77
|
+
if (signal.aborted) {
|
|
78
|
+
deletePendingLocalModelCache(modelName, entry);
|
|
79
|
+
throw providerError("local", "model inference was aborted");
|
|
80
|
+
}
|
|
81
|
+
let rejectAbort = () => {
|
|
82
|
+
};
|
|
83
|
+
const abortPromise = new Promise((_resolve, reject) => {
|
|
84
|
+
rejectAbort = reject;
|
|
85
|
+
});
|
|
86
|
+
const onAbort = () => {
|
|
87
|
+
rejectAbort(providerError("local", "model inference was aborted"));
|
|
88
|
+
};
|
|
89
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
90
|
+
try {
|
|
91
|
+
return await Promise.race([entry.promise, abortPromise]);
|
|
92
|
+
} finally {
|
|
93
|
+
signal.removeEventListener("abort", onAbort);
|
|
38
94
|
}
|
|
39
|
-
console.log(`Loading local embedding model (${modelName})...`);
|
|
40
|
-
const { pipeline } = await import('@xenova/transformers');
|
|
41
|
-
const model = await pipeline("feature-extraction", modelName);
|
|
42
|
-
localModelCacheByModel.set(modelName, { model });
|
|
43
|
-
console.log("Local model loaded successfully");
|
|
44
|
-
return model;
|
|
45
95
|
}
|
|
46
96
|
function getPositiveInteger(value, optionName) {
|
|
47
97
|
if (!Number.isInteger(value) || value <= 0) {
|
|
@@ -283,6 +333,15 @@ function createProviderMetadata(provider, dimensions) {
|
|
|
283
333
|
}
|
|
284
334
|
}
|
|
285
335
|
function getEffectiveDimensions(provider, dimensions) {
|
|
336
|
+
if (provider === "local") {
|
|
337
|
+
if (dimensions !== void 0 && dimensions !== LOCAL_DIMENSIONS) {
|
|
338
|
+
throw providerError(
|
|
339
|
+
"local",
|
|
340
|
+
`${LOCAL_MODEL} returns ${LOCAL_DIMENSIONS} dimensions; received dimensions ${String(dimensions)}`
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
return LOCAL_DIMENSIONS;
|
|
344
|
+
}
|
|
286
345
|
if (provider === "gemini") {
|
|
287
346
|
const effectiveDimensions = dimensions ?? GEMINI_DIMENSIONS;
|
|
288
347
|
if (!Number.isInteger(effectiveDimensions) || effectiveDimensions < GEMINI_MIN_DIMENSIONS || effectiveDimensions > GEMINI_MAX_DIMENSIONS) {
|
|
@@ -311,7 +370,7 @@ function getEffectiveDimensions(provider, dimensions) {
|
|
|
311
370
|
}
|
|
312
371
|
return CLOUDFLARE_DIMENSIONS;
|
|
313
372
|
}
|
|
314
|
-
return getPositiveInteger(dimensions ??
|
|
373
|
+
return getPositiveInteger(dimensions ?? OPENAI_DEFAULT_DIMENSIONS, "dimensions");
|
|
315
374
|
}
|
|
316
375
|
function assertBatchSize(metadata, count) {
|
|
317
376
|
if (metadata.batch.maxSize !== void 0 && count > metadata.batch.maxSize) {
|
|
@@ -348,14 +407,13 @@ class LocalEmbeddingProvider {
|
|
|
348
407
|
"model inference",
|
|
349
408
|
this.#timeoutMs,
|
|
350
409
|
async (signal) => {
|
|
351
|
-
const model = await getLocalEmbeddingModel(this.metadata.model);
|
|
410
|
+
const model = await getLocalEmbeddingModel(this.metadata.model, signal);
|
|
352
411
|
return embedSequentially("local", "model inference", texts, signal, async (text) => {
|
|
353
412
|
const output = await model(text, {
|
|
354
413
|
pooling: "mean",
|
|
355
414
|
normalize: true
|
|
356
415
|
});
|
|
357
|
-
|
|
358
|
-
return padEmbedding(embedding, this.metadata.dimensions);
|
|
416
|
+
return Array.from(output.data);
|
|
359
417
|
});
|
|
360
418
|
},
|
|
361
419
|
options.signal
|
|
@@ -822,7 +880,7 @@ async function insertDocument(client, document, quotedTableName) {
|
|
|
822
880
|
]
|
|
823
881
|
});
|
|
824
882
|
}
|
|
825
|
-
async function createTable(client, tableName = "articles", dimensions =
|
|
883
|
+
async function createTable(client, tableName = "articles", dimensions = 384) {
|
|
826
884
|
const quotedTableName = quoteSqlIdentifier(tableName, "tableName");
|
|
827
885
|
const vectorDimensions = normalizeVectorDimensions(dimensions);
|
|
828
886
|
const quotedEmbeddingIndexName = quoteSqlIdentifier(`${tableName}_embedding_idx`, "embedding index name");
|
package/docs/API.md
CHANGED
|
@@ -42,13 +42,13 @@ It also exports these types:
|
|
|
42
42
|
Creates the table and supporting indexes used by search.
|
|
43
43
|
|
|
44
44
|
```ts
|
|
45
|
-
await createTable(client
|
|
45
|
+
await createTable(client);
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
Defaults:
|
|
49
49
|
|
|
50
50
|
- `tableName`: `"articles"`
|
|
51
|
-
- `dimensions`: `
|
|
51
|
+
- `dimensions`: `384`
|
|
52
52
|
|
|
53
53
|
`tableName` must be an ASCII SQLite identifier matching
|
|
54
54
|
`[A-Za-z_][A-Za-z0-9_]*`. Valid identifiers are quoted internally, so reserved
|
|
@@ -216,6 +216,10 @@ credentials or configuration.
|
|
|
216
216
|
Gemini uses `gemini-embedding-2`. Its default is 3072 dimensions, and explicit
|
|
217
217
|
Gemini dimensions must be an integer from 128 through 3072.
|
|
218
218
|
|
|
219
|
+
Local embeddings use `Xenova/all-MiniLM-L6-v2` through
|
|
220
|
+
`@huggingface/transformers` and are fixed at the model's native 384 dimensions.
|
|
221
|
+
Passing any other local dimension is rejected before the runtime is loaded.
|
|
222
|
+
|
|
219
223
|
### `getEmbeddingProviderMetadata(options?)`
|
|
220
224
|
|
|
221
225
|
Returns the same metadata exposed by `createEmbeddingProvider(options).metadata`
|
|
@@ -279,6 +283,9 @@ Cloudflare uses `accountId` and `apiToken`, which fall back to
|
|
|
279
283
|
|
|
280
284
|
Pads or truncates an embedding array to the requested length.
|
|
281
285
|
|
|
286
|
+
This helper remains exported for callers that used it directly. The local
|
|
287
|
+
provider does not use it; local vectors are validated at native 384 dimensions.
|
|
288
|
+
|
|
282
289
|
### `prepareTextForEmbedding(fields)`
|
|
283
290
|
|
|
284
291
|
Combines title, description, tags, and content into the text sent to the
|
package/docs/INDEXING.md
CHANGED
|
@@ -25,7 +25,6 @@ await indexContent({
|
|
|
25
25
|
tableName: "articles",
|
|
26
26
|
embeddingOptions: {
|
|
27
27
|
provider: "local",
|
|
28
|
-
dimensions: 768,
|
|
29
28
|
},
|
|
30
29
|
});
|
|
31
30
|
```
|
|
@@ -34,6 +33,11 @@ That keeps the implementation simple, but it also means a failed rebuild can
|
|
|
34
33
|
leave the index partially repopulated.
|
|
35
34
|
|
|
36
35
|
Changing an embedding provider or dimension count requires a full re-embed.
|
|
36
|
+
For existing local indexes created with the older padded-local behavior, create
|
|
37
|
+
or recreate a 384-dimensional table before rebuilding. Those older local
|
|
38
|
+
768-dimensional tables stored 384 model values followed by zero padding;
|
|
39
|
+
`indexContent()` clears rows but does not change the table's `F32_BLOB` width.
|
|
40
|
+
|
|
37
41
|
For Gemini specifically, indexes created with the retired `text-embedding-004`
|
|
38
42
|
model must be rebuilt for `gemini-embedding-2` even when staying at 768
|
|
39
43
|
dimensions, because the model and query/document formatting both changed. If
|
|
@@ -72,7 +76,7 @@ database calls or embedding generation.
|
|
|
72
76
|
|
|
73
77
|
## Runtime Notes
|
|
74
78
|
|
|
75
|
-
- local embeddings may download a model on the first run
|
|
79
|
+
- local embeddings may download and cache a model on the first run
|
|
76
80
|
- Node users need `@libsql/client` installed alongside the package
|
|
77
81
|
- the repository validates both the npm package build and `deno check`, but the
|
|
78
82
|
indexing flow itself still depends on filesystem access
|
package/docs/INTEGRATIONS.md
CHANGED
|
@@ -26,7 +26,6 @@ export const POST: APIRoute = async ({ request }) => {
|
|
|
26
26
|
limit,
|
|
27
27
|
embeddingOptions: {
|
|
28
28
|
provider: "local",
|
|
29
|
-
dimensions: 768,
|
|
30
29
|
},
|
|
31
30
|
});
|
|
32
31
|
|
|
@@ -79,7 +78,6 @@ export async function POST(request: NextRequest) {
|
|
|
79
78
|
limit,
|
|
80
79
|
embeddingOptions: {
|
|
81
80
|
provider: "local",
|
|
82
|
-
dimensions: 768,
|
|
83
81
|
},
|
|
84
82
|
});
|
|
85
83
|
|
|
@@ -135,12 +133,16 @@ const embeddingProvider =
|
|
|
135
133
|
| "gemini"
|
|
136
134
|
| "openai"
|
|
137
135
|
| undefined;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
136
|
+
|
|
137
|
+
const dimensionsByProvider = {
|
|
138
|
+
local: 384,
|
|
139
|
+
cloudflare: 1024,
|
|
140
|
+
mistral: 1024,
|
|
141
|
+
gemini: 3072,
|
|
142
|
+
openai: 1536,
|
|
143
|
+
} as const;
|
|
144
|
+
|
|
145
|
+
const embeddingDimensions = dimensionsByProvider[embeddingProvider ?? "local"];
|
|
144
146
|
|
|
145
147
|
await createTable(client, "articles", embeddingDimensions);
|
|
146
148
|
|
package/docs/PROVIDERS.md
CHANGED
|
@@ -29,8 +29,9 @@ interface EmbeddingOptions {
|
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
- `provider` defaults to `"local"`
|
|
32
|
-
- `dimensions` defaults to `
|
|
33
|
-
Provider-specific defaults can differ;
|
|
32
|
+
- `dimensions` defaults to `384` for the library's default local provider.
|
|
33
|
+
Provider-specific defaults can differ; Cloudflare and Mistral use `1024`,
|
|
34
|
+
and Gemini defaults to `3072`.
|
|
34
35
|
- `maxLength` defaults to `8000`
|
|
35
36
|
- `intent` can be `"document"` or `"query"`; indexing defaults to
|
|
36
37
|
`"document"` and search defaults to `"query"` unless explicitly set
|
|
@@ -87,7 +88,8 @@ vectors plus provider, model, dimensions, and intent. The compatibility helpers
|
|
|
87
88
|
|
|
88
89
|
Cloudflare, Mistral, Gemini, and OpenAI clients are scoped to their current
|
|
89
90
|
options. They are not cached globally across different credentials or
|
|
90
|
-
configurations. The local
|
|
91
|
+
configurations. The local Hugging Face Transformers pipeline is loaded lazily
|
|
92
|
+
and cached by model name.
|
|
91
93
|
|
|
92
94
|
Hosted provider failures are reported with bounded provider/status/request-id
|
|
93
95
|
context and without raw upstream bodies, credentials, Authorization headers, or
|
|
@@ -98,24 +100,26 @@ full URLs with query strings.
|
|
|
98
100
|
Provider value: `local`
|
|
99
101
|
|
|
100
102
|
The local provider loads `Xenova/all-MiniLM-L6-v2` through
|
|
101
|
-
`@
|
|
103
|
+
`@huggingface/transformers`.
|
|
102
104
|
|
|
103
105
|
```ts
|
|
104
106
|
embeddingOptions: {
|
|
105
107
|
provider: "local",
|
|
106
|
-
dimensions: 768,
|
|
107
108
|
}
|
|
108
109
|
```
|
|
109
110
|
|
|
110
111
|
Notes:
|
|
111
112
|
|
|
112
|
-
- the model emits 384 dimensions and
|
|
113
|
-
|
|
114
|
-
-
|
|
113
|
+
- the model emits 384 dimensions, and local vectors are validated at exactly
|
|
114
|
+
384 finite numbers
|
|
115
|
+
- `dimensions: 384` is accepted explicitly; any other local dimension is
|
|
116
|
+
rejected before the runtime is imported or loaded
|
|
117
|
+
- metadata reports 384 dimensions
|
|
115
118
|
- batch metadata is `{ mode: "sequential" }`
|
|
116
|
-
- the first run downloads the model and can take longer on a fresh
|
|
119
|
+
- the first run downloads and caches the model and can take longer on a fresh
|
|
120
|
+
machine
|
|
117
121
|
- no API key is required
|
|
118
|
-
- this remains the default provider for
|
|
122
|
+
- this remains the default provider for offline use
|
|
119
123
|
|
|
120
124
|
## Cloudflare Workers AI
|
|
121
125
|
|
|
@@ -227,10 +231,9 @@ Behavior:
|
|
|
227
231
|
|
|
228
232
|
## Dimension Guidelines
|
|
229
233
|
|
|
230
|
-
- `local`
|
|
234
|
+
- `local` is fixed at `384`
|
|
231
235
|
- `cloudflare` is fixed at `1024`
|
|
232
236
|
- `mistral` is fixed at `1024`
|
|
233
|
-
- local embeddings are padded from 384 to your target size
|
|
234
237
|
- Gemini defaults to `3072` and accepts explicit dimensions from `128` through
|
|
235
238
|
`3072`; use `768`, `1536`, or `3072` unless you have a specific reason
|
|
236
239
|
- OpenAI can be used at 1536 or 3072, or another supported OpenAI dimension
|
|
@@ -245,3 +248,14 @@ because both the model and query/document input formatting changed. If you move
|
|
|
245
248
|
to the new 3072-dimensional default, create a new table or recreate the vector
|
|
246
249
|
table first; `indexContent()` clears rows but does not change the `F32_BLOB`
|
|
247
250
|
width. A separate table is safer because rebuilds are not transactional.
|
|
251
|
+
|
|
252
|
+
Existing local indexes created with the older padded-local behavior usually have
|
|
253
|
+
`F32_BLOB(768)` rows containing the 384 model values followed by zero padding.
|
|
254
|
+
The current local contract stores the native 384-dimensional model output. To
|
|
255
|
+
migrate, create or recreate a `F32_BLOB(384)` table and run a full re-index
|
|
256
|
+
before querying it. Using the same model ID avoids an intentional model-space
|
|
257
|
+
change, but bit-identical vectors are not promised across runtime, model
|
|
258
|
+
revision, dtype, pooling, or normalization changes; validate search quality and
|
|
259
|
+
re-index when those details change.
|
|
260
|
+
|
|
261
|
+
Routine unit tests mock the local runtime and do not download the model.
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# Troubleshooting: Transitive `sharp` Install Errors
|
|
2
2
|
|
|
3
|
-
`libsql-search` does not directly
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
`libsql-search` does not directly import `sharp`, but local embeddings use
|
|
4
|
+
`@huggingface/transformers`, which currently brings in `sharp` as a transitive
|
|
5
|
+
runtime dependency. If you see an install error mentioning `sharp`, it is
|
|
6
|
+
usually a native-package install or approval issue.
|
|
6
7
|
|
|
7
|
-
This page exists because the error can show up
|
|
8
|
-
`libsql-search
|
|
8
|
+
This page exists because the error can show up before your application reaches
|
|
9
|
+
any `libsql-search` code.
|
|
9
10
|
|
|
10
11
|
## Typical Error
|
|
11
12
|
|
|
@@ -48,7 +49,7 @@ in `pnpm-workspace.yaml` with `onlyBuiltDependencies`.
|
|
|
48
49
|
|
|
49
50
|
## Relation To `libsql-search`
|
|
50
51
|
|
|
51
|
-
- local embeddings use `@
|
|
52
|
+
- local embeddings use `@huggingface/transformers`
|
|
52
53
|
- the first local embedding run may download a model at runtime
|
|
53
54
|
- that runtime model download is separate from a pnpm native-module install
|
|
54
55
|
failure
|
package/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -15,3 +15,5 @@ Common operational checks:
|
|
|
15
15
|
- after upgrading an existing Gemini index, fully re-embed with
|
|
16
16
|
`gemini-embedding-2`; for 3072-dimensional Gemini indexes, recreate the table
|
|
17
17
|
or use a new table name before rebuilding
|
|
18
|
+
- after upgrading an existing local 768-dimensional padded index, create or
|
|
19
|
+
recreate a 384-dimensional table and fully re-index before querying it
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "libsql-search",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Semantic search for static sites using libSQL/Turso with multi-provider embeddings",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@10.34.5",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"@libsql/client": "^0.15.0"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@
|
|
66
|
+
"@huggingface/transformers": "4.2.0",
|
|
67
67
|
"gray-matter": "^4.0.3"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|