@larkup/vector-stores 0.1.20 → 0.1.21
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 +7 -0
- package/package.json +2 -2
- package/src/adapters/base.ts +24 -22
- package/src/adapters/chroma.ts +49 -73
- package/src/adapters/lancedb.ts +64 -40
- package/src/adapters/pinecone.ts +47 -93
- package/src/factory.ts +45 -25
- package/src/registry.ts +258 -212
- package/tsconfig.tsbuildinfo +1 -1
package/src/adapters/pinecone.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
type Index,
|
|
4
|
-
type RecordSparseValues,
|
|
5
|
-
} from "@pinecone-database/pinecone";
|
|
6
|
-
import type { QueryHit, VectorRecord, VectorStoreAdapter } from "./base";
|
|
1
|
+
import { Pinecone, type Index, type RecordSparseValues } from '@pinecone-database/pinecone';
|
|
2
|
+
import type { QueryHit, VectorRecord, VectorStoreAdapter } from './base';
|
|
7
3
|
|
|
8
4
|
/**
|
|
9
5
|
* Pinecone adapter — fully-managed cloud vector DB.
|
|
@@ -86,8 +82,8 @@ function is429(err: unknown): boolean {
|
|
|
86
82
|
return (
|
|
87
83
|
e?.status === 429 ||
|
|
88
84
|
e?.statusCode === 429 ||
|
|
89
|
-
String(e?.message ??
|
|
90
|
-
String(e?.message ??
|
|
85
|
+
String(e?.message ?? '').includes('429') ||
|
|
86
|
+
String(e?.message ?? '').includes('RESOURCE_EXHAUSTED')
|
|
91
87
|
);
|
|
92
88
|
}
|
|
93
89
|
|
|
@@ -99,10 +95,10 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
99
95
|
private currentInterBatchDelayMs = 0;
|
|
100
96
|
|
|
101
97
|
constructor(private readonly config: PineconeConfig) {
|
|
102
|
-
if (!config.apiKey) throw new Error(
|
|
103
|
-
if (!config.indexName) throw new Error(
|
|
98
|
+
if (!config.apiKey) throw new Error('Pinecone requires an API key.');
|
|
99
|
+
if (!config.indexName) throw new Error('Pinecone requires an index name.');
|
|
104
100
|
this.indexName = config.indexName.trim();
|
|
105
|
-
this.namespace = config.namespace?.trim() ||
|
|
101
|
+
this.namespace = config.namespace?.trim() || 'default';
|
|
106
102
|
}
|
|
107
103
|
|
|
108
104
|
private getClient() {
|
|
@@ -113,9 +109,7 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
113
109
|
}
|
|
114
110
|
|
|
115
111
|
private get needsSparse() {
|
|
116
|
-
return
|
|
117
|
-
this.config.indexType === "lexical" || this.config.indexType === "hybrid"
|
|
118
|
-
);
|
|
112
|
+
return this.config.indexType === 'lexical' || this.config.indexType === 'hybrid';
|
|
119
113
|
}
|
|
120
114
|
|
|
121
115
|
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
@@ -126,12 +120,12 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
126
120
|
const exists = (indexes ?? []).some((i) => i.name === this.indexName);
|
|
127
121
|
|
|
128
122
|
if (!exists) {
|
|
129
|
-
const metric = this.needsSparse ?
|
|
123
|
+
const metric = this.needsSparse ? 'dotproduct' : 'cosine';
|
|
130
124
|
await pc.createIndex({
|
|
131
125
|
name: this.indexName,
|
|
132
126
|
dimension: dimensions,
|
|
133
127
|
metric,
|
|
134
|
-
spec: { serverless: { cloud:
|
|
128
|
+
spec: { serverless: { cloud: 'aws', region: 'us-east-1' } },
|
|
135
129
|
waitUntilReady: true,
|
|
136
130
|
});
|
|
137
131
|
}
|
|
@@ -151,6 +145,15 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
151
145
|
}
|
|
152
146
|
}
|
|
153
147
|
|
|
148
|
+
async deleteByDocumentIds(documentIds: string[]): Promise<void> {
|
|
149
|
+
if (documentIds.length === 0) return;
|
|
150
|
+
try {
|
|
151
|
+
await this.ns().deleteMany({ filter: { documentId: { $in: documentIds } } });
|
|
152
|
+
} catch (err: any) {
|
|
153
|
+
console.error('Failed to delete vectors by documentIds in Pinecone:', err);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
154
157
|
// ── Sparse vector generation with rate-limit handling ─────────────────────
|
|
155
158
|
|
|
156
159
|
/**
|
|
@@ -158,9 +161,7 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
158
161
|
* Before each retry-sleep the `onRateLimit` hook is called so the indexer
|
|
159
162
|
* can patch the run store with a warning that the UI polls.
|
|
160
163
|
*/
|
|
161
|
-
private async embedSparseWithRetry(
|
|
162
|
-
texts: string[],
|
|
163
|
-
): Promise<RecordSparseValues[]> {
|
|
164
|
+
private async embedSparseWithRetry(texts: string[]): Promise<RecordSparseValues[]> {
|
|
164
165
|
const pc = this.getClient();
|
|
165
166
|
|
|
166
167
|
for (let attempt = 1; attempt <= RATE_LIMIT_MAX_RETRIES + 1; attempt++) {
|
|
@@ -168,7 +169,7 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
168
169
|
const result = await pc.inference.embed({
|
|
169
170
|
model: this.config.sparseModel!,
|
|
170
171
|
inputs: texts,
|
|
171
|
-
parameters: { input_type:
|
|
172
|
+
parameters: { input_type: 'passage', truncate: 'END' },
|
|
172
173
|
});
|
|
173
174
|
return result.data.map((emb) => {
|
|
174
175
|
const s = emb as any;
|
|
@@ -182,7 +183,6 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
182
183
|
const waitSecs = Math.round(RATE_LIMIT_WAIT_MS / 1000);
|
|
183
184
|
await this.config.onRateLimit?.(waitSecs, attempt);
|
|
184
185
|
await sleep(RATE_LIMIT_WAIT_MS);
|
|
185
|
-
// Slow down future batches to avoid hitting the rate limit again
|
|
186
186
|
this.currentInterBatchDelayMs = Math.max(
|
|
187
187
|
this.currentInterBatchDelayMs,
|
|
188
188
|
BASE_INTER_BATCH_DELAY_MS,
|
|
@@ -193,12 +193,10 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
// Unreachable but TypeScript needs it
|
|
196
|
-
throw new Error(
|
|
196
|
+
throw new Error('Sparse embedding failed after max retries.');
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
private async buildSparseVectors(
|
|
200
|
-
texts: string[],
|
|
201
|
-
): Promise<RecordSparseValues[]> {
|
|
199
|
+
private async buildSparseVectors(texts: string[]): Promise<RecordSparseValues[]> {
|
|
202
200
|
const result: RecordSparseValues[] = [];
|
|
203
201
|
|
|
204
202
|
for (let i = 0; i < texts.length; i += SPARSE_BATCH) {
|
|
@@ -206,11 +204,7 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
206
204
|
const vecs = await this.embedSparseWithRetry(batch);
|
|
207
205
|
result.push(...vecs);
|
|
208
206
|
|
|
209
|
-
|
|
210
|
-
if (
|
|
211
|
-
i + SPARSE_BATCH < texts.length &&
|
|
212
|
-
this.currentInterBatchDelayMs > 0
|
|
213
|
-
) {
|
|
207
|
+
if (i + SPARSE_BATCH < texts.length && this.currentInterBatchDelayMs > 0) {
|
|
214
208
|
await sleep(this.currentInterBatchDelayMs);
|
|
215
209
|
}
|
|
216
210
|
}
|
|
@@ -218,9 +212,7 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
218
212
|
return result;
|
|
219
213
|
}
|
|
220
214
|
|
|
221
|
-
private async buildQuerySparseVector(
|
|
222
|
-
text: string,
|
|
223
|
-
): Promise<RecordSparseValues> {
|
|
215
|
+
private async buildQuerySparseVector(text: string): Promise<RecordSparseValues> {
|
|
224
216
|
const pc = this.getClient();
|
|
225
217
|
|
|
226
218
|
for (let attempt = 1; attempt <= RATE_LIMIT_MAX_RETRIES + 1; attempt++) {
|
|
@@ -228,7 +220,7 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
228
220
|
const result = await pc.inference.embed({
|
|
229
221
|
model: this.config.sparseModel!,
|
|
230
222
|
inputs: [text],
|
|
231
|
-
parameters: { input_type:
|
|
223
|
+
parameters: { input_type: 'query', truncate: 'END' },
|
|
232
224
|
});
|
|
233
225
|
const s = result.data[0] as any;
|
|
234
226
|
return {
|
|
@@ -243,7 +235,7 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
243
235
|
throw err;
|
|
244
236
|
}
|
|
245
237
|
}
|
|
246
|
-
throw new Error(
|
|
238
|
+
throw new Error('Sparse query embedding failed after max retries.');
|
|
247
239
|
}
|
|
248
240
|
|
|
249
241
|
// ── Upsert ─────────────────────────────────────────────────────────────────
|
|
@@ -256,16 +248,9 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
256
248
|
const safe: Record<string, any> = {};
|
|
257
249
|
for (const [k, v] of Object.entries(meta)) {
|
|
258
250
|
if (v === null || v === undefined) continue;
|
|
259
|
-
if (
|
|
260
|
-
typeof v === "string" ||
|
|
261
|
-
typeof v === "number" ||
|
|
262
|
-
typeof v === "boolean"
|
|
263
|
-
) {
|
|
251
|
+
if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {
|
|
264
252
|
safe[k] = v;
|
|
265
|
-
} else if (
|
|
266
|
-
Array.isArray(v) &&
|
|
267
|
-
v.every((item) => typeof item === "string")
|
|
268
|
-
) {
|
|
253
|
+
} else if (Array.isArray(v) && v.every((item) => typeof item === 'string')) {
|
|
269
254
|
safe[k] = v;
|
|
270
255
|
} else {
|
|
271
256
|
safe[k] = JSON.stringify(v);
|
|
@@ -278,7 +263,7 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
278
263
|
...sanitizeMeta(r.metadata),
|
|
279
264
|
text: r.text,
|
|
280
265
|
title: r.title,
|
|
281
|
-
url: r.url ??
|
|
266
|
+
url: r.url ?? '',
|
|
282
267
|
source: r.source,
|
|
283
268
|
documentId: r.documentId,
|
|
284
269
|
chunkIndex: r.chunkIndex,
|
|
@@ -297,9 +282,7 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
297
282
|
}
|
|
298
283
|
|
|
299
284
|
// ── Hybrid / Lexical ───────────────────────────────────────────────────
|
|
300
|
-
const sparseVecs = await this.buildSparseVectors(
|
|
301
|
-
records.map((r) => r.text),
|
|
302
|
-
);
|
|
285
|
+
const sparseVecs = await this.buildSparseVectors(records.map((r) => r.text));
|
|
303
286
|
|
|
304
287
|
await this.ns().upsert({
|
|
305
288
|
records: records.map((r, i) => ({
|
|
@@ -313,11 +296,7 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
313
296
|
|
|
314
297
|
// ── Query ──────────────────────────────────────────────────────────────────
|
|
315
298
|
|
|
316
|
-
async query(
|
|
317
|
-
vector: number[],
|
|
318
|
-
topK: number,
|
|
319
|
-
queryText?: string,
|
|
320
|
-
): Promise<QueryHit[]> {
|
|
299
|
+
async query(vector: number[], topK: number, queryText?: string): Promise<QueryHit[]> {
|
|
321
300
|
const canHybrid = this.needsSparse && this.config.sparseModel && queryText;
|
|
322
301
|
|
|
323
302
|
if (!canHybrid) {
|
|
@@ -333,10 +312,7 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
333
312
|
return rrfMerge(denseHits, sparseHits, topK);
|
|
334
313
|
}
|
|
335
314
|
|
|
336
|
-
private async denseQuery(
|
|
337
|
-
vector: number[],
|
|
338
|
-
topK: number,
|
|
339
|
-
): Promise<QueryHit[]> {
|
|
315
|
+
private async denseQuery(vector: number[], topK: number): Promise<QueryHit[]> {
|
|
340
316
|
const res = await this.ns().query({ vector, topK, includeMetadata: true });
|
|
341
317
|
return (res.matches ?? []).map(hitFromMatch);
|
|
342
318
|
}
|
|
@@ -370,11 +346,7 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
370
346
|
async count(): Promise<number | null> {
|
|
371
347
|
try {
|
|
372
348
|
const stats = await this.ns().describeIndexStats();
|
|
373
|
-
return
|
|
374
|
-
stats.namespaces?.[this.namespace]?.recordCount ??
|
|
375
|
-
stats.totalRecordCount ??
|
|
376
|
-
0
|
|
377
|
-
);
|
|
349
|
+
return stats.namespaces?.[this.namespace]?.recordCount ?? stats.totalRecordCount ?? 0;
|
|
378
350
|
} catch {
|
|
379
351
|
return null;
|
|
380
352
|
}
|
|
@@ -389,18 +361,16 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
389
361
|
indexList = await pc.listIndexes();
|
|
390
362
|
} catch (err: any) {
|
|
391
363
|
if (
|
|
392
|
-
err.message?.toLowerCase().includes(
|
|
393
|
-
err.name ===
|
|
364
|
+
err.message?.toLowerCase().includes('api key') ||
|
|
365
|
+
err.name === 'PineconeAuthorizationError' ||
|
|
394
366
|
err.status === 401
|
|
395
367
|
) {
|
|
396
|
-
throw new Error(
|
|
368
|
+
throw new Error('Invalid Pinecone API key.');
|
|
397
369
|
}
|
|
398
370
|
throw new Error(`Failed to connect to Pinecone: ${err.message}`);
|
|
399
371
|
}
|
|
400
372
|
|
|
401
|
-
const indexModel = (indexList.indexes ?? []).find(
|
|
402
|
-
(i) => i.name === this.indexName,
|
|
403
|
-
);
|
|
373
|
+
const indexModel = (indexList.indexes ?? []).find((i) => i.name === this.indexName);
|
|
404
374
|
if (!indexModel) {
|
|
405
375
|
throw new Error(
|
|
406
376
|
`Index "${this.indexName}" does not exist in your Pinecone project. Please create it first.`,
|
|
@@ -415,14 +385,11 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
415
385
|
|
|
416
386
|
if (this.needsSparse) {
|
|
417
387
|
if (!this.config.sparseModel) {
|
|
418
|
-
throw new Error(
|
|
419
|
-
"Hybrid/lexical search requires a sparse model to be selected.",
|
|
420
|
-
);
|
|
388
|
+
throw new Error('Hybrid/lexical search requires a sparse model to be selected.');
|
|
421
389
|
}
|
|
422
390
|
|
|
423
|
-
const metric =
|
|
424
|
-
|
|
425
|
-
if (metric && metric !== "dotproduct") {
|
|
391
|
+
const metric = (indexModel as any).metric ?? (indexModel as any).spec?.metric;
|
|
392
|
+
if (metric && metric !== 'dotproduct') {
|
|
426
393
|
throw new Error(
|
|
427
394
|
`Hybrid/lexical requires your Pinecone index to use the "dotproduct" metric, ` +
|
|
428
395
|
`but "${this.indexName}" uses "${metric}". ` +
|
|
@@ -437,33 +404,20 @@ export class PineconeAdapter implements VectorStoreAdapter {
|
|
|
437
404
|
|
|
438
405
|
function hitFromMatch(m: any): QueryHit {
|
|
439
406
|
const meta = (m.metadata ?? {}) as PineMeta;
|
|
440
|
-
const {
|
|
441
|
-
text,
|
|
442
|
-
title,
|
|
443
|
-
url,
|
|
444
|
-
source,
|
|
445
|
-
documentId,
|
|
446
|
-
chunkIndex,
|
|
447
|
-
...customMetadata
|
|
448
|
-
} = meta;
|
|
407
|
+
const { text, title, url, source, documentId, chunkIndex, ...customMetadata } = meta;
|
|
449
408
|
|
|
450
409
|
return {
|
|
451
410
|
id: m.id,
|
|
452
411
|
score: m.score ?? 0,
|
|
453
|
-
text: (text as string) ??
|
|
454
|
-
title: (title as string) ??
|
|
412
|
+
text: (text as string) ?? '',
|
|
413
|
+
title: (title as string) ?? 'Untitled',
|
|
455
414
|
url: (url as string) || undefined,
|
|
456
|
-
documentId: (documentId as string) ??
|
|
457
|
-
metadata:
|
|
458
|
-
Object.keys(customMetadata).length > 0 ? customMetadata : undefined,
|
|
415
|
+
documentId: (documentId as string) ?? '',
|
|
416
|
+
metadata: Object.keys(customMetadata).length > 0 ? customMetadata : undefined,
|
|
459
417
|
};
|
|
460
418
|
}
|
|
461
419
|
|
|
462
|
-
function rrfMerge(
|
|
463
|
-
denseHits: QueryHit[],
|
|
464
|
-
sparseHits: QueryHit[],
|
|
465
|
-
topK: number,
|
|
466
|
-
): QueryHit[] {
|
|
420
|
+
function rrfMerge(denseHits: QueryHit[], sparseHits: QueryHit[], topK: number): QueryHit[] {
|
|
467
421
|
const scores = new Map<string, { hit: QueryHit; score: number }>();
|
|
468
422
|
|
|
469
423
|
const addList = (hits: QueryHit[]) =>
|
package/src/factory.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { RagConfig } from
|
|
2
|
-
import type { VectorStoreAdapter } from
|
|
1
|
+
import type { RagConfig } from '@larkup/core/types';
|
|
2
|
+
import type { VectorStoreAdapter } from './adapters/base';
|
|
3
|
+
import { getActiveServer } from '@larkup/core/workspace';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Build the right adapter from the persisted config. Centralizing this keeps
|
|
@@ -14,42 +15,61 @@ export async function createAdapter(
|
|
|
14
15
|
config: RagConfig,
|
|
15
16
|
onRateLimit?: (waitSecs: number, attempt: number) => void | Promise<void>,
|
|
16
17
|
): Promise<VectorStoreAdapter> {
|
|
18
|
+
const overrides = { ...config.storeConfig };
|
|
19
|
+
const server = await getActiveServer();
|
|
20
|
+
const serverId = server?.id;
|
|
21
|
+
|
|
22
|
+
if (serverId) {
|
|
23
|
+
const safeId = serverId.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
24
|
+
if (config.vectorStore === 'pinecone') {
|
|
25
|
+
overrides.namespace = serverId;
|
|
26
|
+
} else if (config.vectorStore === 'chroma') {
|
|
27
|
+
overrides.collectionName = `documents_${safeId}`;
|
|
28
|
+
} else if (config.vectorStore === 'lancedb' || !config.vectorStore) {
|
|
29
|
+
overrides.tableName = `documents_${safeId}`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
17
33
|
switch (config.vectorStore) {
|
|
18
|
-
case
|
|
19
|
-
const { PineconeAdapter } = await import(
|
|
34
|
+
case 'pinecone': {
|
|
35
|
+
const { PineconeAdapter } = await import('./adapters/pinecone');
|
|
20
36
|
return new PineconeAdapter({
|
|
21
|
-
apiKey:
|
|
22
|
-
indexName:
|
|
23
|
-
namespace:
|
|
24
|
-
sparseModel:
|
|
37
|
+
apiKey: overrides.apiKey,
|
|
38
|
+
indexName: overrides.indexName,
|
|
39
|
+
namespace: overrides.namespace,
|
|
40
|
+
sparseModel: overrides.sparseModel,
|
|
25
41
|
indexType: config.indexType,
|
|
26
42
|
onRateLimit,
|
|
27
43
|
});
|
|
28
44
|
}
|
|
29
|
-
case
|
|
30
|
-
const { ChromaAdapter } = await import(
|
|
45
|
+
case 'chroma': {
|
|
46
|
+
const { ChromaAdapter } = await import('./adapters/chroma');
|
|
31
47
|
return new ChromaAdapter({
|
|
32
|
-
mode:
|
|
33
|
-
host:
|
|
34
|
-
authToken:
|
|
35
|
-
apiKey:
|
|
36
|
-
tenant:
|
|
37
|
-
database:
|
|
38
|
-
collectionName:
|
|
48
|
+
mode: overrides.mode,
|
|
49
|
+
host: overrides.host,
|
|
50
|
+
authToken: overrides.authToken,
|
|
51
|
+
apiKey: overrides.apiKey,
|
|
52
|
+
tenant: overrides.tenant,
|
|
53
|
+
database: overrides.database,
|
|
54
|
+
collectionName: overrides.collectionName,
|
|
39
55
|
indexType: config.indexType,
|
|
40
56
|
});
|
|
41
57
|
}
|
|
42
|
-
case
|
|
58
|
+
case 'lancedb':
|
|
43
59
|
default: {
|
|
44
|
-
const { LanceDBAdapter } = await import(
|
|
60
|
+
const { LanceDBAdapter } = await import('./adapters/lancedb');
|
|
45
61
|
return new LanceDBAdapter({
|
|
46
|
-
mode:
|
|
47
|
-
dbPath:
|
|
48
|
-
uri:
|
|
49
|
-
apiKey:
|
|
50
|
-
|
|
62
|
+
mode: overrides.mode,
|
|
63
|
+
dbPath: overrides.dbPath,
|
|
64
|
+
uri: overrides.uri,
|
|
65
|
+
apiKey: overrides.apiKey,
|
|
66
|
+
s3Uri: overrides.s3Uri,
|
|
67
|
+
s3Endpoint: overrides.s3Endpoint,
|
|
68
|
+
s3Region: overrides.s3Region,
|
|
69
|
+
s3AccessKeyId: overrides.s3AccessKeyId,
|
|
70
|
+
s3SecretAccessKey: overrides.s3SecretAccessKey,
|
|
71
|
+
tableName: overrides.tableName,
|
|
51
72
|
});
|
|
52
73
|
}
|
|
53
74
|
}
|
|
54
|
-
|
|
55
75
|
}
|