vecmindb 1.0.0-beta
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 +349 -0
- package/dist/index.d.mts +934 -0
- package/dist/index.d.ts +934 -0
- package/dist/index.js +1205 -0
- package/dist/index.mjs +1154 -0
- package/package.json +67 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1154 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var VecminError = class extends Error {
|
|
3
|
+
/** HTTP-style status code from the server response. */
|
|
4
|
+
code;
|
|
5
|
+
constructor(message, code) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "VecminError";
|
|
8
|
+
this.code = code;
|
|
9
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var AuthenticationError = class extends VecminError {
|
|
13
|
+
constructor(message = "Authentication failed or missing credentials") {
|
|
14
|
+
super(message, 401);
|
|
15
|
+
this.name = "AuthenticationError";
|
|
16
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var PermissionError = class extends VecminError {
|
|
20
|
+
constructor(message = "Insufficient permissions") {
|
|
21
|
+
super(message, 403);
|
|
22
|
+
this.name = "PermissionError";
|
|
23
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var PaymentRequiredError = class extends VecminError {
|
|
27
|
+
constructor(message = "Subscription payment or valid license key required. Please register/activate your license at https://lingxinmind.com") {
|
|
28
|
+
super(message, 402);
|
|
29
|
+
this.name = "PaymentRequiredError";
|
|
30
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var NotFoundError = class extends VecminError {
|
|
34
|
+
constructor(message = "Resource not found") {
|
|
35
|
+
super(message, 404);
|
|
36
|
+
this.name = "NotFoundError";
|
|
37
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var RateLimitError = class extends VecminError {
|
|
41
|
+
constructor(message = "Rate limit exceeded") {
|
|
42
|
+
super(message, 429);
|
|
43
|
+
this.name = "RateLimitError";
|
|
44
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var ServerError = class extends VecminError {
|
|
48
|
+
constructor(message = "Internal server error", code = 500) {
|
|
49
|
+
super(message, code);
|
|
50
|
+
this.name = "ServerError";
|
|
51
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
function createErrorFromStatus(status, message) {
|
|
55
|
+
switch (status) {
|
|
56
|
+
case 401:
|
|
57
|
+
return new AuthenticationError(message);
|
|
58
|
+
case 402:
|
|
59
|
+
return new PaymentRequiredError(message);
|
|
60
|
+
case 403:
|
|
61
|
+
return new PermissionError(message);
|
|
62
|
+
case 404:
|
|
63
|
+
return new NotFoundError(message);
|
|
64
|
+
case 429:
|
|
65
|
+
return new RateLimitError(message);
|
|
66
|
+
default:
|
|
67
|
+
if (status >= 500 && status < 600) {
|
|
68
|
+
return new ServerError(message, status);
|
|
69
|
+
}
|
|
70
|
+
return new VecminError(message, status);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/retry.ts
|
|
75
|
+
var DEFAULTS = {
|
|
76
|
+
maxRetries: 3,
|
|
77
|
+
backoffFactor: 0.5,
|
|
78
|
+
retryableStatusCodes: [429, 500, 502, 503, 504]
|
|
79
|
+
};
|
|
80
|
+
function resolveRetryOptions(opts) {
|
|
81
|
+
return {
|
|
82
|
+
maxRetries: opts?.maxRetries ?? DEFAULTS.maxRetries,
|
|
83
|
+
backoffFactor: opts?.backoffFactor ?? DEFAULTS.backoffFactor,
|
|
84
|
+
retryableStatusCodes: opts?.retryableStatusCodes ?? DEFAULTS.retryableStatusCodes
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function sleep(ms) {
|
|
88
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
89
|
+
}
|
|
90
|
+
function isRetryable(status, retryableCodes) {
|
|
91
|
+
return retryableCodes.includes(status);
|
|
92
|
+
}
|
|
93
|
+
async function withRetry(fn, options) {
|
|
94
|
+
const opts = resolveRetryOptions(options);
|
|
95
|
+
let lastError;
|
|
96
|
+
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
|
|
97
|
+
try {
|
|
98
|
+
return await fn();
|
|
99
|
+
} catch (err) {
|
|
100
|
+
if (err instanceof VecminError && isRetryable(err.code, opts.retryableStatusCodes)) {
|
|
101
|
+
lastError = err;
|
|
102
|
+
let delayMs;
|
|
103
|
+
if (err instanceof RateLimitError && err.retryAfter) {
|
|
104
|
+
delayMs = (err.retryAfter ?? 1) * 1e3;
|
|
105
|
+
} else {
|
|
106
|
+
delayMs = opts.backoffFactor * Math.pow(2, attempt) * 1e3;
|
|
107
|
+
}
|
|
108
|
+
const jitter = Math.random() * 200 - 100;
|
|
109
|
+
await sleep(Math.max(0, delayMs + jitter));
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
throw err;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
throw lastError ?? new VecminError("All retry attempts exhausted", 0);
|
|
116
|
+
}
|
|
117
|
+
async function fetchJson(url, init, retryOpts) {
|
|
118
|
+
return withRetry(async () => {
|
|
119
|
+
const res = await fetch(url, init);
|
|
120
|
+
const body = await res.json();
|
|
121
|
+
if (!res.ok) {
|
|
122
|
+
const message = body?.error ?? body?.message ?? `HTTP ${res.status}`;
|
|
123
|
+
const code = body?.code ?? res.status;
|
|
124
|
+
throw createErrorFromStatus(code, message);
|
|
125
|
+
}
|
|
126
|
+
return body?.data !== void 0 ? body.data : body;
|
|
127
|
+
}, retryOpts);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/auth.ts
|
|
131
|
+
function decodeJwtPayload(token) {
|
|
132
|
+
try {
|
|
133
|
+
const parts = token.split(".");
|
|
134
|
+
if (parts.length < 2) return null;
|
|
135
|
+
const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
136
|
+
const json = decodeURIComponent(
|
|
137
|
+
atob(base64).split("").map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("")
|
|
138
|
+
);
|
|
139
|
+
return JSON.parse(json);
|
|
140
|
+
} catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function isJwtExpiringSoon(token, bufferMs = 5 * 60 * 1e3) {
|
|
145
|
+
const payload = decodeJwtPayload(token);
|
|
146
|
+
if (!payload) return true;
|
|
147
|
+
const exp = payload["exp"];
|
|
148
|
+
if (typeof exp !== "number") return true;
|
|
149
|
+
return Date.now() >= exp * 1e3 - bufferMs;
|
|
150
|
+
}
|
|
151
|
+
var AuthManager = class {
|
|
152
|
+
apiKey;
|
|
153
|
+
jwt;
|
|
154
|
+
loginParams;
|
|
155
|
+
baseUrl;
|
|
156
|
+
refreshPromise = null;
|
|
157
|
+
constructor(baseUrl, options) {
|
|
158
|
+
this.baseUrl = baseUrl;
|
|
159
|
+
this.apiKey = options.apiKey;
|
|
160
|
+
this.jwt = options.jwt;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Store login credentials so the manager can automatically refresh the JWT.
|
|
164
|
+
* Called internally by {@link VecminClient.login}.
|
|
165
|
+
*/
|
|
166
|
+
setLoginCredentials(params, jwt) {
|
|
167
|
+
this.loginParams = params;
|
|
168
|
+
this.jwt = jwt;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Return the HTTP headers needed for the next request.
|
|
172
|
+
*
|
|
173
|
+
* If a JWT is present and not expiring soon, uses `Authorization: Bearer`.
|
|
174
|
+
* Otherwise falls back to `x-api-key` (if configured).
|
|
175
|
+
*
|
|
176
|
+
* When both are configured, JWT takes precedence; if the JWT is expiring
|
|
177
|
+
* the method triggers a background refresh and falls back to the API key
|
|
178
|
+
* for the current request.
|
|
179
|
+
*/
|
|
180
|
+
async getHeaders() {
|
|
181
|
+
const headers = {};
|
|
182
|
+
if (this.jwt) {
|
|
183
|
+
if (isJwtExpiringSoon(this.jwt)) {
|
|
184
|
+
const freshJwt = await this.refreshJwt();
|
|
185
|
+
if (freshJwt) {
|
|
186
|
+
headers["Authorization"] = `Bearer ${freshJwt}`;
|
|
187
|
+
} else if (this.apiKey) {
|
|
188
|
+
headers["x-api-key"] = this.apiKey;
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
headers["Authorization"] = `Bearer ${this.jwt}`;
|
|
192
|
+
}
|
|
193
|
+
} else if (this.apiKey) {
|
|
194
|
+
headers["x-api-key"] = this.apiKey;
|
|
195
|
+
}
|
|
196
|
+
return headers;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Attempt to refresh the JWT by re-posting the stored login credentials.
|
|
200
|
+
* Uses a singleton promise so concurrent calls don't trigger multiple logins.
|
|
201
|
+
*/
|
|
202
|
+
async refreshJwt() {
|
|
203
|
+
if (!this.loginParams) return null;
|
|
204
|
+
if (this.refreshPromise) return this.refreshPromise;
|
|
205
|
+
this.refreshPromise = (async () => {
|
|
206
|
+
try {
|
|
207
|
+
const result = await fetchJson(
|
|
208
|
+
`${this.baseUrl}/api/v1/cluster/login`,
|
|
209
|
+
{
|
|
210
|
+
method: "POST",
|
|
211
|
+
headers: { "Content-Type": "application/json" },
|
|
212
|
+
body: JSON.stringify(this.loginParams)
|
|
213
|
+
},
|
|
214
|
+
{ maxRetries: 1 }
|
|
215
|
+
// Don't retry aggressively on auth failures.
|
|
216
|
+
);
|
|
217
|
+
this.jwt = result;
|
|
218
|
+
return result;
|
|
219
|
+
} catch {
|
|
220
|
+
return null;
|
|
221
|
+
} finally {
|
|
222
|
+
this.refreshPromise = null;
|
|
223
|
+
}
|
|
224
|
+
})();
|
|
225
|
+
return this.refreshPromise;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// src/client.ts
|
|
230
|
+
var VecminClient = class {
|
|
231
|
+
/** Resolved base URL (trailing slash stripped). */
|
|
232
|
+
baseUrl;
|
|
233
|
+
/** API path prefix. */
|
|
234
|
+
apiUrl;
|
|
235
|
+
/** MCP path prefix. */
|
|
236
|
+
mcpUrl;
|
|
237
|
+
/** Authentication manager. */
|
|
238
|
+
auth;
|
|
239
|
+
/** Request timeout in milliseconds. */
|
|
240
|
+
timeout;
|
|
241
|
+
/** Retry configuration propagated to every request. */
|
|
242
|
+
retryOptions;
|
|
243
|
+
/** Default headers merged into every request. */
|
|
244
|
+
defaultHeaders;
|
|
245
|
+
/** Abort controllers for in-flight requests (used by `close()`). */
|
|
246
|
+
controllers = /* @__PURE__ */ new Set();
|
|
247
|
+
/** Whether the client has been closed. */
|
|
248
|
+
closed = false;
|
|
249
|
+
constructor(options) {
|
|
250
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
251
|
+
this.apiUrl = `${this.baseUrl}/api/v1`;
|
|
252
|
+
this.mcpUrl = `${this.baseUrl}/mcp`;
|
|
253
|
+
this.auth = new AuthManager(this.baseUrl, options);
|
|
254
|
+
this.timeout = options.timeout ?? 3e4;
|
|
255
|
+
this.retryOptions = {
|
|
256
|
+
maxRetries: options.maxRetries ?? 3,
|
|
257
|
+
backoffFactor: options.backoffFactor ?? 0.5
|
|
258
|
+
};
|
|
259
|
+
this.defaultHeaders = {
|
|
260
|
+
"Content-Type": "application/json",
|
|
261
|
+
...options.defaultHeaders
|
|
262
|
+
};
|
|
263
|
+
if (options.agentId) this.defaultHeaders["x-agent-id"] = options.agentId;
|
|
264
|
+
if (options.modelId) this.defaultHeaders["x-model-id"] = options.modelId;
|
|
265
|
+
}
|
|
266
|
+
// -----------------------------------------------------------------------
|
|
267
|
+
// Internal helpers
|
|
268
|
+
// -----------------------------------------------------------------------
|
|
269
|
+
/** Build the full headers map for an outgoing request. */
|
|
270
|
+
async buildHeaders() {
|
|
271
|
+
const authHeaders = await this.auth.getHeaders();
|
|
272
|
+
return { ...this.defaultHeaders, ...authHeaders };
|
|
273
|
+
}
|
|
274
|
+
/** Create an AbortController with the configured timeout and track it. */
|
|
275
|
+
createController() {
|
|
276
|
+
const controller = new AbortController();
|
|
277
|
+
this.controllers.add(controller);
|
|
278
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
279
|
+
if (timer.unref) timer.unref();
|
|
280
|
+
controller.signal.addEventListener("abort", () => {
|
|
281
|
+
this.controllers.delete(controller);
|
|
282
|
+
});
|
|
283
|
+
return controller;
|
|
284
|
+
}
|
|
285
|
+
/** Perform a JSON GET request. */
|
|
286
|
+
async get(path) {
|
|
287
|
+
this.assertOpen();
|
|
288
|
+
const controller = this.createController();
|
|
289
|
+
try {
|
|
290
|
+
return await fetchJson(
|
|
291
|
+
`${this.apiUrl}${path}`,
|
|
292
|
+
{
|
|
293
|
+
method: "GET",
|
|
294
|
+
headers: await this.buildHeaders(),
|
|
295
|
+
signal: controller.signal
|
|
296
|
+
},
|
|
297
|
+
this.retryOptions
|
|
298
|
+
);
|
|
299
|
+
} finally {
|
|
300
|
+
this.controllers.delete(controller);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/** Perform a JSON POST request. */
|
|
304
|
+
async post(path, body) {
|
|
305
|
+
this.assertOpen();
|
|
306
|
+
const controller = this.createController();
|
|
307
|
+
try {
|
|
308
|
+
return await fetchJson(
|
|
309
|
+
`${this.apiUrl}${path}`,
|
|
310
|
+
{
|
|
311
|
+
method: "POST",
|
|
312
|
+
headers: await this.buildHeaders(),
|
|
313
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
314
|
+
signal: controller.signal
|
|
315
|
+
},
|
|
316
|
+
this.retryOptions
|
|
317
|
+
);
|
|
318
|
+
} finally {
|
|
319
|
+
this.controllers.delete(controller);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/** Perform a JSON DELETE request. */
|
|
323
|
+
async del(path) {
|
|
324
|
+
this.assertOpen();
|
|
325
|
+
const controller = this.createController();
|
|
326
|
+
try {
|
|
327
|
+
return await fetchJson(
|
|
328
|
+
`${this.apiUrl}${path}`,
|
|
329
|
+
{
|
|
330
|
+
method: "DELETE",
|
|
331
|
+
headers: await this.buildHeaders(),
|
|
332
|
+
signal: controller.signal
|
|
333
|
+
},
|
|
334
|
+
this.retryOptions
|
|
335
|
+
);
|
|
336
|
+
} finally {
|
|
337
|
+
this.controllers.delete(controller);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/** Throw if the client has been closed. */
|
|
341
|
+
assertOpen() {
|
|
342
|
+
if (this.closed) {
|
|
343
|
+
throw new Error("VecminClient has been closed");
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// -----------------------------------------------------------------------
|
|
347
|
+
// Collection Management
|
|
348
|
+
// -----------------------------------------------------------------------
|
|
349
|
+
/**
|
|
350
|
+
* Create a new collection.
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* ```ts
|
|
354
|
+
* const col = await client.createCollection({
|
|
355
|
+
* name: "docs",
|
|
356
|
+
* dimension: 1536,
|
|
357
|
+
* metric_type: "Cosine",
|
|
358
|
+
* index_type: "HNSW",
|
|
359
|
+
* });
|
|
360
|
+
* ```
|
|
361
|
+
*/
|
|
362
|
+
async createCollection(params) {
|
|
363
|
+
return this.post("/collections", params);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* List all collections.
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
* ```ts
|
|
370
|
+
* const collections = await client.listCollections();
|
|
371
|
+
* ```
|
|
372
|
+
*/
|
|
373
|
+
async listCollections() {
|
|
374
|
+
return this.get("/collections");
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Get details for a specific collection.
|
|
378
|
+
*
|
|
379
|
+
* @example
|
|
380
|
+
* ```ts
|
|
381
|
+
* const col = await client.getCollection("docs");
|
|
382
|
+
* ```
|
|
383
|
+
*/
|
|
384
|
+
async getCollection(name) {
|
|
385
|
+
return this.get(`/collections/${encodeURIComponent(name)}`);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Delete a collection and all its data.
|
|
389
|
+
*
|
|
390
|
+
* @example
|
|
391
|
+
* ```ts
|
|
392
|
+
* await client.deleteCollection("docs");
|
|
393
|
+
* ```
|
|
394
|
+
*/
|
|
395
|
+
async deleteCollection(name) {
|
|
396
|
+
await this.del(`/collections/${encodeURIComponent(name)}`);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Get statistics for a collection.
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* ```ts
|
|
403
|
+
* const stats = await client.getCollectionStats("docs");
|
|
404
|
+
* console.log(`Vectors: ${stats.vector_count}`);
|
|
405
|
+
* ```
|
|
406
|
+
*/
|
|
407
|
+
async getCollectionStats(name) {
|
|
408
|
+
return this.get(`/collections/${encodeURIComponent(name)}/stats`);
|
|
409
|
+
}
|
|
410
|
+
// -----------------------------------------------------------------------
|
|
411
|
+
// Vector Operations
|
|
412
|
+
// -----------------------------------------------------------------------
|
|
413
|
+
/**
|
|
414
|
+
* Insert a single vector into a collection.
|
|
415
|
+
*
|
|
416
|
+
* @returns The vector ID (either the caller-supplied ID or a server-generated one).
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* ```ts
|
|
420
|
+
* const id = await client.insert("docs", {
|
|
421
|
+
* vector: [0.1, 0.2, /* ... *\/],
|
|
422
|
+
* metadata: { title: "Hello" },
|
|
423
|
+
* });
|
|
424
|
+
* ```
|
|
425
|
+
*/
|
|
426
|
+
async insert(collection, params) {
|
|
427
|
+
return this.post(`/collections/${encodeURIComponent(collection)}/insert`, params);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Insert multiple vectors in a single request.
|
|
431
|
+
*
|
|
432
|
+
* @returns An array of vector IDs in the same order as the input.
|
|
433
|
+
*
|
|
434
|
+
* @example
|
|
435
|
+
* ```ts
|
|
436
|
+
* const ids = await client.batchInsert("docs", [
|
|
437
|
+
* { vector: [0.1, 0.2], metadata: { title: "A" } },
|
|
438
|
+
* { vector: [0.3, 0.4], metadata: { title: "B" } },
|
|
439
|
+
* ]);
|
|
440
|
+
* ```
|
|
441
|
+
*/
|
|
442
|
+
async batchInsert(collection, vectors) {
|
|
443
|
+
return this.post(`/collections/${encodeURIComponent(collection)}/batch`, vectors);
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Search for the nearest neighbours of a query vector.
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* ```ts
|
|
450
|
+
* const results = await client.search("docs", {
|
|
451
|
+
* query: [0.1, 0.2, /* ... *\/],
|
|
452
|
+
* k: 5,
|
|
453
|
+
* ef_search: 100,
|
|
454
|
+
* });
|
|
455
|
+
* for (const r of results) {
|
|
456
|
+
* console.log(r.id, r.score);
|
|
457
|
+
* }
|
|
458
|
+
* ```
|
|
459
|
+
*/
|
|
460
|
+
async search(collection, params) {
|
|
461
|
+
return this.post(`/collections/${encodeURIComponent(collection)}/search`, params);
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Retrieve a single vector by its ID.
|
|
465
|
+
*
|
|
466
|
+
* @example
|
|
467
|
+
* ```ts
|
|
468
|
+
* const vec = await client.getVector("docs", "abc-123");
|
|
469
|
+
* ```
|
|
470
|
+
*/
|
|
471
|
+
async getVector(collection, id) {
|
|
472
|
+
return this.get(`/collections/${encodeURIComponent(collection)}/vectors/${encodeURIComponent(id)}`);
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Delete a single vector by its ID.
|
|
476
|
+
*
|
|
477
|
+
* @example
|
|
478
|
+
* ```ts
|
|
479
|
+
* await client.deleteVector("docs", "abc-123");
|
|
480
|
+
* ```
|
|
481
|
+
*/
|
|
482
|
+
async deleteVector(collection, id) {
|
|
483
|
+
await this.del(`/collections/${encodeURIComponent(collection)}/vectors/${encodeURIComponent(id)}`);
|
|
484
|
+
}
|
|
485
|
+
// -----------------------------------------------------------------------
|
|
486
|
+
// Index Management
|
|
487
|
+
// -----------------------------------------------------------------------
|
|
488
|
+
/**
|
|
489
|
+
* Trigger an index rebuild for a collection.
|
|
490
|
+
*
|
|
491
|
+
* @example
|
|
492
|
+
* ```ts
|
|
493
|
+
* await client.rebuildIndex("docs");
|
|
494
|
+
* ```
|
|
495
|
+
*/
|
|
496
|
+
async rebuildIndex(collection) {
|
|
497
|
+
await this.post(`/collections/${encodeURIComponent(collection)}/rebuild_index`);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* List all indexes in the cluster.
|
|
501
|
+
*
|
|
502
|
+
* @example
|
|
503
|
+
* ```ts
|
|
504
|
+
* const indexes = await client.listIndexes();
|
|
505
|
+
* ```
|
|
506
|
+
*/
|
|
507
|
+
async listIndexes() {
|
|
508
|
+
return this.get("/indexes");
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Rebuild a named index.
|
|
512
|
+
*
|
|
513
|
+
* @example
|
|
514
|
+
* ```ts
|
|
515
|
+
* await client.rebuildNamedIndex("docs_hnsw");
|
|
516
|
+
* ```
|
|
517
|
+
*/
|
|
518
|
+
async rebuildNamedIndex(name) {
|
|
519
|
+
await this.post(`/indexes/${encodeURIComponent(name)}/rebuild`);
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Optimize a named index.
|
|
523
|
+
*
|
|
524
|
+
* @example
|
|
525
|
+
* ```ts
|
|
526
|
+
* await client.optimizeIndex("docs_hnsw");
|
|
527
|
+
* ```
|
|
528
|
+
*/
|
|
529
|
+
async optimizeIndex(name) {
|
|
530
|
+
await this.post(`/indexes/${encodeURIComponent(name)}/optimize`);
|
|
531
|
+
}
|
|
532
|
+
// -----------------------------------------------------------------------
|
|
533
|
+
// Cluster Management
|
|
534
|
+
// -----------------------------------------------------------------------
|
|
535
|
+
/**
|
|
536
|
+
* Authenticate and obtain a JWT token.
|
|
537
|
+
* The token is cached and automatically refreshed before expiry.
|
|
538
|
+
*
|
|
539
|
+
* @returns The JWT string.
|
|
540
|
+
*
|
|
541
|
+
* @example
|
|
542
|
+
* ```ts
|
|
543
|
+
* const jwt = await client.login({ username: "admin", password: "secret" });
|
|
544
|
+
* ```
|
|
545
|
+
*/
|
|
546
|
+
async login(credentials) {
|
|
547
|
+
const jwt = await this.post("/cluster/login", credentials);
|
|
548
|
+
this.auth.setLoginCredentials(credentials, jwt);
|
|
549
|
+
return jwt;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* List all nodes in the cluster.
|
|
553
|
+
*
|
|
554
|
+
* @example
|
|
555
|
+
* ```ts
|
|
556
|
+
* const nodes = await client.listNodes();
|
|
557
|
+
* ```
|
|
558
|
+
*/
|
|
559
|
+
async listNodes() {
|
|
560
|
+
return this.get("/cluster/nodes");
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Get the overall cluster status.
|
|
564
|
+
*
|
|
565
|
+
* @example
|
|
566
|
+
* ```ts
|
|
567
|
+
* const status = await client.clusterStatus();
|
|
568
|
+
* console.log(status.status, status.leader_id);
|
|
569
|
+
* ```
|
|
570
|
+
*/
|
|
571
|
+
async clusterStatus() {
|
|
572
|
+
return this.get("/cluster/status");
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Create a cluster snapshot.
|
|
576
|
+
*
|
|
577
|
+
* @example
|
|
578
|
+
* ```ts
|
|
579
|
+
* await client.createSnapshot();
|
|
580
|
+
* ```
|
|
581
|
+
*/
|
|
582
|
+
async createSnapshot() {
|
|
583
|
+
await this.post("/cluster/snapshot");
|
|
584
|
+
}
|
|
585
|
+
// -----------------------------------------------------------------------
|
|
586
|
+
// MCP Convenience Methods
|
|
587
|
+
// -----------------------------------------------------------------------
|
|
588
|
+
/**
|
|
589
|
+
* Store a memory via the MCP `store_memory` tool.
|
|
590
|
+
*
|
|
591
|
+
* This is a convenience wrapper that calls the MCP JSON-RPC endpoint.
|
|
592
|
+
*
|
|
593
|
+
* @example
|
|
594
|
+
* ```ts
|
|
595
|
+
* await client.mcpStoreMemory("User prefers dark mode", "agent-1");
|
|
596
|
+
* ```
|
|
597
|
+
*/
|
|
598
|
+
async mcpStoreMemory(text, agentId, options) {
|
|
599
|
+
const controller = this.createController();
|
|
600
|
+
try {
|
|
601
|
+
await fetchJson(
|
|
602
|
+
`${this.mcpUrl}/messages`,
|
|
603
|
+
{
|
|
604
|
+
method: "POST",
|
|
605
|
+
headers: await this.buildHeaders(),
|
|
606
|
+
body: JSON.stringify({
|
|
607
|
+
jsonrpc: "2.0",
|
|
608
|
+
id: Date.now(),
|
|
609
|
+
method: "tools/call",
|
|
610
|
+
params: {
|
|
611
|
+
name: "store_memory",
|
|
612
|
+
arguments: {
|
|
613
|
+
agent_id: agentId,
|
|
614
|
+
content: text,
|
|
615
|
+
source: options?.source ?? "typescript-sdk",
|
|
616
|
+
collection: options?.collection ?? "agent_memory_mcp"
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}),
|
|
620
|
+
signal: controller.signal
|
|
621
|
+
},
|
|
622
|
+
this.retryOptions
|
|
623
|
+
);
|
|
624
|
+
} finally {
|
|
625
|
+
this.controllers.delete(controller);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Search memories via the MCP `search_memory` tool.
|
|
630
|
+
*
|
|
631
|
+
* @example
|
|
632
|
+
* ```ts
|
|
633
|
+
* const results = await client.mcpSearchMemory("user preferences", "agent-1", 5);
|
|
634
|
+
* ```
|
|
635
|
+
*/
|
|
636
|
+
async mcpSearchMemory(query, agentId, topK = 5) {
|
|
637
|
+
const controller = this.createController();
|
|
638
|
+
try {
|
|
639
|
+
const result = await fetchJson(
|
|
640
|
+
`${this.mcpUrl}/messages`,
|
|
641
|
+
{
|
|
642
|
+
method: "POST",
|
|
643
|
+
headers: await this.buildHeaders(),
|
|
644
|
+
body: JSON.stringify({
|
|
645
|
+
jsonrpc: "2.0",
|
|
646
|
+
id: Date.now(),
|
|
647
|
+
method: "tools/call",
|
|
648
|
+
params: {
|
|
649
|
+
name: "search_memory",
|
|
650
|
+
arguments: {
|
|
651
|
+
agent_id: agentId,
|
|
652
|
+
query,
|
|
653
|
+
top_k: topK
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}),
|
|
657
|
+
signal: controller.signal
|
|
658
|
+
},
|
|
659
|
+
this.retryOptions
|
|
660
|
+
);
|
|
661
|
+
return result;
|
|
662
|
+
} finally {
|
|
663
|
+
this.controllers.delete(controller);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
// -----------------------------------------------------------------------
|
|
667
|
+
// Lifecycle
|
|
668
|
+
// -----------------------------------------------------------------------
|
|
669
|
+
/**
|
|
670
|
+
* Gracefully shut down the client.
|
|
671
|
+
* Aborts all in-flight requests and releases resources.
|
|
672
|
+
*
|
|
673
|
+
* @example
|
|
674
|
+
* ```ts
|
|
675
|
+
* await client.close();
|
|
676
|
+
* ```
|
|
677
|
+
*/
|
|
678
|
+
async close() {
|
|
679
|
+
this.closed = true;
|
|
680
|
+
for (const controller of this.controllers) {
|
|
681
|
+
controller.abort();
|
|
682
|
+
}
|
|
683
|
+
this.controllers.clear();
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
// src/mcp.ts
|
|
688
|
+
var VecminMCPClient = class {
|
|
689
|
+
baseUrl;
|
|
690
|
+
mcpUrl;
|
|
691
|
+
options;
|
|
692
|
+
handlers = /* @__PURE__ */ new Map();
|
|
693
|
+
rpcId = 0;
|
|
694
|
+
pendingRpc = /* @__PURE__ */ new Map();
|
|
695
|
+
abortController = null;
|
|
696
|
+
reader = null;
|
|
697
|
+
connected = false;
|
|
698
|
+
constructor(baseUrl, options) {
|
|
699
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
700
|
+
this.mcpUrl = `${this.baseUrl}/mcp`;
|
|
701
|
+
this.options = options ?? {};
|
|
702
|
+
}
|
|
703
|
+
// -----------------------------------------------------------------------
|
|
704
|
+
// Event emitter
|
|
705
|
+
// -----------------------------------------------------------------------
|
|
706
|
+
/**
|
|
707
|
+
* Register an event handler.
|
|
708
|
+
*
|
|
709
|
+
* @param event - One of the {@link MCPEventMap} keys.
|
|
710
|
+
* @param handler - Callback invoked when the event fires.
|
|
711
|
+
*/
|
|
712
|
+
on(event, handler) {
|
|
713
|
+
if (!this.handlers.has(event)) {
|
|
714
|
+
this.handlers.set(event, /* @__PURE__ */ new Set());
|
|
715
|
+
}
|
|
716
|
+
this.handlers.get(event).add(handler);
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Remove a previously registered event handler.
|
|
720
|
+
*/
|
|
721
|
+
off(event, handler) {
|
|
722
|
+
this.handlers.get(event)?.delete(handler);
|
|
723
|
+
}
|
|
724
|
+
emit(event, data) {
|
|
725
|
+
for (const handler of this.handlers.get(event) ?? []) {
|
|
726
|
+
try {
|
|
727
|
+
handler(data);
|
|
728
|
+
} catch {
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
// -----------------------------------------------------------------------
|
|
733
|
+
// SSE Connection
|
|
734
|
+
// -----------------------------------------------------------------------
|
|
735
|
+
/**
|
|
736
|
+
* Open the SSE event-stream connection to `GET /mcp/sse`.
|
|
737
|
+
*
|
|
738
|
+
* The connection remains open until {@link disconnect} is called.
|
|
739
|
+
* Events are parsed and emitted via the `on("event", ...)` interface.
|
|
740
|
+
*/
|
|
741
|
+
async connect() {
|
|
742
|
+
if (this.connected) return;
|
|
743
|
+
this.abortController = new AbortController();
|
|
744
|
+
const headers = {
|
|
745
|
+
Accept: "text/event-stream"
|
|
746
|
+
};
|
|
747
|
+
if (this.options.apiKey) headers["x-api-key"] = this.options.apiKey;
|
|
748
|
+
if (this.options.jwt) headers["Authorization"] = `Bearer ${this.options.jwt}`;
|
|
749
|
+
if (this.options.agentId) headers["x-agent-id"] = this.options.agentId;
|
|
750
|
+
if (this.options.modelId) headers["x-model-id"] = this.options.modelId;
|
|
751
|
+
const response = await fetch(`${this.mcpUrl}/sse`, {
|
|
752
|
+
method: "GET",
|
|
753
|
+
headers,
|
|
754
|
+
signal: this.abortController.signal
|
|
755
|
+
});
|
|
756
|
+
if (!response.ok) {
|
|
757
|
+
throw createErrorFromStatus(
|
|
758
|
+
response.status,
|
|
759
|
+
`SSE connection failed: HTTP ${response.status}`
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
const body = response.body;
|
|
763
|
+
if (!body) {
|
|
764
|
+
throw new VecminError("SSE connection returned no body", 0);
|
|
765
|
+
}
|
|
766
|
+
this.connected = true;
|
|
767
|
+
this.emit("connected", void 0);
|
|
768
|
+
this.readSSEStream(body).catch((err) => {
|
|
769
|
+
if (this.connected) {
|
|
770
|
+
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Read and parse an SSE stream using `eventsource-parser`.
|
|
776
|
+
*/
|
|
777
|
+
async readSSEStream(stream) {
|
|
778
|
+
const { createParser } = await import("eventsource-parser");
|
|
779
|
+
const parser = createParser((event) => {
|
|
780
|
+
if (!("data" in event)) return;
|
|
781
|
+
this.emit("event", { event: event.event ?? "message", data: event.data });
|
|
782
|
+
if (event.data) {
|
|
783
|
+
try {
|
|
784
|
+
const json = JSON.parse(event.data);
|
|
785
|
+
if (json.id !== void 0 && json.id !== null) {
|
|
786
|
+
const pending = this.pendingRpc.get(json.id);
|
|
787
|
+
if (pending) {
|
|
788
|
+
this.pendingRpc.delete(json.id);
|
|
789
|
+
if (json.error) {
|
|
790
|
+
pending.reject(
|
|
791
|
+
new VecminError(json.error.message ?? "JSON-RPC error", json.error.code)
|
|
792
|
+
);
|
|
793
|
+
} else {
|
|
794
|
+
pending.resolve(json.result);
|
|
795
|
+
}
|
|
796
|
+
this.emit("result", { id: json.id, result: json.result });
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
} catch {
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
this.reader = stream.getReader();
|
|
804
|
+
const decoder = new TextDecoder();
|
|
805
|
+
try {
|
|
806
|
+
while (true) {
|
|
807
|
+
const { done, value } = await this.reader.read();
|
|
808
|
+
if (done) break;
|
|
809
|
+
parser.feed(decoder.decode(value, { stream: true }));
|
|
810
|
+
}
|
|
811
|
+
} finally {
|
|
812
|
+
this.connected = false;
|
|
813
|
+
this.emit("disconnected", void 0);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
// -----------------------------------------------------------------------
|
|
817
|
+
// JSON-RPC Calls
|
|
818
|
+
// -----------------------------------------------------------------------
|
|
819
|
+
/**
|
|
820
|
+
* Send a JSON-RPC 2.0 request to `POST /mcp/messages`.
|
|
821
|
+
*
|
|
822
|
+
* @returns The `result` field of the JSON-RPC response.
|
|
823
|
+
*/
|
|
824
|
+
async rpc(method, params) {
|
|
825
|
+
const id = ++this.rpcId;
|
|
826
|
+
const request = {
|
|
827
|
+
jsonrpc: "2.0",
|
|
828
|
+
id,
|
|
829
|
+
method,
|
|
830
|
+
params
|
|
831
|
+
};
|
|
832
|
+
const headers = {
|
|
833
|
+
"Content-Type": "application/json"
|
|
834
|
+
};
|
|
835
|
+
if (this.options.apiKey) headers["x-api-key"] = this.options.apiKey;
|
|
836
|
+
if (this.options.jwt) headers["Authorization"] = `Bearer ${this.options.jwt}`;
|
|
837
|
+
if (this.options.agentId) headers["x-agent-id"] = this.options.agentId;
|
|
838
|
+
if (this.options.modelId) headers["x-model-id"] = this.options.modelId;
|
|
839
|
+
const rpcPromise = new Promise((resolve, reject) => {
|
|
840
|
+
this.pendingRpc.set(id, { resolve, reject });
|
|
841
|
+
});
|
|
842
|
+
const res = await fetch(`${this.mcpUrl}/messages`, {
|
|
843
|
+
method: "POST",
|
|
844
|
+
headers,
|
|
845
|
+
body: JSON.stringify(request)
|
|
846
|
+
});
|
|
847
|
+
if (!res.ok) {
|
|
848
|
+
this.pendingRpc.delete(id);
|
|
849
|
+
throw createErrorFromStatus(res.status, `MCP RPC call failed: HTTP ${res.status}`);
|
|
850
|
+
}
|
|
851
|
+
const text = await res.text();
|
|
852
|
+
if (text) {
|
|
853
|
+
try {
|
|
854
|
+
const json = JSON.parse(text);
|
|
855
|
+
this.pendingRpc.delete(id);
|
|
856
|
+
if (json.error) {
|
|
857
|
+
throw new VecminError(json.error.message ?? "JSON-RPC error", json.error.code);
|
|
858
|
+
}
|
|
859
|
+
return json.result ?? json;
|
|
860
|
+
} catch (err) {
|
|
861
|
+
if (err instanceof VecminError) throw err;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
return rpcPromise;
|
|
865
|
+
}
|
|
866
|
+
// -----------------------------------------------------------------------
|
|
867
|
+
// MCP Tool Methods
|
|
868
|
+
// -----------------------------------------------------------------------
|
|
869
|
+
/**
|
|
870
|
+
* Call the `store_memory` MCP tool.
|
|
871
|
+
*
|
|
872
|
+
* @example
|
|
873
|
+
* ```ts
|
|
874
|
+
* await mcp.storeMemory({ text: "User prefers dark mode", agent_id: "agent-1" });
|
|
875
|
+
* ```
|
|
876
|
+
*/
|
|
877
|
+
async storeMemory(params) {
|
|
878
|
+
await this.rpc("tools/call", {
|
|
879
|
+
name: "store_memory",
|
|
880
|
+
arguments: {
|
|
881
|
+
agent_id: params.agent_id,
|
|
882
|
+
text: params.text,
|
|
883
|
+
source: params.source ?? "typescript-sdk",
|
|
884
|
+
collection: params.collection ?? "agent_memory_mcp"
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Call the `search_memory` MCP tool.
|
|
890
|
+
*
|
|
891
|
+
* @example
|
|
892
|
+
* ```ts
|
|
893
|
+
* const results = await mcp.searchMemory({ query: "preferences", agent_id: "agent-1", top_k: 5 });
|
|
894
|
+
* ```
|
|
895
|
+
*/
|
|
896
|
+
async searchMemory(params) {
|
|
897
|
+
const result = await this.rpc("tools/call", {
|
|
898
|
+
name: "search_memory",
|
|
899
|
+
arguments: {
|
|
900
|
+
agent_id: params.agent_id,
|
|
901
|
+
query: params.query,
|
|
902
|
+
top_k: params.top_k ?? 5,
|
|
903
|
+
collection: params.collection ?? "agent_memory_mcp"
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
return result;
|
|
907
|
+
}
|
|
908
|
+
// -----------------------------------------------------------------------
|
|
909
|
+
// Lifecycle
|
|
910
|
+
// -----------------------------------------------------------------------
|
|
911
|
+
/**
|
|
912
|
+
* Close the SSE connection and release all resources.
|
|
913
|
+
*/
|
|
914
|
+
async disconnect() {
|
|
915
|
+
this.connected = false;
|
|
916
|
+
this.abortController?.abort();
|
|
917
|
+
this.abortController = null;
|
|
918
|
+
try {
|
|
919
|
+
await this.reader?.cancel();
|
|
920
|
+
} catch {
|
|
921
|
+
}
|
|
922
|
+
this.reader = null;
|
|
923
|
+
for (const [id, { reject }] of this.pendingRpc) {
|
|
924
|
+
reject(new VecminError("MCP client disconnected", 0));
|
|
925
|
+
this.pendingRpc.delete(id);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
// src/langchain.ts
|
|
931
|
+
var LangChain = null;
|
|
932
|
+
var LangChainDocs = null;
|
|
933
|
+
async function ensureLangChain() {
|
|
934
|
+
if (LangChain && LangChainDocs) {
|
|
935
|
+
return {
|
|
936
|
+
VectorStore: LangChain.VectorStore,
|
|
937
|
+
Document: LangChainDocs.Document,
|
|
938
|
+
Embeddings: (await import("@langchain/core/embeddings")).Embeddings
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
try {
|
|
942
|
+
const vc = await import("@langchain/core/vectorstores");
|
|
943
|
+
const docs = await import("@langchain/core/documents");
|
|
944
|
+
const emb = await import("@langchain/core/embeddings");
|
|
945
|
+
LangChain = vc;
|
|
946
|
+
LangChainDocs = docs;
|
|
947
|
+
return {
|
|
948
|
+
VectorStore: vc.VectorStore,
|
|
949
|
+
Document: docs.Document,
|
|
950
|
+
Embeddings: emb.Embeddings
|
|
951
|
+
};
|
|
952
|
+
} catch {
|
|
953
|
+
throw new Error(
|
|
954
|
+
"@langchain/core is required for VecminDBVectorStore. Install it with: npm install @langchain/core"
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
var VecminDBVectorStore = class _VecminDBVectorStore {
|
|
959
|
+
/** Underlying VecminDB client used for all I/O. */
|
|
960
|
+
client;
|
|
961
|
+
/** LangChain embeddings instance. */
|
|
962
|
+
_embeddings;
|
|
963
|
+
// typed as unknown to avoid requiring @langchain/core at import time
|
|
964
|
+
/** Collection name in VecminDB. */
|
|
965
|
+
collectionName;
|
|
966
|
+
/** Vector dimensionality. */
|
|
967
|
+
dimension;
|
|
968
|
+
/** Whether the collection has been ensured to exist. */
|
|
969
|
+
ensured = false;
|
|
970
|
+
// LangChain VectorStore interface fields
|
|
971
|
+
/** @internal */
|
|
972
|
+
lc_namespace = ["vecmindb", "vectorstores"];
|
|
973
|
+
/** @internal */
|
|
974
|
+
lc_serializable = true;
|
|
975
|
+
constructor(embeddings, options) {
|
|
976
|
+
this._embeddings = embeddings;
|
|
977
|
+
this.collectionName = options.collectionName ?? "langchain_memory";
|
|
978
|
+
this.dimension = options.dimension ?? 1536;
|
|
979
|
+
this.client = new VecminClient({
|
|
980
|
+
baseUrl: options.baseUrl,
|
|
981
|
+
apiKey: options.apiKey,
|
|
982
|
+
timeout: options.timeout
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
// -----------------------------------------------------------------------
|
|
986
|
+
// Collection bootstrap
|
|
987
|
+
// -----------------------------------------------------------------------
|
|
988
|
+
/**
|
|
989
|
+
* Ensure the target collection exists in VecminDB.
|
|
990
|
+
* Called lazily before the first write operation.
|
|
991
|
+
*/
|
|
992
|
+
async ensureCollection() {
|
|
993
|
+
if (this.ensured) return;
|
|
994
|
+
try {
|
|
995
|
+
await this.client.createCollection({
|
|
996
|
+
name: this.collectionName,
|
|
997
|
+
dimension: this.dimension,
|
|
998
|
+
metric_type: "Cosine",
|
|
999
|
+
index_type: "HNSW",
|
|
1000
|
+
index_params: { m: 16, ef_construction: 100 }
|
|
1001
|
+
});
|
|
1002
|
+
} catch (err) {
|
|
1003
|
+
if (err instanceof Error && err.message?.includes("already exists")) {
|
|
1004
|
+
} else if (err instanceof Error && (err.message?.includes("409") || err.message?.includes("Conflict"))) {
|
|
1005
|
+
} else if (err instanceof Error && err.message?.includes("404") === false) {
|
|
1006
|
+
throw err;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
this.ensured = true;
|
|
1010
|
+
}
|
|
1011
|
+
// -----------------------------------------------------------------------
|
|
1012
|
+
// LangChain VectorStore interface
|
|
1013
|
+
// -----------------------------------------------------------------------
|
|
1014
|
+
/**
|
|
1015
|
+
* Add documents to the vector store.
|
|
1016
|
+
* Embeds the document texts and inserts them into VecminDB.
|
|
1017
|
+
*/
|
|
1018
|
+
async addDocuments(documents) {
|
|
1019
|
+
const { Embeddings } = await ensureLangChain();
|
|
1020
|
+
const emb = this._embeddings;
|
|
1021
|
+
const texts = documents.map((d) => d.pageContent);
|
|
1022
|
+
const metadatas = documents.map((d) => d.metadata ?? {});
|
|
1023
|
+
const vectors = await emb.embedDocuments(texts);
|
|
1024
|
+
return this.addVectors(vectors, documents, metadatas);
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Add pre-computed vectors to the vector store.
|
|
1028
|
+
*/
|
|
1029
|
+
async addVectors(vectors, documents, metadatas) {
|
|
1030
|
+
await this.ensureCollection();
|
|
1031
|
+
const ids = [];
|
|
1032
|
+
for (let i = 0; i < vectors.length; i++) {
|
|
1033
|
+
const doc = documents[i];
|
|
1034
|
+
const meta = {
|
|
1035
|
+
...metadatas?.[i] ?? doc.metadata ?? {},
|
|
1036
|
+
text: doc.pageContent
|
|
1037
|
+
// Store the text in metadata for retrieval.
|
|
1038
|
+
};
|
|
1039
|
+
const id = await this.client.insert(this.collectionName, {
|
|
1040
|
+
vector: vectors[i],
|
|
1041
|
+
metadata: meta
|
|
1042
|
+
});
|
|
1043
|
+
ids.push(id);
|
|
1044
|
+
}
|
|
1045
|
+
return ids;
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Search for the most similar vectors and return results with scores.
|
|
1049
|
+
*
|
|
1050
|
+
* @param query - Query vector.
|
|
1051
|
+
* @param k - Number of results. @default 4
|
|
1052
|
+
*/
|
|
1053
|
+
async similaritySearchVectorWithScore(query, k = 4) {
|
|
1054
|
+
const results = await this.client.search(this.collectionName, {
|
|
1055
|
+
query,
|
|
1056
|
+
k
|
|
1057
|
+
});
|
|
1058
|
+
return results.map((r) => {
|
|
1059
|
+
const meta = r.metadata ?? {};
|
|
1060
|
+
const text = typeof meta["text"] === "string" ? meta["text"] : "";
|
|
1061
|
+
const cleanMeta = { ...meta };
|
|
1062
|
+
delete cleanMeta["text"];
|
|
1063
|
+
return [
|
|
1064
|
+
{ pageContent: text, metadata: cleanMeta },
|
|
1065
|
+
r.score
|
|
1066
|
+
];
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Convenience method: search for similar documents (without scores).
|
|
1071
|
+
*/
|
|
1072
|
+
async similaritySearch(query, k = 4) {
|
|
1073
|
+
const { Embeddings } = await ensureLangChain();
|
|
1074
|
+
const emb = this._embeddings;
|
|
1075
|
+
const queryVector = await emb.embedQuery(query);
|
|
1076
|
+
const results = await this.similaritySearchVectorWithScore(queryVector, k);
|
|
1077
|
+
return results.map(([doc]) => doc);
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Convenience method: search for similar documents with scores.
|
|
1081
|
+
*/
|
|
1082
|
+
async similaritySearchWithScore(query, k = 4) {
|
|
1083
|
+
const { Embeddings } = await ensureLangChain();
|
|
1084
|
+
const emb = this._embeddings;
|
|
1085
|
+
const queryVector = await emb.embedQuery(query);
|
|
1086
|
+
return this.similaritySearchVectorWithScore(queryVector, k);
|
|
1087
|
+
}
|
|
1088
|
+
// -----------------------------------------------------------------------
|
|
1089
|
+
// Static factory methods
|
|
1090
|
+
// -----------------------------------------------------------------------
|
|
1091
|
+
/**
|
|
1092
|
+
* Create a VecminDBVectorStore from an array of Document objects.
|
|
1093
|
+
*
|
|
1094
|
+
* @example
|
|
1095
|
+
* ```ts
|
|
1096
|
+
* const store = await VecminDBVectorStore.fromDocuments(
|
|
1097
|
+
* [{ pageContent: "Hello", metadata: {} }],
|
|
1098
|
+
* embeddings,
|
|
1099
|
+
* { baseUrl: "http://localhost:8080" },
|
|
1100
|
+
* );
|
|
1101
|
+
* ```
|
|
1102
|
+
*/
|
|
1103
|
+
static async fromDocuments(documents, embeddings, options) {
|
|
1104
|
+
const store = new _VecminDBVectorStore(embeddings, options);
|
|
1105
|
+
await store.addDocuments(documents);
|
|
1106
|
+
return store;
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Create a VecminDBVectorStore from an array of texts.
|
|
1110
|
+
*
|
|
1111
|
+
* @example
|
|
1112
|
+
* ```ts
|
|
1113
|
+
* const store = await VecminDBVectorStore.fromTexts(
|
|
1114
|
+
* ["Hello world", "Goodbye world"],
|
|
1115
|
+
* [{}, {}],
|
|
1116
|
+
* embeddings,
|
|
1117
|
+
* { baseUrl: "http://localhost:8080" },
|
|
1118
|
+
* );
|
|
1119
|
+
* ```
|
|
1120
|
+
*/
|
|
1121
|
+
static async fromTexts(texts, metadatas, embeddings, options) {
|
|
1122
|
+
const documents = texts.map((text, i) => ({
|
|
1123
|
+
pageContent: text,
|
|
1124
|
+
metadata: metadatas[i] ?? {}
|
|
1125
|
+
}));
|
|
1126
|
+
return _VecminDBVectorStore.fromDocuments(documents, embeddings, options);
|
|
1127
|
+
}
|
|
1128
|
+
// -----------------------------------------------------------------------
|
|
1129
|
+
// Cleanup
|
|
1130
|
+
// -----------------------------------------------------------------------
|
|
1131
|
+
/**
|
|
1132
|
+
* Close the underlying client connection.
|
|
1133
|
+
*/
|
|
1134
|
+
async close() {
|
|
1135
|
+
await this.client.close();
|
|
1136
|
+
}
|
|
1137
|
+
};
|
|
1138
|
+
export {
|
|
1139
|
+
AuthManager,
|
|
1140
|
+
AuthenticationError,
|
|
1141
|
+
NotFoundError,
|
|
1142
|
+
PaymentRequiredError,
|
|
1143
|
+
PermissionError,
|
|
1144
|
+
RateLimitError,
|
|
1145
|
+
ServerError,
|
|
1146
|
+
VecminClient,
|
|
1147
|
+
VecminDBVectorStore,
|
|
1148
|
+
VecminError,
|
|
1149
|
+
VecminMCPClient,
|
|
1150
|
+
createErrorFromStatus,
|
|
1151
|
+
fetchJson,
|
|
1152
|
+
resolveRetryOptions,
|
|
1153
|
+
withRetry
|
|
1154
|
+
};
|