ydb-qdrant 3.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +7 -3
  2. package/dist/SmokeTest.js +1 -1
  3. package/dist/config/env.d.ts +6 -0
  4. package/dist/config/env.js +17 -0
  5. package/dist/index.js +5 -2
  6. package/dist/indexing/IndexScheduler.js +6 -51
  7. package/dist/indexing/IndexScheduler.multi-table.d.ts +12 -0
  8. package/dist/indexing/IndexScheduler.multi-table.js +54 -0
  9. package/dist/indexing/IndexScheduler.one-table.d.ts +1 -0
  10. package/dist/indexing/IndexScheduler.one-table.js +4 -0
  11. package/dist/package/{Api.d.ts → api.d.ts} +3 -2
  12. package/dist/package/api.js +55 -0
  13. package/dist/repositories/collectionsRepo.d.ts +3 -2
  14. package/dist/repositories/collectionsRepo.js +27 -53
  15. package/dist/repositories/collectionsRepo.multi-table.d.ts +3 -0
  16. package/dist/repositories/collectionsRepo.multi-table.js +23 -0
  17. package/dist/repositories/collectionsRepo.one-table.d.ts +3 -0
  18. package/dist/repositories/collectionsRepo.one-table.js +25 -0
  19. package/dist/repositories/collectionsRepo.shared.d.ts +2 -0
  20. package/dist/repositories/collectionsRepo.shared.js +23 -0
  21. package/dist/repositories/pointsRepo.d.ts +3 -3
  22. package/dist/repositories/pointsRepo.js +15 -158
  23. package/dist/repositories/pointsRepo.multi-table.d.ts +12 -0
  24. package/dist/repositories/pointsRepo.multi-table.js +129 -0
  25. package/dist/repositories/pointsRepo.one-table.d.ts +12 -0
  26. package/dist/repositories/pointsRepo.one-table.js +111 -0
  27. package/dist/routes/collections.js +2 -1
  28. package/dist/routes/points.js +2 -1
  29. package/dist/services/CollectionService.d.ts +35 -0
  30. package/dist/services/CollectionService.js +85 -0
  31. package/dist/services/CollectionService.multi-table.d.ts +5 -0
  32. package/dist/services/CollectionService.multi-table.js +7 -0
  33. package/dist/services/CollectionService.one-table.d.ts +5 -0
  34. package/dist/services/CollectionService.one-table.js +9 -0
  35. package/dist/services/CollectionService.shared.d.ts +11 -0
  36. package/dist/services/CollectionService.shared.js +17 -0
  37. package/dist/services/PointsService.d.ts +23 -0
  38. package/dist/services/PointsService.js +128 -0
  39. package/dist/services/errors.d.ts +9 -0
  40. package/dist/services/errors.js +9 -0
  41. package/dist/utils/distance.d.ts +6 -0
  42. package/dist/utils/distance.js +28 -0
  43. package/dist/utils/normalization.d.ts +10 -0
  44. package/dist/utils/normalization.js +92 -0
  45. package/dist/utils/retry.d.ts +8 -0
  46. package/dist/utils/retry.js +28 -0
  47. package/dist/utils/tenant.d.ts +1 -0
  48. package/dist/utils/tenant.js +3 -0
  49. package/dist/ydb/schema.d.ts +2 -0
  50. package/dist/ydb/schema.js +27 -0
  51. package/package.json +4 -4
  52. package/dist/package/Api.js +0 -79
  53. package/dist/services/QdrantService.d.ts +0 -54
  54. package/dist/services/QdrantService.js +0 -313
@@ -1,313 +0,0 @@
1
- import { sanitizeCollectionName, sanitizeTenantId, metaKeyFor, tableNameFor, } from "../utils/tenant.js";
2
- import { CreateCollectionReq, DeletePointsReq, SearchReq, UpsertPointsReq, } from "../types.js";
3
- import { ensureMetaTable } from "../ydb/schema.js";
4
- import { createCollection as repoCreateCollection, deleteCollection as repoDeleteCollection, getCollectionMeta, } from "../repositories/collectionsRepo.js";
5
- import { deletePoints as repoDeletePoints, searchPoints as repoSearchPoints, upsertPoints as repoUpsertPoints, } from "../repositories/pointsRepo.js";
6
- import { requestIndexBuild } from "../indexing/IndexScheduler.js";
7
- import { logger } from "../logging/logger.js";
8
- import { VECTOR_INDEX_BUILD_ENABLED } from "../config/env.js";
9
- export class QdrantServiceError extends Error {
10
- statusCode;
11
- payload;
12
- constructor(statusCode, payload, message) {
13
- super(message ?? String(payload.error));
14
- this.statusCode = statusCode;
15
- this.payload = payload;
16
- }
17
- }
18
- function normalizeCollectionContext(input) {
19
- const tenant = sanitizeTenantId(input.tenant);
20
- const collection = sanitizeCollectionName(input.collection);
21
- const metaKey = metaKeyFor(tenant, collection);
22
- return { tenant, collection, metaKey };
23
- }
24
- export async function putCollectionIndex(ctx) {
25
- await ensureMetaTable();
26
- const normalized = normalizeCollectionContext(ctx);
27
- const meta = await getCollectionMeta(normalized.metaKey);
28
- if (!meta) {
29
- throw new QdrantServiceError(404, {
30
- status: "error",
31
- error: "collection not found",
32
- });
33
- }
34
- return { acknowledged: true };
35
- }
36
- export async function createCollection(ctx, body) {
37
- await ensureMetaTable();
38
- const normalized = normalizeCollectionContext(ctx);
39
- const parsed = CreateCollectionReq.safeParse(body);
40
- if (!parsed.success) {
41
- throw new QdrantServiceError(400, {
42
- status: "error",
43
- error: parsed.error.flatten(),
44
- });
45
- }
46
- const dim = parsed.data.vectors.size;
47
- const distance = parsed.data.vectors.distance;
48
- const vectorType = parsed.data.vectors.data_type ?? "float";
49
- const existing = await getCollectionMeta(normalized.metaKey);
50
- if (existing) {
51
- if (existing.dimension === dim &&
52
- existing.distance === distance &&
53
- existing.vectorType === vectorType) {
54
- return { name: normalized.collection, tenant: normalized.tenant };
55
- }
56
- const errorMessage = `Collection already exists with different config: dimension=${existing.dimension}, distance=${existing.distance}, type=${existing.vectorType}`;
57
- throw new QdrantServiceError(400, {
58
- status: "error",
59
- error: errorMessage,
60
- });
61
- }
62
- const tableName = tableNameFor(normalized.tenant, normalized.collection);
63
- await repoCreateCollection(normalized.metaKey, dim, distance, vectorType, tableName);
64
- return { name: normalized.collection, tenant: normalized.tenant };
65
- }
66
- export async function getCollection(ctx) {
67
- await ensureMetaTable();
68
- const normalized = normalizeCollectionContext(ctx);
69
- const meta = await getCollectionMeta(normalized.metaKey);
70
- if (!meta) {
71
- throw new QdrantServiceError(404, {
72
- status: "error",
73
- error: "collection not found",
74
- });
75
- }
76
- return {
77
- name: normalized.collection,
78
- vectors: {
79
- size: meta.dimension,
80
- distance: meta.distance,
81
- data_type: meta.vectorType,
82
- },
83
- };
84
- }
85
- export async function deleteCollection(ctx) {
86
- await ensureMetaTable();
87
- const normalized = normalizeCollectionContext(ctx);
88
- await repoDeleteCollection(normalized.metaKey);
89
- return { acknowledged: true };
90
- }
91
- let loggedIndexBuildDisabled = false;
92
- function isNumberArray(value) {
93
- return Array.isArray(value) && value.every((x) => typeof x === "number");
94
- }
95
- function extractVectorLoose(body, depth = 0) {
96
- if (!body || typeof body !== "object" || depth > 3) {
97
- return undefined;
98
- }
99
- const obj = body;
100
- if (isNumberArray(obj.vector))
101
- return obj.vector;
102
- if (isNumberArray(obj.embedding))
103
- return obj.embedding;
104
- const query = obj.query;
105
- if (query) {
106
- const queryVector = query["vector"];
107
- if (isNumberArray(queryVector))
108
- return queryVector;
109
- const nearest = query["nearest"];
110
- if (nearest && isNumberArray(nearest.vector)) {
111
- return nearest.vector;
112
- }
113
- }
114
- const nearest = obj.nearest;
115
- if (nearest && isNumberArray(nearest.vector)) {
116
- return nearest.vector;
117
- }
118
- for (const key of Object.keys(obj)) {
119
- const value = obj[key];
120
- if (isNumberArray(value)) {
121
- return value;
122
- }
123
- }
124
- for (const key of Object.keys(obj)) {
125
- const value = obj[key];
126
- if (value && typeof value === "object") {
127
- const found = extractVectorLoose(value, depth + 1);
128
- if (found) {
129
- return found;
130
- }
131
- }
132
- }
133
- return undefined;
134
- }
135
- function normalizeSearchBodyForSearch(body) {
136
- if (!body || typeof body !== "object") {
137
- return {
138
- vector: undefined,
139
- top: undefined,
140
- withPayload: undefined,
141
- scoreThreshold: undefined,
142
- };
143
- }
144
- const b = body;
145
- const rawVector = b["vector"];
146
- const vector = isNumberArray(rawVector) ? rawVector : undefined;
147
- const rawTop = b["top"];
148
- const rawLimit = b["limit"];
149
- const topFromTop = typeof rawTop === "number" ? rawTop : undefined;
150
- const topFromLimit = typeof rawLimit === "number" ? rawLimit : undefined;
151
- const top = topFromTop ?? topFromLimit;
152
- let withPayload;
153
- const rawWithPayload = b["with_payload"];
154
- if (typeof rawWithPayload === "boolean") {
155
- withPayload = rawWithPayload;
156
- }
157
- else if (Array.isArray(rawWithPayload) ||
158
- typeof rawWithPayload === "object") {
159
- withPayload = true;
160
- }
161
- const thresholdRaw = b["score_threshold"];
162
- const thresholdValue = typeof thresholdRaw === "number" ? thresholdRaw : Number(thresholdRaw);
163
- const scoreThreshold = Number.isFinite(thresholdValue)
164
- ? thresholdValue
165
- : undefined;
166
- return { vector, top, withPayload, scoreThreshold };
167
- }
168
- function normalizeSearchBodyForQuery(body) {
169
- if (!body || typeof body !== "object") {
170
- return {
171
- vector: undefined,
172
- top: undefined,
173
- withPayload: undefined,
174
- scoreThreshold: undefined,
175
- };
176
- }
177
- const b = body;
178
- const vector = extractVectorLoose(b);
179
- const rawTop = b["top"];
180
- const rawLimit = b["limit"];
181
- const topFromTop = typeof rawTop === "number" ? rawTop : undefined;
182
- const topFromLimit = typeof rawLimit === "number" ? rawLimit : undefined;
183
- const top = topFromTop ?? topFromLimit;
184
- let withPayload;
185
- const rawWithPayload = b["with_payload"];
186
- if (typeof rawWithPayload === "boolean") {
187
- withPayload = rawWithPayload;
188
- }
189
- else if (Array.isArray(rawWithPayload) ||
190
- typeof rawWithPayload === "object") {
191
- withPayload = true;
192
- }
193
- const thresholdRaw = b["score_threshold"];
194
- const thresholdValue = typeof thresholdRaw === "number" ? thresholdRaw : Number(thresholdRaw);
195
- const scoreThreshold = Number.isFinite(thresholdValue)
196
- ? thresholdValue
197
- : undefined;
198
- return { vector, top, withPayload, scoreThreshold };
199
- }
200
- export async function upsertPoints(ctx, body) {
201
- await ensureMetaTable();
202
- const normalized = normalizeCollectionContext(ctx);
203
- const meta = await getCollectionMeta(normalized.metaKey);
204
- if (!meta) {
205
- throw new QdrantServiceError(404, {
206
- status: "error",
207
- error: "collection not found",
208
- });
209
- }
210
- const parsed = UpsertPointsReq.safeParse(body);
211
- if (!parsed.success) {
212
- throw new QdrantServiceError(400, {
213
- status: "error",
214
- error: parsed.error.flatten(),
215
- });
216
- }
217
- const upserted = await repoUpsertPoints(meta.table, parsed.data.points, meta.dimension);
218
- if (VECTOR_INDEX_BUILD_ENABLED) {
219
- requestIndexBuild(meta.table, meta.dimension, meta.distance, meta.vectorType);
220
- }
221
- else if (!loggedIndexBuildDisabled) {
222
- logger.info({ table: meta.table }, "vector index building disabled by env; skipping automatic emb_idx rebuilds");
223
- loggedIndexBuildDisabled = true;
224
- }
225
- return { upserted };
226
- }
227
- async function executeSearch(ctx, normalizedSearch, source) {
228
- await ensureMetaTable();
229
- const normalized = normalizeCollectionContext(ctx);
230
- logger.info({ tenant: normalized.tenant, collection: normalized.collection }, `${source}: resolve collection meta`);
231
- const meta = await getCollectionMeta(normalized.metaKey);
232
- if (!meta) {
233
- logger.warn({
234
- tenant: normalized.tenant,
235
- collection: normalized.collection,
236
- metaKey: normalized.metaKey,
237
- }, `${source}: collection not found`);
238
- throw new QdrantServiceError(404, {
239
- status: "error",
240
- error: "collection not found",
241
- });
242
- }
243
- const parsed = SearchReq.safeParse({
244
- vector: normalizedSearch.vector,
245
- top: normalizedSearch.top,
246
- with_payload: normalizedSearch.withPayload,
247
- });
248
- if (!parsed.success) {
249
- logger.warn({
250
- tenant: normalized.tenant,
251
- collection: normalized.collection,
252
- issues: parsed.error.issues,
253
- }, `${source}: invalid payload`);
254
- throw new QdrantServiceError(400, {
255
- status: "error",
256
- error: parsed.error.flatten(),
257
- });
258
- }
259
- logger.info({
260
- tenant: normalized.tenant,
261
- collection: normalized.collection,
262
- top: parsed.data.top,
263
- queryVectorLen: parsed.data.vector.length,
264
- collectionDim: meta.dimension,
265
- distance: meta.distance,
266
- vectorType: meta.vectorType,
267
- }, `${source}: executing`);
268
- const hits = await repoSearchPoints(meta.table, parsed.data.vector, parsed.data.top, parsed.data.with_payload, meta.distance, meta.dimension);
269
- const threshold = normalizedSearch.scoreThreshold;
270
- const filtered = threshold === undefined
271
- ? hits
272
- : hits.filter((hit) => {
273
- const isSimilarity = meta.distance === "Cosine" || meta.distance === "Dot";
274
- if (isSimilarity) {
275
- return hit.score >= threshold;
276
- }
277
- return hit.score <= threshold;
278
- });
279
- logger.info({
280
- tenant: normalized.tenant,
281
- collection: normalized.collection,
282
- hits: hits.length,
283
- }, `${source}: completed`);
284
- return { points: filtered };
285
- }
286
- export async function searchPoints(ctx, body) {
287
- const normalizedSearch = normalizeSearchBodyForSearch(body);
288
- return await executeSearch(ctx, normalizedSearch, "search");
289
- }
290
- export async function queryPoints(ctx, body) {
291
- const normalizedSearch = normalizeSearchBodyForQuery(body);
292
- return await executeSearch(ctx, normalizedSearch, "query");
293
- }
294
- export async function deletePoints(ctx, body) {
295
- await ensureMetaTable();
296
- const normalized = normalizeCollectionContext(ctx);
297
- const meta = await getCollectionMeta(normalized.metaKey);
298
- if (!meta) {
299
- throw new QdrantServiceError(404, {
300
- status: "error",
301
- error: "collection not found",
302
- });
303
- }
304
- const parsed = DeletePointsReq.safeParse(body);
305
- if (!parsed.success) {
306
- throw new QdrantServiceError(400, {
307
- status: "error",
308
- error: parsed.error.flatten(),
309
- });
310
- }
311
- const deleted = await repoDeletePoints(meta.table, parsed.data.points);
312
- return { deleted };
313
- }