@dudousxd/nestjs-agent-rag 0.13.0 → 0.14.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 +67 -0
- package/dist/index.cjs +331 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +188 -7
- package/dist/index.d.ts +188 -7
- package/dist/index.js +321 -26
- package/dist/index.js.map +1 -1
- package/dist/nestjs/index.cjs +174 -0
- package/dist/nestjs/index.cjs.map +1 -0
- package/dist/nestjs/index.d.cts +60 -0
- package/dist/nestjs/index.d.ts +60 -0
- package/dist/nestjs/index.js +149 -0
- package/dist/nestjs/index.js.map +1 -0
- package/package.json +28 -8
package/dist/index.js
CHANGED
|
@@ -77,6 +77,16 @@ function chunkByRecord(text, chunkSize, separator) {
|
|
|
77
77
|
__name(chunkByRecord, "chunkByRecord");
|
|
78
78
|
|
|
79
79
|
// src/vector-store.ts
|
|
80
|
+
async function replaceDocumentChunks(store, documentId, records) {
|
|
81
|
+
const atomic = store;
|
|
82
|
+
if (typeof atomic.replaceDocument === "function") {
|
|
83
|
+
await atomic.replaceDocument(documentId, records);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
await store.remove(documentId);
|
|
87
|
+
await store.upsert(records);
|
|
88
|
+
}
|
|
89
|
+
__name(replaceDocumentChunks, "replaceDocumentChunks");
|
|
80
90
|
function isLexicalVectorStore(store) {
|
|
81
91
|
return typeof store.searchText === "function";
|
|
82
92
|
}
|
|
@@ -137,12 +147,19 @@ function applyMetadataPatch(metadata, patch) {
|
|
|
137
147
|
__name(applyMetadataPatch, "applyMetadataPatch");
|
|
138
148
|
|
|
139
149
|
// src/filter.ts
|
|
150
|
+
function isMissingValue(value) {
|
|
151
|
+
return value === void 0 || Array.isArray(value) && value.includes(void 0);
|
|
152
|
+
}
|
|
153
|
+
__name(isMissingValue, "isMissingValue");
|
|
140
154
|
function matchesFilter(metadata, filter) {
|
|
141
155
|
const entries = Object.entries(filter);
|
|
142
156
|
if (metadata === void 0) {
|
|
143
157
|
return entries.length === 0;
|
|
144
158
|
}
|
|
145
159
|
return entries.every(([key, expected]) => {
|
|
160
|
+
if (isMissingValue(expected)) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
146
163
|
if (Array.isArray(expected)) {
|
|
147
164
|
const actual = metadata[key];
|
|
148
165
|
return Array.isArray(actual) ? actual.some((value) => expected.includes(value)) : expected.includes(actual);
|
|
@@ -155,9 +172,18 @@ function filterMatchesNothing(filter) {
|
|
|
155
172
|
if (filter === void 0) {
|
|
156
173
|
return false;
|
|
157
174
|
}
|
|
158
|
-
return Object.values(filter).some((value) => Array.isArray(value) && value.length === 0);
|
|
175
|
+
return Object.values(filter).some((value) => isMissingValue(value) || Array.isArray(value) && value.length === 0);
|
|
159
176
|
}
|
|
160
177
|
__name(filterMatchesNothing, "filterMatchesNothing");
|
|
178
|
+
function undefinedFilterKeys(filter) {
|
|
179
|
+
return Object.entries(filter).filter(([, value]) => isMissingValue(value)).map(([key]) => key);
|
|
180
|
+
}
|
|
181
|
+
__name(undefinedFilterKeys, "undefinedFilterKeys");
|
|
182
|
+
function undefinedFilterMessage(keys) {
|
|
183
|
+
const named = keys.map((key) => `"${key}"`).join(", ");
|
|
184
|
+
return `removeWhere() refuses a filter with no value for ${named}: the scope was probably never set.`;
|
|
185
|
+
}
|
|
186
|
+
__name(undefinedFilterMessage, "undefinedFilterMessage");
|
|
161
187
|
|
|
162
188
|
// src/memory-vector-store.ts
|
|
163
189
|
var MemoryVectorStore = class {
|
|
@@ -294,6 +320,10 @@ var MemoryVectorStore = class {
|
|
|
294
320
|
if (Object.keys(filter).length === 0) {
|
|
295
321
|
throw new UnsafeRemovalError("empty-filter", "removeWhere() refuses an empty filter: it would delete every chunk in the store. Pass a filter that scopes the removal, or delete deliberately with removeMany(await store.listDocumentIds()).");
|
|
296
322
|
}
|
|
323
|
+
const missing = undefinedFilterKeys(filter);
|
|
324
|
+
if (missing.length > 0) {
|
|
325
|
+
throw new UnsafeRemovalError("undefined-value", undefinedFilterMessage(missing));
|
|
326
|
+
}
|
|
297
327
|
if (filterMatchesNothing(filter)) {
|
|
298
328
|
return 0;
|
|
299
329
|
}
|
|
@@ -563,6 +593,7 @@ var PgVectorStore = class {
|
|
|
563
593
|
dimensions;
|
|
564
594
|
/** The widths with a partial HNSW index, for a mixed-dimension table. */
|
|
565
595
|
indexedDimensions;
|
|
596
|
+
vectorType;
|
|
566
597
|
nullableEmbeddings;
|
|
567
598
|
iterativeScan;
|
|
568
599
|
efSearch;
|
|
@@ -586,6 +617,10 @@ var PgVectorStore = class {
|
|
|
586
617
|
...new Set(dimensions)
|
|
587
618
|
];
|
|
588
619
|
}
|
|
620
|
+
this.vectorType = options.vectorType ?? "vector";
|
|
621
|
+
if (this.vectorType === "halfvec" && this.dimensions === void 0) {
|
|
622
|
+
throw new Error('PgVectorStore: vectorType "halfvec" needs a single width, not a list');
|
|
623
|
+
}
|
|
589
624
|
this.nullableEmbeddings = options.nullableEmbeddings ?? false;
|
|
590
625
|
this.iterativeScan = options.iterativeScan;
|
|
591
626
|
this.efSearch = options.efSearch;
|
|
@@ -604,7 +639,7 @@ var PgVectorStore = class {
|
|
|
604
639
|
*/
|
|
605
640
|
schemaStatements() {
|
|
606
641
|
const mixed = this.dimensions === void 0;
|
|
607
|
-
const column = mixed ? "vector" :
|
|
642
|
+
const column = mixed ? "vector" : `${this.vectorType}(${this.dimensions})`;
|
|
608
643
|
const statements = [
|
|
609
644
|
"CREATE EXTENSION IF NOT EXISTS vector",
|
|
610
645
|
`CREATE TABLE IF NOT EXISTS ${this.table} (
|
|
@@ -623,7 +658,7 @@ var PgVectorStore = class {
|
|
|
623
658
|
}
|
|
624
659
|
} else {
|
|
625
660
|
statements.push(`CREATE INDEX IF NOT EXISTS ${this.table}_embedding_idx
|
|
626
|
-
ON ${this.table} USING hnsw (embedding
|
|
661
|
+
ON ${this.table} USING hnsw (embedding ${this.vectorType}_cosine_ops)`);
|
|
627
662
|
}
|
|
628
663
|
return statements;
|
|
629
664
|
}
|
|
@@ -639,6 +674,23 @@ var PgVectorStore = class {
|
|
|
639
674
|
* {@link PgVectorStoreOptions.nullableEmbeddings}).
|
|
640
675
|
*/
|
|
641
676
|
async upsert(records) {
|
|
677
|
+
await this.upsertWith(this.client, records);
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Swaps a document's chunks in one transaction, so a crash can't lose it and a search sees the old
|
|
681
|
+
* version or the new one. A client without `transaction` falls back to two statements.
|
|
682
|
+
*/
|
|
683
|
+
async replaceDocument(documentId, records) {
|
|
684
|
+
const swap = /* @__PURE__ */ __name(async (client) => {
|
|
685
|
+
await client.query(`DELETE FROM ${this.table} WHERE ${DOCUMENT_ID_FROM_CHUNK} = $1`, [
|
|
686
|
+
stripNulBytes(documentId)
|
|
687
|
+
]);
|
|
688
|
+
await this.upsertWith(client, records);
|
|
689
|
+
}, "swap");
|
|
690
|
+
const transaction = this.client.transaction?.bind(this.client);
|
|
691
|
+
await (transaction === void 0 ? swap(this.client) : transaction(swap));
|
|
692
|
+
}
|
|
693
|
+
async upsertWith(client, records) {
|
|
642
694
|
const byId = /* @__PURE__ */ new Map();
|
|
643
695
|
for (const record of records) {
|
|
644
696
|
const id = stripNulBytes(record.id);
|
|
@@ -660,9 +712,9 @@ var PgVectorStore = class {
|
|
|
660
712
|
const metadata = record.metadata !== void 0 ? stripNulBytes(record.metadata) : void 0;
|
|
661
713
|
const base = params.length;
|
|
662
714
|
params.push(record.id, text, source ?? null, metadata !== void 0 ? JSON.stringify(metadata) : null, record.embedding.length > 0 ? toVectorLiteral(record.embedding) : null);
|
|
663
|
-
return `($${base + 1}, $${base + 2}, $${base + 3}, $${base + 4}::jsonb, $${base + 5}
|
|
715
|
+
return `($${base + 1}, $${base + 2}, $${base + 3}, $${base + 4}::jsonb, $${base + 5}::${this.vectorType})`;
|
|
664
716
|
});
|
|
665
|
-
await
|
|
717
|
+
await client.query(`INSERT INTO ${this.table} (id, text, source, metadata, embedding)
|
|
666
718
|
VALUES ${rows.join(", ")}
|
|
667
719
|
ON CONFLICT (id) DO UPDATE SET
|
|
668
720
|
text = EXCLUDED.text,
|
|
@@ -786,6 +838,10 @@ var PgVectorStore = class {
|
|
|
786
838
|
if (Object.keys(filter).length === 0) {
|
|
787
839
|
throw new UnsafeRemovalError("empty-filter", "removeWhere() refuses an empty filter: it would delete every chunk in the table. Pass a filter that scopes the removal, or delete deliberately with removeMany(await store.listDocumentIds()).");
|
|
788
840
|
}
|
|
841
|
+
const missing = undefinedFilterKeys(filter);
|
|
842
|
+
if (missing.length > 0) {
|
|
843
|
+
throw new UnsafeRemovalError("undefined-value", undefinedFilterMessage(missing));
|
|
844
|
+
}
|
|
789
845
|
if (filterMatchesNothing(filter)) {
|
|
790
846
|
return 0;
|
|
791
847
|
}
|
|
@@ -817,7 +873,7 @@ var PgVectorStore = class {
|
|
|
817
873
|
options.topK
|
|
818
874
|
];
|
|
819
875
|
const conditions = this.whereConditions(options.filter, params);
|
|
820
|
-
let vectorType =
|
|
876
|
+
let vectorType = this.vectorType;
|
|
821
877
|
if (this.dimensions === void 0) {
|
|
822
878
|
const width = embedding.length;
|
|
823
879
|
if (this.indexedDimensions.includes(width)) {
|
|
@@ -827,7 +883,7 @@ var PgVectorStore = class {
|
|
|
827
883
|
} else if (this.nullableEmbeddings) {
|
|
828
884
|
conditions.push("embedding IS NOT NULL");
|
|
829
885
|
}
|
|
830
|
-
const column = vectorType ===
|
|
886
|
+
const column = vectorType === this.vectorType ? "embedding" : `(embedding::${vectorType})`;
|
|
831
887
|
const sql = `SELECT id, text, source, metadata, 1 - (${column} <=> $1::${vectorType}) AS score
|
|
832
888
|
FROM ${this.table}
|
|
833
889
|
${conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""}
|
|
@@ -1014,6 +1070,10 @@ function buildWhere(filter, params) {
|
|
|
1014
1070
|
const clauses = [];
|
|
1015
1071
|
const scalar = {};
|
|
1016
1072
|
for (const [key, value] of Object.entries(stripped)) {
|
|
1073
|
+
if (value === void 0 || Array.isArray(value) && value.includes(void 0)) {
|
|
1074
|
+
clauses.push("false");
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1017
1077
|
if (Array.isArray(value)) {
|
|
1018
1078
|
if (value.length === 0) {
|
|
1019
1079
|
clauses.push("false");
|
|
@@ -1555,6 +1615,10 @@ var RedisVectorStore = class {
|
|
|
1555
1615
|
if (keys.length === 0) {
|
|
1556
1616
|
throw new UnsafeRemovalError("empty-filter", "removeWhere() refuses an empty filter: it would delete every chunk in the index. Pass a filter that scopes the removal, or delete deliberately with removeMany(await store.listDocumentIds()).");
|
|
1557
1617
|
}
|
|
1618
|
+
const missing = undefinedFilterKeys(filter);
|
|
1619
|
+
if (missing.length > 0) {
|
|
1620
|
+
throw new UnsafeRemovalError("undefined-value", undefinedFilterMessage(missing));
|
|
1621
|
+
}
|
|
1558
1622
|
const unindexed = keys.filter((key) => !this.filterableFields.includes(key));
|
|
1559
1623
|
if (unindexed.length > 0) {
|
|
1560
1624
|
const named = unindexed.map((key) => `"${key}"`).join(", ");
|
|
@@ -2105,7 +2169,7 @@ var KeywordRetriever = class {
|
|
|
2105
2169
|
}
|
|
2106
2170
|
};
|
|
2107
2171
|
function tokenize(text) {
|
|
2108
|
-
return text.toLowerCase().match(/[
|
|
2172
|
+
return text.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase().match(/[\p{L}\p{N}_]+/gu) ?? [];
|
|
2109
2173
|
}
|
|
2110
2174
|
__name(tokenize, "tokenize");
|
|
2111
2175
|
|
|
@@ -2426,13 +2490,19 @@ function createRetrievalTool(retriever, options = {}) {
|
|
|
2426
2490
|
inputSchema: querySchema
|
|
2427
2491
|
},
|
|
2428
2492
|
handler: {
|
|
2429
|
-
async execute(input) {
|
|
2493
|
+
async execute(input, ctx) {
|
|
2430
2494
|
if (!isQueryInput(input)) {
|
|
2431
2495
|
throw new Error("retrieval tool expects { query: string }");
|
|
2432
2496
|
}
|
|
2433
|
-
const
|
|
2434
|
-
|
|
2435
|
-
|
|
2497
|
+
const filter = options.filter?.(ctx);
|
|
2498
|
+
const passages = await instrumented.retrieve(input.query, {
|
|
2499
|
+
...topK !== void 0 ? {
|
|
2500
|
+
topK
|
|
2501
|
+
} : {},
|
|
2502
|
+
...filter !== void 0 ? {
|
|
2503
|
+
filter
|
|
2504
|
+
} : {}
|
|
2505
|
+
});
|
|
2436
2506
|
return {
|
|
2437
2507
|
passages
|
|
2438
2508
|
};
|
|
@@ -2472,7 +2542,7 @@ function openAiEmbeddings(options) {
|
|
|
2472
2542
|
const maxInputChars = options.maxInputChars ?? 24e3;
|
|
2473
2543
|
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
2474
2544
|
const doFetch = options.fetch ?? fetch;
|
|
2475
|
-
const request = /* @__PURE__ */ __name(async (input) => {
|
|
2545
|
+
const request = /* @__PURE__ */ __name(async (input, count) => {
|
|
2476
2546
|
const response = await doFetch(url, {
|
|
2477
2547
|
method: "POST",
|
|
2478
2548
|
headers: {
|
|
@@ -2505,19 +2575,20 @@ function openAiEmbeddings(options) {
|
|
|
2505
2575
|
const tokens = json2?.usage?.total_tokens ?? json2?.usage?.prompt_tokens;
|
|
2506
2576
|
if (typeof tokens === "number" && Number.isFinite(tokens)) {
|
|
2507
2577
|
options.onUsage?.(tokens);
|
|
2578
|
+
count(tokens);
|
|
2508
2579
|
}
|
|
2509
2580
|
return items.map((item) => item.embedding);
|
|
2510
2581
|
}, "request");
|
|
2511
|
-
const requestSplitting = /* @__PURE__ */ __name(async (input) => {
|
|
2582
|
+
const requestSplitting = /* @__PURE__ */ __name(async (input, count) => {
|
|
2512
2583
|
if (input.length > batchSize) {
|
|
2513
2584
|
const vectors = [];
|
|
2514
2585
|
for (let start = 0; start < input.length; start += batchSize) {
|
|
2515
|
-
vectors.push(...await requestSplitting(input.slice(start, start + batchSize)));
|
|
2586
|
+
vectors.push(...await requestSplitting(input.slice(start, start + batchSize), count));
|
|
2516
2587
|
}
|
|
2517
2588
|
return vectors;
|
|
2518
2589
|
}
|
|
2519
2590
|
try {
|
|
2520
|
-
return await request(input);
|
|
2591
|
+
return await request(input, count);
|
|
2521
2592
|
} catch (error) {
|
|
2522
2593
|
if (!isBatchTooLarge(error) || input.length <= 1) {
|
|
2523
2594
|
throw error;
|
|
@@ -2529,20 +2600,37 @@ function openAiEmbeddings(options) {
|
|
|
2529
2600
|
options.onWarn?.(`embeddings server ${url} refused a batch of ${input.length} inputs (${error.message}); retrying in batches of ${half}. Raise the server's batch limit (TEI: --max-client-batch-size) or lower batchSize.`);
|
|
2530
2601
|
}
|
|
2531
2602
|
return [
|
|
2532
|
-
...await requestSplitting(input.slice(0, half)),
|
|
2533
|
-
...await requestSplitting(input.slice(half))
|
|
2603
|
+
...await requestSplitting(input.slice(0, half), count),
|
|
2604
|
+
...await requestSplitting(input.slice(half), count)
|
|
2534
2605
|
];
|
|
2535
2606
|
}
|
|
2536
2607
|
}, "requestSplitting");
|
|
2608
|
+
const embedAll = /* @__PURE__ */ __name(async (texts, count) => {
|
|
2609
|
+
const vectors = [];
|
|
2610
|
+
for (let start = 0; start < texts.length; ) {
|
|
2611
|
+
const input = texts.slice(start, start + batchSize).map((text) => (text.length > maxInputChars ? text.slice(0, maxInputChars) : text) || " ");
|
|
2612
|
+
vectors.push(...await requestSplitting(input, count));
|
|
2613
|
+
start += input.length;
|
|
2614
|
+
}
|
|
2615
|
+
return vectors;
|
|
2616
|
+
}, "embedAll");
|
|
2537
2617
|
return {
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
}
|
|
2545
|
-
return
|
|
2618
|
+
embed: /* @__PURE__ */ __name((texts) => embedAll(texts, () => {
|
|
2619
|
+
}), "embed"),
|
|
2620
|
+
async embedWithUsage(texts) {
|
|
2621
|
+
let inputTokens;
|
|
2622
|
+
const vectors = await embedAll(texts, (tokens) => {
|
|
2623
|
+
inputTokens = (inputTokens ?? 0) + tokens;
|
|
2624
|
+
});
|
|
2625
|
+
return {
|
|
2626
|
+
vectors,
|
|
2627
|
+
...inputTokens !== void 0 ? {
|
|
2628
|
+
usage: {
|
|
2629
|
+
inputTokens,
|
|
2630
|
+
modelId: options.model
|
|
2631
|
+
}
|
|
2632
|
+
} : {}
|
|
2633
|
+
};
|
|
2546
2634
|
}
|
|
2547
2635
|
};
|
|
2548
2636
|
}
|
|
@@ -2647,6 +2735,206 @@ function itemsOf(body) {
|
|
|
2647
2735
|
}
|
|
2648
2736
|
__name(itemsOf, "itemsOf");
|
|
2649
2737
|
|
|
2738
|
+
// src/embedding-usage.ts
|
|
2739
|
+
async function embedCountingUsage(provider, texts) {
|
|
2740
|
+
const reporting = provider;
|
|
2741
|
+
return typeof reporting.embedWithUsage === "function" ? reporting.embedWithUsage(texts) : {
|
|
2742
|
+
vectors: await provider.embed(texts)
|
|
2743
|
+
};
|
|
2744
|
+
}
|
|
2745
|
+
__name(embedCountingUsage, "embedCountingUsage");
|
|
2746
|
+
|
|
2747
|
+
// src/rag-index.ts
|
|
2748
|
+
var RAG_ENTITY_KEY = "ragEntity";
|
|
2749
|
+
var RagScopeRequiredError = class extends Error {
|
|
2750
|
+
static {
|
|
2751
|
+
__name(this, "RagScopeRequiredError");
|
|
2752
|
+
}
|
|
2753
|
+
model;
|
|
2754
|
+
key;
|
|
2755
|
+
constructor(model, key) {
|
|
2756
|
+
super(`${model} needs scope.${key}: its chunks are isolated by it`), this.model = model, this.key = key;
|
|
2757
|
+
this.name = "RagScopeRequiredError";
|
|
2758
|
+
}
|
|
2759
|
+
};
|
|
2760
|
+
var ANY_OF = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent-rag/anyOf");
|
|
2761
|
+
function anyOf(values) {
|
|
2762
|
+
return {
|
|
2763
|
+
[ANY_OF]: values
|
|
2764
|
+
};
|
|
2765
|
+
}
|
|
2766
|
+
__name(anyOf, "anyOf");
|
|
2767
|
+
function isScopeValue(value) {
|
|
2768
|
+
return typeof value === "number" ? Number.isFinite(value) : typeof value === "string" && value !== "";
|
|
2769
|
+
}
|
|
2770
|
+
__name(isScopeValue, "isScopeValue");
|
|
2771
|
+
function isAnyOf(value) {
|
|
2772
|
+
return typeof value === "object" && value !== null && ANY_OF in value;
|
|
2773
|
+
}
|
|
2774
|
+
__name(isAnyOf, "isAnyOf");
|
|
2775
|
+
var RagIndex = class {
|
|
2776
|
+
static {
|
|
2777
|
+
__name(this, "RagIndex");
|
|
2778
|
+
}
|
|
2779
|
+
definition;
|
|
2780
|
+
deps;
|
|
2781
|
+
embedder;
|
|
2782
|
+
constructor(definition, deps) {
|
|
2783
|
+
this.definition = definition;
|
|
2784
|
+
this.deps = deps;
|
|
2785
|
+
const { onUsage } = deps;
|
|
2786
|
+
this.embedder = onUsage === void 0 ? deps.embedder : {
|
|
2787
|
+
embed: /* @__PURE__ */ __name(async (texts) => {
|
|
2788
|
+
const { vectors, usage } = await embedCountingUsage(deps.embedder, texts);
|
|
2789
|
+
if (usage !== void 0) {
|
|
2790
|
+
await onUsage(usage);
|
|
2791
|
+
}
|
|
2792
|
+
return vectors;
|
|
2793
|
+
}, "embed")
|
|
2794
|
+
};
|
|
2795
|
+
}
|
|
2796
|
+
/** The id this row's chunks are stored under: `<name>:<id>`. */
|
|
2797
|
+
documentId(id) {
|
|
2798
|
+
if (!isScopeValue(id)) {
|
|
2799
|
+
throw new Error(`${this.definition.name}: a row needs an id to be indexed or removed`);
|
|
2800
|
+
}
|
|
2801
|
+
return `${this.definition.name}:${String(id)}`;
|
|
2802
|
+
}
|
|
2803
|
+
/** A `Retriever` locked to this scope, for `HybridRetriever`, `createRetrievalTool` and so on. */
|
|
2804
|
+
scoped(scope) {
|
|
2805
|
+
return new FilteredRetriever(this.baseRetriever(), this.scopeFilter(scope));
|
|
2806
|
+
}
|
|
2807
|
+
async search(query, options) {
|
|
2808
|
+
const { scope, filter, topK } = options;
|
|
2809
|
+
return this.scoped(scope).retrieve(query, {
|
|
2810
|
+
...topK !== void 0 ? {
|
|
2811
|
+
topK
|
|
2812
|
+
} : {},
|
|
2813
|
+
...filter !== void 0 ? {
|
|
2814
|
+
filter
|
|
2815
|
+
} : {}
|
|
2816
|
+
});
|
|
2817
|
+
}
|
|
2818
|
+
/**
|
|
2819
|
+
* Replaces the row's chunks and returns how many were written. Embeddings come first: if the
|
|
2820
|
+
* provider fails, the previous chunks are still there. The swap is one transaction on Postgres; on a
|
|
2821
|
+
* store without `replaceDocument` a crash mid-swap loses the document until the next `index`.
|
|
2822
|
+
*/
|
|
2823
|
+
async index(row) {
|
|
2824
|
+
const { definition, deps } = this;
|
|
2825
|
+
const documentId = this.documentId(definition.id(row));
|
|
2826
|
+
const scope = this.scopeFilter(Object.fromEntries(Object.entries(definition.scope).map(([key, read]) => [
|
|
2827
|
+
key,
|
|
2828
|
+
read(row)
|
|
2829
|
+
])));
|
|
2830
|
+
const text = definition.text(row)?.trim();
|
|
2831
|
+
if (!text) {
|
|
2832
|
+
await deps.store.remove(documentId);
|
|
2833
|
+
return 0;
|
|
2834
|
+
}
|
|
2835
|
+
const pieces = chunkText(text, definition.chunk).slice(0, definition.maxChunks);
|
|
2836
|
+
const embeddings = await this.embedder.embed(pieces);
|
|
2837
|
+
const source = definition.source?.(row) ?? void 0;
|
|
2838
|
+
const metadata = {
|
|
2839
|
+
...definition.metadata?.(row),
|
|
2840
|
+
...scope
|
|
2841
|
+
};
|
|
2842
|
+
await replaceDocumentChunks(deps.store, documentId, pieces.map((piece, index) => ({
|
|
2843
|
+
id: `${documentId}#${index}`,
|
|
2844
|
+
text: piece,
|
|
2845
|
+
embedding: embeddings[index] ?? [],
|
|
2846
|
+
...source !== void 0 ? {
|
|
2847
|
+
source
|
|
2848
|
+
} : {},
|
|
2849
|
+
metadata
|
|
2850
|
+
})));
|
|
2851
|
+
return pieces.length;
|
|
2852
|
+
}
|
|
2853
|
+
/** Takes the id itself once the row is gone. */
|
|
2854
|
+
remove(rowOrId) {
|
|
2855
|
+
const id = typeof rowOrId === "string" || typeof rowOrId === "number" ? rowOrId : this.definition.id(rowOrId);
|
|
2856
|
+
return this.deps.store.remove(this.documentId(id));
|
|
2857
|
+
}
|
|
2858
|
+
/**
|
|
2859
|
+
* Diffs the index against the ids that exist, and removes what no hook could: `ON DELETE CASCADE`,
|
|
2860
|
+
* raw SQL, a crash after a commit. `liveIds` must be complete for the part being compared, because
|
|
2861
|
+
* anything left out is removed. Use `dryRun` to look first.
|
|
2862
|
+
*/
|
|
2863
|
+
async reconcile(liveIds, options = {}) {
|
|
2864
|
+
const filter = {
|
|
2865
|
+
[RAG_ENTITY_KEY]: this.definition.name
|
|
2866
|
+
};
|
|
2867
|
+
for (const [key, value] of Object.entries(options.scope ?? {})) {
|
|
2868
|
+
if (!isScopeValue(value)) {
|
|
2869
|
+
throw new RagScopeRequiredError(this.definition.name, key);
|
|
2870
|
+
}
|
|
2871
|
+
filter[key] = String(value);
|
|
2872
|
+
}
|
|
2873
|
+
const live = /* @__PURE__ */ new Set();
|
|
2874
|
+
for await (const id of liveIds) {
|
|
2875
|
+
live.add(this.documentId(id));
|
|
2876
|
+
}
|
|
2877
|
+
const indexed = await this.deps.store.listDocumentIds(filter);
|
|
2878
|
+
const orphans = indexed.filter((id) => !live.has(id));
|
|
2879
|
+
if (options.dryRun !== true) {
|
|
2880
|
+
await this.deps.store.removeMany(orphans);
|
|
2881
|
+
}
|
|
2882
|
+
const known = new Set(indexed);
|
|
2883
|
+
return {
|
|
2884
|
+
orphans,
|
|
2885
|
+
missing: [
|
|
2886
|
+
...live
|
|
2887
|
+
].filter((id) => !known.has(id))
|
|
2888
|
+
};
|
|
2889
|
+
}
|
|
2890
|
+
/** A `search_*` tool whose scope comes from the context. Pass it to `provideAgentTool`. */
|
|
2891
|
+
tool(options) {
|
|
2892
|
+
return createRetrievalTool(this.baseRetriever(), {
|
|
2893
|
+
name: options.name,
|
|
2894
|
+
...options.description !== void 0 ? {
|
|
2895
|
+
description: options.description
|
|
2896
|
+
} : {},
|
|
2897
|
+
...options.topK !== void 0 ? {
|
|
2898
|
+
topK: options.topK
|
|
2899
|
+
} : {},
|
|
2900
|
+
filter: /* @__PURE__ */ __name((ctx) => this.scopeFilter(options.scope(ctx)), "filter")
|
|
2901
|
+
});
|
|
2902
|
+
}
|
|
2903
|
+
baseRetriever() {
|
|
2904
|
+
const { minScore } = this.definition;
|
|
2905
|
+
return this.deps.retriever ?? new EmbeddingRetriever(this.embedder, this.deps.store, minScore !== void 0 ? {
|
|
2906
|
+
minScore
|
|
2907
|
+
} : {});
|
|
2908
|
+
}
|
|
2909
|
+
/** The filter a scope compiles to. Throws on anything but a usable value, before any paid call. */
|
|
2910
|
+
scopeFilter(scope) {
|
|
2911
|
+
const given = scope ?? {};
|
|
2912
|
+
const filter = {
|
|
2913
|
+
[RAG_ENTITY_KEY]: this.definition.name
|
|
2914
|
+
};
|
|
2915
|
+
for (const key of Object.keys(this.definition.scope)) {
|
|
2916
|
+
const value = given[key];
|
|
2917
|
+
if (isAnyOf(value) && value[ANY_OF].every(isScopeValue)) {
|
|
2918
|
+
filter[key] = value[ANY_OF].map(String);
|
|
2919
|
+
} else if (isScopeValue(value)) {
|
|
2920
|
+
filter[key] = String(value);
|
|
2921
|
+
} else {
|
|
2922
|
+
throw new RagScopeRequiredError(this.definition.name, key);
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
return filter;
|
|
2926
|
+
}
|
|
2927
|
+
};
|
|
2928
|
+
function ragFilterableFields(...scopeKeys) {
|
|
2929
|
+
return [
|
|
2930
|
+
.../* @__PURE__ */ new Set([
|
|
2931
|
+
RAG_ENTITY_KEY,
|
|
2932
|
+
...scopeKeys
|
|
2933
|
+
])
|
|
2934
|
+
];
|
|
2935
|
+
}
|
|
2936
|
+
__name(ragFilterableFields, "ragFilterableFields");
|
|
2937
|
+
|
|
2650
2938
|
// src/tree/types.ts
|
|
2651
2939
|
function* walkTree(nodes, path = []) {
|
|
2652
2940
|
for (const node of nodes) {
|
|
@@ -4925,8 +5213,11 @@ export {
|
|
|
4925
5213
|
PgLexicalVectorStore,
|
|
4926
5214
|
PgVectorStore,
|
|
4927
5215
|
RAG_DIAGNOSTIC_LIB,
|
|
5216
|
+
RAG_ENTITY_KEY,
|
|
4928
5217
|
RAG_RETRIEVAL_CHANNEL,
|
|
4929
5218
|
RAG_RETRIEVAL_EVENT,
|
|
5219
|
+
RagIndex,
|
|
5220
|
+
RagScopeRequiredError,
|
|
4930
5221
|
RedisVectorSchemaMismatchError,
|
|
4931
5222
|
RedisVectorStore,
|
|
4932
5223
|
RerankingRetriever,
|
|
@@ -4934,6 +5225,7 @@ export {
|
|
|
4934
5225
|
TreeNavigationRetriever,
|
|
4935
5226
|
TwoStageRetriever,
|
|
4936
5227
|
UnsafeRemovalError,
|
|
5228
|
+
anyOf,
|
|
4937
5229
|
anyTermTsquery,
|
|
4938
5230
|
applyMetadataPatch,
|
|
4939
5231
|
buildDocumentTree,
|
|
@@ -4949,6 +5241,7 @@ export {
|
|
|
4949
5241
|
describesRetrieval,
|
|
4950
5242
|
detectHeadings,
|
|
4951
5243
|
documentIdOf,
|
|
5244
|
+
embedCountingUsage,
|
|
4952
5245
|
emitRagRetrieval,
|
|
4953
5246
|
estimateTokens,
|
|
4954
5247
|
hasSearchSyntax,
|
|
@@ -4964,6 +5257,8 @@ export {
|
|
|
4964
5257
|
openAiChatTreeLlm,
|
|
4965
5258
|
openAiEmbeddings,
|
|
4966
5259
|
parseJsonReply,
|
|
5260
|
+
ragFilterableFields,
|
|
5261
|
+
replaceDocumentChunks,
|
|
4967
5262
|
spanText,
|
|
4968
5263
|
stableHash,
|
|
4969
5264
|
toPgClient,
|