@nacre.work/core 0.0.0 → 0.2.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.
- package/LICENSE +202 -0
- package/NOTICE +9 -0
- package/dist/audit.d.ts +41 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +18 -0
- package/dist/audit.js.map +1 -0
- package/dist/authz/cache.d.ts +20 -0
- package/dist/authz/cache.d.ts.map +1 -1
- package/dist/authz/cache.js +29 -0
- package/dist/authz/cache.js.map +1 -1
- package/dist/authz/filter.d.ts +54 -3
- package/dist/authz/filter.d.ts.map +1 -1
- package/dist/authz/filter.js +45 -9
- package/dist/authz/filter.js.map +1 -1
- package/dist/authz/index.d.ts +3 -3
- package/dist/authz/index.d.ts.map +1 -1
- package/dist/authz/index.js +2 -2
- package/dist/authz/index.js.map +1 -1
- package/dist/config.d.ts +55 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +245 -7
- package/dist/config.js.map +1 -1
- package/dist/db/client.d.ts +39 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +82 -0
- package/dist/db/client.js.map +1 -1
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/migrate.js +49 -0
- package/dist/db/migrate.js.map +1 -1
- package/dist/index.d.ts +21 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/lifecycle.d.ts +57 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +84 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/logging.d.ts +46 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +110 -0
- package/dist/logging.js.map +1 -0
- package/dist/metadata.d.ts +50 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +127 -0
- package/dist/metadata.js.map +1 -0
- package/dist/metrics.d.ts +19 -1
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +84 -2
- package/dist/metrics.js.map +1 -1
- package/dist/migrations/0008_authenticating_lookup.sql +124 -0
- package/dist/migrations/0009_refresh_tokens.sql +73 -0
- package/dist/migrations/0010_definer_search_path.sql +22 -0
- package/dist/migrations/0011_sweep_lease.sql +36 -0
- package/dist/migrations/0012_prune.sql +118 -0
- package/dist/migrations/0013_reindex.sql +65 -0
- package/dist/migrations/0014_retired_collections.sql +68 -0
- package/dist/oauth.d.ts +45 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +53 -0
- package/dist/oauth.js.map +1 -0
- package/dist/observability.d.ts.map +1 -1
- package/dist/observability.js +100 -13
- package/dist/observability.js.map +1 -1
- package/dist/passwords.d.ts +37 -0
- package/dist/passwords.d.ts.map +1 -0
- package/dist/passwords.js +234 -0
- package/dist/passwords.js.map +1 -0
- package/dist/redis.d.ts +46 -0
- package/dist/redis.d.ts.map +1 -0
- package/dist/redis.js +234 -0
- package/dist/redis.js.map +1 -0
- package/dist/reindex.d.ts +87 -0
- package/dist/reindex.d.ts.map +1 -0
- package/dist/reindex.js +97 -0
- package/dist/reindex.js.map +1 -0
- package/dist/s3.d.ts +111 -0
- package/dist/s3.d.ts.map +1 -0
- package/dist/s3.js +242 -0
- package/dist/s3.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/vector/query.d.ts +49 -4
- package/dist/vector/query.d.ts.map +1 -1
- package/dist/vector/query.js +43 -10
- package/dist/vector/query.js.map +1 -1
- package/dist/vector/search.d.ts +175 -4
- package/dist/vector/search.d.ts.map +1 -1
- package/dist/vector/search.js +320 -7
- package/dist/vector/search.js.map +1 -1
- package/package.json +26 -8
package/dist/vector/search.d.ts
CHANGED
|
@@ -1,22 +1,102 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { QdrantClient } from '@qdrant/js-client-rest';
|
|
2
|
+
import { type Narrowing, type QueryablePlan } from '../authz/filter.js';
|
|
2
3
|
import { type Branch } from './query.js';
|
|
4
|
+
/**
|
|
5
|
+
* What Qdrant actually said.
|
|
6
|
+
*
|
|
7
|
+
* The REST client throws `Error: Bad Request` with the reason in a `data`
|
|
8
|
+
* property nobody looks at, so an unhandled one reaches a log as four words
|
|
9
|
+
* that name no collection, no vector and no cause. Both times a query in this
|
|
10
|
+
* repository was malformed, the message alone was useless and the reason was
|
|
11
|
+
* one line inside `data`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function explainQdrant(cause: unknown): string;
|
|
3
14
|
export interface VectorStoreOptions {
|
|
4
15
|
readonly url: string;
|
|
5
16
|
readonly apiKey?: string;
|
|
6
17
|
}
|
|
7
18
|
export interface SearchRequest {
|
|
8
19
|
readonly orgId: string;
|
|
9
|
-
|
|
20
|
+
/**
|
|
21
|
+
* The organization's collection, read from `organizations.vector_collection`.
|
|
22
|
+
*
|
|
23
|
+
* Not derived from the slug. It was, everywhere, and the column was written
|
|
24
|
+
* once at `init` and read by nothing — so a reindex that moved the pointer
|
|
25
|
+
* moved a pointer no query followed, and the copy it had just finished sat
|
|
26
|
+
* unused while search carried on against the old collection. The name is a
|
|
27
|
+
* value the database owns; deriving it in six places is what let the two
|
|
28
|
+
* disagree silently.
|
|
29
|
+
*/
|
|
30
|
+
readonly collection: string;
|
|
10
31
|
/** What the caller may reach. `none` is not accepted — see buildFilter. */
|
|
11
32
|
readonly plan: QueryablePlan;
|
|
12
33
|
readonly branches: readonly Branch[];
|
|
13
34
|
readonly topK: number;
|
|
35
|
+
/**
|
|
36
|
+
* A restriction the caller asked for, layered on top of the permission one.
|
|
37
|
+
*
|
|
38
|
+
* Still not a filter the caller assembles — it is a list of layer ids that
|
|
39
|
+
* `buildFilter` turns into a `must`, so it can only ever remove results. The
|
|
40
|
+
* distinction is the whole reason this is not `filter?: VectorFilter`.
|
|
41
|
+
*/
|
|
42
|
+
readonly narrow?: Narrowing;
|
|
14
43
|
}
|
|
15
44
|
export interface Hit {
|
|
16
45
|
readonly id: string;
|
|
17
46
|
readonly score: number;
|
|
18
47
|
readonly payload: Record<string, unknown>;
|
|
19
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* How many metadata keys one collection will have indexes built for.
|
|
51
|
+
*
|
|
52
|
+
* The keys are the caller's, so unlike `PAYLOAD_INDEXES` the set is not known
|
|
53
|
+
* in advance and has no natural end: `parseMetadata` bounds a *document* to 32
|
|
54
|
+
* keys and says nothing about how many distinct ones an organization uses, so
|
|
55
|
+
* a caller putting an identifier in a key name would otherwise ask for an index
|
|
56
|
+
* per document.
|
|
57
|
+
*
|
|
58
|
+
* A constant rather than a `NACRE_` variable. Every configuration variable is a
|
|
59
|
+
* thing an operator has to understand and a thing `docs/config.md` has to
|
|
60
|
+
* explain, and the failure this bounds — a slower filter past the sixty-fourth
|
|
61
|
+
* distinct key — needs no per-deployment answer.
|
|
62
|
+
*/
|
|
63
|
+
export declare const METADATA_INDEX_LIMIT = 64;
|
|
64
|
+
/**
|
|
65
|
+
* Payload indexes for the keys a caller filters on.
|
|
66
|
+
*
|
|
67
|
+
* `PAYLOAD_INDEXES` covers the fields the permission filter uses, which are
|
|
68
|
+
* fixed. A metadata filter reaches `meta.<key>`, and those fields had no index
|
|
69
|
+
* at all — so the narrowing that `filters` performs was evaluated by scanning,
|
|
70
|
+
* which is the failure the comment on `PAYLOAD_INDEXES` describes: it does not
|
|
71
|
+
* fail a test, it gets slow at a customer's volume.
|
|
72
|
+
*
|
|
73
|
+
* Built when metadata is written, because that is the only moment the keys are
|
|
74
|
+
* known. Three properties make that safe, and each was checked against a real
|
|
75
|
+
* Qdrant rather than read:
|
|
76
|
+
*
|
|
77
|
+
* - **A missing index is slow, never wrong.** A filter on an unindexed field —
|
|
78
|
+
* and on one indexed under the wrong type — still returns exactly the right
|
|
79
|
+
* points. Qdrant falls back to a scan. So this is an optimization end to end,
|
|
80
|
+
* and everything below may fail without changing an answer.
|
|
81
|
+
* - **Creating one is idempotent**, and it back-fills: an index added after the
|
|
82
|
+
* points covers them, so nothing has to be rebuilt or ordered.
|
|
83
|
+
* - **`keyword` is safe for every value type.** A number or a boolean under a
|
|
84
|
+
* keyword index simply indexes nothing and still matches, and strings and
|
|
85
|
+
* lists of strings — the case `filters` exists for, "only documents from this
|
|
86
|
+
* source" — index properly.
|
|
87
|
+
*
|
|
88
|
+
* Which is why a failure here warns and returns. This is the opposite of
|
|
89
|
+
* invariant I3 and deliberately so: nothing about an index decides who sees
|
|
90
|
+
* what, and failing an ingest because an index could not be built would trade a
|
|
91
|
+
* slow query for a document that never arrives.
|
|
92
|
+
*/
|
|
93
|
+
export declare class MetadataIndexer {
|
|
94
|
+
#private;
|
|
95
|
+
constructor(client: QdrantClient, limit?: number);
|
|
96
|
+
ensure(collection: string, keys: Iterable<string>): Promise<void>;
|
|
97
|
+
/** The `meta.` indexes a collection already has, for carrying across a copy. */
|
|
98
|
+
static fieldsOf(client: QdrantClient, collection: string): Promise<readonly string[]>;
|
|
99
|
+
}
|
|
20
100
|
/**
|
|
21
101
|
* The vector store.
|
|
22
102
|
*
|
|
@@ -32,7 +112,15 @@ export interface Hit {
|
|
|
32
112
|
export declare class VectorStore {
|
|
33
113
|
#private;
|
|
34
114
|
constructor(options: VectorStoreOptions);
|
|
35
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Create an organization's first collection, if it is not there.
|
|
117
|
+
*
|
|
118
|
+
* `collectionName(orgSlug)` is the *initial* name and only that. Once a
|
|
119
|
+
* reindex has rebuilt the collection the organization's name no longer
|
|
120
|
+
* derives from its slug, which is why every other method here takes the
|
|
121
|
+
* collection rather than working it out.
|
|
122
|
+
*/
|
|
123
|
+
ensureCollection(orgSlug: string, vectorName: string, size: number): Promise<string>;
|
|
36
124
|
/**
|
|
37
125
|
* Mark every point of a document deleted.
|
|
38
126
|
*
|
|
@@ -46,7 +134,82 @@ export declare class VectorStore {
|
|
|
46
134
|
* is tombstoned in Postgres but not here stays searchable until a background
|
|
47
135
|
* job gets to it. That window is exactly what invariant I5 forbids.
|
|
48
136
|
*/
|
|
49
|
-
tombstone(
|
|
137
|
+
tombstone(collection: string, documentId: string): Promise<void>;
|
|
138
|
+
/**
|
|
139
|
+
* Replace the caller's metadata on every point of a document.
|
|
140
|
+
*
|
|
141
|
+
* `setPayload` under the reserved key, which merges at the top level and so
|
|
142
|
+
* leaves `org_id`, `deleted`, `acl_tags` and the rest exactly as they were —
|
|
143
|
+
* the namespace is what makes that safe rather than a list of fields to
|
|
144
|
+
* preserve.
|
|
145
|
+
*
|
|
146
|
+
* Whole-object replacement inside that key, not a merge: a caller sending
|
|
147
|
+
* `{source: "notion"}` after `{source: "confluence", team: "legal"}` means
|
|
148
|
+
* the document now has one tag, and merging would leave a tag they removed
|
|
149
|
+
* still matching filters. `PATCH` names the resource, not the field.
|
|
150
|
+
*
|
|
151
|
+
* The vectors are untouched. Re-embedding a document because a tag moved
|
|
152
|
+
* would make a bulk retag cost as much as a bulk ingest, which is the same
|
|
153
|
+
* argument the ACL retag path is built on.
|
|
154
|
+
*/
|
|
155
|
+
setMetadata(collection: string, documentId: string, metadata: Record<string, unknown>): Promise<void>;
|
|
156
|
+
/**
|
|
157
|
+
* Remove a collection.
|
|
158
|
+
*
|
|
159
|
+
* Tolerates one that is not there. The caller is the retire sweep, which
|
|
160
|
+
* drops before it forgets the row — so a pass that dropped and then failed to
|
|
161
|
+
* forget comes back to a name whose collection is already gone, and that is a
|
|
162
|
+
* completed job rather than an error. The alternative order loses the only
|
|
163
|
+
* record that the collection exists.
|
|
164
|
+
*
|
|
165
|
+
* There is no confirmation and no dry run because there is no ambiguity at
|
|
166
|
+
* this level: by the time a name reaches here it has been through the
|
|
167
|
+
* rollback window and been checked against every organization's pointer.
|
|
168
|
+
*/
|
|
169
|
+
dropCollection(collection: string): Promise<void>;
|
|
170
|
+
/**
|
|
171
|
+
* The named vectors a collection has, and their widths.
|
|
172
|
+
*
|
|
173
|
+
* The set is fixed when the collection is created. That is the fact the whole
|
|
174
|
+
* reindex design turns on, and it was worth checking rather than assuming:
|
|
175
|
+
* `update_collection` adjusts the parameters of vectors that already exist and
|
|
176
|
+
* answers `Not existing vector name error` for anything else, so there is no
|
|
177
|
+
* way to add one to a live collection.
|
|
178
|
+
*/
|
|
179
|
+
vectorsOf(collection: string): Promise<Record<string, number>>;
|
|
180
|
+
/**
|
|
181
|
+
* Copy an organization's collection into a new one that also has room for a
|
|
182
|
+
* new model.
|
|
183
|
+
*
|
|
184
|
+
* This exists because a named vector cannot be added to a collection after it
|
|
185
|
+
* is created. The way around that is not to add one — it is to create a
|
|
186
|
+
* collection that already has the slot, move the points across as they are,
|
|
187
|
+
* and switch which collection the organization points at.
|
|
188
|
+
*
|
|
189
|
+
* **No embeddings are computed here.** The vectors that come out of the old
|
|
190
|
+
* collection go into the new one unchanged. That is what makes this the cheap
|
|
191
|
+
* half of a model migration: the expensive half — computing vectors with the
|
|
192
|
+
* new model — happens afterwards, one layer at a time, into the slot this
|
|
193
|
+
* created.
|
|
194
|
+
*
|
|
195
|
+
* Idempotent by construction: if the target already exists it is deleted and
|
|
196
|
+
* rebuilt. A half-copied collection is worse than none, and there is no way to
|
|
197
|
+
* tell one from a complete one by looking at it — the source is still live and
|
|
198
|
+
* still authoritative until the pointer moves, so throwing away a partial
|
|
199
|
+
* attempt costs only the time it took.
|
|
200
|
+
*/
|
|
201
|
+
copyCollection(input: {
|
|
202
|
+
from: string;
|
|
203
|
+
to: string;
|
|
204
|
+
/** Added alongside everything the source already has. */
|
|
205
|
+
addVector: {
|
|
206
|
+
name: string;
|
|
207
|
+
size: number;
|
|
208
|
+
};
|
|
209
|
+
/** Points per scroll page and per upsert. */
|
|
210
|
+
batch?: number;
|
|
211
|
+
onProgress?: (copied: number) => void;
|
|
212
|
+
}): Promise<void>;
|
|
50
213
|
search(request: SearchRequest): Promise<readonly Hit[]>;
|
|
51
214
|
/**
|
|
52
215
|
* Re-check the tenant on the way out.
|
|
@@ -57,6 +220,14 @@ export declare class VectorStore {
|
|
|
57
220
|
* and a bug that produces one cross-tenant row will produce more.
|
|
58
221
|
*/
|
|
59
222
|
static assertTenant(orgId: string, hits: readonly Hit[]): readonly Hit[];
|
|
223
|
+
/**
|
|
224
|
+
* Whether the vector store is answering.
|
|
225
|
+
*
|
|
226
|
+
* `getCollections` rather than a bare TCP connect: Qdrant accepting a socket
|
|
227
|
+
* while refusing queries is the failure a readiness probe exists to catch,
|
|
228
|
+
* and the Compose healthcheck already covers the socket.
|
|
229
|
+
*/
|
|
230
|
+
ready(): Promise<boolean>;
|
|
60
231
|
close(): Promise<void>;
|
|
61
232
|
}
|
|
62
233
|
//# sourceMappingURL=search.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../vector/search.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../vector/search.ts"],"names":[],"mappings":"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"}
|
package/dist/vector/search.js
CHANGED
|
@@ -1,6 +1,132 @@
|
|
|
1
1
|
import { QdrantClient } from '@qdrant/js-client-rest';
|
|
2
|
-
import { buildFilter } from '../authz/filter.js';
|
|
3
|
-
import {
|
|
2
|
+
import { buildFilter, METADATA_PREFIX } from '../authz/filter.js';
|
|
3
|
+
import { logger } from '../logging.js';
|
|
4
|
+
import { buildHybridQuery, collectionConfig, collectionName, PAYLOAD_INDEXES, vectorParams } from './query.js';
|
|
5
|
+
/**
|
|
6
|
+
* What Qdrant actually said.
|
|
7
|
+
*
|
|
8
|
+
* The REST client throws `Error: Bad Request` with the reason in a `data`
|
|
9
|
+
* property nobody looks at, so an unhandled one reaches a log as four words
|
|
10
|
+
* that name no collection, no vector and no cause. Both times a query in this
|
|
11
|
+
* repository was malformed, the message alone was useless and the reason was
|
|
12
|
+
* one line inside `data`.
|
|
13
|
+
*/
|
|
14
|
+
export function explainQdrant(cause) {
|
|
15
|
+
const data = cause?.data;
|
|
16
|
+
return data === undefined ? String(cause) : `${String(cause)} — ${JSON.stringify(data)}`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* How many metadata keys one collection will have indexes built for.
|
|
20
|
+
*
|
|
21
|
+
* The keys are the caller's, so unlike `PAYLOAD_INDEXES` the set is not known
|
|
22
|
+
* in advance and has no natural end: `parseMetadata` bounds a *document* to 32
|
|
23
|
+
* keys and says nothing about how many distinct ones an organization uses, so
|
|
24
|
+
* a caller putting an identifier in a key name would otherwise ask for an index
|
|
25
|
+
* per document.
|
|
26
|
+
*
|
|
27
|
+
* A constant rather than a `NACRE_` variable. Every configuration variable is a
|
|
28
|
+
* thing an operator has to understand and a thing `docs/config.md` has to
|
|
29
|
+
* explain, and the failure this bounds — a slower filter past the sixty-fourth
|
|
30
|
+
* distinct key — needs no per-deployment answer.
|
|
31
|
+
*/
|
|
32
|
+
export const METADATA_INDEX_LIMIT = 64;
|
|
33
|
+
/**
|
|
34
|
+
* Payload indexes for the keys a caller filters on.
|
|
35
|
+
*
|
|
36
|
+
* `PAYLOAD_INDEXES` covers the fields the permission filter uses, which are
|
|
37
|
+
* fixed. A metadata filter reaches `meta.<key>`, and those fields had no index
|
|
38
|
+
* at all — so the narrowing that `filters` performs was evaluated by scanning,
|
|
39
|
+
* which is the failure the comment on `PAYLOAD_INDEXES` describes: it does not
|
|
40
|
+
* fail a test, it gets slow at a customer's volume.
|
|
41
|
+
*
|
|
42
|
+
* Built when metadata is written, because that is the only moment the keys are
|
|
43
|
+
* known. Three properties make that safe, and each was checked against a real
|
|
44
|
+
* Qdrant rather than read:
|
|
45
|
+
*
|
|
46
|
+
* - **A missing index is slow, never wrong.** A filter on an unindexed field —
|
|
47
|
+
* and on one indexed under the wrong type — still returns exactly the right
|
|
48
|
+
* points. Qdrant falls back to a scan. So this is an optimization end to end,
|
|
49
|
+
* and everything below may fail without changing an answer.
|
|
50
|
+
* - **Creating one is idempotent**, and it back-fills: an index added after the
|
|
51
|
+
* points covers them, so nothing has to be rebuilt or ordered.
|
|
52
|
+
* - **`keyword` is safe for every value type.** A number or a boolean under a
|
|
53
|
+
* keyword index simply indexes nothing and still matches, and strings and
|
|
54
|
+
* lists of strings — the case `filters` exists for, "only documents from this
|
|
55
|
+
* source" — index properly.
|
|
56
|
+
*
|
|
57
|
+
* Which is why a failure here warns and returns. This is the opposite of
|
|
58
|
+
* invariant I3 and deliberately so: nothing about an index decides who sees
|
|
59
|
+
* what, and failing an ingest because an index could not be built would trade a
|
|
60
|
+
* slow query for a document that never arrives.
|
|
61
|
+
*/
|
|
62
|
+
export class MetadataIndexer {
|
|
63
|
+
#client;
|
|
64
|
+
#limit;
|
|
65
|
+
/** Per collection, the `meta.` fields known to be indexed. */
|
|
66
|
+
#known = new Map();
|
|
67
|
+
#atLimit = new Set();
|
|
68
|
+
constructor(client, limit = METADATA_INDEX_LIMIT) {
|
|
69
|
+
this.#client = client;
|
|
70
|
+
this.#limit = limit;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Held on the instance rather than in a module-level map: two stores against
|
|
74
|
+
* different collections must not share it, and a cache that outlives the
|
|
75
|
+
* process that built it is a cache nothing invalidates. Staleness is harmless
|
|
76
|
+
* anyway — the operation it saves is idempotent.
|
|
77
|
+
*/
|
|
78
|
+
async #indexed(collection) {
|
|
79
|
+
const cached = this.#known.get(collection);
|
|
80
|
+
if (cached !== undefined)
|
|
81
|
+
return cached;
|
|
82
|
+
const info = await this.#client.getCollection(collection);
|
|
83
|
+
const schema = (info.payload_schema ?? {});
|
|
84
|
+
const fields = new Set(Object.keys(schema).filter((field) => field.startsWith(`${METADATA_PREFIX}.`)));
|
|
85
|
+
this.#known.set(collection, fields);
|
|
86
|
+
return fields;
|
|
87
|
+
}
|
|
88
|
+
async ensure(collection, keys) {
|
|
89
|
+
const wanted = [...keys];
|
|
90
|
+
if (wanted.length === 0)
|
|
91
|
+
return;
|
|
92
|
+
try {
|
|
93
|
+
const indexed = await this.#indexed(collection);
|
|
94
|
+
for (const key of wanted) {
|
|
95
|
+
const field = `${METADATA_PREFIX}.${key}`;
|
|
96
|
+
if (indexed.has(field))
|
|
97
|
+
continue;
|
|
98
|
+
if (indexed.size >= this.#limit) {
|
|
99
|
+
// Once per collection per process. The filter still answers
|
|
100
|
+
// correctly; it stops being answered from an index.
|
|
101
|
+
if (!this.#atLimit.has(collection)) {
|
|
102
|
+
this.#atLimit.add(collection);
|
|
103
|
+
logger.warn('metadata index limit reached', { collection,
|
|
104
|
+
limit: this.#limit,
|
|
105
|
+
unindexed: field });
|
|
106
|
+
}
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
await this.#client.createPayloadIndex(collection, {
|
|
110
|
+
field_name: field,
|
|
111
|
+
field_schema: 'keyword',
|
|
112
|
+
wait: true,
|
|
113
|
+
});
|
|
114
|
+
indexed.add(field);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (cause) {
|
|
118
|
+
// Never fatal. See the note on this class.
|
|
119
|
+
logger.warn('metadata index build failed', { collection,
|
|
120
|
+
error: explainQdrant(cause) });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** The `meta.` indexes a collection already has, for carrying across a copy. */
|
|
124
|
+
static async fieldsOf(client, collection) {
|
|
125
|
+
const info = await client.getCollection(collection);
|
|
126
|
+
const schema = (info.payload_schema ?? {});
|
|
127
|
+
return Object.keys(schema).filter((field) => field.startsWith(`${METADATA_PREFIX}.`));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
4
130
|
/**
|
|
5
131
|
* The vector store.
|
|
6
132
|
*
|
|
@@ -15,6 +141,7 @@ import { buildHybridQuery, collectionConfig, collectionName, PAYLOAD_INDEXES } f
|
|
|
15
141
|
*/
|
|
16
142
|
export class VectorStore {
|
|
17
143
|
#client;
|
|
144
|
+
#metadataIndexes;
|
|
18
145
|
constructor(options) {
|
|
19
146
|
// exactOptionalPropertyTypes distinguishes "absent" from "present and
|
|
20
147
|
// undefined", and the client's parameters mean it: passing apiKey:
|
|
@@ -22,12 +149,21 @@ export class VectorStore {
|
|
|
22
149
|
this.#client = new QdrantClient(options.apiKey === undefined
|
|
23
150
|
? { url: options.url }
|
|
24
151
|
: { url: options.url, apiKey: options.apiKey });
|
|
152
|
+
this.#metadataIndexes = new MetadataIndexer(this.#client);
|
|
25
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Create an organization's first collection, if it is not there.
|
|
156
|
+
*
|
|
157
|
+
* `collectionName(orgSlug)` is the *initial* name and only that. Once a
|
|
158
|
+
* reindex has rebuilt the collection the organization's name no longer
|
|
159
|
+
* derives from its slug, which is why every other method here takes the
|
|
160
|
+
* collection rather than working it out.
|
|
161
|
+
*/
|
|
26
162
|
async ensureCollection(orgSlug, vectorName, size) {
|
|
27
163
|
const name = collectionName(orgSlug);
|
|
28
164
|
const existing = await this.#client.getCollections();
|
|
29
165
|
if (existing.collections.some((c) => c.name === name))
|
|
30
|
-
return;
|
|
166
|
+
return name;
|
|
31
167
|
await this.#client.createCollection(name, collectionConfig(vectorName, size));
|
|
32
168
|
// Created here rather than left to an operator: a filter that falls back to
|
|
33
169
|
// a scan is a latency problem that only shows up at a customer's volume.
|
|
@@ -38,6 +174,7 @@ export class VectorStore {
|
|
|
38
174
|
wait: true,
|
|
39
175
|
});
|
|
40
176
|
}
|
|
177
|
+
return name;
|
|
41
178
|
}
|
|
42
179
|
/**
|
|
43
180
|
* Mark every point of a document deleted.
|
|
@@ -52,21 +189,186 @@ export class VectorStore {
|
|
|
52
189
|
* is tombstoned in Postgres but not here stays searchable until a background
|
|
53
190
|
* job gets to it. That window is exactly what invariant I5 forbids.
|
|
54
191
|
*/
|
|
55
|
-
async tombstone(
|
|
56
|
-
await this.#client.setPayload(
|
|
192
|
+
async tombstone(collection, documentId) {
|
|
193
|
+
await this.#client.setPayload(collection, {
|
|
57
194
|
wait: true,
|
|
58
195
|
payload: { deleted: true },
|
|
59
196
|
filter: { must: [{ key: 'doc_id', match: { value: documentId } }] },
|
|
60
197
|
});
|
|
61
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Replace the caller's metadata on every point of a document.
|
|
201
|
+
*
|
|
202
|
+
* `setPayload` under the reserved key, which merges at the top level and so
|
|
203
|
+
* leaves `org_id`, `deleted`, `acl_tags` and the rest exactly as they were —
|
|
204
|
+
* the namespace is what makes that safe rather than a list of fields to
|
|
205
|
+
* preserve.
|
|
206
|
+
*
|
|
207
|
+
* Whole-object replacement inside that key, not a merge: a caller sending
|
|
208
|
+
* `{source: "notion"}` after `{source: "confluence", team: "legal"}` means
|
|
209
|
+
* the document now has one tag, and merging would leave a tag they removed
|
|
210
|
+
* still matching filters. `PATCH` names the resource, not the field.
|
|
211
|
+
*
|
|
212
|
+
* The vectors are untouched. Re-embedding a document because a tag moved
|
|
213
|
+
* would make a bulk retag cost as much as a bulk ingest, which is the same
|
|
214
|
+
* argument the ACL retag path is built on.
|
|
215
|
+
*/
|
|
216
|
+
async setMetadata(collection, documentId, metadata) {
|
|
217
|
+
try {
|
|
218
|
+
await this.#client.setPayload(collection, {
|
|
219
|
+
wait: true,
|
|
220
|
+
payload: { [METADATA_PREFIX]: metadata },
|
|
221
|
+
filter: { must: [{ key: 'doc_id', match: { value: documentId } }] },
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
catch (cause) {
|
|
225
|
+
throw new Error(`metadata write to ${collection} rejected: ${explainQdrant(cause)}`, { cause });
|
|
226
|
+
}
|
|
227
|
+
// After the write, not before: an index built for a write that then failed
|
|
228
|
+
// is an index for a key no point carries.
|
|
229
|
+
await this.#metadataIndexes.ensure(collection, Object.keys(metadata));
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Remove a collection.
|
|
233
|
+
*
|
|
234
|
+
* Tolerates one that is not there. The caller is the retire sweep, which
|
|
235
|
+
* drops before it forgets the row — so a pass that dropped and then failed to
|
|
236
|
+
* forget comes back to a name whose collection is already gone, and that is a
|
|
237
|
+
* completed job rather than an error. The alternative order loses the only
|
|
238
|
+
* record that the collection exists.
|
|
239
|
+
*
|
|
240
|
+
* There is no confirmation and no dry run because there is no ambiguity at
|
|
241
|
+
* this level: by the time a name reaches here it has been through the
|
|
242
|
+
* rollback window and been checked against every organization's pointer.
|
|
243
|
+
*/
|
|
244
|
+
async dropCollection(collection) {
|
|
245
|
+
try {
|
|
246
|
+
await this.#client.deleteCollection(collection);
|
|
247
|
+
}
|
|
248
|
+
catch (cause) {
|
|
249
|
+
const existing = await this.#client.getCollections();
|
|
250
|
+
if (existing.collections.some((c) => c.name === collection)) {
|
|
251
|
+
throw new Error(`deleting ${collection} rejected: ${explainQdrant(cause)}`, { cause });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* The named vectors a collection has, and their widths.
|
|
257
|
+
*
|
|
258
|
+
* The set is fixed when the collection is created. That is the fact the whole
|
|
259
|
+
* reindex design turns on, and it was worth checking rather than assuming:
|
|
260
|
+
* `update_collection` adjusts the parameters of vectors that already exist and
|
|
261
|
+
* answers `Not existing vector name error` for anything else, so there is no
|
|
262
|
+
* way to add one to a live collection.
|
|
263
|
+
*/
|
|
264
|
+
async vectorsOf(collection) {
|
|
265
|
+
const info = await this.#client.getCollection(collection);
|
|
266
|
+
const vectors = (info.config?.params?.vectors ?? {});
|
|
267
|
+
return Object.fromEntries(Object.entries(vectors).map(([name, v]) => [name, Number(v.size ?? 0)]));
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Copy an organization's collection into a new one that also has room for a
|
|
271
|
+
* new model.
|
|
272
|
+
*
|
|
273
|
+
* This exists because a named vector cannot be added to a collection after it
|
|
274
|
+
* is created. The way around that is not to add one — it is to create a
|
|
275
|
+
* collection that already has the slot, move the points across as they are,
|
|
276
|
+
* and switch which collection the organization points at.
|
|
277
|
+
*
|
|
278
|
+
* **No embeddings are computed here.** The vectors that come out of the old
|
|
279
|
+
* collection go into the new one unchanged. That is what makes this the cheap
|
|
280
|
+
* half of a model migration: the expensive half — computing vectors with the
|
|
281
|
+
* new model — happens afterwards, one layer at a time, into the slot this
|
|
282
|
+
* created.
|
|
283
|
+
*
|
|
284
|
+
* Idempotent by construction: if the target already exists it is deleted and
|
|
285
|
+
* rebuilt. A half-copied collection is worse than none, and there is no way to
|
|
286
|
+
* tell one from a complete one by looking at it — the source is still live and
|
|
287
|
+
* still authoritative until the pointer moves, so throwing away a partial
|
|
288
|
+
* attempt costs only the time it took.
|
|
289
|
+
*/
|
|
290
|
+
async copyCollection(input) {
|
|
291
|
+
const existing = await this.vectorsOf(input.from);
|
|
292
|
+
if (input.addVector.name in existing) {
|
|
293
|
+
throw new Error(`${input.from} already has a vector called ${input.addVector.name}; nothing to copy for`);
|
|
294
|
+
}
|
|
295
|
+
const vectors = {};
|
|
296
|
+
for (const [name, size] of Object.entries(existing)) {
|
|
297
|
+
vectors[name] = vectorParams(size);
|
|
298
|
+
}
|
|
299
|
+
vectors[input.addVector.name] = vectorParams(input.addVector.size);
|
|
300
|
+
// Rebuilt rather than resumed. See the note above.
|
|
301
|
+
await this.#client.deleteCollection(input.to).catch(() => undefined);
|
|
302
|
+
await this.#client.createCollection(input.to, {
|
|
303
|
+
vectors: vectors,
|
|
304
|
+
sparse_vectors: { bm25: {} },
|
|
305
|
+
optimizers_config: { default_segment_number: 4 },
|
|
306
|
+
on_disk_payload: true,
|
|
307
|
+
});
|
|
308
|
+
for (const index of PAYLOAD_INDEXES) {
|
|
309
|
+
await this.#client.createPayloadIndex(input.to, {
|
|
310
|
+
field_name: index.field_name,
|
|
311
|
+
field_schema: index.field_schema,
|
|
312
|
+
wait: true,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
// The metadata indexes too, or a reindex quietly undoes them: the new
|
|
316
|
+
// collection is built from `PAYLOAD_INDEXES`, which by construction knows
|
|
317
|
+
// nothing about keys a caller chose, so every metadata filter in that
|
|
318
|
+
// organization would go back to scanning the moment the pointer moved. The
|
|
319
|
+
// source is the only record of which keys are in use.
|
|
320
|
+
for (const field of await MetadataIndexer.fieldsOf(this.#client, input.from)) {
|
|
321
|
+
await this.#client.createPayloadIndex(input.to, {
|
|
322
|
+
field_name: field,
|
|
323
|
+
field_schema: 'keyword',
|
|
324
|
+
wait: true,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
const batch = input.batch ?? 256;
|
|
328
|
+
let offset = undefined;
|
|
329
|
+
let copied = 0;
|
|
330
|
+
do {
|
|
331
|
+
const page = await this.#client.scroll(input.from, {
|
|
332
|
+
limit: batch,
|
|
333
|
+
with_payload: true,
|
|
334
|
+
with_vector: true,
|
|
335
|
+
...(offset === undefined || offset === null ? {} : { offset }),
|
|
336
|
+
});
|
|
337
|
+
const points = page.points ?? [];
|
|
338
|
+
if (points.length > 0) {
|
|
339
|
+
await this.#client.upsert(input.to, {
|
|
340
|
+
wait: true,
|
|
341
|
+
points: points.map((p) => ({
|
|
342
|
+
id: p.id,
|
|
343
|
+
// Whatever the source had, unchanged. The new slot stays empty
|
|
344
|
+
// until a layer is reindexed into it.
|
|
345
|
+
vector: p.vector,
|
|
346
|
+
payload: p.payload ?? {},
|
|
347
|
+
})),
|
|
348
|
+
});
|
|
349
|
+
copied += points.length;
|
|
350
|
+
input.onProgress?.(copied);
|
|
351
|
+
}
|
|
352
|
+
offset = page.next_page_offset;
|
|
353
|
+
} while (offset !== undefined && offset !== null);
|
|
354
|
+
}
|
|
62
355
|
async search(request) {
|
|
63
|
-
const filter = buildFilter(request.orgId, request.plan);
|
|
356
|
+
const filter = buildFilter(request.orgId, request.plan, request.narrow);
|
|
64
357
|
const query = buildHybridQuery({
|
|
65
358
|
branches: request.branches,
|
|
66
359
|
filter,
|
|
67
360
|
limit: request.topK,
|
|
68
361
|
});
|
|
69
|
-
|
|
362
|
+
let result;
|
|
363
|
+
try {
|
|
364
|
+
result = await this.#client.query(request.collection, query);
|
|
365
|
+
}
|
|
366
|
+
catch (cause) {
|
|
367
|
+
// Named, because the query has as many branches as the organization has
|
|
368
|
+
// models and "Bad Request" does not say which one Qdrant objected to.
|
|
369
|
+
throw new Error(`query against ${request.collection} using ` +
|
|
370
|
+
`${query.prefetch.map((b) => b.using).join(', ')} rejected: ${explainQdrant(cause)}`, { cause });
|
|
371
|
+
}
|
|
70
372
|
return result.points.map((p) => ({
|
|
71
373
|
id: String(p.id),
|
|
72
374
|
score: p.score,
|
|
@@ -90,6 +392,17 @@ export class VectorStore {
|
|
|
90
392
|
}
|
|
91
393
|
return hits;
|
|
92
394
|
}
|
|
395
|
+
/**
|
|
396
|
+
* Whether the vector store is answering.
|
|
397
|
+
*
|
|
398
|
+
* `getCollections` rather than a bare TCP connect: Qdrant accepting a socket
|
|
399
|
+
* while refusing queries is the failure a readiness probe exists to catch,
|
|
400
|
+
* and the Compose healthcheck already covers the socket.
|
|
401
|
+
*/
|
|
402
|
+
async ready() {
|
|
403
|
+
await this.#client.getCollections();
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
93
406
|
async close() {
|
|
94
407
|
// The REST client holds no pool; this exists so callers have one shape to
|
|
95
408
|
// depend on when it does.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../vector/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAErD,OAAO,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../vector/search.ts"],"names":[],"mappings":"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"}
|