@dudousxd/nestjs-agent-rag 0.3.10 → 0.4.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 +36 -1
- package/dist/index.cjs +234 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -3
- package/dist/index.d.ts +75 -3
- package/dist/index.js +233 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -69,10 +69,45 @@ const store = new PgVectorStore(
|
|
|
69
69
|
await store.ensureSchema(); // CREATE EXTENSION vector + table + HNSW cosine index
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
## Hybrid search — and deleting from it
|
|
73
|
+
|
|
74
|
+
Hybrid search means chunking once and feeding the SAME chunks to both halves, so their ids line up
|
|
75
|
+
and `HybridRetriever` can fuse the rankings:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const chunks = chunkDocuments(docs);
|
|
79
|
+
await ingestChunks(chunks, { embedder, store });
|
|
80
|
+
keyword.add(chunks);
|
|
81
|
+
|
|
82
|
+
const retriever = new HybridRetriever([new EmbeddingRetriever(embedder, store), keyword]);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Because there are two indexes, **deletes have to hit both**. `KeywordRetriever` keeps its own copy of
|
|
86
|
+
each chunk's text, so a document dropped from the vector store alone stays fully retrievable —
|
|
87
|
+
`retrieve` will keep returning the removed passage:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
await store.remove(documentId);
|
|
91
|
+
keyword.remove(documentId); // ← the other half. Same document id; chunk ids collapse for you.
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`KeywordRetriever` also exposes `clear()` and `size` for rebuilding the index wholesale.
|
|
95
|
+
|
|
96
|
+
## RediSearch store
|
|
97
|
+
|
|
98
|
+
`RedisVectorStore` needs `ensureSchema()` at boot. It creates the index if missing — and if the index
|
|
99
|
+
already exists, it compares it against your configuration rather than assuming they agree. A
|
|
100
|
+
`filterableFields` entry added after the index was created is repaired in place with
|
|
101
|
+
`FT.ALTER … SCHEMA ADD` (chunks written before the repair only become filterable on that key once
|
|
102
|
+
re-ingested). A changed `dimensions` — a model swap — can't be repaired without a full reindex, so it
|
|
103
|
+
throws `RedisVectorSchemaMismatchError` instead of silently leaving the index on the old width.
|
|
104
|
+
|
|
72
105
|
## API
|
|
73
106
|
|
|
74
107
|
- `chunkText(text, { chunkSize?, overlap? })` — overlapping, boundary-aware chunks.
|
|
75
108
|
- `ingestDocuments(docs, { embedder, store, chunkSize?, overlap? })` — chunk → embed → upsert.
|
|
76
109
|
- `EmbeddingRetriever(embedder, store)` — the core `Retriever` from an embedder + vector store.
|
|
77
|
-
- `
|
|
110
|
+
- `KeywordRetriever` — BM25 lexical half of hybrid search: `add(chunks)`, `remove(documentId)`,
|
|
111
|
+
`clear()`, `size`.
|
|
112
|
+
- `MemoryVectorStore` / `PgVectorStore` / `RedisVectorStore` — `VectorStore` adapters.
|
|
78
113
|
- `createRetrievalTool(retriever, { name?, description?, topK? })` — the agentic-retrieval tool.
|
package/dist/index.cjs
CHANGED
|
@@ -27,6 +27,7 @@ __export(index_exports, {
|
|
|
27
27
|
KeywordRetriever: () => KeywordRetriever,
|
|
28
28
|
MemoryVectorStore: () => MemoryVectorStore,
|
|
29
29
|
PgVectorStore: () => PgVectorStore,
|
|
30
|
+
RedisVectorSchemaMismatchError: () => RedisVectorSchemaMismatchError,
|
|
30
31
|
RedisVectorStore: () => RedisVectorStore,
|
|
31
32
|
RerankingRetriever: () => RerankingRetriever,
|
|
32
33
|
chunkDocuments: () => chunkDocuments,
|
|
@@ -84,10 +85,17 @@ __name(documentIdOf, "documentIdOf");
|
|
|
84
85
|
|
|
85
86
|
// src/filter.ts
|
|
86
87
|
function matchesFilter(metadata, filter) {
|
|
88
|
+
const entries = Object.entries(filter);
|
|
87
89
|
if (metadata === void 0) {
|
|
88
|
-
return
|
|
90
|
+
return entries.length === 0;
|
|
89
91
|
}
|
|
90
|
-
return
|
|
92
|
+
return entries.every(([key, expected]) => {
|
|
93
|
+
if (Array.isArray(expected)) {
|
|
94
|
+
const actual = metadata[key];
|
|
95
|
+
return Array.isArray(actual) ? actual.some((value) => expected.includes(value)) : expected.includes(actual);
|
|
96
|
+
}
|
|
97
|
+
return metadata[key] === expected;
|
|
98
|
+
});
|
|
91
99
|
}
|
|
92
100
|
__name(matchesFilter, "matchesFilter");
|
|
93
101
|
|
|
@@ -216,13 +224,11 @@ var PgVectorStore = class {
|
|
|
216
224
|
]);
|
|
217
225
|
}
|
|
218
226
|
async listDocuments(filter) {
|
|
219
|
-
const
|
|
227
|
+
const where = buildWhere(filter, 1);
|
|
220
228
|
const rows = await this.client.query(`SELECT DISTINCT ON (${DOCUMENT_ID_FROM_CHUNK}) ${DOCUMENT_ID_FROM_CHUNK} AS doc_id, metadata
|
|
221
229
|
FROM ${this.table}
|
|
222
|
-
${
|
|
223
|
-
ORDER BY ${DOCUMENT_ID_FROM_CHUNK}`,
|
|
224
|
-
JSON.stringify(filter)
|
|
225
|
-
] : []);
|
|
230
|
+
${where.sql}
|
|
231
|
+
ORDER BY ${DOCUMENT_ID_FROM_CHUNK}`, where.params);
|
|
226
232
|
return rows.map((row) => ({
|
|
227
233
|
id: row.doc_id,
|
|
228
234
|
...row.metadata !== null ? {
|
|
@@ -232,18 +238,15 @@ var PgVectorStore = class {
|
|
|
232
238
|
}
|
|
233
239
|
async search(embedding, options) {
|
|
234
240
|
const vector = toVectorLiteral(embedding);
|
|
235
|
-
const
|
|
241
|
+
const where = buildWhere(options.filter, 3);
|
|
236
242
|
const rows = await this.client.query(`SELECT id, text, source, metadata, 1 - (embedding <=> $1::vector) AS score
|
|
237
243
|
FROM ${this.table}
|
|
238
|
-
${
|
|
244
|
+
${where.sql}
|
|
239
245
|
ORDER BY embedding <=> $1::vector
|
|
240
|
-
LIMIT $2`,
|
|
246
|
+
LIMIT $2`, [
|
|
241
247
|
vector,
|
|
242
248
|
options.topK,
|
|
243
|
-
|
|
244
|
-
] : [
|
|
245
|
-
vector,
|
|
246
|
-
options.topK
|
|
249
|
+
...where.params
|
|
247
250
|
]);
|
|
248
251
|
return rows.map((row) => ({
|
|
249
252
|
id: row.id,
|
|
@@ -258,6 +261,45 @@ var PgVectorStore = class {
|
|
|
258
261
|
}));
|
|
259
262
|
}
|
|
260
263
|
};
|
|
264
|
+
function buildWhere(filter, startIndex) {
|
|
265
|
+
if (filter === void 0 || Object.keys(filter).length === 0) {
|
|
266
|
+
return {
|
|
267
|
+
sql: "",
|
|
268
|
+
params: []
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
const clauses = [];
|
|
272
|
+
const params = [];
|
|
273
|
+
const scalar = {};
|
|
274
|
+
let index = startIndex;
|
|
275
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
276
|
+
if (Array.isArray(value)) {
|
|
277
|
+
if (value.length === 0) {
|
|
278
|
+
clauses.push("false");
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const keyParam = `$${index}`;
|
|
282
|
+
params.push(key);
|
|
283
|
+
index += 1;
|
|
284
|
+
const arrParam = `$${index}`;
|
|
285
|
+
params.push(value.map(String));
|
|
286
|
+
index += 1;
|
|
287
|
+
clauses.push(`(CASE WHEN jsonb_typeof(metadata->${keyParam}) = 'array' THEN metadata->${keyParam} ELSE jsonb_build_array(metadata->${keyParam}) END) ?| ${arrParam}::text[]`);
|
|
288
|
+
} else {
|
|
289
|
+
scalar[key] = value;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (Object.keys(scalar).length > 0) {
|
|
293
|
+
clauses.push(`metadata @> $${index}::jsonb`);
|
|
294
|
+
params.push(JSON.stringify(scalar));
|
|
295
|
+
index += 1;
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
sql: `WHERE ${clauses.join(" AND ")}`,
|
|
299
|
+
params
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
__name(buildWhere, "buildWhere");
|
|
261
303
|
var DOCUMENT_ID_FROM_CHUNK = "regexp_replace(id, '#[0-9]+$', '')";
|
|
262
304
|
function toVectorLiteral(embedding) {
|
|
263
305
|
return `[${embedding.join(",")}]`;
|
|
@@ -265,6 +307,19 @@ function toVectorLiteral(embedding) {
|
|
|
265
307
|
__name(toVectorLiteral, "toVectorLiteral");
|
|
266
308
|
|
|
267
309
|
// src/redis-vector-store.ts
|
|
310
|
+
var RedisVectorSchemaMismatchError = class extends Error {
|
|
311
|
+
static {
|
|
312
|
+
__name(this, "RedisVectorSchemaMismatchError");
|
|
313
|
+
}
|
|
314
|
+
index;
|
|
315
|
+
field;
|
|
316
|
+
expected;
|
|
317
|
+
actual;
|
|
318
|
+
constructor(index, field, expected, actual, remedy) {
|
|
319
|
+
super(`RediSearch index "${index}" disagrees with this RedisVectorStore's configuration: field "${field}" is ${actual}, expected ${expected}. ${remedy}`), this.index = index, this.field = field, this.expected = expected, this.actual = actual;
|
|
320
|
+
this.name = "RedisVectorSchemaMismatchError";
|
|
321
|
+
}
|
|
322
|
+
};
|
|
268
323
|
var RedisVectorStore = class {
|
|
269
324
|
static {
|
|
270
325
|
__name(this, "RedisVectorStore");
|
|
@@ -281,16 +336,74 @@ var RedisVectorStore = class {
|
|
|
281
336
|
this.dimensions = options.dimensions ?? 1536;
|
|
282
337
|
this.filterableFields = options.filterableFields ?? [];
|
|
283
338
|
}
|
|
284
|
-
/**
|
|
339
|
+
/**
|
|
340
|
+
* Idempotent, and drift-aware: create the HNSW/cosine index if `FT.INFO` says it doesn't exist —
|
|
341
|
+
* and if it DOES exist, compare what's there against this store's configuration instead of
|
|
342
|
+
* assuming they agree. An index that outlives a config change is the normal case (schemas live in
|
|
343
|
+
* Redis, config lives in a redeployed process), and the two failure modes it produces are both
|
|
344
|
+
* invisible at runtime: a `filterableFields` entry added later has no `meta_*` TAG, so every search
|
|
345
|
+
* filtering on it matches *nothing*; a changed `dimensions` leaves the index on the old width, so
|
|
346
|
+
* writes are rejected or KNN ranks garbage. Neither raises anything on its own.
|
|
347
|
+
*
|
|
348
|
+
* The two are repaired differently, because only one of them CAN be repaired:
|
|
349
|
+
*
|
|
350
|
+
* - **A missing filterable TAG is additive** → `FT.ALTER … SCHEMA ADD`. Nothing that already works
|
|
351
|
+
* changes, so doing it automatically is safe. Note the chunks written *before* the alter carry no
|
|
352
|
+
* `meta_*` hash field at all (`upsert` only writes the fields configured at the time), so they
|
|
353
|
+
* only become filterable on that key once they're re-ingested — the schema is repaired here, the
|
|
354
|
+
* backfill is the host's call.
|
|
355
|
+
* - **A dimension (or field-type) change is not repairable** → {@link RedisVectorSchemaMismatchError}.
|
|
356
|
+
* It needs a drop + full reindex, which would destroy the host's corpus; failing loudly at boot is
|
|
357
|
+
* the only honest option.
|
|
358
|
+
*
|
|
359
|
+
* If `FT.INFO` comes back in a shape this parser doesn't recognise, no drift is inferred — an
|
|
360
|
+
* unreadable reply must not be turned into a false alarm that blocks a boot.
|
|
361
|
+
*/
|
|
285
362
|
async ensureSchema() {
|
|
363
|
+
let info;
|
|
286
364
|
try {
|
|
287
|
-
await this.client.sendCommand([
|
|
365
|
+
info = await this.client.sendCommand([
|
|
288
366
|
"FT.INFO",
|
|
289
367
|
this.index
|
|
290
368
|
]);
|
|
291
|
-
return;
|
|
292
369
|
} catch {
|
|
370
|
+
await this.createIndex();
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
await this.reconcileSchema(info);
|
|
374
|
+
}
|
|
375
|
+
/** Repair what can be repaired in place; throw on what can't. */
|
|
376
|
+
async reconcileSchema(info) {
|
|
377
|
+
const attributes = parseIndexAttributes(info);
|
|
378
|
+
if (attributes.size === 0) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const vector = attributes.get("embedding");
|
|
382
|
+
if (vector?.dimensions !== void 0 && vector.dimensions !== this.dimensions) {
|
|
383
|
+
throw new RedisVectorSchemaMismatchError(this.index, "embedding", `DIM ${this.dimensions}`, `DIM ${vector.dimensions}`, `A vector field's width is fixed at FT.CREATE and every stored embedding has the old one, so this cannot be altered in place: drop the index (FT.DROPINDEX ${this.index} DD) and re-ingest with the new embedding model.`);
|
|
384
|
+
}
|
|
385
|
+
const missing = [];
|
|
386
|
+
for (const field of this.filterableFields) {
|
|
387
|
+
const name = `meta_${field}`;
|
|
388
|
+
const existing = attributes.get(name);
|
|
389
|
+
if (existing === void 0) {
|
|
390
|
+
missing.push(name);
|
|
391
|
+
} else if (existing.type !== "TAG") {
|
|
392
|
+
throw new RedisVectorSchemaMismatchError(this.index, name, "TAG", existing.type, `A field's type cannot be changed in place: drop the index (FT.DROPINDEX ${this.index} DD) and re-ingest, or rename the metadata key.`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
for (const name of missing) {
|
|
396
|
+
await this.client.sendCommand([
|
|
397
|
+
"FT.ALTER",
|
|
398
|
+
this.index,
|
|
399
|
+
"SCHEMA",
|
|
400
|
+
"ADD",
|
|
401
|
+
name,
|
|
402
|
+
"TAG"
|
|
403
|
+
]);
|
|
293
404
|
}
|
|
405
|
+
}
|
|
406
|
+
async createIndex() {
|
|
294
407
|
const schema = [
|
|
295
408
|
"text",
|
|
296
409
|
"TEXT",
|
|
@@ -334,7 +447,8 @@ var RedisVectorStore = class {
|
|
|
334
447
|
for (const field of this.filterableFields) {
|
|
335
448
|
const value = record.metadata[field];
|
|
336
449
|
if (value !== void 0) {
|
|
337
|
-
|
|
450
|
+
const tag = Array.isArray(value) ? value.map(String).join(",") : String(value);
|
|
451
|
+
args.push(`meta_${field}`, tag);
|
|
338
452
|
}
|
|
339
453
|
}
|
|
340
454
|
}
|
|
@@ -370,6 +484,9 @@ var RedisVectorStore = class {
|
|
|
370
484
|
}
|
|
371
485
|
}
|
|
372
486
|
async listDocuments(filter) {
|
|
487
|
+
if (filterMatchesNothing(filter)) {
|
|
488
|
+
return [];
|
|
489
|
+
}
|
|
373
490
|
const query = buildFilter(filter);
|
|
374
491
|
const documents = /* @__PURE__ */ new Map();
|
|
375
492
|
const batchSize = 1e3;
|
|
@@ -410,6 +527,9 @@ var RedisVectorStore = class {
|
|
|
410
527
|
];
|
|
411
528
|
}
|
|
412
529
|
async search(embedding, options) {
|
|
530
|
+
if (filterMatchesNothing(options.filter)) {
|
|
531
|
+
return [];
|
|
532
|
+
}
|
|
413
533
|
const query = `${buildFilter(options.filter)}=>[KNN ${options.topK} @embedding $BLOB AS vector_score]`;
|
|
414
534
|
const reply = await this.client.sendCommand([
|
|
415
535
|
"FT.SEARCH",
|
|
@@ -437,6 +557,58 @@ var RedisVectorStore = class {
|
|
|
437
557
|
return parseSearchReply(reply, this.prefix);
|
|
438
558
|
}
|
|
439
559
|
};
|
|
560
|
+
function parseIndexAttributes(reply) {
|
|
561
|
+
const attributes = /* @__PURE__ */ new Map();
|
|
562
|
+
for (const entry of readInfoField(reply, "attributes")) {
|
|
563
|
+
const fields = readKeyValues(entry);
|
|
564
|
+
const name = fields.attribute ?? fields.identifier;
|
|
565
|
+
if (name === void 0) {
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
const dim = Number(fields.dim);
|
|
569
|
+
attributes.set(name, {
|
|
570
|
+
type: (fields.type ?? "").toUpperCase(),
|
|
571
|
+
...fields.dim !== void 0 && Number.isFinite(dim) ? {
|
|
572
|
+
dimensions: dim
|
|
573
|
+
} : {}
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
return attributes;
|
|
577
|
+
}
|
|
578
|
+
__name(parseIndexAttributes, "parseIndexAttributes");
|
|
579
|
+
function readInfoField(reply, key) {
|
|
580
|
+
if (Array.isArray(reply)) {
|
|
581
|
+
for (let index = 0; index + 1 < reply.length; index += 2) {
|
|
582
|
+
if (toStr(reply[index]) === key) {
|
|
583
|
+
const value = reply[index + 1];
|
|
584
|
+
return Array.isArray(value) ? value : [];
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return [];
|
|
588
|
+
}
|
|
589
|
+
if (typeof reply === "object" && reply !== null && key in reply) {
|
|
590
|
+
const value = reply[key];
|
|
591
|
+
return Array.isArray(value) ? value : [];
|
|
592
|
+
}
|
|
593
|
+
return [];
|
|
594
|
+
}
|
|
595
|
+
__name(readInfoField, "readInfoField");
|
|
596
|
+
function readKeyValues(entry) {
|
|
597
|
+
const fields = {};
|
|
598
|
+
if (Array.isArray(entry)) {
|
|
599
|
+
for (let index = 0; index + 1 < entry.length; index += 2) {
|
|
600
|
+
fields[toStr(entry[index]).toLowerCase()] = toStr(entry[index + 1]);
|
|
601
|
+
}
|
|
602
|
+
return fields;
|
|
603
|
+
}
|
|
604
|
+
if (typeof entry === "object" && entry !== null) {
|
|
605
|
+
for (const [key, value] of Object.entries(entry)) {
|
|
606
|
+
fields[key.toLowerCase()] = toStr(value);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return fields;
|
|
610
|
+
}
|
|
611
|
+
__name(readKeyValues, "readKeyValues");
|
|
440
612
|
function encodeVector(embedding) {
|
|
441
613
|
return Buffer.from(new Float32Array(embedding).buffer);
|
|
442
614
|
}
|
|
@@ -445,10 +617,23 @@ function buildFilter(filter) {
|
|
|
445
617
|
if (filter === void 0 || Object.keys(filter).length === 0) {
|
|
446
618
|
return "*";
|
|
447
619
|
}
|
|
448
|
-
const clauses = Object.entries(filter).map(([key, value]) =>
|
|
620
|
+
const clauses = Object.entries(filter).map(([key, value]) => {
|
|
621
|
+
const values = Array.isArray(value) ? value : [
|
|
622
|
+
value
|
|
623
|
+
];
|
|
624
|
+
const alternation = values.map((entry) => escapeTag(String(entry))).join("|");
|
|
625
|
+
return `@meta_${key}:{${alternation}}`;
|
|
626
|
+
});
|
|
449
627
|
return `(${clauses.join(" ")})`;
|
|
450
628
|
}
|
|
451
629
|
__name(buildFilter, "buildFilter");
|
|
630
|
+
function filterMatchesNothing(filter) {
|
|
631
|
+
if (filter === void 0) {
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
return Object.values(filter).some((value) => Array.isArray(value) && value.length === 0);
|
|
635
|
+
}
|
|
636
|
+
__name(filterMatchesNothing, "filterMatchesNothing");
|
|
452
637
|
function escapeTag(value) {
|
|
453
638
|
return value.replace(/[^a-zA-Z0-9_]/g, (char) => `\\${char}`);
|
|
454
639
|
}
|
|
@@ -601,10 +786,14 @@ var KeywordRetriever = class {
|
|
|
601
786
|
this.k1 = options.k1 ?? 1.5;
|
|
602
787
|
this.b = options.b ?? 0.75;
|
|
603
788
|
}
|
|
789
|
+
/** How many chunks are currently indexed. */
|
|
790
|
+
get size() {
|
|
791
|
+
return this.docs.size;
|
|
792
|
+
}
|
|
604
793
|
/** Index (or re-index) records. Re-adding an existing id replaces its posting and updates stats. */
|
|
605
794
|
add(records) {
|
|
606
795
|
for (const record of records) {
|
|
607
|
-
this.
|
|
796
|
+
this.removeChunk(record.id);
|
|
608
797
|
const tokens = tokenize(record.text);
|
|
609
798
|
const frequencies = /* @__PURE__ */ new Map();
|
|
610
799
|
for (const token of tokens) {
|
|
@@ -621,7 +810,30 @@ var KeywordRetriever = class {
|
|
|
621
810
|
this.totalLength += tokens.length;
|
|
622
811
|
}
|
|
623
812
|
}
|
|
624
|
-
|
|
813
|
+
/**
|
|
814
|
+
* Forget a source document — every chunk whose id is `${documentId}` or `${documentId}#<n>`, the
|
|
815
|
+
* same id scheme (and the same {@link documentIdOf} collapse) the vector stores use for their
|
|
816
|
+
* `remove`. The keyword half of a hybrid index keeps its own copy of the chunk text, so a document
|
|
817
|
+
* dropped from the vector store stays fully retrievable here until this is called. Unknown ids are
|
|
818
|
+
* a no-op.
|
|
819
|
+
*/
|
|
820
|
+
remove(documentId) {
|
|
821
|
+
for (const id of [
|
|
822
|
+
...this.docs.keys()
|
|
823
|
+
]) {
|
|
824
|
+
if (id === documentId || documentIdOf(id) === documentId) {
|
|
825
|
+
this.removeChunk(id);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
/** Drop every indexed chunk, resetting the corpus statistics with it. */
|
|
830
|
+
clear() {
|
|
831
|
+
this.docs.clear();
|
|
832
|
+
this.documentFrequency.clear();
|
|
833
|
+
this.totalLength = 0;
|
|
834
|
+
}
|
|
835
|
+
/** Drop ONE chunk by its exact id, decrementing the corpus statistics it contributed. */
|
|
836
|
+
removeChunk(id) {
|
|
625
837
|
const existing = this.docs.get(id);
|
|
626
838
|
if (existing === void 0) {
|
|
627
839
|
return;
|
|
@@ -871,6 +1083,7 @@ __name(createRetrievalTool, "createRetrievalTool");
|
|
|
871
1083
|
KeywordRetriever,
|
|
872
1084
|
MemoryVectorStore,
|
|
873
1085
|
PgVectorStore,
|
|
1086
|
+
RedisVectorSchemaMismatchError,
|
|
874
1087
|
RedisVectorStore,
|
|
875
1088
|
RerankingRetriever,
|
|
876
1089
|
chunkDocuments,
|