@dudousxd/nestjs-agent-rag 0.8.0 → 0.10.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/README.md +88 -2
- package/dist/index.cjs +347 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +345 -1
- package/dist/index.d.ts +345 -1
- package/dist/index.js +337 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -91,6 +91,11 @@ var UnsafeRemovalError = class extends Error {
|
|
|
91
91
|
this.name = "UnsafeRemovalError";
|
|
92
92
|
}
|
|
93
93
|
};
|
|
94
|
+
function chunkIndexOf(chunkId) {
|
|
95
|
+
const match = /#(\d+)$/.exec(chunkId);
|
|
96
|
+
return match?.[1] === void 0 ? 0 : Number(match[1]);
|
|
97
|
+
}
|
|
98
|
+
__name(chunkIndexOf, "chunkIndexOf");
|
|
94
99
|
function documentIdOf(chunkId) {
|
|
95
100
|
return chunkId.replace(/#\d+$/, "");
|
|
96
101
|
}
|
|
@@ -160,6 +165,16 @@ var MemoryVectorStore = class {
|
|
|
160
165
|
__name(this, "MemoryVectorStore");
|
|
161
166
|
}
|
|
162
167
|
records = /* @__PURE__ */ new Map();
|
|
168
|
+
/**
|
|
169
|
+
* Telemetry self-description. No `collection`: there is no namespace to name — the corpus IS this
|
|
170
|
+
* instance, and two `MemoryVectorStore`s in one process are two corpora with nothing to tell them
|
|
171
|
+
* apart on a dashboard.
|
|
172
|
+
*/
|
|
173
|
+
describeRetrieval() {
|
|
174
|
+
return {
|
|
175
|
+
store: "memory"
|
|
176
|
+
};
|
|
177
|
+
}
|
|
163
178
|
async upsert(records) {
|
|
164
179
|
for (const record of records) {
|
|
165
180
|
this.records.set(record.id, record);
|
|
@@ -215,6 +230,34 @@ var MemoryVectorStore = class {
|
|
|
215
230
|
...documents.values()
|
|
216
231
|
];
|
|
217
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* The chunks of one document, in document order. See {@link VectorStore.listChunks}.
|
|
235
|
+
*
|
|
236
|
+
* Insertion order is not document order — `upsert` stores whatever the caller passed, and a
|
|
237
|
+
* re-ingest that rewrites chunk 3 alone moves it to the end of the Map. So this sorts by the index
|
|
238
|
+
* parsed from the id, the same thing the SQL and Redis adapters order by, rather than trusting the
|
|
239
|
+
* Map. A reference adapter that returned "usually ordered" would let a consumer ship a bug that
|
|
240
|
+
* only shows up on the store tests are NOT written against.
|
|
241
|
+
*/
|
|
242
|
+
async listChunks(documentId, options) {
|
|
243
|
+
const chunks = [];
|
|
244
|
+
for (const record of this.records.values()) {
|
|
245
|
+
if (documentIdOf(record.id) !== documentId) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
chunks.push({
|
|
249
|
+
id: record.id,
|
|
250
|
+
index: chunkIndexOf(record.id),
|
|
251
|
+
text: record.text,
|
|
252
|
+
...record.metadata !== void 0 ? {
|
|
253
|
+
metadata: record.metadata
|
|
254
|
+
} : {}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
chunks.sort((a, b) => a.index - b.index);
|
|
258
|
+
const offset = options?.offset ?? 0;
|
|
259
|
+
return options?.limit === void 0 ? chunks.slice(offset) : chunks.slice(offset, offset + options.limit);
|
|
260
|
+
}
|
|
218
261
|
async listDocumentIds(filter) {
|
|
219
262
|
if (filterMatchesNothing(filter)) {
|
|
220
263
|
return [];
|
|
@@ -326,6 +369,13 @@ var PgVectorStore = class {
|
|
|
326
369
|
this.table = options.table ?? "agent_rag_chunks";
|
|
327
370
|
this.dimensions = options.dimensions ?? 1536;
|
|
328
371
|
}
|
|
372
|
+
/** Telemetry self-description — the table is the namespace a retrieval was served from. */
|
|
373
|
+
describeRetrieval() {
|
|
374
|
+
return {
|
|
375
|
+
store: "pg",
|
|
376
|
+
collection: this.table
|
|
377
|
+
};
|
|
378
|
+
}
|
|
329
379
|
/** Idempotent DDL: the `vector` extension, the chunks table, and the cosine HNSW index. */
|
|
330
380
|
async ensureSchema() {
|
|
331
381
|
await this.client.query("CREATE EXTENSION IF NOT EXISTS vector");
|
|
@@ -401,6 +451,44 @@ var PgVectorStore = class {
|
|
|
401
451
|
} : {}
|
|
402
452
|
}));
|
|
403
453
|
}
|
|
454
|
+
/**
|
|
455
|
+
* The chunks of one document, in document order. See {@link VectorStore.listChunks}.
|
|
456
|
+
*
|
|
457
|
+
* The order is computed from the id rather than stored: `#12` must sort after `#2`, which a plain
|
|
458
|
+
* `ORDER BY id` gets wrong on every document past ten chunks because it compares text. So the
|
|
459
|
+
* trailing number is extracted and cast to `int`, and a bare id (a single-chunk document, which
|
|
460
|
+
* carries no `#n` suffix) collapses to `0` — the same rule `chunkIndexOf` applies in the other two
|
|
461
|
+
* adapters.
|
|
462
|
+
*
|
|
463
|
+
* `embedding` is left out of the projection: it is the widest column in the table and useless to a
|
|
464
|
+
* caller reading text back.
|
|
465
|
+
*/
|
|
466
|
+
async listChunks(documentId, options) {
|
|
467
|
+
const params = [
|
|
468
|
+
documentId
|
|
469
|
+
];
|
|
470
|
+
let sql = `SELECT id, ${CHUNK_INDEX_FROM_ID} AS chunk_index, text, metadata
|
|
471
|
+
FROM ${this.table}
|
|
472
|
+
WHERE ${DOCUMENT_ID_FROM_CHUNK} = $1
|
|
473
|
+
ORDER BY chunk_index`;
|
|
474
|
+
if (options?.limit !== void 0) {
|
|
475
|
+
params.push(options.limit);
|
|
476
|
+
sql += ` LIMIT $${params.length}`;
|
|
477
|
+
}
|
|
478
|
+
if (options?.offset !== void 0) {
|
|
479
|
+
params.push(options.offset);
|
|
480
|
+
sql += ` OFFSET $${params.length}`;
|
|
481
|
+
}
|
|
482
|
+
const rows = await this.client.query(sql, params);
|
|
483
|
+
return rows.map((row) => ({
|
|
484
|
+
id: row.id,
|
|
485
|
+
index: Number(row.chunk_index),
|
|
486
|
+
text: row.text,
|
|
487
|
+
...row.metadata !== null ? {
|
|
488
|
+
metadata: row.metadata
|
|
489
|
+
} : {}
|
|
490
|
+
}));
|
|
491
|
+
}
|
|
404
492
|
/** {@link listDocuments} without the metadata: one `DISTINCT` over the derived document id. */
|
|
405
493
|
async listDocumentIds(filter) {
|
|
406
494
|
if (filterMatchesNothing(filter)) {
|
|
@@ -511,6 +599,7 @@ function buildWhere(filter, startIndex) {
|
|
|
511
599
|
}
|
|
512
600
|
__name(buildWhere, "buildWhere");
|
|
513
601
|
var DOCUMENT_ID_FROM_CHUNK = "regexp_replace(id, '#[0-9]+$', '')";
|
|
602
|
+
var CHUNK_INDEX_FROM_ID = "COALESCE((substring(id from '#([0-9]+)$'))::int, 0)";
|
|
514
603
|
function toVectorLiteral(embedding) {
|
|
515
604
|
return `[${embedding.join(",")}]`;
|
|
516
605
|
}
|
|
@@ -549,6 +638,17 @@ var RedisVectorStore = class {
|
|
|
549
638
|
this.lexicalScorer = options.lexicalScorer ?? "BM25";
|
|
550
639
|
}
|
|
551
640
|
/**
|
|
641
|
+
* Telemetry self-description — the RediSearch INDEX, not the key prefix: the index is what both
|
|
642
|
+
* legs query, and two stores sharing one index (the dense and lexical halves of a hybrid) must
|
|
643
|
+
* report the same namespace or `describeSharedSource` would see them disagree and drop the store.
|
|
644
|
+
*/
|
|
645
|
+
describeRetrieval() {
|
|
646
|
+
return {
|
|
647
|
+
store: "redis",
|
|
648
|
+
collection: this.index
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
552
652
|
* Idempotent, and drift-aware: create the HNSW/cosine index if `FT.INFO` says it doesn't exist —
|
|
553
653
|
* and if it DOES exist, compare what's there against this store's configuration instead of
|
|
554
654
|
* assuming they agree. An index that outlives a config change is the normal case (schemas live in
|
|
@@ -803,6 +903,58 @@ var RedisVectorStore = class {
|
|
|
803
903
|
...keys
|
|
804
904
|
];
|
|
805
905
|
}
|
|
906
|
+
/**
|
|
907
|
+
* The chunks of one document, in document order. See {@link VectorStore.listChunks}.
|
|
908
|
+
*
|
|
909
|
+
* Reads the hashes directly (`HMGET text metadata_json`) rather than going through `FT.SEARCH`.
|
|
910
|
+
* Two reasons, and the second is the one that matters: the query language has no way to ask for
|
|
911
|
+
* "every chunk of this document" — `id` is the key name, not an indexed field — and `FT.SEARCH`
|
|
912
|
+
* caps at `LIMIT`, so paging a document through it would mean ordering by a field the index does
|
|
913
|
+
* not hold. `chunkKeys` is the same enumeration {@link RedisVectorStore.remove} and
|
|
914
|
+
* {@link RedisVectorStore.updateMetadata} already use, so "the chunks this returns" and "the chunks
|
|
915
|
+
* a delete would take" cannot drift apart.
|
|
916
|
+
*
|
|
917
|
+
* It inherits that method's cost too: one keyspace `SCAN` per call. Acceptable here because this
|
|
918
|
+
* is an inspection path (one document, on demand) and not something retrieval runs — but it is why
|
|
919
|
+
* this must not be put on a hot path or called in a loop over a corpus.
|
|
920
|
+
*
|
|
921
|
+
* `embedding` is deliberately not fetched: it is by far the largest field on the hash and nothing
|
|
922
|
+
* a caller reading text back can do with it.
|
|
923
|
+
*/
|
|
924
|
+
async listChunks(documentId, options) {
|
|
925
|
+
const keys = await this.chunkKeys(documentId);
|
|
926
|
+
const ordered = keys.map((key) => ({
|
|
927
|
+
key,
|
|
928
|
+
id: key.slice(this.prefix.length)
|
|
929
|
+
})).map((entry) => ({
|
|
930
|
+
...entry,
|
|
931
|
+
index: chunkIndexOf(entry.id)
|
|
932
|
+
})).sort((a, b) => a.index - b.index);
|
|
933
|
+
const offset = options?.offset ?? 0;
|
|
934
|
+
const page = options?.limit === void 0 ? ordered.slice(offset) : ordered.slice(offset, offset + options.limit);
|
|
935
|
+
const chunks = [];
|
|
936
|
+
for (const entry of page) {
|
|
937
|
+
const reply = await this.client.sendCommand([
|
|
938
|
+
"HMGET",
|
|
939
|
+
entry.key,
|
|
940
|
+
"text",
|
|
941
|
+
"metadata_json"
|
|
942
|
+
]);
|
|
943
|
+
const fields = Array.isArray(reply) ? reply : [];
|
|
944
|
+
const text = fields[0] === null || fields[0] === void 0 ? void 0 : toStr(fields[0]);
|
|
945
|
+
if (text === void 0) {
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
const raw = fields[1] === null || fields[1] === void 0 ? void 0 : toStr(fields[1]);
|
|
949
|
+
chunks.push({
|
|
950
|
+
id: entry.id,
|
|
951
|
+
index: entry.index,
|
|
952
|
+
text,
|
|
953
|
+
...parseMetadata(raw)
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
return chunks;
|
|
957
|
+
}
|
|
806
958
|
async listDocuments(filter) {
|
|
807
959
|
if (filterMatchesNothing(filter)) {
|
|
808
960
|
return [];
|
|
@@ -1325,6 +1477,31 @@ function parseMetadata(raw) {
|
|
|
1325
1477
|
}
|
|
1326
1478
|
__name(parseMetadata, "parseMetadata");
|
|
1327
1479
|
|
|
1480
|
+
// src/retrieval-descriptor.ts
|
|
1481
|
+
function describesRetrieval(value) {
|
|
1482
|
+
return typeof value === "object" && value !== null && typeof Reflect.get(value, "describeRetrieval") === "function";
|
|
1483
|
+
}
|
|
1484
|
+
__name(describesRetrieval, "describesRetrieval");
|
|
1485
|
+
function describeRetrieval(candidate) {
|
|
1486
|
+
return describesRetrieval(candidate) ? candidate.describeRetrieval() : {};
|
|
1487
|
+
}
|
|
1488
|
+
__name(describeRetrieval, "describeRetrieval");
|
|
1489
|
+
function describeSource(base) {
|
|
1490
|
+
const { retriever: _ignored, ...source } = describeRetrieval(base);
|
|
1491
|
+
return source;
|
|
1492
|
+
}
|
|
1493
|
+
__name(describeSource, "describeSource");
|
|
1494
|
+
function describeSharedSource(legs) {
|
|
1495
|
+
const sources = legs.map(describeSource);
|
|
1496
|
+
const [first] = sources;
|
|
1497
|
+
if (first === void 0) {
|
|
1498
|
+
return {};
|
|
1499
|
+
}
|
|
1500
|
+
const agrees = sources.every((source) => source.store === first.store && source.collection === first.collection);
|
|
1501
|
+
return agrees ? first : {};
|
|
1502
|
+
}
|
|
1503
|
+
__name(describeSharedSource, "describeSharedSource");
|
|
1504
|
+
|
|
1328
1505
|
// src/embedding-retriever.ts
|
|
1329
1506
|
var EmbeddingRetriever = class {
|
|
1330
1507
|
static {
|
|
@@ -1338,6 +1515,13 @@ var EmbeddingRetriever = class {
|
|
|
1338
1515
|
this.store = store;
|
|
1339
1516
|
this.options = options;
|
|
1340
1517
|
}
|
|
1518
|
+
/** Telemetry self-description — the dense strategy, over whatever store it was handed. */
|
|
1519
|
+
describeRetrieval() {
|
|
1520
|
+
return {
|
|
1521
|
+
retriever: "embedding",
|
|
1522
|
+
...describeSource(this.store)
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1341
1525
|
async retrieve(query, options = {}) {
|
|
1342
1526
|
const [embedding] = await this.embedder.embed([
|
|
1343
1527
|
query
|
|
@@ -1370,6 +1554,16 @@ var KeywordRetriever = class {
|
|
|
1370
1554
|
this.k1 = options.k1 ?? 1.5;
|
|
1371
1555
|
this.b = options.b ?? 0.75;
|
|
1372
1556
|
}
|
|
1557
|
+
/**
|
|
1558
|
+
* Telemetry self-description. No `store`: this retriever holds its own copy of the corpus in
|
|
1559
|
+
* process, so attributing its retrievals to whichever vector store happens to hold the same chunks
|
|
1560
|
+
* would credit a backend that was never queried.
|
|
1561
|
+
*/
|
|
1562
|
+
describeRetrieval() {
|
|
1563
|
+
return {
|
|
1564
|
+
retriever: "keyword"
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1373
1567
|
/** How many chunks are currently indexed. */
|
|
1374
1568
|
get size() {
|
|
1375
1569
|
return this.docs.size;
|
|
@@ -1487,6 +1681,13 @@ var LexicalRetriever = class {
|
|
|
1487
1681
|
constructor(store) {
|
|
1488
1682
|
this.store = store;
|
|
1489
1683
|
}
|
|
1684
|
+
/** Telemetry self-description — the store's own lexical index, so the store is the same one. */
|
|
1685
|
+
describeRetrieval() {
|
|
1686
|
+
return {
|
|
1687
|
+
retriever: "lexical",
|
|
1688
|
+
...describeSource(this.store)
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1490
1691
|
async retrieve(query, options = {}) {
|
|
1491
1692
|
return this.store.searchText(query, {
|
|
1492
1693
|
topK: options.topK ?? 5,
|
|
@@ -1508,6 +1709,13 @@ var HybridRetriever = class {
|
|
|
1508
1709
|
this.retrievers = retrievers;
|
|
1509
1710
|
this.options = options;
|
|
1510
1711
|
}
|
|
1712
|
+
/** Telemetry self-description — the store only when every leg agrees; see `describeSharedSource`. */
|
|
1713
|
+
describeRetrieval() {
|
|
1714
|
+
return {
|
|
1715
|
+
retriever: "hybrid",
|
|
1716
|
+
...describeSharedSource(this.retrievers)
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1511
1719
|
async retrieve(query, options = {}) {
|
|
1512
1720
|
const k = this.options.k ?? 60;
|
|
1513
1721
|
const fetchTopK = this.options.fetchTopK ?? 20;
|
|
@@ -1556,6 +1764,13 @@ var RerankingRetriever = class {
|
|
|
1556
1764
|
this.reranker = reranker;
|
|
1557
1765
|
this.options = options;
|
|
1558
1766
|
}
|
|
1767
|
+
/** Telemetry self-description — the two-stage strategy, over the base's store. */
|
|
1768
|
+
describeRetrieval() {
|
|
1769
|
+
return {
|
|
1770
|
+
retriever: "reranking",
|
|
1771
|
+
...describeSource(this.base)
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1559
1774
|
async retrieve(query, options = {}) {
|
|
1560
1775
|
const candidates = await this.base.retrieve(query, {
|
|
1561
1776
|
topK: this.options.fetchTopK ?? 20,
|
|
@@ -1580,6 +1795,13 @@ var FilteredRetriever = class {
|
|
|
1580
1795
|
this.base = base;
|
|
1581
1796
|
this.filter = filter;
|
|
1582
1797
|
}
|
|
1798
|
+
/** Telemetry self-description — this scope, over the base's store. */
|
|
1799
|
+
describeRetrieval() {
|
|
1800
|
+
return {
|
|
1801
|
+
retriever: "filtered",
|
|
1802
|
+
...describeSource(this.base)
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1583
1805
|
async retrieve(query, options = {}) {
|
|
1584
1806
|
return this.base.retrieve(query, {
|
|
1585
1807
|
...options,
|
|
@@ -1629,6 +1851,108 @@ async function ingestDocuments(documents, options) {
|
|
|
1629
1851
|
}
|
|
1630
1852
|
__name(ingestDocuments, "ingestDocuments");
|
|
1631
1853
|
|
|
1854
|
+
// src/retrieval-telemetry.ts
|
|
1855
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1856
|
+
import { channelName, emit, getChannel } from "@dudousxd/nestjs-diagnostics";
|
|
1857
|
+
var RAG_DIAGNOSTIC_LIB = "rag";
|
|
1858
|
+
var RAG_RETRIEVAL_EVENT = "retrieval";
|
|
1859
|
+
var RAG_RETRIEVAL_CHANNEL = channelName(RAG_DIAGNOSTIC_LIB, RAG_RETRIEVAL_EVENT);
|
|
1860
|
+
function emitRagRetrieval(event, durationMs) {
|
|
1861
|
+
emit(RAG_DIAGNOSTIC_LIB, RAG_RETRIEVAL_EVENT, event, {
|
|
1862
|
+
durationMs
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
__name(emitRagRetrieval, "emitRagRetrieval");
|
|
1866
|
+
var DEFAULT_TOP_K = 5;
|
|
1867
|
+
var inRetrieval = new AsyncLocalStorage();
|
|
1868
|
+
function round4(value) {
|
|
1869
|
+
return Math.round(value * 1e4) / 1e4;
|
|
1870
|
+
}
|
|
1871
|
+
__name(round4, "round4");
|
|
1872
|
+
function scoreStats(passages) {
|
|
1873
|
+
const scores = passages.map((passage) => passage.score).filter((score) => Number.isFinite(score));
|
|
1874
|
+
if (scores.length === 0) {
|
|
1875
|
+
return {};
|
|
1876
|
+
}
|
|
1877
|
+
const sum = scores.reduce((total, score) => total + score, 0);
|
|
1878
|
+
return {
|
|
1879
|
+
topScore: round4(Math.max(...scores)),
|
|
1880
|
+
meanScore: round4(sum / scores.length)
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
__name(scoreStats, "scoreStats");
|
|
1884
|
+
var InstrumentedRetriever = class InstrumentedRetriever2 {
|
|
1885
|
+
static {
|
|
1886
|
+
__name(this, "InstrumentedRetriever");
|
|
1887
|
+
}
|
|
1888
|
+
base;
|
|
1889
|
+
options;
|
|
1890
|
+
constructor(base, options = {}) {
|
|
1891
|
+
this.base = base;
|
|
1892
|
+
this.options = options;
|
|
1893
|
+
}
|
|
1894
|
+
/** Transparent to detection: wrapping a retriever must not turn it into an `unknown` one. */
|
|
1895
|
+
describeRetrieval() {
|
|
1896
|
+
return {
|
|
1897
|
+
...describeRetrieval(this.base),
|
|
1898
|
+
...this.options.describe
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
async retrieve(query, options = {}) {
|
|
1902
|
+
if (!getChannel(RAG_DIAGNOSTIC_LIB, RAG_RETRIEVAL_EVENT).hasSubscribers) {
|
|
1903
|
+
return this.base.retrieve(query, options);
|
|
1904
|
+
}
|
|
1905
|
+
if (inRetrieval.getStore() === true) {
|
|
1906
|
+
return this.base.retrieve(query, options);
|
|
1907
|
+
}
|
|
1908
|
+
const descriptor = this.describeRetrieval();
|
|
1909
|
+
const topK = options.topK ?? DEFAULT_TOP_K;
|
|
1910
|
+
const startedAt = Date.now();
|
|
1911
|
+
try {
|
|
1912
|
+
const passages = await inRetrieval.run(true, () => this.base.retrieve(query, options));
|
|
1913
|
+
emitRagRetrieval({
|
|
1914
|
+
retriever: descriptor.retriever ?? "unknown",
|
|
1915
|
+
...descriptor.store !== void 0 ? {
|
|
1916
|
+
store: descriptor.store
|
|
1917
|
+
} : {},
|
|
1918
|
+
...this.collectionOf(options.filter, descriptor),
|
|
1919
|
+
topK,
|
|
1920
|
+
chunks: passages.length,
|
|
1921
|
+
zeroHit: passages.length === 0,
|
|
1922
|
+
...scoreStats(passages),
|
|
1923
|
+
failed: false
|
|
1924
|
+
}, Date.now() - startedAt);
|
|
1925
|
+
return passages;
|
|
1926
|
+
} catch (error) {
|
|
1927
|
+
emitRagRetrieval({
|
|
1928
|
+
retriever: descriptor.retriever ?? "unknown",
|
|
1929
|
+
...descriptor.store !== void 0 ? {
|
|
1930
|
+
store: descriptor.store
|
|
1931
|
+
} : {},
|
|
1932
|
+
...this.collectionOf(options.filter, descriptor),
|
|
1933
|
+
topK,
|
|
1934
|
+
chunks: 0,
|
|
1935
|
+
zeroHit: true,
|
|
1936
|
+
failed: true,
|
|
1937
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1938
|
+
}, Date.now() - startedAt);
|
|
1939
|
+
throw error;
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
/** The host's collection for this call, else the store namespace, else nothing. */
|
|
1943
|
+
collectionOf(filter, descriptor) {
|
|
1944
|
+
const { collection } = this.options;
|
|
1945
|
+
const resolved = typeof collection === "function" ? collection(filter) : collection ?? descriptor.collection;
|
|
1946
|
+
return resolved !== void 0 ? {
|
|
1947
|
+
collection: resolved
|
|
1948
|
+
} : {};
|
|
1949
|
+
}
|
|
1950
|
+
};
|
|
1951
|
+
function instrumentRetriever(retriever, options = {}) {
|
|
1952
|
+
return new InstrumentedRetriever(retriever, options);
|
|
1953
|
+
}
|
|
1954
|
+
__name(instrumentRetriever, "instrumentRetriever");
|
|
1955
|
+
|
|
1632
1956
|
// src/retrieval-tool.ts
|
|
1633
1957
|
function isQueryInput(value) {
|
|
1634
1958
|
return typeof value === "object" && value !== null && "query" in value && typeof value.query === "string";
|
|
@@ -1655,6 +1979,8 @@ var querySchema = {
|
|
|
1655
1979
|
};
|
|
1656
1980
|
function createRetrievalTool(retriever, options = {}) {
|
|
1657
1981
|
const topK = options.topK;
|
|
1982
|
+
const { telemetry = true } = options;
|
|
1983
|
+
const instrumented = telemetry === false ? retriever : instrumentRetriever(retriever, telemetry === true ? {} : telemetry);
|
|
1658
1984
|
return {
|
|
1659
1985
|
spec: {
|
|
1660
1986
|
name: options.name ?? "search_knowledge",
|
|
@@ -1667,7 +1993,7 @@ function createRetrievalTool(retriever, options = {}) {
|
|
|
1667
1993
|
if (!isQueryInput(input)) {
|
|
1668
1994
|
throw new Error("retrieval tool expects { query: string }");
|
|
1669
1995
|
}
|
|
1670
|
-
const passages = await
|
|
1996
|
+
const passages = await instrumented.retrieve(input.query, topK !== void 0 ? {
|
|
1671
1997
|
topK
|
|
1672
1998
|
} : {});
|
|
1673
1999
|
return {
|
|
@@ -1686,17 +2012,27 @@ export {
|
|
|
1686
2012
|
LexicalRetriever,
|
|
1687
2013
|
MemoryVectorStore,
|
|
1688
2014
|
PgVectorStore,
|
|
2015
|
+
RAG_DIAGNOSTIC_LIB,
|
|
2016
|
+
RAG_RETRIEVAL_CHANNEL,
|
|
2017
|
+
RAG_RETRIEVAL_EVENT,
|
|
1689
2018
|
RedisVectorSchemaMismatchError,
|
|
1690
2019
|
RedisVectorStore,
|
|
1691
2020
|
RerankingRetriever,
|
|
1692
2021
|
UnsafeRemovalError,
|
|
1693
2022
|
applyMetadataPatch,
|
|
1694
2023
|
chunkDocuments,
|
|
2024
|
+
chunkIndexOf,
|
|
1695
2025
|
chunkText,
|
|
1696
2026
|
createRetrievalTool,
|
|
2027
|
+
describeRetrieval,
|
|
2028
|
+
describeSharedSource,
|
|
2029
|
+
describeSource,
|
|
2030
|
+
describesRetrieval,
|
|
1697
2031
|
documentIdOf,
|
|
2032
|
+
emitRagRetrieval,
|
|
1698
2033
|
ingestChunks,
|
|
1699
2034
|
ingestDocuments,
|
|
2035
|
+
instrumentRetriever,
|
|
1700
2036
|
isLexicalVectorStore
|
|
1701
2037
|
};
|
|
1702
2038
|
//# sourceMappingURL=index.js.map
|