libsql-search 0.7.0 → 0.8.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 +28 -48
- package/dist/index.cjs +184 -31
- package/dist/index.d.ts +56 -8
- package/dist/index.esm.js +184 -32
- package/docs/API.md +182 -112
- package/docs/INDEXING.md +88 -26
- package/docs/INTEGRATIONS.md +220 -40
- package/docs/MIGRATIONS.md +176 -0
- package/docs/PROVIDERS.md +86 -245
- package/docs/README.md +7 -9
- package/docs/RELEASING.md +11 -3
- package/docs/TESTING.md +138 -0
- package/package.json +1 -1
package/docs/INTEGRATIONS.md
CHANGED
|
@@ -1,7 +1,120 @@
|
|
|
1
1
|
# Integration Examples
|
|
2
2
|
|
|
3
|
-
These examples show the current exported API wired into
|
|
4
|
-
|
|
3
|
+
These examples show the current exported API wired into common server-side flows. Start with the shared provider flow, then adapt the framework snippets to your app.
|
|
4
|
+
|
|
5
|
+
## Shared Provider Flow
|
|
6
|
+
|
|
7
|
+
Use one provider preset, one dimension count, and one table name per embedding space.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { createClient } from "@libsql/client";
|
|
11
|
+
import { createTable, indexContent, search } from "libsql-search";
|
|
12
|
+
|
|
13
|
+
const client = createClient({
|
|
14
|
+
url: process.env.TURSO_DB_URL!,
|
|
15
|
+
authToken: process.env.TURSO_AUTH_TOKEN!,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const providerConfig = {
|
|
19
|
+
tableName: "articles_openai_1536",
|
|
20
|
+
dimensions: 1536,
|
|
21
|
+
embeddingOptions: {
|
|
22
|
+
provider: "openai" as const,
|
|
23
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
24
|
+
dimensions: 1536,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
await createTable(client, providerConfig.tableName, providerConfig.dimensions);
|
|
29
|
+
|
|
30
|
+
await indexContent({
|
|
31
|
+
client,
|
|
32
|
+
contentPath: "./content",
|
|
33
|
+
tableName: providerConfig.tableName,
|
|
34
|
+
embeddingOptions: {
|
|
35
|
+
...providerConfig.embeddingOptions,
|
|
36
|
+
intent: "document",
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const results = await search({
|
|
41
|
+
client,
|
|
42
|
+
tableName: providerConfig.tableName,
|
|
43
|
+
query: "deployment checklist",
|
|
44
|
+
limit: 5,
|
|
45
|
+
embeddingOptions: {
|
|
46
|
+
...providerConfig.embeddingOptions,
|
|
47
|
+
intent: "query",
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Provider Presets
|
|
53
|
+
|
|
54
|
+
These presets keep credentials out of the source file while making dimensions and table names explicit. Hosted presets send content to external providers and may incur provider charges when you run indexing or search.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const providerPresets = {
|
|
58
|
+
local: {
|
|
59
|
+
tableName: "articles_local_384",
|
|
60
|
+
dimensions: 384,
|
|
61
|
+
embeddingOptions: {
|
|
62
|
+
provider: "local" as const,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
cloudflare: {
|
|
66
|
+
tableName: "articles_cf_bgem3_1024",
|
|
67
|
+
dimensions: 1024,
|
|
68
|
+
embeddingOptions: {
|
|
69
|
+
provider: "cloudflare" as const,
|
|
70
|
+
accountId: process.env.CLOUDFLARE_ACCOUNT_ID,
|
|
71
|
+
apiToken: process.env.CLOUDFLARE_API_TOKEN,
|
|
72
|
+
dimensions: 1024,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
mistral: {
|
|
76
|
+
tableName: "articles_mistral_1024",
|
|
77
|
+
dimensions: 1024,
|
|
78
|
+
embeddingOptions: {
|
|
79
|
+
provider: "mistral" as const,
|
|
80
|
+
apiKey: process.env.MISTRAL_API_KEY,
|
|
81
|
+
dimensions: 1024,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
gemini: {
|
|
85
|
+
tableName: "articles_gemini_3072",
|
|
86
|
+
dimensions: 3072,
|
|
87
|
+
embeddingOptions: {
|
|
88
|
+
provider: "gemini" as const,
|
|
89
|
+
apiKey: process.env.GEMINI_API_KEY,
|
|
90
|
+
dimensions: 3072,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
openai: {
|
|
94
|
+
tableName: "articles_openai_1536",
|
|
95
|
+
dimensions: 1536,
|
|
96
|
+
embeddingOptions: {
|
|
97
|
+
provider: "openai" as const,
|
|
98
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
99
|
+
dimensions: 1536,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
openaiCompatible: {
|
|
103
|
+
tableName: "articles_tei_1024",
|
|
104
|
+
dimensions: 1024,
|
|
105
|
+
embeddingOptions: {
|
|
106
|
+
provider: "openai-compatible" as const,
|
|
107
|
+
baseUrl: process.env.EMBEDDING_BASE_URL,
|
|
108
|
+
model: process.env.EMBEDDING_MODEL,
|
|
109
|
+
dimensions: 1024,
|
|
110
|
+
apiKey: process.env.EMBEDDING_API_KEY,
|
|
111
|
+
batchSize: 32,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
} as const;
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Pick one preset and use its `tableName` and `dimensions` all the way through `createTable()`, `indexContent()`, and `search()`. Do not point two providers or two dimension counts at the same table.
|
|
5
118
|
|
|
6
119
|
## Astro Search Endpoint
|
|
7
120
|
|
|
@@ -24,8 +137,10 @@ export const POST: APIRoute = async ({ request }) => {
|
|
|
24
137
|
client,
|
|
25
138
|
query,
|
|
26
139
|
limit,
|
|
140
|
+
tableName: "articles_local_384",
|
|
27
141
|
embeddingOptions: {
|
|
28
142
|
provider: "local",
|
|
143
|
+
intent: "query",
|
|
29
144
|
},
|
|
30
145
|
});
|
|
31
146
|
|
|
@@ -47,14 +162,14 @@ const client = createClient({
|
|
|
47
162
|
});
|
|
48
163
|
|
|
49
164
|
export async function getStaticPaths() {
|
|
50
|
-
const articles = await getAllArticles(client);
|
|
165
|
+
const articles = await getAllArticles(client, "articles_local_384");
|
|
51
166
|
|
|
52
167
|
return articles.map((article) => ({
|
|
53
168
|
params: { slug: article.slug },
|
|
54
169
|
}));
|
|
55
170
|
}
|
|
56
171
|
|
|
57
|
-
const article = await getArticleBySlug(client, "guides/getting-started");
|
|
172
|
+
const article = await getArticleBySlug(client, "guides/getting-started", "articles_local_384");
|
|
58
173
|
```
|
|
59
174
|
|
|
60
175
|
## Next.js Route Handler
|
|
@@ -76,8 +191,10 @@ export async function POST(request: NextRequest) {
|
|
|
76
191
|
client,
|
|
77
192
|
query,
|
|
78
193
|
limit,
|
|
194
|
+
tableName: "articles_local_384",
|
|
79
195
|
embeddingOptions: {
|
|
80
196
|
provider: "local",
|
|
197
|
+
intent: "query",
|
|
81
198
|
},
|
|
82
199
|
});
|
|
83
200
|
|
|
@@ -97,7 +214,7 @@ const client = createClient({
|
|
|
97
214
|
});
|
|
98
215
|
|
|
99
216
|
export async function generateStaticParams() {
|
|
100
|
-
const articles = await getAllArticles(client);
|
|
217
|
+
const articles = await getAllArticles(client, "articles_local_384");
|
|
101
218
|
|
|
102
219
|
return articles.map((article) => ({
|
|
103
220
|
slug: article.slug,
|
|
@@ -106,7 +223,7 @@ export async function generateStaticParams() {
|
|
|
106
223
|
|
|
107
224
|
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
|
|
108
225
|
const { slug } = await params;
|
|
109
|
-
const article = await getArticleBySlug(client, slug);
|
|
226
|
+
const article = await getArticleBySlug(client, slug, "articles_local_384");
|
|
110
227
|
|
|
111
228
|
return <article>{article?.title}</article>;
|
|
112
229
|
}
|
|
@@ -114,8 +231,6 @@ export default async function Page({ params }: { params: Promise<{ slug: string
|
|
|
114
231
|
|
|
115
232
|
## Build-Time Index Script
|
|
116
233
|
|
|
117
|
-
A small script is usually enough to rebuild the index before a site build.
|
|
118
|
-
|
|
119
234
|
```ts
|
|
120
235
|
import { createClient } from "@libsql/client";
|
|
121
236
|
import { createTable, indexContent } from "libsql-search";
|
|
@@ -125,48 +240,113 @@ const client = createClient({
|
|
|
125
240
|
authToken: process.env.TURSO_AUTH_TOKEN!,
|
|
126
241
|
});
|
|
127
242
|
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
243
|
+
const providerPresets = {
|
|
244
|
+
local: {
|
|
245
|
+
tableName: "articles_local_384",
|
|
246
|
+
dimensions: 384,
|
|
247
|
+
embeddingOptions: {
|
|
248
|
+
provider: "local" as const,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
cloudflare: {
|
|
252
|
+
tableName: "articles_cf_bgem3_1024",
|
|
253
|
+
dimensions: 1024,
|
|
254
|
+
embeddingOptions: {
|
|
255
|
+
provider: "cloudflare" as const,
|
|
256
|
+
accountId: process.env.CLOUDFLARE_ACCOUNT_ID,
|
|
257
|
+
apiToken: process.env.CLOUDFLARE_API_TOKEN,
|
|
258
|
+
dimensions: 1024,
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
mistral: {
|
|
262
|
+
tableName: "articles_mistral_1024",
|
|
263
|
+
dimensions: 1024,
|
|
264
|
+
embeddingOptions: {
|
|
265
|
+
provider: "mistral" as const,
|
|
266
|
+
apiKey: process.env.MISTRAL_API_KEY,
|
|
267
|
+
dimensions: 1024,
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
gemini: {
|
|
271
|
+
tableName: "articles_gemini_3072",
|
|
272
|
+
dimensions: 3072,
|
|
273
|
+
embeddingOptions: {
|
|
274
|
+
provider: "gemini" as const,
|
|
275
|
+
apiKey: process.env.GEMINI_API_KEY,
|
|
276
|
+
dimensions: 3072,
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
openai: {
|
|
280
|
+
tableName: "articles_openai_1536",
|
|
281
|
+
dimensions: 1536,
|
|
282
|
+
embeddingOptions: {
|
|
283
|
+
provider: "openai" as const,
|
|
284
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
285
|
+
dimensions: 1536,
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
"openai-compatible": {
|
|
289
|
+
tableName: "articles_tei_1024",
|
|
290
|
+
dimensions: 1024,
|
|
291
|
+
embeddingOptions: {
|
|
292
|
+
provider: "openai-compatible" as const,
|
|
293
|
+
baseUrl: process.env.EMBEDDING_BASE_URL,
|
|
294
|
+
model: process.env.EMBEDDING_MODEL,
|
|
295
|
+
dimensions: 1024,
|
|
296
|
+
apiKey: process.env.EMBEDDING_API_KEY,
|
|
297
|
+
batchSize: 32,
|
|
298
|
+
},
|
|
299
|
+
},
|
|
144
300
|
} as const;
|
|
145
301
|
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
302
|
+
const provider = process.env.EMBEDDING_PROVIDER ?? "local";
|
|
303
|
+
|
|
304
|
+
if (!(provider in providerPresets)) {
|
|
305
|
+
throw new Error(
|
|
306
|
+
`Unknown EMBEDDING_PROVIDER: ${provider}. Expected one of ${Object.keys(providerPresets).join(", ")}`
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const preset = providerPresets[provider as keyof typeof providerPresets];
|
|
150
311
|
|
|
151
|
-
await createTable(client,
|
|
312
|
+
await createTable(client, preset.tableName, preset.dimensions);
|
|
152
313
|
|
|
153
314
|
await indexContent({
|
|
154
315
|
client,
|
|
155
316
|
contentPath: "./content",
|
|
317
|
+
tableName: preset.tableName,
|
|
156
318
|
embeddingOptions: {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
model: process.env.EMBEDDING_MODEL,
|
|
160
|
-
dimensions: embeddingDimensions,
|
|
319
|
+
...preset.embeddingOptions,
|
|
320
|
+
intent: "document",
|
|
161
321
|
},
|
|
162
322
|
});
|
|
163
323
|
```
|
|
164
324
|
|
|
165
|
-
|
|
166
|
-
|
|
325
|
+
## CI-safe Provider Coverage
|
|
326
|
+
|
|
327
|
+
Keep routine CI on mocks only:
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
import { vi } from "vitest";
|
|
331
|
+
import { generateEmbeddings } from "libsql-search";
|
|
332
|
+
|
|
333
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
|
334
|
+
ok: true,
|
|
335
|
+
headers: new Headers(),
|
|
336
|
+
json: async () => ({
|
|
337
|
+
data: [
|
|
338
|
+
{ index: 0, embedding: [1, 2] },
|
|
339
|
+
{ index: 1, embedding: [3, 4] },
|
|
340
|
+
],
|
|
341
|
+
}),
|
|
342
|
+
}));
|
|
343
|
+
|
|
344
|
+
await generateEmbeddings(["doc one", "doc two"], {
|
|
345
|
+
provider: "openai-compatible",
|
|
346
|
+
baseUrl: "https://tei.example.internal/v1",
|
|
347
|
+
model: "tei-model",
|
|
348
|
+
dimensions: 2,
|
|
349
|
+
});
|
|
350
|
+
```
|
|
167
351
|
|
|
168
|
-
|
|
169
|
-
server-side configuration, not user request input. Recreate the vector table or
|
|
170
|
-
rebuild into a separate table whenever the provider, endpoint, model, or
|
|
171
|
-
dimension count changes; stored vectors and query vectors must come from the
|
|
172
|
-
same embedding space.
|
|
352
|
+
The repository test suite should not require real provider credentials. See [Testing guidance](./TESTING.md) for local model mocks, Gemini SDK mocks, and validation-before-network assertions.
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Migration And Reindexing
|
|
2
|
+
|
|
3
|
+
Changing an embedding provider is not just a config flip. Stored vectors and query vectors must stay in the same embedding space for search quality to hold.
|
|
4
|
+
|
|
5
|
+
## What The Table Stores
|
|
6
|
+
|
|
7
|
+
`createTable(client, tableName, dimensions)` creates an `embedding` column with the exact Turso/libSQL vector type:
|
|
8
|
+
|
|
9
|
+
```sql
|
|
10
|
+
embedding F32_BLOB(dimensions)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
That width is fixed by the table schema. `CREATE TABLE IF NOT EXISTS` does not resize an existing vector column.
|
|
14
|
+
|
|
15
|
+
References:
|
|
16
|
+
|
|
17
|
+
- [Turso AI and embeddings](https://docs.turso.tech/features/ai-and-embeddings)
|
|
18
|
+
- [Gemini model versions](https://ai.google.dev/gemini-api/docs/embeddings#model-versions)
|
|
19
|
+
|
|
20
|
+
## Rules
|
|
21
|
+
|
|
22
|
+
- if dimensions change, create a new table or recreate the old table, then fully re-embed
|
|
23
|
+
- if dimensions stay the same but provider, model, endpoint, model revision, pooling, normalization, or input formatting changes, fully reindex anyway
|
|
24
|
+
- never mix two embedding spaces in one table
|
|
25
|
+
- prefer a parallel table migration because `indexContent()` replaces the whole target table, so an in-place rebuild leaves no way back to the old vectors
|
|
26
|
+
|
|
27
|
+
In practice, this means:
|
|
28
|
+
|
|
29
|
+
- `384 local` and `1024 Mistral` can never share a table
|
|
30
|
+
- `1024 Cloudflare` and `1024 Mistral` still need separate rebuilds because equal width does not make the vectors compatible
|
|
31
|
+
- a custom endpoint change at the same width still needs a new table because the model or serving stack may have changed
|
|
32
|
+
|
|
33
|
+
## Safe Migration Pattern
|
|
34
|
+
|
|
35
|
+
1. Pick a new table name that makes the provider and dimensions obvious.
|
|
36
|
+
2. Create that table with the target width.
|
|
37
|
+
3. Reindex all content into the new table.
|
|
38
|
+
4. Run search quality checks against the new table.
|
|
39
|
+
5. Switch application reads and writes to the new table.
|
|
40
|
+
6. Retire the old table in a separate cleanup step.
|
|
41
|
+
|
|
42
|
+
Step 3 is all or nothing. `indexContent()` throws `IndexingError` and leaves the target table untouched when a file or the replacement transaction fails, so a failed migration step can be retried without cleanup. See [Indexing and operational behavior](./INDEXING.md) for `failurePolicy` and `allowEmptyIndex`.
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
await createTable(client, "articles_gemini2_3072", 3072);
|
|
48
|
+
|
|
49
|
+
await indexContent({
|
|
50
|
+
client,
|
|
51
|
+
contentPath: "./content",
|
|
52
|
+
tableName: "articles_gemini2_3072",
|
|
53
|
+
embeddingOptions: {
|
|
54
|
+
provider: "gemini",
|
|
55
|
+
apiKey: process.env.GEMINI_API_KEY,
|
|
56
|
+
dimensions: 3072,
|
|
57
|
+
intent: "document",
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Then query the new table explicitly during validation:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const results = await search({
|
|
66
|
+
client,
|
|
67
|
+
tableName: "articles_gemini2_3072",
|
|
68
|
+
query: "deployment checklist",
|
|
69
|
+
embeddingOptions: {
|
|
70
|
+
provider: "gemini",
|
|
71
|
+
apiKey: process.env.GEMINI_API_KEY,
|
|
72
|
+
dimensions: 3072,
|
|
73
|
+
intent: "query",
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Common Migration Paths
|
|
79
|
+
|
|
80
|
+
| From | To | Why a rebuild is required | Recommended table move |
|
|
81
|
+
| --- | --- | --- | --- |
|
|
82
|
+
| Legacy padded local `768` | Native local `384` | Old tables stored `384` model values plus zero padding; current local provider is a native `384`-dimension space | Build into `articles_local_384`, validate, then retire the legacy table |
|
|
83
|
+
| Any `768` space | Any `1024` space | Width changes from `F32_BLOB(768)` to `F32_BLOB(1024)` | Create a new `1024` table and reindex |
|
|
84
|
+
| Cloudflare `1024` | Mistral `1024` | Width stays the same, but provider/model space changes | Use a parallel `1024` table such as `articles_mistral_1024` |
|
|
85
|
+
| Mistral `1024` | Cloudflare `1024` | Same reason in reverse | Use a parallel `1024` table such as `articles_cf_bgem3_1024` |
|
|
86
|
+
| Any `1024` space | OpenAI `1536` | Width changes and model changes | Create `articles_openai_1536` and reindex |
|
|
87
|
+
| OpenAI `1536` | OpenAI or Gemini `3072` | Width changes to `3072` | Create a new `3072` table and reindex |
|
|
88
|
+
| Gemini legacy `text-embedding-004` at `768` | Gemini `gemini-embedding-2` at `768` | Same width, but the upstream model changed and the adapter now distinguishes document/query formatting | Build a parallel `articles_gemini2_768` table |
|
|
89
|
+
| Any custom endpoint/model | Any other custom endpoint/model, same width or different width | Endpoint, model, or serving revision may change the embedding space even when dimensions match | Always create a new table named for the target provider/model and reindex |
|
|
90
|
+
|
|
91
|
+
## Scenario Notes
|
|
92
|
+
|
|
93
|
+
### Legacy Local `768` To Native Local `384`
|
|
94
|
+
|
|
95
|
+
Earlier local migrations sometimes relied on zero padding to fit a `768`-wide table. The current local adapter emits the model's native `384` dimensions and rejects any other local dimension count.
|
|
96
|
+
|
|
97
|
+
Safe path:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
await createTable(client, "articles_local_384", 384);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Reindex into `articles_local_384`; do not keep writing new local vectors into the legacy padded table.
|
|
104
|
+
|
|
105
|
+
### `768` To `1024`
|
|
106
|
+
|
|
107
|
+
Any move from `768` dimensions to `1024` dimensions changes the schema width. Examples include a legacy local table moving to Cloudflare or Mistral.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
await createTable(client, "articles_mistral_1024", 1024);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Cloudflare `1024` To Mistral `1024`
|
|
114
|
+
|
|
115
|
+
This is the main same-width example. The schema width can stay `1024`, but the vector space still changes.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
await createTable(client, "articles_cf_bgem3_1024", 1024);
|
|
119
|
+
await createTable(client, "articles_mistral_1024", 1024);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Keep both tables side by side during validation. Do not clear the Cloudflare table until the Mistral table is validated in production-like queries.
|
|
123
|
+
|
|
124
|
+
### `1024` To OpenAI `1536`
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
await createTable(client, "articles_openai_1536", 1536);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
This is both a width change and a provider/model change.
|
|
131
|
+
|
|
132
|
+
### `1536` To `3072`
|
|
133
|
+
|
|
134
|
+
Applies when moving from OpenAI `1536` to OpenAI `3072`, or from OpenAI `1536` to Gemini `3072`.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
await createTable(client, "articles_openai_3072", 3072);
|
|
138
|
+
await createTable(client, "articles_gemini_3072", 3072);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Pick one target space and validate it before switching reads.
|
|
142
|
+
|
|
143
|
+
### Gemini Older Model `768` To Gemini 2 `768`
|
|
144
|
+
|
|
145
|
+
Even if you stay at `768`, rebuild because `text-embedding-004` has been replaced by `gemini-embedding-2`, and the current adapter uses different text formatting for document versus query intent.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
await createTable(client, "articles_gemini2_768", 768);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Custom Endpoint Or Model Change
|
|
152
|
+
|
|
153
|
+
Treat any `openai-compatible` move as a new embedding space:
|
|
154
|
+
|
|
155
|
+
- TEI model upgrade
|
|
156
|
+
- base URL change
|
|
157
|
+
- serving stack change
|
|
158
|
+
- pooling or normalization change
|
|
159
|
+
- gateway rewrite that changes input formatting
|
|
160
|
+
|
|
161
|
+
Example:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
await createTable(client, "articles_tei_bge_1024_v2", 1024);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Quality Checks Before Cutover
|
|
168
|
+
|
|
169
|
+
Run a small set of real queries against both old and new tables before switching:
|
|
170
|
+
|
|
171
|
+
- high-value navigation queries
|
|
172
|
+
- acronym or product-name queries
|
|
173
|
+
- queries that depend on tags or frontmatter wording
|
|
174
|
+
- long-tail queries that previously returned useful content
|
|
175
|
+
|
|
176
|
+
If the new table looks worse, keep the old table live while you inspect chunk size, source content, provider choice, and dimensionality.
|