@dudousxd/nestjs-agent-rag 0.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,883 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ EmbeddingRetriever: () => EmbeddingRetriever,
25
+ FilteredRetriever: () => FilteredRetriever,
26
+ HybridRetriever: () => HybridRetriever,
27
+ KeywordRetriever: () => KeywordRetriever,
28
+ MemoryVectorStore: () => MemoryVectorStore,
29
+ PgVectorStore: () => PgVectorStore,
30
+ RedisVectorStore: () => RedisVectorStore,
31
+ RerankingRetriever: () => RerankingRetriever,
32
+ chunkDocuments: () => chunkDocuments,
33
+ chunkText: () => chunkText,
34
+ createRetrievalTool: () => createRetrievalTool,
35
+ documentIdOf: () => documentIdOf,
36
+ ingestChunks: () => ingestChunks,
37
+ ingestDocuments: () => ingestDocuments
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+
41
+ // src/chunk.ts
42
+ function chunkText(text, options = {}) {
43
+ const chunkSize = options.chunkSize ?? 800;
44
+ const overlap = options.overlap ?? 100;
45
+ const normalized = text.trim();
46
+ if (normalized.length === 0) {
47
+ return [];
48
+ }
49
+ if (normalized.length <= chunkSize) {
50
+ return [
51
+ normalized
52
+ ];
53
+ }
54
+ const chunks = [];
55
+ let start = 0;
56
+ while (start < normalized.length) {
57
+ const end = Math.min(start + chunkSize, normalized.length);
58
+ let breakAt = end;
59
+ if (end < normalized.length) {
60
+ const window = normalized.slice(start, end);
61
+ const boundary = Math.max(window.lastIndexOf("\n\n"), window.lastIndexOf(". "), window.lastIndexOf(" "));
62
+ if (boundary > chunkSize / 2) {
63
+ breakAt = start + boundary + 1;
64
+ }
65
+ }
66
+ const chunk = normalized.slice(start, breakAt).trim();
67
+ if (chunk.length > 0) {
68
+ chunks.push(chunk);
69
+ }
70
+ if (breakAt >= normalized.length) {
71
+ break;
72
+ }
73
+ start = Math.max(breakAt - overlap, start + 1);
74
+ }
75
+ return chunks;
76
+ }
77
+ __name(chunkText, "chunkText");
78
+
79
+ // src/vector-store.ts
80
+ function documentIdOf(chunkId) {
81
+ return chunkId.replace(/#\d+$/, "");
82
+ }
83
+ __name(documentIdOf, "documentIdOf");
84
+
85
+ // src/filter.ts
86
+ function matchesFilter(metadata, filter) {
87
+ if (metadata === void 0) {
88
+ return Object.keys(filter).length === 0;
89
+ }
90
+ return Object.entries(filter).every(([key, value]) => metadata[key] === value);
91
+ }
92
+ __name(matchesFilter, "matchesFilter");
93
+
94
+ // src/memory-vector-store.ts
95
+ var MemoryVectorStore = class {
96
+ static {
97
+ __name(this, "MemoryVectorStore");
98
+ }
99
+ records = /* @__PURE__ */ new Map();
100
+ async upsert(records) {
101
+ for (const record of records) {
102
+ this.records.set(record.id, record);
103
+ }
104
+ }
105
+ async remove(documentId) {
106
+ for (const id of this.records.keys()) {
107
+ if (documentIdOf(id) === documentId) {
108
+ this.records.delete(id);
109
+ }
110
+ }
111
+ }
112
+ async listDocuments(filter) {
113
+ const documents = /* @__PURE__ */ new Map();
114
+ for (const record of this.records.values()) {
115
+ if (filter !== void 0 && !matchesFilter(record.metadata, filter)) {
116
+ continue;
117
+ }
118
+ const id = documentIdOf(record.id);
119
+ if (!documents.has(id)) {
120
+ documents.set(id, {
121
+ id,
122
+ ...record.metadata !== void 0 ? {
123
+ metadata: record.metadata
124
+ } : {}
125
+ });
126
+ }
127
+ }
128
+ return [
129
+ ...documents.values()
130
+ ];
131
+ }
132
+ async search(embedding, options) {
133
+ const scored = [];
134
+ for (const record of this.records.values()) {
135
+ if (options.filter !== void 0 && !matchesFilter(record.metadata, options.filter)) {
136
+ continue;
137
+ }
138
+ scored.push({
139
+ id: record.id,
140
+ text: record.text,
141
+ score: cosineSimilarity(embedding, record.embedding),
142
+ ...record.source !== void 0 ? {
143
+ source: record.source
144
+ } : {},
145
+ ...record.metadata !== void 0 ? {
146
+ metadata: record.metadata
147
+ } : {}
148
+ });
149
+ }
150
+ return scored.sort((a, b) => b.score - a.score).slice(0, options.topK);
151
+ }
152
+ };
153
+ function cosineSimilarity(a, b) {
154
+ let dot = 0;
155
+ let normA = 0;
156
+ let normB = 0;
157
+ const length = Math.min(a.length, b.length);
158
+ for (let index = 0; index < length; index += 1) {
159
+ const valueA = a[index] ?? 0;
160
+ const valueB = b[index] ?? 0;
161
+ dot += valueA * valueB;
162
+ normA += valueA * valueA;
163
+ normB += valueB * valueB;
164
+ }
165
+ const denominator = Math.sqrt(normA) * Math.sqrt(normB);
166
+ return denominator === 0 ? 0 : dot / denominator;
167
+ }
168
+ __name(cosineSimilarity, "cosineSimilarity");
169
+
170
+ // src/pg-vector-store.ts
171
+ var PgVectorStore = class {
172
+ static {
173
+ __name(this, "PgVectorStore");
174
+ }
175
+ client;
176
+ table;
177
+ dimensions;
178
+ constructor(client, options = {}) {
179
+ this.client = client;
180
+ this.table = options.table ?? "agent_rag_chunks";
181
+ this.dimensions = options.dimensions ?? 1536;
182
+ }
183
+ /** Idempotent DDL: the `vector` extension, the chunks table, and the cosine HNSW index. */
184
+ async ensureSchema() {
185
+ await this.client.query("CREATE EXTENSION IF NOT EXISTS vector");
186
+ await this.client.query(`CREATE TABLE IF NOT EXISTS ${this.table} (
187
+ id TEXT PRIMARY KEY,
188
+ text TEXT NOT NULL,
189
+ source TEXT,
190
+ metadata JSONB,
191
+ embedding vector(${this.dimensions}) NOT NULL
192
+ )`);
193
+ await this.client.query(`CREATE INDEX IF NOT EXISTS ${this.table}_embedding_idx
194
+ ON ${this.table} USING hnsw (embedding vector_cosine_ops)`);
195
+ }
196
+ async upsert(records) {
197
+ for (const record of records) {
198
+ await this.client.query(`INSERT INTO ${this.table} (id, text, source, metadata, embedding)
199
+ VALUES ($1, $2, $3, $4, $5)
200
+ ON CONFLICT (id) DO UPDATE SET
201
+ text = EXCLUDED.text,
202
+ source = EXCLUDED.source,
203
+ metadata = EXCLUDED.metadata,
204
+ embedding = EXCLUDED.embedding`, [
205
+ record.id,
206
+ record.text,
207
+ record.source ?? null,
208
+ record.metadata !== void 0 ? JSON.stringify(record.metadata) : null,
209
+ toVectorLiteral(record.embedding)
210
+ ]);
211
+ }
212
+ }
213
+ async remove(documentId) {
214
+ await this.client.query(`DELETE FROM ${this.table} WHERE ${DOCUMENT_ID_FROM_CHUNK} = $1`, [
215
+ documentId
216
+ ]);
217
+ }
218
+ async listDocuments(filter) {
219
+ const hasFilter = filter !== void 0 && Object.keys(filter).length > 0;
220
+ const rows = await this.client.query(`SELECT DISTINCT ON (${DOCUMENT_ID_FROM_CHUNK}) ${DOCUMENT_ID_FROM_CHUNK} AS doc_id, metadata
221
+ FROM ${this.table}
222
+ ${hasFilter ? "WHERE metadata @> $1::jsonb" : ""}
223
+ ORDER BY ${DOCUMENT_ID_FROM_CHUNK}`, hasFilter ? [
224
+ JSON.stringify(filter)
225
+ ] : []);
226
+ return rows.map((row) => ({
227
+ id: row.doc_id,
228
+ ...row.metadata !== null ? {
229
+ metadata: row.metadata
230
+ } : {}
231
+ }));
232
+ }
233
+ async search(embedding, options) {
234
+ const vector = toVectorLiteral(embedding);
235
+ const hasFilter = options.filter !== void 0 && Object.keys(options.filter).length > 0;
236
+ const rows = await this.client.query(`SELECT id, text, source, metadata, 1 - (embedding <=> $1::vector) AS score
237
+ FROM ${this.table}
238
+ ${hasFilter ? "WHERE metadata @> $3::jsonb" : ""}
239
+ ORDER BY embedding <=> $1::vector
240
+ LIMIT $2`, hasFilter ? [
241
+ vector,
242
+ options.topK,
243
+ JSON.stringify(options.filter)
244
+ ] : [
245
+ vector,
246
+ options.topK
247
+ ]);
248
+ return rows.map((row) => ({
249
+ id: row.id,
250
+ text: row.text,
251
+ score: Number(row.score),
252
+ ...row.source !== null ? {
253
+ source: row.source
254
+ } : {},
255
+ ...row.metadata !== null ? {
256
+ metadata: row.metadata
257
+ } : {}
258
+ }));
259
+ }
260
+ };
261
+ var DOCUMENT_ID_FROM_CHUNK = "regexp_replace(id, '#[0-9]+$', '')";
262
+ function toVectorLiteral(embedding) {
263
+ return `[${embedding.join(",")}]`;
264
+ }
265
+ __name(toVectorLiteral, "toVectorLiteral");
266
+
267
+ // src/redis-vector-store.ts
268
+ var RedisVectorStore = class {
269
+ static {
270
+ __name(this, "RedisVectorStore");
271
+ }
272
+ client;
273
+ index;
274
+ prefix;
275
+ dimensions;
276
+ filterableFields;
277
+ constructor(client, options = {}) {
278
+ this.client = client;
279
+ this.index = options.index ?? "agent_rag_idx";
280
+ this.prefix = options.prefix ?? "agent_rag:";
281
+ this.dimensions = options.dimensions ?? 1536;
282
+ this.filterableFields = options.filterableFields ?? [];
283
+ }
284
+ /** Idempotent: create the HNSW/cosine index if `FT.INFO` says it doesn't exist yet. */
285
+ async ensureSchema() {
286
+ try {
287
+ await this.client.sendCommand([
288
+ "FT.INFO",
289
+ this.index
290
+ ]);
291
+ return;
292
+ } catch {
293
+ }
294
+ const schema = [
295
+ "text",
296
+ "TEXT",
297
+ "source",
298
+ "TAG",
299
+ "metadata_json",
300
+ "TEXT",
301
+ "NOINDEX"
302
+ ];
303
+ for (const field of this.filterableFields) {
304
+ schema.push(`meta_${field}`, "TAG");
305
+ }
306
+ schema.push("embedding", "VECTOR", "HNSW", "6", "TYPE", "FLOAT32", "DIM", String(this.dimensions), "DISTANCE_METRIC", "COSINE");
307
+ await this.client.sendCommand([
308
+ "FT.CREATE",
309
+ this.index,
310
+ "ON",
311
+ "HASH",
312
+ "PREFIX",
313
+ "1",
314
+ this.prefix,
315
+ "SCHEMA",
316
+ ...schema
317
+ ]);
318
+ }
319
+ async upsert(records) {
320
+ for (const record of records) {
321
+ const args = [
322
+ "HSET",
323
+ `${this.prefix}${record.id}`,
324
+ "text",
325
+ record.text,
326
+ "embedding",
327
+ encodeVector(record.embedding)
328
+ ];
329
+ if (record.source !== void 0) {
330
+ args.push("source", record.source);
331
+ }
332
+ if (record.metadata !== void 0) {
333
+ args.push("metadata_json", JSON.stringify(record.metadata));
334
+ for (const field of this.filterableFields) {
335
+ const value = record.metadata[field];
336
+ if (value !== void 0) {
337
+ args.push(`meta_${field}`, String(value));
338
+ }
339
+ }
340
+ }
341
+ await this.client.sendCommand(args);
342
+ }
343
+ }
344
+ async remove(documentId) {
345
+ const keys = /* @__PURE__ */ new Set([
346
+ `${this.prefix}${documentId}`
347
+ ]);
348
+ const pattern = `${this.prefix}${escapeGlob(documentId)}#*`;
349
+ let cursor = "0";
350
+ do {
351
+ const reply = await this.client.sendCommand([
352
+ "SCAN",
353
+ cursor,
354
+ "MATCH",
355
+ pattern,
356
+ "COUNT",
357
+ "256"
358
+ ]);
359
+ const page = parseScanReply(reply);
360
+ cursor = page.cursor;
361
+ for (const key of page.keys) {
362
+ keys.add(key);
363
+ }
364
+ } while (cursor !== "0");
365
+ if (keys.size > 0) {
366
+ await this.client.sendCommand([
367
+ "DEL",
368
+ ...keys
369
+ ]);
370
+ }
371
+ }
372
+ async listDocuments(filter) {
373
+ const query = buildFilter(filter);
374
+ const documents = /* @__PURE__ */ new Map();
375
+ const batchSize = 1e3;
376
+ let offset = 0;
377
+ for (; ; ) {
378
+ const reply = await this.client.sendCommand([
379
+ "FT.SEARCH",
380
+ this.index,
381
+ query,
382
+ "RETURN",
383
+ "1",
384
+ "metadata_json",
385
+ "LIMIT",
386
+ String(offset),
387
+ String(batchSize),
388
+ "DIALECT",
389
+ "2"
390
+ ]);
391
+ const passages = parseSearchReply(reply, this.prefix);
392
+ for (const passage of passages) {
393
+ const id = documentIdOf(passage.id);
394
+ if (!documents.has(id)) {
395
+ documents.set(id, {
396
+ id,
397
+ ...passage.metadata !== void 0 ? {
398
+ metadata: passage.metadata
399
+ } : {}
400
+ });
401
+ }
402
+ }
403
+ if (passages.length < batchSize) {
404
+ break;
405
+ }
406
+ offset += batchSize;
407
+ }
408
+ return [
409
+ ...documents.values()
410
+ ];
411
+ }
412
+ async search(embedding, options) {
413
+ const query = `${buildFilter(options.filter)}=>[KNN ${options.topK} @embedding $BLOB AS vector_score]`;
414
+ const reply = await this.client.sendCommand([
415
+ "FT.SEARCH",
416
+ this.index,
417
+ query,
418
+ "PARAMS",
419
+ "2",
420
+ "BLOB",
421
+ encodeVector(embedding),
422
+ "RETURN",
423
+ "4",
424
+ "text",
425
+ "source",
426
+ "metadata_json",
427
+ "vector_score",
428
+ "SORTBY",
429
+ "vector_score",
430
+ "ASC",
431
+ "DIALECT",
432
+ "2",
433
+ "LIMIT",
434
+ "0",
435
+ String(options.topK)
436
+ ]);
437
+ return parseSearchReply(reply, this.prefix);
438
+ }
439
+ };
440
+ function encodeVector(embedding) {
441
+ return Buffer.from(new Float32Array(embedding).buffer);
442
+ }
443
+ __name(encodeVector, "encodeVector");
444
+ function buildFilter(filter) {
445
+ if (filter === void 0 || Object.keys(filter).length === 0) {
446
+ return "*";
447
+ }
448
+ const clauses = Object.entries(filter).map(([key, value]) => `@meta_${key}:{${escapeTag(String(value))}}`);
449
+ return `(${clauses.join(" ")})`;
450
+ }
451
+ __name(buildFilter, "buildFilter");
452
+ function escapeTag(value) {
453
+ return value.replace(/[^a-zA-Z0-9_]/g, (char) => `\\${char}`);
454
+ }
455
+ __name(escapeTag, "escapeTag");
456
+ function escapeGlob(value) {
457
+ return value.replace(/[*?[\]\\]/g, (char) => `\\${char}`);
458
+ }
459
+ __name(escapeGlob, "escapeGlob");
460
+ function parseScanReply(reply) {
461
+ if (Array.isArray(reply) && reply.length >= 2) {
462
+ const rawKeys = reply[1];
463
+ return {
464
+ cursor: toStr(reply[0]),
465
+ keys: Array.isArray(rawKeys) ? rawKeys.map(toStr) : []
466
+ };
467
+ }
468
+ if (typeof reply === "object" && reply !== null && "cursor" in reply && "keys" in reply) {
469
+ const { cursor, keys } = reply;
470
+ return {
471
+ cursor: toStr(cursor),
472
+ keys: Array.isArray(keys) ? keys.map(toStr) : []
473
+ };
474
+ }
475
+ return {
476
+ cursor: "0",
477
+ keys: []
478
+ };
479
+ }
480
+ __name(parseScanReply, "parseScanReply");
481
+ function toStr(value) {
482
+ if (typeof value === "string") {
483
+ return value;
484
+ }
485
+ return Buffer.isBuffer(value) ? value.toString("utf8") : String(value);
486
+ }
487
+ __name(toStr, "toStr");
488
+ function parseSearchReply(reply, prefix) {
489
+ if (Array.isArray(reply)) {
490
+ const passages = [];
491
+ for (let index = 1; index + 1 < reply.length; index += 2) {
492
+ const rawFields = reply[index + 1];
493
+ if (Array.isArray(rawFields)) {
494
+ const attrs = {};
495
+ for (let field = 0; field + 1 < rawFields.length; field += 2) {
496
+ attrs[toStr(rawFields[field])] = toStr(rawFields[field + 1]);
497
+ }
498
+ passages.push(toPassage(toStr(reply[index]), attrs, prefix));
499
+ }
500
+ }
501
+ return passages;
502
+ }
503
+ if (typeof reply === "object" && reply !== null && "results" in reply) {
504
+ const results = reply.results;
505
+ if (Array.isArray(results)) {
506
+ return results.map((result) => toPassage(readId(result), readAttributes(result), prefix));
507
+ }
508
+ }
509
+ return [];
510
+ }
511
+ __name(parseSearchReply, "parseSearchReply");
512
+ function readId(result) {
513
+ if (typeof result === "object" && result !== null && "id" in result) {
514
+ return toStr(result.id);
515
+ }
516
+ return "";
517
+ }
518
+ __name(readId, "readId");
519
+ function readAttributes(result) {
520
+ const attrs = {};
521
+ if (typeof result === "object" && result !== null && "extra_attributes" in result) {
522
+ const raw = result.extra_attributes;
523
+ if (typeof raw === "object" && raw !== null) {
524
+ for (const [key, value] of Object.entries(raw)) {
525
+ attrs[key] = toStr(value);
526
+ }
527
+ }
528
+ }
529
+ return attrs;
530
+ }
531
+ __name(readAttributes, "readAttributes");
532
+ function toPassage(rawId, attrs, prefix) {
533
+ const distance = Number(attrs.vector_score ?? "1");
534
+ const source = attrs.source;
535
+ return {
536
+ id: rawId.startsWith(prefix) ? rawId.slice(prefix.length) : rawId,
537
+ text: attrs.text ?? "",
538
+ score: 1 - distance,
539
+ ...source !== void 0 ? {
540
+ source
541
+ } : {},
542
+ ...parseMetadata(attrs.metadata_json)
543
+ };
544
+ }
545
+ __name(toPassage, "toPassage");
546
+ function parseMetadata(raw) {
547
+ if (raw === void 0) {
548
+ return {};
549
+ }
550
+ try {
551
+ const parsed = JSON.parse(raw);
552
+ if (typeof parsed === "object" && parsed !== null) {
553
+ return {
554
+ metadata: parsed
555
+ };
556
+ }
557
+ } catch {
558
+ }
559
+ return {};
560
+ }
561
+ __name(parseMetadata, "parseMetadata");
562
+
563
+ // src/embedding-retriever.ts
564
+ var EmbeddingRetriever = class {
565
+ static {
566
+ __name(this, "EmbeddingRetriever");
567
+ }
568
+ embedder;
569
+ store;
570
+ constructor(embedder, store) {
571
+ this.embedder = embedder;
572
+ this.store = store;
573
+ }
574
+ async retrieve(query, options = {}) {
575
+ const [embedding] = await this.embedder.embed([
576
+ query
577
+ ]);
578
+ if (embedding === void 0) {
579
+ return [];
580
+ }
581
+ return this.store.search(embedding, {
582
+ topK: options.topK ?? 5,
583
+ ...options.filter !== void 0 ? {
584
+ filter: options.filter
585
+ } : {}
586
+ });
587
+ }
588
+ };
589
+
590
+ // src/keyword-retriever.ts
591
+ var KeywordRetriever = class {
592
+ static {
593
+ __name(this, "KeywordRetriever");
594
+ }
595
+ docs = /* @__PURE__ */ new Map();
596
+ documentFrequency = /* @__PURE__ */ new Map();
597
+ totalLength = 0;
598
+ k1;
599
+ b;
600
+ constructor(options = {}) {
601
+ this.k1 = options.k1 ?? 1.5;
602
+ this.b = options.b ?? 0.75;
603
+ }
604
+ /** Index (or re-index) records. Re-adding an existing id replaces its posting and updates stats. */
605
+ add(records) {
606
+ for (const record of records) {
607
+ this.remove(record.id);
608
+ const tokens = tokenize(record.text);
609
+ const frequencies = /* @__PURE__ */ new Map();
610
+ for (const token of tokens) {
611
+ frequencies.set(token, (frequencies.get(token) ?? 0) + 1);
612
+ }
613
+ for (const term of frequencies.keys()) {
614
+ this.documentFrequency.set(term, (this.documentFrequency.get(term) ?? 0) + 1);
615
+ }
616
+ this.docs.set(record.id, {
617
+ record,
618
+ frequencies,
619
+ length: tokens.length
620
+ });
621
+ this.totalLength += tokens.length;
622
+ }
623
+ }
624
+ remove(id) {
625
+ const existing = this.docs.get(id);
626
+ if (existing === void 0) {
627
+ return;
628
+ }
629
+ for (const term of existing.frequencies.keys()) {
630
+ const next = (this.documentFrequency.get(term) ?? 1) - 1;
631
+ if (next <= 0) {
632
+ this.documentFrequency.delete(term);
633
+ } else {
634
+ this.documentFrequency.set(term, next);
635
+ }
636
+ }
637
+ this.totalLength -= existing.length;
638
+ this.docs.delete(id);
639
+ }
640
+ async retrieve(query, options = {}) {
641
+ const total = this.docs.size;
642
+ if (total === 0) {
643
+ return [];
644
+ }
645
+ const averageLength = this.totalLength / total;
646
+ const queryTerms = new Set(tokenize(query));
647
+ const scored = [];
648
+ for (const { record, frequencies, length } of this.docs.values()) {
649
+ if (options.filter !== void 0 && !matchesFilter(record.metadata, options.filter)) {
650
+ continue;
651
+ }
652
+ let score = 0;
653
+ for (const term of queryTerms) {
654
+ const termFrequency = frequencies.get(term);
655
+ if (termFrequency === void 0) {
656
+ continue;
657
+ }
658
+ const df = this.documentFrequency.get(term) ?? 0;
659
+ const idf = Math.log(1 + (total - df + 0.5) / (df + 0.5));
660
+ const denominator = termFrequency + this.k1 * (1 - this.b + this.b * length / averageLength);
661
+ score += idf * (termFrequency * (this.k1 + 1) / denominator);
662
+ }
663
+ if (score > 0) {
664
+ scored.push({
665
+ id: record.id,
666
+ text: record.text,
667
+ score,
668
+ ...record.source !== void 0 ? {
669
+ source: record.source
670
+ } : {},
671
+ ...record.metadata !== void 0 ? {
672
+ metadata: record.metadata
673
+ } : {}
674
+ });
675
+ }
676
+ }
677
+ return scored.sort((a, b) => b.score - a.score).slice(0, options.topK ?? 5);
678
+ }
679
+ };
680
+ function tokenize(text) {
681
+ return text.toLowerCase().match(/[a-z0-9]+/g) ?? [];
682
+ }
683
+ __name(tokenize, "tokenize");
684
+
685
+ // src/hybrid-retriever.ts
686
+ var HybridRetriever = class {
687
+ static {
688
+ __name(this, "HybridRetriever");
689
+ }
690
+ retrievers;
691
+ options;
692
+ constructor(retrievers, options = {}) {
693
+ this.retrievers = retrievers;
694
+ this.options = options;
695
+ }
696
+ async retrieve(query, options = {}) {
697
+ const k = this.options.k ?? 60;
698
+ const fetchTopK = this.options.fetchTopK ?? 20;
699
+ const weights = this.options.weights;
700
+ const lists = await Promise.all(this.retrievers.map((retriever) => retriever.retrieve(query, {
701
+ topK: fetchTopK,
702
+ ...options.filter !== void 0 ? {
703
+ filter: options.filter
704
+ } : {}
705
+ })));
706
+ const fused = /* @__PURE__ */ new Map();
707
+ lists.forEach((list, listIndex) => {
708
+ const weight = weights?.[listIndex] ?? 1;
709
+ list.forEach((passage, rank) => {
710
+ const contribution = weight / (k + rank + 1);
711
+ const existing = fused.get(passage.id);
712
+ if (existing !== void 0) {
713
+ existing.score += contribution;
714
+ } else {
715
+ fused.set(passage.id, {
716
+ passage,
717
+ score: contribution
718
+ });
719
+ }
720
+ });
721
+ });
722
+ return [
723
+ ...fused.values()
724
+ ].sort((a, b) => b.score - a.score).slice(0, options.topK ?? 5).map((entry) => ({
725
+ ...entry.passage,
726
+ score: entry.score
727
+ }));
728
+ }
729
+ };
730
+
731
+ // src/reranking-retriever.ts
732
+ var RerankingRetriever = class {
733
+ static {
734
+ __name(this, "RerankingRetriever");
735
+ }
736
+ base;
737
+ reranker;
738
+ options;
739
+ constructor(base, reranker, options = {}) {
740
+ this.base = base;
741
+ this.reranker = reranker;
742
+ this.options = options;
743
+ }
744
+ async retrieve(query, options = {}) {
745
+ const candidates = await this.base.retrieve(query, {
746
+ topK: this.options.fetchTopK ?? 20,
747
+ ...options.filter !== void 0 ? {
748
+ filter: options.filter
749
+ } : {}
750
+ });
751
+ return this.reranker.rerank(query, candidates, {
752
+ topK: options.topK ?? 5
753
+ });
754
+ }
755
+ };
756
+
757
+ // src/filtered-retriever.ts
758
+ var FilteredRetriever = class {
759
+ static {
760
+ __name(this, "FilteredRetriever");
761
+ }
762
+ base;
763
+ filter;
764
+ constructor(base, filter) {
765
+ this.base = base;
766
+ this.filter = filter;
767
+ }
768
+ async retrieve(query, options = {}) {
769
+ return this.base.retrieve(query, {
770
+ ...options,
771
+ filter: {
772
+ ...options.filter,
773
+ ...this.filter
774
+ }
775
+ });
776
+ }
777
+ };
778
+
779
+ // src/ingest.ts
780
+ function chunkDocuments(documents, options = {}) {
781
+ const chunks = [];
782
+ for (const document of documents) {
783
+ chunkText(document.text, options).forEach((text, index) => {
784
+ chunks.push({
785
+ id: `${document.id}#${index}`,
786
+ text,
787
+ ...document.source !== void 0 ? {
788
+ source: document.source
789
+ } : {},
790
+ ...document.metadata !== void 0 ? {
791
+ metadata: document.metadata
792
+ } : {}
793
+ });
794
+ });
795
+ }
796
+ return chunks;
797
+ }
798
+ __name(chunkDocuments, "chunkDocuments");
799
+ async function ingestChunks(chunks, options) {
800
+ if (chunks.length === 0) {
801
+ return 0;
802
+ }
803
+ const embeddings = await options.embedder.embed(chunks.map((chunk) => chunk.text));
804
+ const records = chunks.map((chunk, index) => ({
805
+ ...chunk,
806
+ embedding: embeddings[index] ?? []
807
+ }));
808
+ await options.store.upsert(records);
809
+ return records.length;
810
+ }
811
+ __name(ingestChunks, "ingestChunks");
812
+ async function ingestDocuments(documents, options) {
813
+ return ingestChunks(chunkDocuments(documents, options), options);
814
+ }
815
+ __name(ingestDocuments, "ingestDocuments");
816
+
817
+ // src/retrieval-tool.ts
818
+ function isQueryInput(value) {
819
+ return typeof value === "object" && value !== null && "query" in value && typeof value.query === "string";
820
+ }
821
+ __name(isQueryInput, "isQueryInput");
822
+ var querySchema = {
823
+ "~standard": {
824
+ version: 1,
825
+ vendor: "nestjs-agent-rag",
826
+ validate(value) {
827
+ return isQueryInput(value) ? {
828
+ value: {
829
+ query: value.query
830
+ }
831
+ } : {
832
+ issues: [
833
+ {
834
+ message: "retrieval tool expects { query: string }"
835
+ }
836
+ ]
837
+ };
838
+ }
839
+ }
840
+ };
841
+ function createRetrievalTool(retriever, options = {}) {
842
+ const topK = options.topK;
843
+ return {
844
+ spec: {
845
+ name: options.name ?? "search_knowledge",
846
+ kind: "read",
847
+ description: options.description ?? "Search the knowledge base for passages relevant to a query. Returns passages with their source for citation.",
848
+ inputSchema: querySchema
849
+ },
850
+ handler: {
851
+ async execute(input) {
852
+ if (!isQueryInput(input)) {
853
+ throw new Error("retrieval tool expects { query: string }");
854
+ }
855
+ const passages = await retriever.retrieve(input.query, topK !== void 0 ? {
856
+ topK
857
+ } : {});
858
+ return {
859
+ passages
860
+ };
861
+ }
862
+ }
863
+ };
864
+ }
865
+ __name(createRetrievalTool, "createRetrievalTool");
866
+ // Annotate the CommonJS export names for ESM import in node:
867
+ 0 && (module.exports = {
868
+ EmbeddingRetriever,
869
+ FilteredRetriever,
870
+ HybridRetriever,
871
+ KeywordRetriever,
872
+ MemoryVectorStore,
873
+ PgVectorStore,
874
+ RedisVectorStore,
875
+ RerankingRetriever,
876
+ chunkDocuments,
877
+ chunkText,
878
+ createRetrievalTool,
879
+ documentIdOf,
880
+ ingestChunks,
881
+ ingestDocuments
882
+ });
883
+ //# sourceMappingURL=index.cjs.map