@h-ai/vecdb 1.0.0-beta.2 → 1.0.0-beta.20
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.en.md +68 -25
- package/README.ja.md +69 -26
- package/README.md +70 -27
- package/dist/index.d.ts +3 -2
- package/dist/index.js +515 -157
- package/dist/index.js.map +1 -1
- package/package.json +9 -10
package/dist/index.js
CHANGED
|
@@ -38,7 +38,8 @@ var en_US_default = {
|
|
|
38
38
|
vecdb_configDatabaseRequired: "Database name is required",
|
|
39
39
|
vecdb_sharedBackendRequired: "Production mode requires a shared backend; local files and memory backends are for development or explicit end-to-end tests.",
|
|
40
40
|
vecdb_chromaEndpointInvalid: "Chroma requires an HTTP(S) root endpoint without credentials, path prefix, query or fragment",
|
|
41
|
-
vecdb_chromaTokenInvalid: "Chroma Bearer token must contain only printable ASCII characters without whitespace"
|
|
41
|
+
vecdb_chromaTokenInvalid: "Chroma Bearer token must contain only printable ASCII characters without whitespace",
|
|
42
|
+
vecdb_collectionMetricMissing: "Collection distance metric is missing; restore its vector index or column metadata"
|
|
42
43
|
};
|
|
43
44
|
|
|
44
45
|
// messages/ja-JP.json
|
|
@@ -71,7 +72,8 @@ var ja_JP_default = {
|
|
|
71
72
|
vecdb_configDatabaseRequired: "\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u540D\u306F\u5FC5\u9808\u3067\u3059",
|
|
72
73
|
vecdb_sharedBackendRequired: "\u672C\u756A\u74B0\u5883\u306B\u306F\u5171\u6709\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u304C\u5FC5\u8981\u3067\u3059\u3002\u30ED\u30FC\u30AB\u30EB\u30D5\u30A1\u30A4\u30EB\u3068\u30E1\u30E2\u30EA\u30FC\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u306F\u958B\u767A\u74B0\u5883\u307E\u305F\u306F\u660E\u793A\u7684\u306A E2E \u30C6\u30B9\u30C8\u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059\u3002",
|
|
73
74
|
vecdb_chromaEndpointInvalid: "Chroma \u306B\u306F HTTP(S) \u306E\u30EB\u30FC\u30C8\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u8A8D\u8A3C\u60C5\u5831\u3001\u30D1\u30B9\u30D7\u30EC\u30D5\u30A3\u30C3\u30AF\u30B9\u3001\u30AF\u30A8\u30EA\u3001\u30D5\u30E9\u30B0\u30E1\u30F3\u30C8\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093",
|
|
74
|
-
vecdb_chromaTokenInvalid: "Chroma Bearer \u30C8\u30FC\u30AF\u30F3\u306B\u306F\u7A7A\u767D\u3092\u542B\u307E\u306A\u3044\u8868\u793A\u53EF\u80FD\u306A ASCII \u6587\u5B57\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059"
|
|
75
|
+
vecdb_chromaTokenInvalid: "Chroma Bearer \u30C8\u30FC\u30AF\u30F3\u306B\u306F\u7A7A\u767D\u3092\u542B\u307E\u306A\u3044\u8868\u793A\u53EF\u80FD\u306A ASCII \u6587\u5B57\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059",
|
|
76
|
+
vecdb_collectionMetricMissing: "\u30B3\u30EC\u30AF\u30B7\u30E7\u30F3\u306E\u8DDD\u96E2\u5C3A\u5EA6\u304C\u3042\u308A\u307E\u305B\u3093\u3002\u30D9\u30AF\u30C8\u30EB\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u307E\u305F\u306F\u5217\u30E1\u30BF\u30C7\u30FC\u30BF\u3092\u5FA9\u5143\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
75
77
|
};
|
|
76
78
|
|
|
77
79
|
// messages/zh-CN.json
|
|
@@ -104,7 +106,8 @@ var zh_CN_default = {
|
|
|
104
106
|
vecdb_configDatabaseRequired: "\u6570\u636E\u5E93\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A",
|
|
105
107
|
vecdb_sharedBackendRequired: "\u751F\u4EA7\u6A21\u5F0F\u9700\u8981\u8DE8\u8282\u70B9\u5171\u4EAB\u540E\u7AEF\uFF1B\u672C\u673A\u6587\u4EF6\u6216\u5185\u5B58\u540E\u7AEF\u4EC5\u7528\u4E8E\u5F00\u53D1\u548C\u663E\u5F0F\u7AEF\u5230\u7AEF\u6D4B\u8BD5\u3002",
|
|
106
108
|
vecdb_chromaEndpointInvalid: "Chroma \u4EC5\u652F\u6301 HTTP(S) \u6839\u7AEF\u70B9\uFF0C\u4E0D\u5F97\u5305\u542B\u51ED\u636E\u3001\u8DEF\u5F84\u524D\u7F00\u3001query \u6216 fragment",
|
|
107
|
-
vecdb_chromaTokenInvalid: "Chroma Bearer token \u4EC5\u652F\u6301\u65E0\u7A7A\u767D\u7684\u53EF\u6253\u5370 ASCII \u5B57\u7B26"
|
|
109
|
+
vecdb_chromaTokenInvalid: "Chroma Bearer token \u4EC5\u652F\u6301\u65E0\u7A7A\u767D\u7684\u53EF\u6253\u5370 ASCII \u5B57\u7B26",
|
|
110
|
+
vecdb_collectionMetricMissing: "\u96C6\u5408\u7F3A\u5C11\u6709\u6548\u8DDD\u79BB\u7B97\u6CD5\u5143\u6570\u636E\uFF0C\u8BF7\u6838\u5BF9\u5E76\u6062\u590D\u5411\u91CF\u7D22\u5F15\u6216\u5217\u6CE8\u91CA"
|
|
108
111
|
};
|
|
109
112
|
|
|
110
113
|
// src/vecdb-i18n.ts
|
|
@@ -178,13 +181,17 @@ var ChromaConfigSchema = z.object({
|
|
|
178
181
|
} catch {
|
|
179
182
|
return false;
|
|
180
183
|
}
|
|
181
|
-
}, {
|
|
184
|
+
}, {
|
|
185
|
+
error: () => vecdbM("vecdb_chromaEndpointInvalid")
|
|
186
|
+
}).optional(),
|
|
182
187
|
/** 主机(默认 localhost) */
|
|
183
188
|
host: z.string().default("localhost"),
|
|
184
189
|
/** 端口(默认 8000) */
|
|
185
190
|
port: z.number().int().min(1).max(65535).default(8e3),
|
|
186
191
|
/** 可选 Bearer token,发送 Authorization 请求头;用于采用此协议的自托管服务/网关,不是 Chroma Cloud 配置。空串表示未配置。 */
|
|
187
|
-
apiKey: z.string().regex(/^[\x21-\x7E]*$/, {
|
|
192
|
+
apiKey: z.string().regex(/^[\x21-\x7E]*$/, {
|
|
193
|
+
error: () => vecdbM("vecdb_chromaTokenInvalid")
|
|
194
|
+
}).optional(),
|
|
188
195
|
/** 拉起本地服务的可执行命令(默认 'chroma',可指定绝对路径) */
|
|
189
196
|
serverCommand: z.string().default("chroma"),
|
|
190
197
|
/** 拉起本地服务的就绪等待超时(毫秒,默认 30000) */
|
|
@@ -216,6 +223,13 @@ var VecdbErrorInfo = {
|
|
|
216
223
|
SERIALIZATION_FAILED: "014:500"
|
|
217
224
|
};
|
|
218
225
|
var HaiVecdbError = core.error.buildHaiErrorsDef("vecdb", VecdbErrorInfo);
|
|
226
|
+
function vectorScoreFromDistance(distance, metric) {
|
|
227
|
+
if (metric === "cosine")
|
|
228
|
+
return Math.max(0, Math.min(1, 1 - distance / 2));
|
|
229
|
+
if (metric === "dot")
|
|
230
|
+
return 1 / (1 + Math.exp(distance));
|
|
231
|
+
return 1 / (1 + Math.max(0, distance));
|
|
232
|
+
}
|
|
219
233
|
function stringifyOperationPayload(payload, maxLength) {
|
|
220
234
|
if (maxLength <= 0) {
|
|
221
235
|
return "";
|
|
@@ -250,13 +264,21 @@ function logVecdbOperation(ctx, category, operation, payload) {
|
|
|
250
264
|
function errorMsgFromCode(def, errorStr) {
|
|
251
265
|
switch (def) {
|
|
252
266
|
case HaiVecdbError.DELETE_FAILED:
|
|
253
|
-
return vecdbM("vecdb_deleteFailed", {
|
|
267
|
+
return vecdbM("vecdb_deleteFailed", {
|
|
268
|
+
params: { error: errorStr }
|
|
269
|
+
});
|
|
254
270
|
case HaiVecdbError.INSERT_FAILED:
|
|
255
|
-
return vecdbM("vecdb_insertFailed", {
|
|
271
|
+
return vecdbM("vecdb_insertFailed", {
|
|
272
|
+
params: { error: errorStr }
|
|
273
|
+
});
|
|
256
274
|
case HaiVecdbError.UPDATE_FAILED:
|
|
257
|
-
return vecdbM("vecdb_updateFailed", {
|
|
275
|
+
return vecdbM("vecdb_updateFailed", {
|
|
276
|
+
params: { error: errorStr }
|
|
277
|
+
});
|
|
258
278
|
default:
|
|
259
|
-
return vecdbM("vecdb_queryFailed", {
|
|
279
|
+
return vecdbM("vecdb_queryFailed", {
|
|
280
|
+
params: { error: errorStr }
|
|
281
|
+
});
|
|
260
282
|
}
|
|
261
283
|
}
|
|
262
284
|
async function wrapOp(ctx, fn, errorDef, errorLabel, errorMeta) {
|
|
@@ -276,7 +298,11 @@ function createBaseCollectionOps(ctx, driver) {
|
|
|
276
298
|
if (!Number.isInteger(options.dimension) || options.dimension <= 0) {
|
|
277
299
|
return err(
|
|
278
300
|
HaiVecdbError.DIMENSION_MISMATCH,
|
|
279
|
-
vecdbM("vecdb_invalidDimension", {
|
|
301
|
+
vecdbM("vecdb_invalidDimension", {
|
|
302
|
+
params: {
|
|
303
|
+
dimension: String(options.dimension)
|
|
304
|
+
}
|
|
305
|
+
})
|
|
280
306
|
);
|
|
281
307
|
}
|
|
282
308
|
logVecdbOperation(ctx, "write", "collection.create", { name, options });
|
|
@@ -413,9 +439,7 @@ function fromChromaSpace(space) {
|
|
|
413
439
|
return "cosine";
|
|
414
440
|
}
|
|
415
441
|
function distanceToScore(distance, metric) {
|
|
416
|
-
|
|
417
|
-
return Math.max(0, Math.min(1, 1 - distance));
|
|
418
|
-
return 1 / (1 + Math.max(0, distance));
|
|
442
|
+
return vectorScoreFromDistance(metric === "dot" ? distance - 1 : distance, metric);
|
|
419
443
|
}
|
|
420
444
|
function buildWhere(filter) {
|
|
421
445
|
if (!filter)
|
|
@@ -423,7 +447,9 @@ function buildWhere(filter) {
|
|
|
423
447
|
const entries = Object.entries(filter);
|
|
424
448
|
if (entries.length === 0)
|
|
425
449
|
return void 0;
|
|
426
|
-
const where = entries.length === 1 ? { [entries[0][0]]: entries[0][1] } : {
|
|
450
|
+
const where = entries.length === 1 ? { [entries[0][0]]: entries[0][1] } : {
|
|
451
|
+
$and: entries.map(([key, value]) => ({ [key]: value }))
|
|
452
|
+
};
|
|
427
453
|
return where;
|
|
428
454
|
}
|
|
429
455
|
function createChromaProvider() {
|
|
@@ -442,19 +468,33 @@ function createChromaProvider() {
|
|
|
442
468
|
if (!isChromaModule(mod)) {
|
|
443
469
|
const error = new Error("chromadb driver module does not export ChromaClient");
|
|
444
470
|
logger.error("Failed to load Chroma driver", { error: error.message });
|
|
445
|
-
return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", {
|
|
471
|
+
return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", {
|
|
472
|
+
params: { driver: "chromadb" }
|
|
473
|
+
}), error);
|
|
446
474
|
}
|
|
447
475
|
return ok(mod);
|
|
448
476
|
} catch (error) {
|
|
449
477
|
if (isMissingModuleError(error)) {
|
|
450
|
-
logger.error("Failed to load Chroma driver", {
|
|
451
|
-
|
|
478
|
+
logger.error("Failed to load Chroma driver", {
|
|
479
|
+
error: error instanceof Error ? error.message : String(error)
|
|
480
|
+
});
|
|
481
|
+
return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", {
|
|
482
|
+
params: { driver: "chromadb" }
|
|
483
|
+
}), error);
|
|
452
484
|
}
|
|
453
485
|
throw error;
|
|
454
486
|
}
|
|
455
487
|
}
|
|
456
488
|
async function startEmbeddedServer(chromaConfig) {
|
|
457
|
-
const args = [
|
|
489
|
+
const args = [
|
|
490
|
+
"run",
|
|
491
|
+
"--path",
|
|
492
|
+
chromaConfig.path ?? "./data/chroma",
|
|
493
|
+
"--host",
|
|
494
|
+
chromaConfig.host,
|
|
495
|
+
"--port",
|
|
496
|
+
String(chromaConfig.port)
|
|
497
|
+
];
|
|
458
498
|
logger.info("Starting embedded Chroma server", { command: chromaConfig.serverCommand, host: chromaConfig.host, port: chromaConfig.port });
|
|
459
499
|
spawnFailed = false;
|
|
460
500
|
const proc = spawn(chromaConfig.serverCommand, args, { stdio: "ignore" });
|
|
@@ -533,7 +573,16 @@ function createChromaProvider() {
|
|
|
533
573
|
logger.info("Chroma connection closed");
|
|
534
574
|
return ok(void 0);
|
|
535
575
|
} catch (cause) {
|
|
536
|
-
return err({
|
|
576
|
+
return err({
|
|
577
|
+
...HaiVecdbError.CONNECTION_FAILED,
|
|
578
|
+
message: vecdbM("vecdb_closeFailed", {
|
|
579
|
+
params: {
|
|
580
|
+
error: cause instanceof Error ? cause.message : String(cause)
|
|
581
|
+
}
|
|
582
|
+
}),
|
|
583
|
+
cause,
|
|
584
|
+
ext: { cleanupPending: true }
|
|
585
|
+
});
|
|
537
586
|
}
|
|
538
587
|
})().finally(() => {
|
|
539
588
|
closing = null;
|
|
@@ -567,21 +616,36 @@ function createChromaProvider() {
|
|
|
567
616
|
return null;
|
|
568
617
|
return err(
|
|
569
618
|
HaiVecdbError.DIMENSION_MISMATCH,
|
|
570
|
-
vecdbM("vecdb_dimensionMismatch", {
|
|
619
|
+
vecdbM("vecdb_dimensionMismatch", {
|
|
620
|
+
params: {
|
|
621
|
+
expected: String(expected),
|
|
622
|
+
actual: String(actual)
|
|
623
|
+
}
|
|
624
|
+
})
|
|
571
625
|
);
|
|
572
626
|
}
|
|
573
|
-
const ctx = {
|
|
627
|
+
const ctx = {
|
|
628
|
+
isConnected: () => client !== null,
|
|
629
|
+
logger,
|
|
630
|
+
operationLog: () => config?.operationLog
|
|
631
|
+
};
|
|
574
632
|
const collectionDriver = {
|
|
575
633
|
async create(name, options) {
|
|
576
634
|
logger.debug("Creating collection", { name, dimension: options.dimension, metric: options.metric });
|
|
577
635
|
const existing = await client.listCollections();
|
|
578
636
|
const exists = existing.some((item) => item.name === name);
|
|
579
|
-
if (exists)
|
|
580
|
-
return err(HaiVecdbError.COLLECTION_ALREADY_EXISTS, vecdbM("vecdb_collectionAlreadyExists", {
|
|
637
|
+
if (exists) {
|
|
638
|
+
return err(HaiVecdbError.COLLECTION_ALREADY_EXISTS, vecdbM("vecdb_collectionAlreadyExists", {
|
|
639
|
+
params: { name }
|
|
640
|
+
}));
|
|
641
|
+
}
|
|
581
642
|
const metric = options.metric ?? config?.metric ?? "cosine";
|
|
582
643
|
await client.getOrCreateCollection({
|
|
583
644
|
name,
|
|
584
|
-
metadata: {
|
|
645
|
+
metadata: {
|
|
646
|
+
[SPACE_METADATA_KEY]: toChromaSpace(metric),
|
|
647
|
+
[DIMENSION_METADATA_KEY]: options.dimension
|
|
648
|
+
},
|
|
585
649
|
embeddingFunction: NOOP_EMBEDDING_FUNCTION
|
|
586
650
|
});
|
|
587
651
|
logger.info("Collection created", { name, dimension: options.dimension, metric });
|
|
@@ -591,8 +655,11 @@ function createChromaProvider() {
|
|
|
591
655
|
logger.debug("Dropping collection", { name });
|
|
592
656
|
const existing = await client.listCollections();
|
|
593
657
|
const exists = existing.some((item) => item.name === name);
|
|
594
|
-
if (!exists)
|
|
595
|
-
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
|
|
658
|
+
if (!exists) {
|
|
659
|
+
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
|
|
660
|
+
params: { name }
|
|
661
|
+
}));
|
|
662
|
+
}
|
|
596
663
|
await client.deleteCollection({ name });
|
|
597
664
|
logger.info("Collection dropped", { name });
|
|
598
665
|
return ok(void 0);
|
|
@@ -605,8 +672,11 @@ function createChromaProvider() {
|
|
|
605
672
|
async info(name) {
|
|
606
673
|
logger.debug("Getting collection info", { name });
|
|
607
674
|
const existing = await client.listCollections();
|
|
608
|
-
if (!existing.some((item) => item.name === name))
|
|
609
|
-
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
|
|
675
|
+
if (!existing.some((item) => item.name === name)) {
|
|
676
|
+
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
|
|
677
|
+
params: { name }
|
|
678
|
+
}));
|
|
679
|
+
}
|
|
610
680
|
const collection = await getCollection(name);
|
|
611
681
|
const count = await collection.count();
|
|
612
682
|
return ok({
|
|
@@ -676,7 +746,11 @@ function createChromaProvider() {
|
|
|
676
746
|
if (validation)
|
|
677
747
|
return validation;
|
|
678
748
|
const metric = fromChromaSpace(target.metadata?.[SPACE_METADATA_KEY]);
|
|
679
|
-
const response = await target.query({
|
|
749
|
+
const response = await target.query({
|
|
750
|
+
queryEmbeddings: [vector],
|
|
751
|
+
nResults: topK,
|
|
752
|
+
where: buildWhere(options?.filter)
|
|
753
|
+
});
|
|
680
754
|
const ids = response.ids[0] ?? [];
|
|
681
755
|
const distances = response.distances?.[0] ?? [];
|
|
682
756
|
const metadatas = response.metadatas?.[0] ?? [];
|
|
@@ -705,8 +779,11 @@ function createChromaProvider() {
|
|
|
705
779
|
return {
|
|
706
780
|
name: "chroma",
|
|
707
781
|
async connect(cfg) {
|
|
708
|
-
if (cfg.type !== "chroma")
|
|
709
|
-
return err(HaiVecdbError.UNSUPPORTED_TYPE, vecdbM("vecdb_unsupportedType", {
|
|
782
|
+
if (cfg.type !== "chroma") {
|
|
783
|
+
return err(HaiVecdbError.UNSUPPORTED_TYPE, vecdbM("vecdb_unsupportedType", {
|
|
784
|
+
params: { type: cfg.type }
|
|
785
|
+
}));
|
|
786
|
+
}
|
|
710
787
|
if (serverProcess || client || closing) {
|
|
711
788
|
const cleaned = await closeResources();
|
|
712
789
|
if (!cleaned.success)
|
|
@@ -724,7 +801,11 @@ function createChromaProvider() {
|
|
|
724
801
|
if (embedded)
|
|
725
802
|
await startEmbeddedServer(chromaConfig);
|
|
726
803
|
const endpoint = chromaConfig.url ? new URL(chromaConfig.url) : void 0;
|
|
727
|
-
const clientParams = endpoint ? {
|
|
804
|
+
const clientParams = endpoint ? {
|
|
805
|
+
host: endpoint.hostname,
|
|
806
|
+
port: Number(endpoint.port || (endpoint.protocol === "https:" ? 443 : 80)),
|
|
807
|
+
ssl: endpoint.protocol === "https:"
|
|
808
|
+
} : { host: chromaConfig.host, port: chromaConfig.port, ssl: false };
|
|
728
809
|
if (chromaConfig.apiKey)
|
|
729
810
|
clientParams.headers = { Authorization: `Bearer ${chromaConfig.apiKey}` };
|
|
730
811
|
clientParams.fetchOptions = { signal: startupAbort.signal };
|
|
@@ -736,14 +817,28 @@ function createChromaProvider() {
|
|
|
736
817
|
throw new Error("Chroma child exited before readiness was confirmed");
|
|
737
818
|
client = chromaClient;
|
|
738
819
|
config = chromaConfig;
|
|
739
|
-
logger.info("Chroma connected", {
|
|
820
|
+
logger.info("Chroma connected", {
|
|
821
|
+
embedded,
|
|
822
|
+
host: clientParams.host,
|
|
823
|
+
port: clientParams.port,
|
|
824
|
+
ssl: clientParams.ssl
|
|
825
|
+
});
|
|
740
826
|
return ok(void 0);
|
|
741
827
|
} catch (error) {
|
|
742
828
|
startupAbort.abort();
|
|
743
829
|
const cleanup = await closeResources();
|
|
744
|
-
logger.error("Failed to connect to Chroma", {
|
|
745
|
-
|
|
746
|
-
|
|
830
|
+
logger.error("Failed to connect to Chroma", {
|
|
831
|
+
error: error instanceof Error ? error.message : String(error)
|
|
832
|
+
});
|
|
833
|
+
const failure = err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_connectionFailed", {
|
|
834
|
+
params: {
|
|
835
|
+
error: String(error)
|
|
836
|
+
}
|
|
837
|
+
}), error);
|
|
838
|
+
return !failure.success && !cleanup.success ? err({
|
|
839
|
+
...failure.error,
|
|
840
|
+
ext: { cleanupPending: true, cleanupError: cleanup.error }
|
|
841
|
+
}) : failure;
|
|
747
842
|
} finally {
|
|
748
843
|
clearTimeout(startupTimer);
|
|
749
844
|
}
|
|
@@ -789,19 +884,26 @@ function createLancedbProvider() {
|
|
|
789
884
|
return ok(mod);
|
|
790
885
|
} catch (error) {
|
|
791
886
|
logger2.error("Failed to load @lancedb/lancedb", { error });
|
|
792
|
-
return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", {
|
|
887
|
+
return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", {
|
|
888
|
+
params: { driver: "@lancedb/lancedb" }
|
|
889
|
+
}), error);
|
|
793
890
|
}
|
|
794
891
|
}
|
|
795
892
|
async function withCollection(name, action) {
|
|
796
|
-
if (!(await connection.tableNames()).includes(name))
|
|
797
|
-
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
|
|
893
|
+
if (!(await connection.tableNames()).includes(name)) {
|
|
894
|
+
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
|
|
895
|
+
params: { name }
|
|
896
|
+
}));
|
|
897
|
+
}
|
|
798
898
|
const table = await connection.openTable(name);
|
|
799
899
|
try {
|
|
800
900
|
const schema = await table.schema();
|
|
801
901
|
const vectorType = schema.fields.find((field) => field.name === "vector")?.type;
|
|
802
902
|
const metric = DistanceMetricSchema.safeParse(schema.metadata.get(COLLECTION_METRIC_KEY) ?? null);
|
|
803
903
|
if (!vectorType || !("listSize" in vectorType) || typeof vectorType.listSize !== "number" || !Number.isInteger(vectorType.listSize) || vectorType.listSize <= 0 || !metric.success) {
|
|
804
|
-
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", {
|
|
904
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", {
|
|
905
|
+
params: { name }
|
|
906
|
+
}));
|
|
805
907
|
}
|
|
806
908
|
const primaryKeys = schema.fields.filter((field) => field.metadata.has(PRIMARY_KEY_POSITION) || ["true", "1", "yes"].includes(field.metadata.get(PRIMARY_KEY_FLAG)?.toLowerCase() ?? ""));
|
|
807
909
|
const idField = primaryKeys[0];
|
|
@@ -810,8 +912,15 @@ function createLancedbProvider() {
|
|
|
810
912
|
if (!driver.success)
|
|
811
913
|
return driver;
|
|
812
914
|
const jsonMetadata = schema.fields.find((field) => field.name === "metadata")?.metadata.get("ARROW:extension:name") === "lance.json";
|
|
813
|
-
const inputSchema = jsonMetadata ? schema.assign([
|
|
814
|
-
|
|
915
|
+
const inputSchema = jsonMetadata ? schema.assign([
|
|
916
|
+
driver.data.makeJsonField("metadata", false)
|
|
917
|
+
]) : void 0;
|
|
918
|
+
return await action(table, {
|
|
919
|
+
dimension: vectorType.listSize,
|
|
920
|
+
metric: metric.data,
|
|
921
|
+
atomicWrites,
|
|
922
|
+
inputSchema
|
|
923
|
+
});
|
|
815
924
|
} finally {
|
|
816
925
|
table.close();
|
|
817
926
|
}
|
|
@@ -820,7 +929,10 @@ function createLancedbProvider() {
|
|
|
820
929
|
return err(
|
|
821
930
|
HaiVecdbError.DIMENSION_MISMATCH,
|
|
822
931
|
vecdbM("vecdb_dimensionMismatch", {
|
|
823
|
-
params: {
|
|
932
|
+
params: {
|
|
933
|
+
expected: String(expected),
|
|
934
|
+
actual: String(actual)
|
|
935
|
+
}
|
|
824
936
|
})
|
|
825
937
|
);
|
|
826
938
|
}
|
|
@@ -847,29 +959,49 @@ function createLancedbProvider() {
|
|
|
847
959
|
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_duplicateDocumentIds"));
|
|
848
960
|
return null;
|
|
849
961
|
}
|
|
850
|
-
const ctx = {
|
|
962
|
+
const ctx = {
|
|
963
|
+
isConnected: () => connection !== null,
|
|
964
|
+
logger: logger2,
|
|
965
|
+
operationLog: () => config?.operationLog
|
|
966
|
+
};
|
|
851
967
|
const collectionDriver = {
|
|
852
968
|
async create(name, options) {
|
|
853
969
|
logger2.debug("Creating collection", { name, dimension: options.dimension, metric: options.metric });
|
|
854
970
|
const tableNames = await connection.tableNames();
|
|
855
971
|
if (tableNames.includes(name)) {
|
|
856
|
-
return err(HaiVecdbError.COLLECTION_ALREADY_EXISTS, vecdbM("vecdb_collectionAlreadyExists", {
|
|
972
|
+
return err(HaiVecdbError.COLLECTION_ALREADY_EXISTS, vecdbM("vecdb_collectionAlreadyExists", {
|
|
973
|
+
params: { name }
|
|
974
|
+
}));
|
|
857
975
|
}
|
|
858
976
|
const dimension = options.dimension;
|
|
859
977
|
const parsedMetric = DistanceMetricSchema.safeParse(options.metric ?? config?.metric);
|
|
860
|
-
if (!parsedMetric.success)
|
|
861
|
-
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", {
|
|
978
|
+
if (!parsedMetric.success) {
|
|
979
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", {
|
|
980
|
+
params: { name }
|
|
981
|
+
}));
|
|
982
|
+
}
|
|
862
983
|
const metric = parsedMetric.data;
|
|
863
984
|
const driver = loadLancedb();
|
|
864
985
|
if (!driver.success)
|
|
865
986
|
return driver;
|
|
866
|
-
const inferred = driver.data.makeArrowTable([
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
987
|
+
const inferred = driver.data.makeArrowTable([
|
|
988
|
+
{
|
|
989
|
+
id: "",
|
|
990
|
+
vector: Array.from({ length: dimension }).fill(0),
|
|
991
|
+
content: "",
|
|
992
|
+
metadata: "{}"
|
|
993
|
+
}
|
|
994
|
+
], {}, /* @__PURE__ */ new Map([
|
|
995
|
+
[COLLECTION_METRIC_KEY, metric]
|
|
996
|
+
])).schema;
|
|
997
|
+
const schema = inferred.assign(inferred.fields.map((field) => field.name === "id" ? field.clone({
|
|
998
|
+
nullable: false,
|
|
999
|
+
metadata: new Map([
|
|
1000
|
+
...field.metadata,
|
|
1001
|
+
[PRIMARY_KEY_FLAG, "true"],
|
|
1002
|
+
[PRIMARY_KEY_POSITION, "1"]
|
|
1003
|
+
])
|
|
1004
|
+
}) : field.name === "metadata" ? driver.data.makeJsonField("metadata", false) : field));
|
|
873
1005
|
const table = await connection.createEmptyTable(name, schema);
|
|
874
1006
|
table.close();
|
|
875
1007
|
logger2.info("Collection created", { name, dimension, metric });
|
|
@@ -879,7 +1011,9 @@ function createLancedbProvider() {
|
|
|
879
1011
|
logger2.debug("Dropping collection", { name });
|
|
880
1012
|
const tableNames = await connection.tableNames();
|
|
881
1013
|
if (!tableNames.includes(name)) {
|
|
882
|
-
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
|
|
1014
|
+
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
|
|
1015
|
+
params: { name }
|
|
1016
|
+
}));
|
|
883
1017
|
}
|
|
884
1018
|
await connection.dropTable(name);
|
|
885
1019
|
logger2.info("Collection dropped", { name });
|
|
@@ -892,7 +1026,12 @@ function createLancedbProvider() {
|
|
|
892
1026
|
},
|
|
893
1027
|
async info(name) {
|
|
894
1028
|
logger2.debug("Getting collection info", { name });
|
|
895
|
-
return withCollection(name, async (table, meta) => ok({
|
|
1029
|
+
return withCollection(name, async (table, meta) => ok({
|
|
1030
|
+
name,
|
|
1031
|
+
dimension: meta.dimension,
|
|
1032
|
+
metric: meta.metric,
|
|
1033
|
+
count: await table.countRows()
|
|
1034
|
+
}));
|
|
896
1035
|
},
|
|
897
1036
|
async list() {
|
|
898
1037
|
logger2.debug("Listing collections");
|
|
@@ -980,7 +1119,7 @@ function createLancedbProvider() {
|
|
|
980
1119
|
const results = await query.toArray();
|
|
981
1120
|
const searchResults = results.map((row) => {
|
|
982
1121
|
const distance = row._distance ?? 0;
|
|
983
|
-
const score =
|
|
1122
|
+
const score = vectorScoreFromDistance(meta.metric === "dot" ? distance - 1 : distance, meta.metric);
|
|
984
1123
|
return {
|
|
985
1124
|
id: row.id,
|
|
986
1125
|
score,
|
|
@@ -1003,7 +1142,9 @@ function createLancedbProvider() {
|
|
|
1003
1142
|
name: "lancedb",
|
|
1004
1143
|
async connect(cfg) {
|
|
1005
1144
|
if (cfg.type !== "lancedb") {
|
|
1006
|
-
return err(HaiVecdbError.UNSUPPORTED_TYPE, vecdbM("vecdb_unsupportedType", {
|
|
1145
|
+
return err(HaiVecdbError.UNSUPPORTED_TYPE, vecdbM("vecdb_unsupportedType", {
|
|
1146
|
+
params: { type: cfg.type }
|
|
1147
|
+
}));
|
|
1007
1148
|
}
|
|
1008
1149
|
const lanceConfig = cfg;
|
|
1009
1150
|
const loadResult = loadLancedb();
|
|
@@ -1020,7 +1161,11 @@ function createLancedbProvider() {
|
|
|
1020
1161
|
connection = null;
|
|
1021
1162
|
config = null;
|
|
1022
1163
|
logger2.error("Failed to connect to LanceDB", { error });
|
|
1023
|
-
return err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_connectionFailed", {
|
|
1164
|
+
return err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_connectionFailed", {
|
|
1165
|
+
params: {
|
|
1166
|
+
error: String(error)
|
|
1167
|
+
}
|
|
1168
|
+
}), error);
|
|
1024
1169
|
}
|
|
1025
1170
|
},
|
|
1026
1171
|
async close() {
|
|
@@ -1054,8 +1199,7 @@ function createPgvectorProvider() {
|
|
|
1054
1199
|
function quoteIdent(name) {
|
|
1055
1200
|
return `"${name.replace(/"/g, '""')}"`;
|
|
1056
1201
|
}
|
|
1057
|
-
function distanceOp() {
|
|
1058
|
-
const metric = config?.metric ?? "cosine";
|
|
1202
|
+
function distanceOp(metric) {
|
|
1059
1203
|
switch (metric) {
|
|
1060
1204
|
case "cosine":
|
|
1061
1205
|
return "<=>";
|
|
@@ -1067,18 +1211,8 @@ function createPgvectorProvider() {
|
|
|
1067
1211
|
return "<=>";
|
|
1068
1212
|
}
|
|
1069
1213
|
}
|
|
1070
|
-
function distanceToScore2(distance) {
|
|
1071
|
-
|
|
1072
|
-
switch (metric) {
|
|
1073
|
-
case "cosine":
|
|
1074
|
-
return 1 - distance / 2;
|
|
1075
|
-
case "euclidean":
|
|
1076
|
-
return 1 / (1 + distance);
|
|
1077
|
-
case "dot":
|
|
1078
|
-
return -distance;
|
|
1079
|
-
default:
|
|
1080
|
-
return 1 / (1 + distance);
|
|
1081
|
-
}
|
|
1214
|
+
function distanceToScore2(distance, metric) {
|
|
1215
|
+
return vectorScoreFromDistance(distance, metric);
|
|
1082
1216
|
}
|
|
1083
1217
|
function isMissingModuleError(error, packageName) {
|
|
1084
1218
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1090,13 +1224,19 @@ function createPgvectorProvider() {
|
|
|
1090
1224
|
if (!isPgModule(mod)) {
|
|
1091
1225
|
const error = new Error("pg driver module does not export Pool");
|
|
1092
1226
|
logger3.error("Failed to load pg driver", { error: error.message });
|
|
1093
|
-
return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", {
|
|
1227
|
+
return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", {
|
|
1228
|
+
params: { driver: "pg" }
|
|
1229
|
+
}), error);
|
|
1094
1230
|
}
|
|
1095
1231
|
return ok(mod);
|
|
1096
1232
|
} catch (error) {
|
|
1097
1233
|
if (isMissingModuleError(error, "pg")) {
|
|
1098
|
-
logger3.error("Failed to load pg driver", {
|
|
1099
|
-
|
|
1234
|
+
logger3.error("Failed to load pg driver", {
|
|
1235
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1236
|
+
});
|
|
1237
|
+
return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", {
|
|
1238
|
+
params: { driver: "pg" }
|
|
1239
|
+
}), error);
|
|
1100
1240
|
}
|
|
1101
1241
|
throw error;
|
|
1102
1242
|
}
|
|
@@ -1108,14 +1248,34 @@ function createPgvectorProvider() {
|
|
|
1108
1248
|
[table]
|
|
1109
1249
|
);
|
|
1110
1250
|
if (!existsResult.rows[0].exists) {
|
|
1111
|
-
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
|
|
1251
|
+
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
|
|
1252
|
+
params: { name }
|
|
1253
|
+
}));
|
|
1112
1254
|
}
|
|
1113
1255
|
const dimResult = await pool.query(`
|
|
1114
1256
|
SELECT atttypmod FROM pg_attribute
|
|
1115
1257
|
WHERE attrelid = $1::regclass AND attname = 'vector'
|
|
1116
|
-
`, [
|
|
1258
|
+
`, [
|
|
1259
|
+
quoteIdent(table)
|
|
1260
|
+
]);
|
|
1117
1261
|
return ok(dimResult.rows.length > 0 ? Number(dimResult.rows[0].atttypmod) : 0);
|
|
1118
1262
|
}
|
|
1263
|
+
async function getCollectionMetric(name) {
|
|
1264
|
+
const result = await pool.query(`SELECT col_description(a.attrelid, a.attnum) AS definition,
|
|
1265
|
+
(SELECT opc.opcname FROM pg_index i JOIN pg_opclass opc ON opc.oid = ANY(i.indclass)
|
|
1266
|
+
WHERE i.indrelid = a.attrelid AND opc.opcname IN ('vector_cosine_ops', 'vector_l2_ops', 'vector_ip_ops') LIMIT 1) AS operator
|
|
1267
|
+
FROM pg_attribute a WHERE a.attrelid = $1::regclass AND a.attname = 'vector'`, [
|
|
1268
|
+
quoteIdent(tableName(name))
|
|
1269
|
+
]);
|
|
1270
|
+
const row = result.rows[0];
|
|
1271
|
+
const stored = typeof row?.definition === "string" && row.definition.startsWith("hai.vecdb.metric=") ? row.definition.slice("hai.vecdb.metric=".length) : void 0;
|
|
1272
|
+
const inherited = row?.operator === "vector_cosine_ops" ? "cosine" : row?.operator === "vector_l2_ops" ? "euclidean" : row?.operator === "vector_ip_ops" ? "dot" : void 0;
|
|
1273
|
+
const metric = stored ?? inherited;
|
|
1274
|
+
const parsed = DistanceMetricSchema.safeParse(metric);
|
|
1275
|
+
if (metric === void 0 || !parsed.success)
|
|
1276
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_collectionMetricMissing"));
|
|
1277
|
+
return ok(parsed.data);
|
|
1278
|
+
}
|
|
1119
1279
|
function validateVectorDimension(expected, actual) {
|
|
1120
1280
|
if (expected === actual) {
|
|
1121
1281
|
return null;
|
|
@@ -1123,7 +1283,10 @@ function createPgvectorProvider() {
|
|
|
1123
1283
|
return err(
|
|
1124
1284
|
HaiVecdbError.DIMENSION_MISMATCH,
|
|
1125
1285
|
vecdbM("vecdb_dimensionMismatch", {
|
|
1126
|
-
params: {
|
|
1286
|
+
params: {
|
|
1287
|
+
expected: String(expected),
|
|
1288
|
+
actual: String(actual)
|
|
1289
|
+
}
|
|
1127
1290
|
})
|
|
1128
1291
|
);
|
|
1129
1292
|
}
|
|
@@ -1136,12 +1299,16 @@ function createPgvectorProvider() {
|
|
|
1136
1299
|
}
|
|
1137
1300
|
return null;
|
|
1138
1301
|
}
|
|
1139
|
-
const ctx = {
|
|
1302
|
+
const ctx = {
|
|
1303
|
+
isConnected: () => pool !== null,
|
|
1304
|
+
logger: logger3,
|
|
1305
|
+
operationLog: () => config?.operationLog
|
|
1306
|
+
};
|
|
1140
1307
|
const collectionDriver = {
|
|
1141
1308
|
async create(name, options) {
|
|
1142
1309
|
const table = tableName(name);
|
|
1143
1310
|
const dimension = options.dimension;
|
|
1144
|
-
const metric = options.metric ?? config?.metric ?? "cosine";
|
|
1311
|
+
const metric = DistanceMetricSchema.parse(options.metric ?? config?.metric ?? "cosine");
|
|
1145
1312
|
const indexType = config?.indexType ?? "hnsw";
|
|
1146
1313
|
logger3.debug("Creating collection", { name, dimension, metric, indexType });
|
|
1147
1314
|
const checkResult = await pool.query(
|
|
@@ -1149,7 +1316,9 @@ function createPgvectorProvider() {
|
|
|
1149
1316
|
[table]
|
|
1150
1317
|
);
|
|
1151
1318
|
if (checkResult.rows[0].exists) {
|
|
1152
|
-
return err(HaiVecdbError.COLLECTION_ALREADY_EXISTS, vecdbM("vecdb_collectionAlreadyExists", {
|
|
1319
|
+
return err(HaiVecdbError.COLLECTION_ALREADY_EXISTS, vecdbM("vecdb_collectionAlreadyExists", {
|
|
1320
|
+
params: { name }
|
|
1321
|
+
}));
|
|
1153
1322
|
}
|
|
1154
1323
|
const qi = quoteIdent(table);
|
|
1155
1324
|
await pool.query(`
|
|
@@ -1160,7 +1329,7 @@ function createPgvectorProvider() {
|
|
|
1160
1329
|
metadata JSONB DEFAULT '{}'::jsonb
|
|
1161
1330
|
)
|
|
1162
1331
|
`);
|
|
1163
|
-
const distOp = distanceOp();
|
|
1332
|
+
const distOp = distanceOp(metric);
|
|
1164
1333
|
const opsClass = distOp === "<=>" ? "vector_cosine_ops" : distOp === "<->" ? "vector_l2_ops" : "vector_ip_ops";
|
|
1165
1334
|
if (indexType === "hnsw") {
|
|
1166
1335
|
await pool.query(`
|
|
@@ -1174,6 +1343,7 @@ function createPgvectorProvider() {
|
|
|
1174
1343
|
WITH (lists = 100)
|
|
1175
1344
|
`);
|
|
1176
1345
|
}
|
|
1346
|
+
await pool.query(`COMMENT ON COLUMN ${qi}.vector IS 'hai.vecdb.metric=${metric}'`);
|
|
1177
1347
|
logger3.info("Collection created", { name, dimension, metric, indexType });
|
|
1178
1348
|
return ok(void 0);
|
|
1179
1349
|
},
|
|
@@ -1185,7 +1355,9 @@ function createPgvectorProvider() {
|
|
|
1185
1355
|
[table]
|
|
1186
1356
|
);
|
|
1187
1357
|
if (!checkResult.rows[0].exists) {
|
|
1188
|
-
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
|
|
1358
|
+
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
|
|
1359
|
+
params: { name }
|
|
1360
|
+
}));
|
|
1189
1361
|
}
|
|
1190
1362
|
await pool.query(`DROP TABLE ${quoteIdent(table)}`);
|
|
1191
1363
|
logger3.info("Collection dropped", { name });
|
|
@@ -1195,7 +1367,9 @@ function createPgvectorProvider() {
|
|
|
1195
1367
|
logger3.debug("Checking collection exists", { name });
|
|
1196
1368
|
const result = await pool.query(
|
|
1197
1369
|
`SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = $1)`,
|
|
1198
|
-
[
|
|
1370
|
+
[
|
|
1371
|
+
tableName(name)
|
|
1372
|
+
]
|
|
1199
1373
|
);
|
|
1200
1374
|
return ok(result.rows[0].exists);
|
|
1201
1375
|
},
|
|
@@ -1206,12 +1380,15 @@ function createPgvectorProvider() {
|
|
|
1206
1380
|
if (!dimensionResult.success) {
|
|
1207
1381
|
return dimensionResult;
|
|
1208
1382
|
}
|
|
1383
|
+
const metric = await getCollectionMetric(name);
|
|
1384
|
+
if (!metric.success)
|
|
1385
|
+
return metric;
|
|
1209
1386
|
const countResult = await pool.query(`SELECT COUNT(*) as count FROM ${quoteIdent(table)}`);
|
|
1210
1387
|
const count = Number.parseInt(String(countResult.rows[0].count), 10);
|
|
1211
1388
|
return ok({
|
|
1212
1389
|
name,
|
|
1213
1390
|
dimension: dimensionResult.data,
|
|
1214
|
-
metric:
|
|
1391
|
+
metric: metric.data,
|
|
1215
1392
|
count
|
|
1216
1393
|
});
|
|
1217
1394
|
},
|
|
@@ -1299,7 +1476,6 @@ function createPgvectorProvider() {
|
|
|
1299
1476
|
const table = tableName(collection);
|
|
1300
1477
|
const topK = options?.topK ?? 10;
|
|
1301
1478
|
const minScore = options?.minScore ?? 0;
|
|
1302
|
-
const op = distanceOp();
|
|
1303
1479
|
logger3.debug("Searching vectors", { collection, topK, hasFilter: !!options?.filter });
|
|
1304
1480
|
const dimensionResult = await getCollectionDimension(collection);
|
|
1305
1481
|
if (!dimensionResult.success) {
|
|
@@ -1309,6 +1485,10 @@ function createPgvectorProvider() {
|
|
|
1309
1485
|
if (dimensionValidation) {
|
|
1310
1486
|
return dimensionValidation;
|
|
1311
1487
|
}
|
|
1488
|
+
const metric = await getCollectionMetric(collection);
|
|
1489
|
+
if (!metric.success)
|
|
1490
|
+
return metric;
|
|
1491
|
+
const op = distanceOp(metric.data);
|
|
1312
1492
|
const vectorStr = `[${vector.join(",")}]`;
|
|
1313
1493
|
let filterSQL = "";
|
|
1314
1494
|
const filterParams = [vectorStr, topK];
|
|
@@ -1333,7 +1513,7 @@ function createPgvectorProvider() {
|
|
|
1333
1513
|
filterParams
|
|
1334
1514
|
);
|
|
1335
1515
|
const searchResults = result.rows.map((row) => {
|
|
1336
|
-
const score = distanceToScore2(row.distance);
|
|
1516
|
+
const score = distanceToScore2(row.distance, metric.data);
|
|
1337
1517
|
return {
|
|
1338
1518
|
id: row.id,
|
|
1339
1519
|
score,
|
|
@@ -1354,7 +1534,9 @@ function createPgvectorProvider() {
|
|
|
1354
1534
|
name: "pgvector",
|
|
1355
1535
|
async connect(cfg) {
|
|
1356
1536
|
if (cfg.type !== "pgvector") {
|
|
1357
|
-
return err(HaiVecdbError.UNSUPPORTED_TYPE, vecdbM("vecdb_unsupportedType", {
|
|
1537
|
+
return err(HaiVecdbError.UNSUPPORTED_TYPE, vecdbM("vecdb_unsupportedType", {
|
|
1538
|
+
params: { type: cfg.type }
|
|
1539
|
+
}));
|
|
1358
1540
|
}
|
|
1359
1541
|
const pgConfig = cfg;
|
|
1360
1542
|
try {
|
|
@@ -1376,8 +1558,14 @@ function createPgvectorProvider() {
|
|
|
1376
1558
|
logger3.info("pgvector connected", { host: pgConfig.host, database: pgConfig.database });
|
|
1377
1559
|
return ok(void 0);
|
|
1378
1560
|
} catch (error) {
|
|
1379
|
-
logger3.error("Failed to connect to pgvector", {
|
|
1380
|
-
|
|
1561
|
+
logger3.error("Failed to connect to pgvector", {
|
|
1562
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1563
|
+
});
|
|
1564
|
+
return err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_connectionFailed", {
|
|
1565
|
+
params: {
|
|
1566
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1567
|
+
}
|
|
1568
|
+
}), error);
|
|
1381
1569
|
}
|
|
1382
1570
|
},
|
|
1383
1571
|
async close() {
|
|
@@ -1390,8 +1578,14 @@ function createPgvectorProvider() {
|
|
|
1390
1578
|
logger3.info("pgvector connection closed");
|
|
1391
1579
|
return ok(void 0);
|
|
1392
1580
|
} catch (error) {
|
|
1393
|
-
logger3.error("Failed to close pgvector connection", {
|
|
1394
|
-
|
|
1581
|
+
logger3.error("Failed to close pgvector connection", {
|
|
1582
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1583
|
+
});
|
|
1584
|
+
return err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_closeFailed", {
|
|
1585
|
+
params: {
|
|
1586
|
+
error: String(error)
|
|
1587
|
+
}
|
|
1588
|
+
}), error);
|
|
1395
1589
|
}
|
|
1396
1590
|
},
|
|
1397
1591
|
isConnected() {
|
|
@@ -1401,14 +1595,152 @@ function createPgvectorProvider() {
|
|
|
1401
1595
|
vector: createBaseVectorOps(ctx, vectorDriver)
|
|
1402
1596
|
};
|
|
1403
1597
|
}
|
|
1404
|
-
var
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1598
|
+
var MAX_RESPONSE_BYTES = 16 * 1024 * 1024;
|
|
1599
|
+
var pointSchema = z.object({
|
|
1600
|
+
id: z.union([
|
|
1601
|
+
z.string(),
|
|
1602
|
+
z.number()
|
|
1603
|
+
]),
|
|
1604
|
+
score: z.number().optional(),
|
|
1605
|
+
payload: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
1606
|
+
vector: z.array(z.number()).optional()
|
|
1607
|
+
});
|
|
1608
|
+
var collectionSchema = z.object({
|
|
1609
|
+
config: z.object({
|
|
1610
|
+
params: z.object({
|
|
1611
|
+
vectors: z.unknown()
|
|
1612
|
+
})
|
|
1613
|
+
}),
|
|
1614
|
+
points_count: z.number().nullable().optional()
|
|
1615
|
+
});
|
|
1616
|
+
var QdrantHttpError = class extends Error {
|
|
1617
|
+
constructor(status) {
|
|
1618
|
+
super(`Qdrant HTTP request failed (${status})`);
|
|
1619
|
+
this.status = status;
|
|
1408
1620
|
}
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1621
|
+
status;
|
|
1622
|
+
};
|
|
1623
|
+
var QdrantRestClient = class {
|
|
1624
|
+
constructor(url, apiKey) {
|
|
1625
|
+
this.apiKey = apiKey;
|
|
1626
|
+
this.base = new URL(url);
|
|
1627
|
+
if (!["http:", "https:"].includes(this.base.protocol) || this.base.username || this.base.password)
|
|
1628
|
+
throw new Error("Qdrant URL must use HTTP(S) without embedded credentials");
|
|
1629
|
+
}
|
|
1630
|
+
apiKey;
|
|
1631
|
+
base;
|
|
1632
|
+
async perform(path, method, schema, body) {
|
|
1633
|
+
const url = new URL(this.base);
|
|
1634
|
+
const [pathname, query] = path.split("?");
|
|
1635
|
+
url.pathname = `${url.pathname.replace(/\/$/, "")}${pathname}`;
|
|
1636
|
+
for (const [key, value] of new URLSearchParams(query)) url.searchParams.set(key, value);
|
|
1637
|
+
const response = await fetch(url, {
|
|
1638
|
+
method,
|
|
1639
|
+
redirect: "error",
|
|
1640
|
+
signal: AbortSignal.timeout(3e4),
|
|
1641
|
+
headers: {
|
|
1642
|
+
"content-type": "application/json",
|
|
1643
|
+
...this.apiKey ? { "api-key": this.apiKey } : {}
|
|
1644
|
+
},
|
|
1645
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
1646
|
+
});
|
|
1647
|
+
if (!response.ok) {
|
|
1648
|
+
await response.body?.cancel();
|
|
1649
|
+
throw new QdrantHttpError(response.status);
|
|
1650
|
+
}
|
|
1651
|
+
const reader = response.body?.getReader();
|
|
1652
|
+
if (!reader)
|
|
1653
|
+
throw new Error("Qdrant response body is missing");
|
|
1654
|
+
let size = 0;
|
|
1655
|
+
const chunks = [];
|
|
1656
|
+
try {
|
|
1657
|
+
while (true) {
|
|
1658
|
+
const chunk = await reader.read();
|
|
1659
|
+
if (chunk.done)
|
|
1660
|
+
break;
|
|
1661
|
+
size += chunk.value.byteLength;
|
|
1662
|
+
if (size > MAX_RESPONSE_BYTES)
|
|
1663
|
+
throw new Error("Qdrant response exceeds 16 MiB");
|
|
1664
|
+
chunks.push(chunk.value);
|
|
1665
|
+
}
|
|
1666
|
+
} finally {
|
|
1667
|
+
await reader.cancel().catch(() => void 0);
|
|
1668
|
+
reader.releaseLock();
|
|
1669
|
+
}
|
|
1670
|
+
const bytes = new Uint8Array(size);
|
|
1671
|
+
let offset = 0;
|
|
1672
|
+
for (const chunk of chunks) {
|
|
1673
|
+
bytes.set(chunk, offset);
|
|
1674
|
+
offset += chunk.byteLength;
|
|
1675
|
+
}
|
|
1676
|
+
let bodyJson;
|
|
1677
|
+
try {
|
|
1678
|
+
bodyJson = JSON.parse(new TextDecoder().decode(bytes));
|
|
1679
|
+
} catch {
|
|
1680
|
+
throw new Error("Qdrant returned invalid JSON");
|
|
1681
|
+
}
|
|
1682
|
+
const envelope = z.object({
|
|
1683
|
+
status: z.literal("ok"),
|
|
1684
|
+
result: schema
|
|
1685
|
+
}).safeParse(bodyJson);
|
|
1686
|
+
if (!envelope.success)
|
|
1687
|
+
throw new Error("Qdrant returned an invalid response");
|
|
1688
|
+
return envelope.data.result;
|
|
1689
|
+
}
|
|
1690
|
+
async request(path, method, schema, body) {
|
|
1691
|
+
try {
|
|
1692
|
+
return await this.perform(path, method, schema, body);
|
|
1693
|
+
} catch (error) {
|
|
1694
|
+
if (error instanceof QdrantHttpError)
|
|
1695
|
+
throw error;
|
|
1696
|
+
core.logger.child({ module: "vecdb", scope: "qdrant-http" }).error("Qdrant HTTP transport failed", {
|
|
1697
|
+
error: core.sanitize.sanitizeSensitiveFields(error)
|
|
1698
|
+
});
|
|
1699
|
+
throw new Error("Qdrant request failed");
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
getCollections() {
|
|
1703
|
+
return this.request("/collections", "GET", z.object({
|
|
1704
|
+
collections: z.array(z.object({
|
|
1705
|
+
name: z.string()
|
|
1706
|
+
}))
|
|
1707
|
+
}));
|
|
1708
|
+
}
|
|
1709
|
+
getCollection(name) {
|
|
1710
|
+
return this.request(`/collections/${encodeURIComponent(name)}`, "GET", collectionSchema);
|
|
1711
|
+
}
|
|
1712
|
+
createCollection(name, options) {
|
|
1713
|
+
return this.request(`/collections/${encodeURIComponent(name)}`, "PUT", z.boolean(), options);
|
|
1714
|
+
}
|
|
1715
|
+
deleteCollection(name) {
|
|
1716
|
+
return this.request(`/collections/${encodeURIComponent(name)}`, "DELETE", z.boolean());
|
|
1717
|
+
}
|
|
1718
|
+
upsert(name, options) {
|
|
1719
|
+
return this.request(`/collections/${encodeURIComponent(name)}/points?wait=${options.wait}`, "PUT", z.object({
|
|
1720
|
+
status: z.literal("completed")
|
|
1721
|
+
}), { points: options.points });
|
|
1722
|
+
}
|
|
1723
|
+
delete(name, options) {
|
|
1724
|
+
return this.request(`/collections/${encodeURIComponent(name)}/points/delete?wait=${options.wait}`, "POST", z.object({
|
|
1725
|
+
status: z.literal("completed")
|
|
1726
|
+
}), { points: options.points });
|
|
1727
|
+
}
|
|
1728
|
+
query(name, options) {
|
|
1729
|
+
return this.request(`/collections/${encodeURIComponent(name)}/points/query`, "POST", z.object({
|
|
1730
|
+
points: z.array(pointSchema.extend({
|
|
1731
|
+
score: z.number()
|
|
1732
|
+
}))
|
|
1733
|
+
}), options);
|
|
1734
|
+
}
|
|
1735
|
+
count(name, options) {
|
|
1736
|
+
return this.request(`/collections/${encodeURIComponent(name)}/points/count`, "POST", z.object({
|
|
1737
|
+
count: z.number()
|
|
1738
|
+
}), options);
|
|
1739
|
+
}
|
|
1740
|
+
};
|
|
1741
|
+
|
|
1742
|
+
// src/providers/vecdb-provider-qdrant.ts
|
|
1743
|
+
var logger4 = core.logger.child({ module: "vecdb", scope: "qdrant" });
|
|
1412
1744
|
function hashToUuid(id) {
|
|
1413
1745
|
const hash = createHash("sha256").update(id).digest("hex");
|
|
1414
1746
|
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
|
|
@@ -1426,27 +1758,6 @@ function toQdrantDistance(metric) {
|
|
|
1426
1758
|
function createQdrantProvider() {
|
|
1427
1759
|
let client = null;
|
|
1428
1760
|
let config = null;
|
|
1429
|
-
function isMissingModuleError(error, packageName) {
|
|
1430
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1431
|
-
return message.includes(`'${packageName}'`) || message.includes(`"${packageName}"`) || message.includes(packageName);
|
|
1432
|
-
}
|
|
1433
|
-
async function loadQdrantClient() {
|
|
1434
|
-
try {
|
|
1435
|
-
const mod = await import('@qdrant/js-client-rest');
|
|
1436
|
-
if (!isQdrantModule(mod)) {
|
|
1437
|
-
const error = new Error("@qdrant/js-client-rest driver module does not export QdrantClient");
|
|
1438
|
-
logger4.error("Failed to load Qdrant driver", { error: error.message });
|
|
1439
|
-
return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", { params: { driver: "@qdrant/js-client-rest" } }), error);
|
|
1440
|
-
}
|
|
1441
|
-
return ok(mod);
|
|
1442
|
-
} catch (error) {
|
|
1443
|
-
if (isMissingModuleError(error, "@qdrant/js-client-rest")) {
|
|
1444
|
-
logger4.error("Failed to load Qdrant driver", { error: error instanceof Error ? error.message : String(error) });
|
|
1445
|
-
return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", { params: { driver: "@qdrant/js-client-rest" } }), error);
|
|
1446
|
-
}
|
|
1447
|
-
throw error;
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
1761
|
function isQdrantNotFoundError(error) {
|
|
1451
1762
|
return typeof error === "object" && error !== null && "status" in error && error.status === 404;
|
|
1452
1763
|
}
|
|
@@ -1455,14 +1766,16 @@ function createQdrantProvider() {
|
|
|
1455
1766
|
return ok(await client.getCollection(name));
|
|
1456
1767
|
} catch (error) {
|
|
1457
1768
|
if (isQdrantNotFoundError(error)) {
|
|
1458
|
-
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
|
|
1769
|
+
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
|
|
1770
|
+
params: { name }
|
|
1771
|
+
}));
|
|
1459
1772
|
}
|
|
1460
1773
|
throw error;
|
|
1461
1774
|
}
|
|
1462
1775
|
}
|
|
1463
1776
|
function readVectorDefinition(info) {
|
|
1464
1777
|
const vectors = info.config.params.vectors;
|
|
1465
|
-
if (!vectors || !("size" in vectors) || typeof vectors.size !== "number" || !Number.isSafeInteger(vectors.size) || vectors.size <= 0)
|
|
1778
|
+
if (!vectors || typeof vectors !== "object" || !("size" in vectors) || typeof vectors.size !== "number" || !Number.isSafeInteger(vectors.size) || vectors.size <= 0)
|
|
1466
1779
|
return void 0;
|
|
1467
1780
|
if (!("distance" in vectors))
|
|
1468
1781
|
return void 0;
|
|
@@ -1475,7 +1788,9 @@ function createQdrantProvider() {
|
|
|
1475
1788
|
return infoResult;
|
|
1476
1789
|
}
|
|
1477
1790
|
const definition = readVectorDefinition(infoResult.data);
|
|
1478
|
-
return definition ? ok(definition.dimension) : err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", {
|
|
1791
|
+
return definition ? ok(definition.dimension) : err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", {
|
|
1792
|
+
params: { name }
|
|
1793
|
+
}));
|
|
1479
1794
|
}
|
|
1480
1795
|
function validateVectorDimension(expected, actual) {
|
|
1481
1796
|
if (expected === actual) {
|
|
@@ -1484,18 +1799,27 @@ function createQdrantProvider() {
|
|
|
1484
1799
|
return err(
|
|
1485
1800
|
HaiVecdbError.DIMENSION_MISMATCH,
|
|
1486
1801
|
vecdbM("vecdb_dimensionMismatch", {
|
|
1487
|
-
params: {
|
|
1802
|
+
params: {
|
|
1803
|
+
expected: String(expected),
|
|
1804
|
+
actual: String(actual)
|
|
1805
|
+
}
|
|
1488
1806
|
})
|
|
1489
1807
|
);
|
|
1490
1808
|
}
|
|
1491
|
-
const ctx = {
|
|
1809
|
+
const ctx = {
|
|
1810
|
+
isConnected: () => client !== null,
|
|
1811
|
+
logger: logger4,
|
|
1812
|
+
operationLog: () => config?.operationLog
|
|
1813
|
+
};
|
|
1492
1814
|
const collectionDriver = {
|
|
1493
1815
|
async create(name, options) {
|
|
1494
1816
|
logger4.debug("Creating collection", { name, dimension: options.dimension, metric: options.metric });
|
|
1495
1817
|
const collections = await client.getCollections();
|
|
1496
1818
|
const exists = collections.collections.some((c) => c.name === name);
|
|
1497
1819
|
if (exists) {
|
|
1498
|
-
return err(HaiVecdbError.COLLECTION_ALREADY_EXISTS, vecdbM("vecdb_collectionAlreadyExists", {
|
|
1820
|
+
return err(HaiVecdbError.COLLECTION_ALREADY_EXISTS, vecdbM("vecdb_collectionAlreadyExists", {
|
|
1821
|
+
params: { name }
|
|
1822
|
+
}));
|
|
1499
1823
|
}
|
|
1500
1824
|
const metric = options.metric ?? config?.metric ?? "cosine";
|
|
1501
1825
|
await client.createCollection(name, {
|
|
@@ -1537,8 +1861,11 @@ function createQdrantProvider() {
|
|
|
1537
1861
|
}
|
|
1538
1862
|
const collectionInfo = infoResult.data;
|
|
1539
1863
|
const definition = readVectorDefinition(collectionInfo);
|
|
1540
|
-
if (!definition)
|
|
1541
|
-
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", {
|
|
1864
|
+
if (!definition) {
|
|
1865
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", {
|
|
1866
|
+
params: { name }
|
|
1867
|
+
}));
|
|
1868
|
+
}
|
|
1542
1869
|
return ok({
|
|
1543
1870
|
name,
|
|
1544
1871
|
...definition,
|
|
@@ -1614,11 +1941,16 @@ function createQdrantProvider() {
|
|
|
1614
1941
|
const topK = options?.topK ?? 10;
|
|
1615
1942
|
const minScore = options?.minScore ?? 0;
|
|
1616
1943
|
logger4.debug("Searching vectors", { collection, topK, hasFilter: !!options?.filter });
|
|
1617
|
-
const
|
|
1618
|
-
if (!
|
|
1619
|
-
return
|
|
1944
|
+
const collectionInfo = await getCollectionInfo(collection);
|
|
1945
|
+
if (!collectionInfo.success)
|
|
1946
|
+
return collectionInfo;
|
|
1947
|
+
const definition = readVectorDefinition(collectionInfo.data);
|
|
1948
|
+
if (!definition) {
|
|
1949
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", {
|
|
1950
|
+
params: { name: collection }
|
|
1951
|
+
}));
|
|
1620
1952
|
}
|
|
1621
|
-
const dimensionValidation = validateVectorDimension(
|
|
1953
|
+
const dimensionValidation = validateVectorDimension(definition.dimension, vector.length);
|
|
1622
1954
|
if (dimensionValidation) {
|
|
1623
1955
|
return dimensionValidation;
|
|
1624
1956
|
}
|
|
@@ -1633,7 +1965,6 @@ function createQdrantProvider() {
|
|
|
1633
1965
|
const searchResult = await client.query(collection, {
|
|
1634
1966
|
query: vector,
|
|
1635
1967
|
limit: topK,
|
|
1636
|
-
score_threshold: minScore > 0 ? minScore : void 0,
|
|
1637
1968
|
filter,
|
|
1638
1969
|
with_payload: true
|
|
1639
1970
|
});
|
|
@@ -1642,11 +1973,12 @@ function createQdrantProvider() {
|
|
|
1642
1973
|
const { _id, _content, ...metadata } = payload;
|
|
1643
1974
|
return {
|
|
1644
1975
|
id: typeof _id === "string" ? _id : "",
|
|
1645
|
-
|
|
1976
|
+
// Qdrant 余弦/内积返回相似值,欧氏返回距离;统一后再应用公开阈值。
|
|
1977
|
+
score: vectorScoreFromDistance(definition.metric === "cosine" ? 1 - point.score : definition.metric === "dot" ? -point.score : point.score, definition.metric),
|
|
1646
1978
|
content: typeof _content === "string" && _content.length > 0 ? _content : void 0,
|
|
1647
1979
|
metadata: Object.keys(metadata).length > 0 ? metadata : void 0
|
|
1648
1980
|
};
|
|
1649
|
-
});
|
|
1981
|
+
}).filter((point) => point.score >= minScore);
|
|
1650
1982
|
return ok(results);
|
|
1651
1983
|
},
|
|
1652
1984
|
async count(collection) {
|
|
@@ -1659,27 +1991,27 @@ function createQdrantProvider() {
|
|
|
1659
1991
|
name: "qdrant",
|
|
1660
1992
|
async connect(cfg) {
|
|
1661
1993
|
if (cfg.type !== "qdrant") {
|
|
1662
|
-
return err(HaiVecdbError.UNSUPPORTED_TYPE, vecdbM("vecdb_unsupportedType", {
|
|
1994
|
+
return err(HaiVecdbError.UNSUPPORTED_TYPE, vecdbM("vecdb_unsupportedType", {
|
|
1995
|
+
params: { type: cfg.type }
|
|
1996
|
+
}));
|
|
1663
1997
|
}
|
|
1664
1998
|
const qdrantConfig = cfg;
|
|
1665
1999
|
try {
|
|
1666
|
-
const
|
|
1667
|
-
if (!loadResult.success) {
|
|
1668
|
-
return loadResult;
|
|
1669
|
-
}
|
|
1670
|
-
const { QdrantClient: QdrantClientClass } = loadResult.data;
|
|
1671
|
-
const qdrantClient = new QdrantClientClass({
|
|
1672
|
-
url: qdrantConfig.url,
|
|
1673
|
-
apiKey: qdrantConfig.apiKey
|
|
1674
|
-
});
|
|
2000
|
+
const qdrantClient = new QdrantRestClient(qdrantConfig.url, qdrantConfig.apiKey);
|
|
1675
2001
|
await qdrantClient.getCollections();
|
|
1676
2002
|
client = qdrantClient;
|
|
1677
2003
|
config = qdrantConfig;
|
|
1678
|
-
logger4.info("Qdrant connected", {
|
|
2004
|
+
logger4.info("Qdrant connected", {
|
|
2005
|
+
url: core.sanitize.sanitizeUrl(qdrantConfig.url)
|
|
2006
|
+
});
|
|
1679
2007
|
return ok(void 0);
|
|
1680
2008
|
} catch (error) {
|
|
1681
|
-
logger4.error("Failed to connect to Qdrant", {
|
|
1682
|
-
|
|
2009
|
+
logger4.error("Failed to connect to Qdrant", {
|
|
2010
|
+
error: core.sanitize.sanitizeSensitiveFields(error)
|
|
2011
|
+
});
|
|
2012
|
+
return err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_connectionFailed", {
|
|
2013
|
+
params: { error: "Qdrant connection failed" }
|
|
2014
|
+
}));
|
|
1683
2015
|
}
|
|
1684
2016
|
},
|
|
1685
2017
|
async close() {
|
|
@@ -1714,24 +2046,44 @@ async function closeOwnedProvider() {
|
|
|
1714
2046
|
return ok(void 0);
|
|
1715
2047
|
try {
|
|
1716
2048
|
const result = await pendingProvider.close();
|
|
1717
|
-
if (!result.success)
|
|
1718
|
-
return err({
|
|
2049
|
+
if (!result.success) {
|
|
2050
|
+
return err({
|
|
2051
|
+
...result.error,
|
|
2052
|
+
ext: { ...result.error.ext, cleanupPending: true }
|
|
2053
|
+
});
|
|
2054
|
+
}
|
|
1719
2055
|
pendingProvider = null;
|
|
1720
2056
|
return result;
|
|
1721
2057
|
} catch (cause) {
|
|
1722
|
-
return err({
|
|
2058
|
+
return err({
|
|
2059
|
+
...HaiVecdbError.CONNECTION_FAILED,
|
|
2060
|
+
message: vecdbM("vecdb_closeFailed", {
|
|
2061
|
+
params: {
|
|
2062
|
+
error: cause instanceof Error ? cause.message : String(cause)
|
|
2063
|
+
}
|
|
2064
|
+
}),
|
|
2065
|
+
cause,
|
|
2066
|
+
ext: { cleanupPending: true }
|
|
2067
|
+
});
|
|
1723
2068
|
}
|
|
1724
2069
|
}
|
|
1725
2070
|
async function failInitialization(result) {
|
|
1726
2071
|
const cleanup = await closeOwnedProvider();
|
|
1727
2072
|
if (result.success)
|
|
1728
2073
|
return result;
|
|
1729
|
-
if (!cleanup.success)
|
|
1730
|
-
return err({
|
|
2074
|
+
if (!cleanup.success) {
|
|
2075
|
+
return err({
|
|
2076
|
+
...result.error,
|
|
2077
|
+
ext: { ...result.error.ext, cleanupPending: true, cleanupError: cleanup.error }
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
1731
2080
|
const ext = { ...result.error.ext };
|
|
1732
2081
|
delete ext.cleanupPending;
|
|
1733
2082
|
delete ext.cleanupError;
|
|
1734
|
-
return err({
|
|
2083
|
+
return err({
|
|
2084
|
+
...result.error,
|
|
2085
|
+
ext: Object.keys(ext).length ? ext : void 0
|
|
2086
|
+
});
|
|
1735
2087
|
}
|
|
1736
2088
|
function createProvider(config) {
|
|
1737
2089
|
switch (config.type) {
|
|
@@ -1790,7 +2142,9 @@ var vecdb = {
|
|
|
1790
2142
|
const parseResult = VecdbConfigSchema.safeParse(config);
|
|
1791
2143
|
if (!parseResult.success) {
|
|
1792
2144
|
logger5.error("Vecdb config validation failed", { error: parseResult.error.message });
|
|
1793
|
-
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_configError", {
|
|
2145
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_configError", {
|
|
2146
|
+
params: { error: parseResult.error.message }
|
|
2147
|
+
}), parseResult.error);
|
|
1794
2148
|
}
|
|
1795
2149
|
const parsed = parseResult.data;
|
|
1796
2150
|
if (core.module.requiresSharedState(process.env) && (parsed.type === "lancedb" || parsed.type === "chroma" && !!parsed.path && !parsed.url))
|
|
@@ -1813,7 +2167,11 @@ var vecdb = {
|
|
|
1813
2167
|
return ok(void 0);
|
|
1814
2168
|
} catch (error) {
|
|
1815
2169
|
logger5.error("Vecdb module initialization failed", { error });
|
|
1816
|
-
return await failInitialization(err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_initFailed", {
|
|
2170
|
+
return await failInitialization(err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_initFailed", {
|
|
2171
|
+
params: {
|
|
2172
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2173
|
+
}
|
|
2174
|
+
}), error));
|
|
1817
2175
|
}
|
|
1818
2176
|
} finally {
|
|
1819
2177
|
initInProgress = false;
|