ydb-qdrant 5.2.1 → 7.0.1
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 +2 -2
- package/dist/config/env.d.ts +9 -3
- package/dist/config/env.js +16 -5
- package/dist/package/api.d.ts +2 -2
- package/dist/package/api.js +2 -2
- package/dist/qdrant/QdrantTypes.d.ts +19 -0
- package/dist/qdrant/QdrantTypes.js +1 -0
- package/dist/repositories/collectionsRepo.d.ts +12 -7
- package/dist/repositories/collectionsRepo.js +157 -39
- package/dist/repositories/collectionsRepo.one-table.js +47 -129
- package/dist/repositories/pointsRepo.d.ts +5 -7
- package/dist/repositories/pointsRepo.js +6 -3
- package/dist/repositories/pointsRepo.one-table/Delete.d.ts +2 -0
- package/dist/repositories/pointsRepo.one-table/Delete.js +111 -0
- package/dist/repositories/pointsRepo.one-table/PathSegmentsFilter.d.ts +11 -0
- package/dist/repositories/pointsRepo.one-table/PathSegmentsFilter.js +32 -0
- package/dist/repositories/pointsRepo.one-table/Search/Approximate.d.ts +18 -0
- package/dist/repositories/pointsRepo.one-table/Search/Approximate.js +119 -0
- package/dist/repositories/pointsRepo.one-table/Search/Exact.d.ts +17 -0
- package/dist/repositories/pointsRepo.one-table/Search/Exact.js +101 -0
- package/dist/repositories/pointsRepo.one-table/Search/index.d.ts +8 -0
- package/dist/repositories/pointsRepo.one-table/Search/index.js +30 -0
- package/dist/repositories/pointsRepo.one-table/Upsert.d.ts +2 -0
- package/dist/repositories/pointsRepo.one-table/Upsert.js +100 -0
- package/dist/repositories/pointsRepo.one-table.d.ts +3 -13
- package/dist/repositories/pointsRepo.one-table.js +3 -403
- package/dist/routes/collections.js +61 -7
- package/dist/routes/points.js +71 -3
- package/dist/server.d.ts +1 -0
- package/dist/server.js +70 -2
- package/dist/services/CollectionService.d.ts +9 -0
- package/dist/services/CollectionService.js +13 -1
- package/dist/services/CollectionService.shared.d.ts +1 -0
- package/dist/services/CollectionService.shared.js +3 -3
- package/dist/services/PointsService.d.ts +8 -10
- package/dist/services/PointsService.js +82 -5
- package/dist/types.d.ts +85 -8
- package/dist/types.js +43 -17
- package/dist/utils/normalization.d.ts +1 -0
- package/dist/utils/normalization.js +15 -13
- package/dist/utils/retry.js +29 -19
- package/dist/utils/tenant.d.ts +2 -2
- package/dist/utils/tenant.js +21 -6
- package/dist/utils/typeGuards.d.ts +1 -0
- package/dist/utils/typeGuards.js +3 -0
- package/dist/utils/vectorBinary.js +88 -9
- package/dist/ydb/QueryDiagnostics.d.ts +6 -0
- package/dist/ydb/QueryDiagnostics.js +52 -0
- package/dist/ydb/SessionPool.d.ts +36 -0
- package/dist/ydb/SessionPool.js +248 -0
- package/dist/ydb/bulkUpsert.d.ts +6 -0
- package/dist/ydb/bulkUpsert.js +52 -0
- package/dist/ydb/client.d.ts +17 -16
- package/dist/ydb/client.js +427 -62
- package/dist/ydb/helpers.d.ts +0 -2
- package/dist/ydb/helpers.js +0 -7
- package/dist/ydb/schema.js +172 -54
- package/package.json +12 -7
- package/dist/repositories/collectionsRepo.shared.d.ts +0 -2
- package/dist/repositories/collectionsRepo.shared.js +0 -23
package/dist/server.js
CHANGED
|
@@ -22,18 +22,86 @@ export async function healthHandler(_req, res) {
|
|
|
22
22
|
logger.error({ err }, isTimeout
|
|
23
23
|
? "YDB compilation timeout during health probe; scheduling process exit"
|
|
24
24
|
: "YDB health probe failed; scheduling process exit");
|
|
25
|
-
res.status(503).json({
|
|
25
|
+
res.status(503).json({
|
|
26
|
+
status: "error",
|
|
27
|
+
error: "YDB health probe failed",
|
|
28
|
+
});
|
|
26
29
|
scheduleExit(1);
|
|
27
30
|
return;
|
|
28
31
|
}
|
|
29
32
|
res.json({ status: "ok" });
|
|
30
33
|
}
|
|
34
|
+
export function rootHandler(_req, res) {
|
|
35
|
+
const version = process.env.npm_package_version ?? "unknown";
|
|
36
|
+
res.json({ title: "ydb-qdrant", version });
|
|
37
|
+
}
|
|
31
38
|
export function buildServer() {
|
|
32
39
|
const app = express();
|
|
33
|
-
app.use(express.json({ limit: "20mb" }));
|
|
34
40
|
app.use(requestLogger);
|
|
41
|
+
app.use(express.json({ limit: "20mb" }));
|
|
42
|
+
app.get("/", rootHandler);
|
|
35
43
|
app.get("/health", healthHandler);
|
|
36
44
|
app.use("/collections", collectionsRouter);
|
|
37
45
|
app.use("/collections", pointsRouter);
|
|
46
|
+
app.use((err, req, res, next) => {
|
|
47
|
+
if (!isRequestAbortedError(err)) {
|
|
48
|
+
next(err);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Client closed the connection while the request body was being read.
|
|
52
|
+
// Avoid Express default handler printing a stacktrace to stderr.
|
|
53
|
+
if (res.headersSent || res.writableEnded) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
res.status(400).json({ status: "error", error: "request aborted" });
|
|
57
|
+
});
|
|
58
|
+
// Catch-all error handler: avoid Express default handler printing stacktraces to stderr
|
|
59
|
+
// and provide consistent JSON error responses.
|
|
60
|
+
app.use((err, _req, res, _next) => {
|
|
61
|
+
logger.error({ err }, "Unhandled error in Express middleware");
|
|
62
|
+
void _next;
|
|
63
|
+
if (res.headersSent || res.writableEnded) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const statusCode = extractHttpStatusCode(err) ?? 500;
|
|
67
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
68
|
+
res.status(statusCode).json({
|
|
69
|
+
status: "error",
|
|
70
|
+
error: errorMessage,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
38
73
|
return app;
|
|
39
74
|
}
|
|
75
|
+
function isRequestAbortedError(err) {
|
|
76
|
+
if (!err || typeof err !== "object") {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
const typeValue = "type" in err && typeof err.type === "string" ? err.type : undefined;
|
|
80
|
+
if (typeValue === "request.aborted") {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
if ("message" in err && typeof err.message === "string") {
|
|
84
|
+
return err.message.includes("request aborted");
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
function extractHttpStatusCode(err) {
|
|
89
|
+
if (!err || typeof err !== "object") {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
const obj = err;
|
|
93
|
+
let statusCodeValue;
|
|
94
|
+
if (typeof obj.statusCode === "number") {
|
|
95
|
+
statusCodeValue = obj.statusCode;
|
|
96
|
+
}
|
|
97
|
+
else if (typeof obj.status === "number") {
|
|
98
|
+
statusCodeValue = obj.status;
|
|
99
|
+
}
|
|
100
|
+
if (statusCodeValue === undefined ||
|
|
101
|
+
!Number.isInteger(statusCodeValue) ||
|
|
102
|
+
statusCodeValue < 400 ||
|
|
103
|
+
statusCodeValue > 599) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
return statusCodeValue;
|
|
107
|
+
}
|
|
@@ -24,6 +24,15 @@ export declare function getCollection(ctx: CollectionContextInput): Promise<{
|
|
|
24
24
|
distance: DistanceKind;
|
|
25
25
|
data_type: string;
|
|
26
26
|
};
|
|
27
|
+
config: {
|
|
28
|
+
params: {
|
|
29
|
+
vectors: {
|
|
30
|
+
size: number;
|
|
31
|
+
distance: DistanceKind;
|
|
32
|
+
data_type: string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
};
|
|
27
36
|
}>;
|
|
28
37
|
export declare function deleteCollection(ctx: CollectionContextInput): Promise<{
|
|
29
38
|
acknowledged: boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CreateCollectionReq } from "../types.js";
|
|
2
2
|
import { ensureMetaTable } from "../ydb/schema.js";
|
|
3
|
-
import { createCollection as repoCreateCollection, deleteCollection as repoDeleteCollection, getCollectionMeta, } from "../repositories/collectionsRepo.js";
|
|
3
|
+
import { createCollection as repoCreateCollection, deleteCollection as repoDeleteCollection, getCollectionMeta, touchCollectionLastAccess, } from "../repositories/collectionsRepo.js";
|
|
4
4
|
import { QdrantServiceError } from "./errors.js";
|
|
5
5
|
import { normalizeCollectionContextShared } from "./CollectionService.shared.js";
|
|
6
6
|
export async function putCollectionIndex(ctx) {
|
|
@@ -13,6 +13,7 @@ export async function putCollectionIndex(ctx) {
|
|
|
13
13
|
error: "collection not found",
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
|
+
await touchCollectionLastAccess(normalized.metaKey);
|
|
16
17
|
return { acknowledged: true };
|
|
17
18
|
}
|
|
18
19
|
export async function createCollection(ctx, body) {
|
|
@@ -33,6 +34,7 @@ export async function createCollection(ctx, body) {
|
|
|
33
34
|
if (existing.dimension === dim &&
|
|
34
35
|
existing.distance === distance &&
|
|
35
36
|
existing.vectorType === vectorType) {
|
|
37
|
+
await touchCollectionLastAccess(normalized.metaKey);
|
|
36
38
|
return { name: normalized.collection, tenant: normalized.tenant };
|
|
37
39
|
}
|
|
38
40
|
const errorMessage = `Collection already exists with different config: dimension=${existing.dimension}, distance=${existing.distance}, type=${existing.vectorType}`;
|
|
@@ -54,6 +56,7 @@ export async function getCollection(ctx) {
|
|
|
54
56
|
error: "collection not found",
|
|
55
57
|
});
|
|
56
58
|
}
|
|
59
|
+
await touchCollectionLastAccess(normalized.metaKey);
|
|
57
60
|
return {
|
|
58
61
|
name: normalized.collection,
|
|
59
62
|
vectors: {
|
|
@@ -61,6 +64,15 @@ export async function getCollection(ctx) {
|
|
|
61
64
|
distance: meta.distance,
|
|
62
65
|
data_type: meta.vectorType,
|
|
63
66
|
},
|
|
67
|
+
config: {
|
|
68
|
+
params: {
|
|
69
|
+
vectors: {
|
|
70
|
+
size: meta.dimension,
|
|
71
|
+
distance: meta.distance,
|
|
72
|
+
data_type: meta.vectorType,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
64
76
|
};
|
|
65
77
|
}
|
|
66
78
|
export async function deleteCollection(ctx) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface NormalizedCollectionContextLike {
|
|
2
2
|
tenant: string;
|
|
3
3
|
collection: string;
|
|
4
|
+
metaKey: string;
|
|
4
5
|
}
|
|
5
6
|
export declare function tableNameFor(tenantId: string, collection: string): string;
|
|
6
7
|
export declare function uidFor(tenantId: string, collection: string): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sanitizeCollectionName, sanitizeTenantId, metaKeyFor, tableNameFor as tableNameForInternal, uidFor as uidForInternal, hashApiKey,
|
|
1
|
+
import { sanitizeCollectionName, sanitizeTenantId, metaKeyFor, tableNameFor as tableNameForInternal, uidFor as uidForInternal, hashApiKey, normalizeUserAgent, } from "../utils/tenant.js";
|
|
2
2
|
export function tableNameFor(tenantId, collection) {
|
|
3
3
|
return tableNameForInternal(tenantId, collection);
|
|
4
4
|
}
|
|
@@ -8,8 +8,8 @@ export function uidFor(tenantId, collection) {
|
|
|
8
8
|
export function normalizeCollectionContextShared(tenant, collection, apiKey, userAgent) {
|
|
9
9
|
const normalizedTenant = sanitizeTenantId(tenant);
|
|
10
10
|
const apiKeyHash = hashApiKey(apiKey);
|
|
11
|
-
const
|
|
12
|
-
const normalizedCollection = sanitizeCollectionName(collection, apiKeyHash,
|
|
11
|
+
const userAgentNormalized = normalizeUserAgent(userAgent);
|
|
12
|
+
const normalizedCollection = sanitizeCollectionName(collection, apiKeyHash, userAgentNormalized);
|
|
13
13
|
const metaKey = metaKeyFor(normalizedTenant, normalizedCollection);
|
|
14
14
|
return {
|
|
15
15
|
tenant: normalizedTenant,
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
import { type CollectionContextInput } from "./CollectionService.js";
|
|
2
|
+
import type { QdrantPayload } from "../qdrant/QdrantTypes.js";
|
|
2
3
|
type PointsContextInput = CollectionContextInput;
|
|
4
|
+
type InternalScoredPoint = {
|
|
5
|
+
id: string;
|
|
6
|
+
score: number;
|
|
7
|
+
payload?: QdrantPayload;
|
|
8
|
+
};
|
|
3
9
|
export declare function upsertPoints(ctx: PointsContextInput, body: unknown): Promise<{
|
|
4
10
|
upserted: number;
|
|
5
11
|
}>;
|
|
6
12
|
export declare function searchPoints(ctx: PointsContextInput, body: unknown): Promise<{
|
|
7
|
-
points:
|
|
8
|
-
id: string;
|
|
9
|
-
score: number;
|
|
10
|
-
payload?: Record<string, unknown>;
|
|
11
|
-
}>;
|
|
13
|
+
points: InternalScoredPoint[];
|
|
12
14
|
}>;
|
|
13
15
|
export declare function queryPoints(ctx: PointsContextInput, body: unknown): Promise<{
|
|
14
|
-
points:
|
|
15
|
-
id: string;
|
|
16
|
-
score: number;
|
|
17
|
-
payload?: Record<string, unknown>;
|
|
18
|
-
}>;
|
|
16
|
+
points: InternalScoredPoint[];
|
|
19
17
|
}>;
|
|
20
18
|
export declare function deletePoints(ctx: PointsContextInput, body: unknown): Promise<{
|
|
21
19
|
deleted: number;
|
|
@@ -1,12 +1,70 @@
|
|
|
1
1
|
import { UpsertPointsReq, SearchReq, DeletePointsReq } from "../types.js";
|
|
2
2
|
import { ensureMetaTable } from "../ydb/schema.js";
|
|
3
|
-
import { getCollectionMeta } from "../repositories/collectionsRepo.js";
|
|
4
|
-
import { deletePoints as repoDeletePoints, searchPoints as repoSearchPoints, upsertPoints as repoUpsertPoints, } from "../repositories/pointsRepo.js";
|
|
3
|
+
import { getCollectionMeta, touchCollectionLastAccess, } from "../repositories/collectionsRepo.js";
|
|
4
|
+
import { deletePoints as repoDeletePoints, deletePointsByPathSegments as repoDeletePointsByPathSegments, searchPoints as repoSearchPoints, upsertPoints as repoUpsertPoints, } from "../repositories/pointsRepo.js";
|
|
5
5
|
import { logger } from "../logging/logger.js";
|
|
6
6
|
import { QdrantServiceError, isVectorDimensionMismatchError, } from "./errors.js";
|
|
7
7
|
import { normalizeCollectionContextShared } from "./CollectionService.shared.js";
|
|
8
8
|
import { resolvePointsTableAndUidOneTable } from "./CollectionService.one-table.js";
|
|
9
9
|
import { normalizeSearchBodyForSearch, normalizeSearchBodyForQuery, } from "../utils/normalization.js";
|
|
10
|
+
import { isRecord } from "../utils/typeGuards.js";
|
|
11
|
+
function parsePathSegmentsFilterToPaths(filter) {
|
|
12
|
+
const extractMust = (must) => {
|
|
13
|
+
if (!Array.isArray(must) || must.length === 0)
|
|
14
|
+
return null;
|
|
15
|
+
const pairs = [];
|
|
16
|
+
for (const cond of must) {
|
|
17
|
+
if (!isRecord(cond))
|
|
18
|
+
return null;
|
|
19
|
+
const key = cond.key;
|
|
20
|
+
if (typeof key !== "string")
|
|
21
|
+
return null;
|
|
22
|
+
const m = /^pathSegments\.(\d+)$/.exec(key);
|
|
23
|
+
if (!m)
|
|
24
|
+
return null;
|
|
25
|
+
const idx = Number(m[1]);
|
|
26
|
+
if (!Number.isInteger(idx) || idx < 0)
|
|
27
|
+
return null;
|
|
28
|
+
const match = cond.match;
|
|
29
|
+
if (!isRecord(match))
|
|
30
|
+
return null;
|
|
31
|
+
const value = match.value;
|
|
32
|
+
if (typeof value !== "string")
|
|
33
|
+
return null;
|
|
34
|
+
pairs.push({ idx, value });
|
|
35
|
+
}
|
|
36
|
+
pairs.sort((a, b) => a.idx - b.idx);
|
|
37
|
+
// Require contiguous indexes starting from 0 to avoid ambiguous matches.
|
|
38
|
+
for (let i = 0; i < pairs.length; i += 1) {
|
|
39
|
+
if (pairs[i].idx !== i)
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return pairs.map((p) => p.value);
|
|
43
|
+
};
|
|
44
|
+
if (!isRecord(filter))
|
|
45
|
+
return null;
|
|
46
|
+
const must = filter.must;
|
|
47
|
+
if (must !== undefined) {
|
|
48
|
+
const path = extractMust(must);
|
|
49
|
+
return path ? [path] : null;
|
|
50
|
+
}
|
|
51
|
+
const should = filter.should;
|
|
52
|
+
if (should !== undefined) {
|
|
53
|
+
if (!Array.isArray(should) || should.length === 0)
|
|
54
|
+
return null;
|
|
55
|
+
const paths = [];
|
|
56
|
+
for (const g of should) {
|
|
57
|
+
if (!isRecord(g))
|
|
58
|
+
return null;
|
|
59
|
+
const path = extractMust(g.must);
|
|
60
|
+
if (!path)
|
|
61
|
+
return null;
|
|
62
|
+
paths.push(path);
|
|
63
|
+
}
|
|
64
|
+
return paths;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
10
68
|
export async function upsertPoints(ctx, body) {
|
|
11
69
|
await ensureMetaTable();
|
|
12
70
|
const normalized = normalizeCollectionContextShared(ctx.tenant, ctx.collection, ctx.apiKey, ctx.userAgent);
|
|
@@ -27,7 +85,9 @@ export async function upsertPoints(ctx, body) {
|
|
|
27
85
|
const { tableName, uid } = await resolvePointsTableAndUidOneTable(normalized);
|
|
28
86
|
let upserted;
|
|
29
87
|
try {
|
|
30
|
-
|
|
88
|
+
// Narrow Qdrant OpenAPI types to the dense-vector subset we support.
|
|
89
|
+
const points = parsed.data.points;
|
|
90
|
+
upserted = await repoUpsertPoints(tableName, points, meta.dimension, uid);
|
|
31
91
|
}
|
|
32
92
|
catch (err) {
|
|
33
93
|
if (isVectorDimensionMismatchError(err)) {
|
|
@@ -44,6 +104,7 @@ export async function upsertPoints(ctx, body) {
|
|
|
44
104
|
}
|
|
45
105
|
throw err;
|
|
46
106
|
}
|
|
107
|
+
await touchCollectionLastAccess(normalized.metaKey);
|
|
47
108
|
return { upserted };
|
|
48
109
|
}
|
|
49
110
|
async function executeSearch(ctx, normalizedSearch, source) {
|
|
@@ -88,9 +149,10 @@ async function executeSearch(ctx, normalizedSearch, source) {
|
|
|
88
149
|
distance: meta.distance,
|
|
89
150
|
vectorType: meta.vectorType,
|
|
90
151
|
}, `${source}: executing`);
|
|
152
|
+
const filterPaths = parsePathSegmentsFilterToPaths(normalizedSearch.filter);
|
|
91
153
|
let hits;
|
|
92
154
|
try {
|
|
93
|
-
hits = await repoSearchPoints(tableName, parsed.data.vector, parsed.data.top, parsed.data.with_payload, meta.distance, meta.dimension, uid);
|
|
155
|
+
hits = await repoSearchPoints(tableName, parsed.data.vector, parsed.data.top, parsed.data.with_payload, meta.distance, meta.dimension, uid, filterPaths ?? undefined);
|
|
94
156
|
}
|
|
95
157
|
catch (err) {
|
|
96
158
|
if (isVectorDimensionMismatchError(err)) {
|
|
@@ -108,6 +170,7 @@ async function executeSearch(ctx, normalizedSearch, source) {
|
|
|
108
170
|
}
|
|
109
171
|
throw err;
|
|
110
172
|
}
|
|
173
|
+
await touchCollectionLastAccess(normalized.metaKey);
|
|
111
174
|
const threshold = normalizedSearch.scoreThreshold;
|
|
112
175
|
// For Cosine, repository hits use distance scores; convert to a
|
|
113
176
|
// similarity-like score so API consumers and IDE thresholds see
|
|
@@ -161,6 +224,20 @@ export async function deletePoints(ctx, body) {
|
|
|
161
224
|
});
|
|
162
225
|
}
|
|
163
226
|
const { tableName, uid } = await resolvePointsTableAndUidOneTable(normalized);
|
|
164
|
-
|
|
227
|
+
let deleted;
|
|
228
|
+
if ("points" in parsed.data) {
|
|
229
|
+
deleted = await repoDeletePoints(tableName, parsed.data.points, uid);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
const paths = parsePathSegmentsFilterToPaths(parsed.data.filter);
|
|
233
|
+
if (!paths) {
|
|
234
|
+
throw new QdrantServiceError(400, {
|
|
235
|
+
status: "error",
|
|
236
|
+
error: "unsupported delete filter: only pathSegments.N match filters with must/should are supported",
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
deleted = await repoDeletePointsByPathSegments(tableName, uid, paths);
|
|
240
|
+
}
|
|
241
|
+
await touchCollectionLastAccess(normalized.metaKey);
|
|
165
242
|
return { deleted };
|
|
166
243
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,20 +1,53 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
export
|
|
2
|
+
import type { QdrantDistance, QdrantPointId, QdrantDenseVector } from "./qdrant/QdrantTypes.js";
|
|
3
|
+
export declare const DistanceKindSchema: z.ZodEnum<{
|
|
4
|
+
Cosine: "Cosine";
|
|
5
|
+
Euclid: "Euclid";
|
|
6
|
+
Dot: "Dot";
|
|
7
|
+
Manhattan: "Manhattan";
|
|
8
|
+
}>;
|
|
9
|
+
export type DistanceKind = QdrantDistance;
|
|
10
|
+
export declare const VectorTypeSchema: z.ZodLiteral<"float">;
|
|
11
|
+
export type VectorType = z.infer<typeof VectorTypeSchema>;
|
|
12
|
+
export declare const PointIdSchema: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
|
|
13
|
+
export type PointId = QdrantPointId;
|
|
14
|
+
export declare const DenseVectorSchema: z.ZodArray<z.ZodNumber>;
|
|
15
|
+
export type DenseVector = QdrantDenseVector;
|
|
16
|
+
/**
|
|
17
|
+
* Collection metadata from qdr__collections table.
|
|
18
|
+
*
|
|
19
|
+
* @property lastAccessedAt - Timestamp of last access; undefined for collections
|
|
20
|
+
* created before this feature, null if explicitly unset.
|
|
21
|
+
*/
|
|
22
|
+
export interface CollectionMeta {
|
|
23
|
+
table: string;
|
|
24
|
+
dimension: number;
|
|
25
|
+
distance: DistanceKind;
|
|
26
|
+
vectorType: VectorType;
|
|
27
|
+
lastAccessedAt?: Date | null;
|
|
28
|
+
}
|
|
4
29
|
export declare const CreateCollectionReq: z.ZodObject<{
|
|
5
30
|
vectors: z.ZodObject<{
|
|
6
31
|
size: z.ZodNumber;
|
|
7
|
-
distance: z.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
32
|
+
distance: z.ZodEnum<{
|
|
33
|
+
Cosine: "Cosine";
|
|
34
|
+
Euclid: "Euclid";
|
|
35
|
+
Dot: "Dot";
|
|
36
|
+
Manhattan: "Manhattan";
|
|
37
|
+
}>;
|
|
38
|
+
data_type: z.ZodOptional<z.ZodLiteral<"float">>;
|
|
11
39
|
}, z.core.$strip>;
|
|
12
40
|
}, z.core.$strip>;
|
|
41
|
+
export declare const UpsertPointSchema: z.ZodObject<{
|
|
42
|
+
id: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
|
|
43
|
+
vector: z.ZodArray<z.ZodNumber>;
|
|
44
|
+
payload: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
45
|
+
}, z.core.$strip>;
|
|
13
46
|
export declare const UpsertPointsReq: z.ZodObject<{
|
|
14
47
|
points: z.ZodArray<z.ZodObject<{
|
|
15
48
|
id: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
|
|
16
49
|
vector: z.ZodArray<z.ZodNumber>;
|
|
17
|
-
payload: z.ZodOptional<z.ZodRecord<z.ZodString, z.
|
|
50
|
+
payload: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
18
51
|
}, z.core.$strip>>;
|
|
19
52
|
}, z.core.$strip>;
|
|
20
53
|
export declare const SearchReq: z.ZodObject<{
|
|
@@ -22,6 +55,50 @@ export declare const SearchReq: z.ZodObject<{
|
|
|
22
55
|
top: z.ZodNumber;
|
|
23
56
|
with_payload: z.ZodOptional<z.ZodBoolean>;
|
|
24
57
|
}, z.core.$strip>;
|
|
25
|
-
export declare const
|
|
58
|
+
export declare const DeletePointsByIdsReq: z.ZodObject<{
|
|
26
59
|
points: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
27
60
|
}, z.core.$strip>;
|
|
61
|
+
export declare const DeletePointsByFilterReq: z.ZodObject<{
|
|
62
|
+
filter: z.ZodUnion<readonly [z.ZodObject<{
|
|
63
|
+
must: z.ZodArray<z.ZodObject<{
|
|
64
|
+
key: z.ZodString;
|
|
65
|
+
match: z.ZodObject<{
|
|
66
|
+
value: z.ZodString;
|
|
67
|
+
}, z.core.$strip>;
|
|
68
|
+
}, z.core.$strip>>;
|
|
69
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
70
|
+
should: z.ZodArray<z.ZodObject<{
|
|
71
|
+
must: z.ZodArray<z.ZodObject<{
|
|
72
|
+
key: z.ZodString;
|
|
73
|
+
match: z.ZodObject<{
|
|
74
|
+
value: z.ZodString;
|
|
75
|
+
}, z.core.$strip>;
|
|
76
|
+
}, z.core.$strip>>;
|
|
77
|
+
}, z.core.$strip>>;
|
|
78
|
+
}, z.core.$strip>]>;
|
|
79
|
+
}, z.core.$strip>;
|
|
80
|
+
export declare const DeletePointsReq: z.ZodUnion<readonly [z.ZodObject<{
|
|
81
|
+
points: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
82
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
83
|
+
filter: z.ZodUnion<readonly [z.ZodObject<{
|
|
84
|
+
must: z.ZodArray<z.ZodObject<{
|
|
85
|
+
key: z.ZodString;
|
|
86
|
+
match: z.ZodObject<{
|
|
87
|
+
value: z.ZodString;
|
|
88
|
+
}, z.core.$strip>;
|
|
89
|
+
}, z.core.$strip>>;
|
|
90
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
91
|
+
should: z.ZodArray<z.ZodObject<{
|
|
92
|
+
must: z.ZodArray<z.ZodObject<{
|
|
93
|
+
key: z.ZodString;
|
|
94
|
+
match: z.ZodObject<{
|
|
95
|
+
value: z.ZodString;
|
|
96
|
+
}, z.core.$strip>;
|
|
97
|
+
}, z.core.$strip>>;
|
|
98
|
+
}, z.core.$strip>>;
|
|
99
|
+
}, z.core.$strip>]>;
|
|
100
|
+
}, z.core.$strip>]>;
|
|
101
|
+
export type CreateCollectionBody = z.infer<typeof CreateCollectionReq>;
|
|
102
|
+
export type UpsertPointsBody = z.infer<typeof UpsertPointsReq>;
|
|
103
|
+
export type SearchBody = z.infer<typeof SearchReq>;
|
|
104
|
+
export type DeletePointsBody = z.infer<typeof DeletePointsReq>;
|
package/dist/types.js
CHANGED
|
@@ -1,30 +1,56 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
const DISTANCE_KIND_VALUES = [
|
|
3
|
+
"Cosine",
|
|
4
|
+
"Euclid",
|
|
5
|
+
"Dot",
|
|
6
|
+
"Manhattan",
|
|
7
|
+
];
|
|
8
|
+
export const DistanceKindSchema = z.enum(DISTANCE_KIND_VALUES);
|
|
9
|
+
export const VectorTypeSchema = z.literal("float");
|
|
10
|
+
export const PointIdSchema = z.union([z.string(), z.number()]);
|
|
11
|
+
export const DenseVectorSchema = z.array(z.number());
|
|
2
12
|
export const CreateCollectionReq = z.object({
|
|
3
13
|
vectors: z.object({
|
|
4
14
|
size: z.number().int().positive(),
|
|
5
|
-
distance:
|
|
6
|
-
|
|
7
|
-
"Euclid",
|
|
8
|
-
"Dot",
|
|
9
|
-
"Manhattan",
|
|
10
|
-
]),
|
|
11
|
-
data_type: z.enum(["float"]).optional(),
|
|
15
|
+
distance: DistanceKindSchema,
|
|
16
|
+
data_type: VectorTypeSchema.optional(),
|
|
12
17
|
}),
|
|
13
18
|
});
|
|
19
|
+
export const UpsertPointSchema = z.object({
|
|
20
|
+
id: PointIdSchema,
|
|
21
|
+
vector: DenseVectorSchema,
|
|
22
|
+
payload: z.record(z.string(), z.unknown()).optional(),
|
|
23
|
+
});
|
|
14
24
|
export const UpsertPointsReq = z.object({
|
|
15
|
-
points: z
|
|
16
|
-
.array(z.object({
|
|
17
|
-
id: z.union([z.string(), z.number()]),
|
|
18
|
-
vector: z.array(z.number()),
|
|
19
|
-
payload: z.record(z.string(), z.any()).optional(),
|
|
20
|
-
}))
|
|
21
|
-
.min(1),
|
|
25
|
+
points: z.array(UpsertPointSchema).min(1),
|
|
22
26
|
});
|
|
23
27
|
export const SearchReq = z.object({
|
|
24
|
-
vector:
|
|
28
|
+
vector: DenseVectorSchema.min(1),
|
|
25
29
|
top: z.number().int().positive().max(1000),
|
|
26
30
|
with_payload: z.boolean().optional(),
|
|
27
31
|
});
|
|
28
|
-
export const
|
|
29
|
-
points: z.array(
|
|
32
|
+
export const DeletePointsByIdsReq = z.object({
|
|
33
|
+
points: z.array(PointIdSchema).min(1),
|
|
34
|
+
});
|
|
35
|
+
const DeletePointsFilterCondition = z.object({
|
|
36
|
+
key: z.string(),
|
|
37
|
+
match: z.object({
|
|
38
|
+
value: z.string(),
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
41
|
+
const DeletePointsFilterMust = z.object({
|
|
42
|
+
must: z.array(DeletePointsFilterCondition).min(1),
|
|
43
|
+
});
|
|
44
|
+
const DeletePointsFilter = z.union([
|
|
45
|
+
DeletePointsFilterMust,
|
|
46
|
+
z.object({
|
|
47
|
+
should: z.array(DeletePointsFilterMust).min(1),
|
|
48
|
+
}),
|
|
49
|
+
]);
|
|
50
|
+
export const DeletePointsByFilterReq = z.object({
|
|
51
|
+
filter: DeletePointsFilter,
|
|
30
52
|
});
|
|
53
|
+
export const DeletePointsReq = z.union([
|
|
54
|
+
DeletePointsByIdsReq,
|
|
55
|
+
DeletePointsByFilterReq,
|
|
56
|
+
]);
|
|
@@ -3,6 +3,7 @@ export interface SearchNormalizationResult {
|
|
|
3
3
|
top: number | undefined;
|
|
4
4
|
withPayload: boolean | undefined;
|
|
5
5
|
scoreThreshold: number | undefined;
|
|
6
|
+
filter?: unknown;
|
|
6
7
|
}
|
|
7
8
|
export declare function isNumberArray(value: unknown): value is number[];
|
|
8
9
|
export declare function extractVectorLoose(body: unknown, depth?: number): number[] | undefined;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { isRecord } from "./typeGuards.js";
|
|
1
2
|
export function isNumberArray(value) {
|
|
2
3
|
return Array.isArray(value) && value.every((x) => typeof x === "number");
|
|
3
4
|
}
|
|
4
5
|
export function extractVectorLoose(body, depth = 0) {
|
|
5
|
-
if (!body ||
|
|
6
|
+
if (!isRecord(body) || depth > 3) {
|
|
6
7
|
return undefined;
|
|
7
8
|
}
|
|
8
9
|
const obj = body;
|
|
@@ -10,17 +11,17 @@ export function extractVectorLoose(body, depth = 0) {
|
|
|
10
11
|
return obj.vector;
|
|
11
12
|
if (isNumberArray(obj.embedding))
|
|
12
13
|
return obj.embedding;
|
|
13
|
-
const query = obj.query;
|
|
14
|
+
const query = isRecord(obj.query) ? obj.query : undefined;
|
|
14
15
|
if (query) {
|
|
15
|
-
const queryVector = query
|
|
16
|
+
const queryVector = query.vector;
|
|
16
17
|
if (isNumberArray(queryVector))
|
|
17
18
|
return queryVector;
|
|
18
|
-
const nearest = query
|
|
19
|
+
const nearest = isRecord(query.nearest) ? query.nearest : undefined;
|
|
19
20
|
if (nearest && isNumberArray(nearest.vector)) {
|
|
20
21
|
return nearest.vector;
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
|
-
const nearest = obj.nearest;
|
|
24
|
+
const nearest = isRecord(obj.nearest) ? obj.nearest : undefined;
|
|
24
25
|
if (nearest && isNumberArray(nearest.vector)) {
|
|
25
26
|
return nearest.vector;
|
|
26
27
|
}
|
|
@@ -42,7 +43,7 @@ export function extractVectorLoose(body, depth = 0) {
|
|
|
42
43
|
return undefined;
|
|
43
44
|
}
|
|
44
45
|
export function normalizeSearchBodyForSearch(body) {
|
|
45
|
-
if (!body
|
|
46
|
+
if (!isRecord(body)) {
|
|
46
47
|
return {
|
|
47
48
|
vector: undefined,
|
|
48
49
|
top: undefined,
|
|
@@ -51,12 +52,12 @@ export function normalizeSearchBodyForSearch(body) {
|
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
54
|
const b = body;
|
|
54
|
-
const rawVector = b
|
|
55
|
+
const rawVector = b.vector;
|
|
55
56
|
const vector = isNumberArray(rawVector) ? rawVector : undefined;
|
|
56
57
|
return normalizeSearchCommon(b, vector);
|
|
57
58
|
}
|
|
58
59
|
export function normalizeSearchBodyForQuery(body) {
|
|
59
|
-
if (!body
|
|
60
|
+
if (!isRecord(body)) {
|
|
60
61
|
return {
|
|
61
62
|
vector: undefined,
|
|
62
63
|
top: undefined,
|
|
@@ -69,13 +70,14 @@ export function normalizeSearchBodyForQuery(body) {
|
|
|
69
70
|
return normalizeSearchCommon(b, vector);
|
|
70
71
|
}
|
|
71
72
|
function normalizeSearchCommon(b, vector) {
|
|
72
|
-
const rawTop = b
|
|
73
|
-
const rawLimit = b
|
|
73
|
+
const rawTop = b.top;
|
|
74
|
+
const rawLimit = b.limit;
|
|
74
75
|
const topFromTop = typeof rawTop === "number" ? rawTop : undefined;
|
|
75
76
|
const topFromLimit = typeof rawLimit === "number" ? rawLimit : undefined;
|
|
76
77
|
const top = topFromTop ?? topFromLimit;
|
|
78
|
+
const filter = b.filter;
|
|
77
79
|
let withPayload;
|
|
78
|
-
const rawWithPayload = b
|
|
80
|
+
const rawWithPayload = b.with_payload;
|
|
79
81
|
if (typeof rawWithPayload === "boolean") {
|
|
80
82
|
withPayload = rawWithPayload;
|
|
81
83
|
}
|
|
@@ -83,10 +85,10 @@ function normalizeSearchCommon(b, vector) {
|
|
|
83
85
|
typeof rawWithPayload === "object") {
|
|
84
86
|
withPayload = true;
|
|
85
87
|
}
|
|
86
|
-
const thresholdRaw = b
|
|
88
|
+
const thresholdRaw = b.score_threshold;
|
|
87
89
|
const thresholdValue = typeof thresholdRaw === "number" ? thresholdRaw : Number(thresholdRaw);
|
|
88
90
|
const scoreThreshold = Number.isFinite(thresholdValue)
|
|
89
91
|
? thresholdValue
|
|
90
92
|
: undefined;
|
|
91
|
-
return { vector, top, withPayload, scoreThreshold };
|
|
93
|
+
return { vector, top, withPayload, scoreThreshold, filter };
|
|
92
94
|
}
|