@mastra/rag 0.0.0-commonjs-20250227130920
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/.turbo/turbo-build.log +23 -0
- package/CHANGELOG.md +1151 -0
- package/LICENSE +44 -0
- package/README.md +26 -0
- package/dist/_tsup-dts-rollup.d.cts +620 -0
- package/dist/_tsup-dts-rollup.d.ts +620 -0
- package/dist/index.cjs +2524 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +2505 -0
- package/docker-compose.yaml +22 -0
- package/eslint.config.js +6 -0
- package/package.json +72 -0
- package/src/document/document.test.ts +985 -0
- package/src/document/document.ts +281 -0
- package/src/document/index.ts +2 -0
- package/src/document/transformers/character.ts +279 -0
- package/src/document/transformers/html.ts +346 -0
- package/src/document/transformers/json.ts +265 -0
- package/src/document/transformers/latex.ts +19 -0
- package/src/document/transformers/markdown.ts +244 -0
- package/src/document/transformers/text.ts +134 -0
- package/src/document/transformers/token.ts +148 -0
- package/src/document/transformers/transformer.ts +5 -0
- package/src/document/types.ts +103 -0
- package/src/graph-rag/index.test.ts +235 -0
- package/src/graph-rag/index.ts +306 -0
- package/src/index.ts +6 -0
- package/src/rerank/index.test.ts +150 -0
- package/src/rerank/index.ts +154 -0
- package/src/tools/document-chunker.ts +30 -0
- package/src/tools/graph-rag.ts +117 -0
- package/src/tools/index.ts +3 -0
- package/src/tools/vector-query.ts +90 -0
- package/src/utils/default-settings.ts +33 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/vector-prompts.ts +729 -0
- package/src/utils/vector-search.ts +41 -0
- package/tsconfig.json +5 -0
- package/vitest.config.ts +11 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2505 @@
|
|
|
1
|
+
import { Document, SummaryExtractor, QuestionsAnsweredExtractor, KeywordExtractor, TitleExtractor, IngestionPipeline } from 'llamaindex';
|
|
2
|
+
import { parse } from 'node-html-better-parser';
|
|
3
|
+
import { encodingForModel, getEncoding } from 'js-tiktoken';
|
|
4
|
+
import { CohereRelevanceScorer, MastraAgentRelevanceScorer } from '@mastra/core/relevance';
|
|
5
|
+
import { createTool } from '@mastra/core/tools';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { embed } from 'ai';
|
|
8
|
+
|
|
9
|
+
// src/document/document.ts
|
|
10
|
+
|
|
11
|
+
// src/document/types.ts
|
|
12
|
+
var Language = /* @__PURE__ */ ((Language2) => {
|
|
13
|
+
Language2["CPP"] = "cpp";
|
|
14
|
+
Language2["GO"] = "go";
|
|
15
|
+
Language2["JAVA"] = "java";
|
|
16
|
+
Language2["KOTLIN"] = "kotlin";
|
|
17
|
+
Language2["JS"] = "js";
|
|
18
|
+
Language2["TS"] = "ts";
|
|
19
|
+
Language2["PHP"] = "php";
|
|
20
|
+
Language2["PROTO"] = "proto";
|
|
21
|
+
Language2["PYTHON"] = "python";
|
|
22
|
+
Language2["RST"] = "rst";
|
|
23
|
+
Language2["RUBY"] = "ruby";
|
|
24
|
+
Language2["RUST"] = "rust";
|
|
25
|
+
Language2["SCALA"] = "scala";
|
|
26
|
+
Language2["SWIFT"] = "swift";
|
|
27
|
+
Language2["MARKDOWN"] = "markdown";
|
|
28
|
+
Language2["LATEX"] = "latex";
|
|
29
|
+
Language2["HTML"] = "html";
|
|
30
|
+
Language2["SOL"] = "sol";
|
|
31
|
+
Language2["CSHARP"] = "csharp";
|
|
32
|
+
Language2["COBOL"] = "cobol";
|
|
33
|
+
Language2["C"] = "c";
|
|
34
|
+
Language2["LUA"] = "lua";
|
|
35
|
+
Language2["PERL"] = "perl";
|
|
36
|
+
Language2["HASKELL"] = "haskell";
|
|
37
|
+
Language2["ELIXIR"] = "elixir";
|
|
38
|
+
Language2["POWERSHELL"] = "powershell";
|
|
39
|
+
return Language2;
|
|
40
|
+
})(Language || {});
|
|
41
|
+
var TextTransformer = class {
|
|
42
|
+
size;
|
|
43
|
+
overlap;
|
|
44
|
+
lengthFunction;
|
|
45
|
+
keepSeparator;
|
|
46
|
+
addStartIndex;
|
|
47
|
+
stripWhitespace;
|
|
48
|
+
constructor({
|
|
49
|
+
size = 4e3,
|
|
50
|
+
overlap = 200,
|
|
51
|
+
lengthFunction = (text) => text.length,
|
|
52
|
+
keepSeparator = false,
|
|
53
|
+
addStartIndex = false,
|
|
54
|
+
stripWhitespace = true
|
|
55
|
+
}) {
|
|
56
|
+
if (overlap > size) {
|
|
57
|
+
throw new Error(`Got a larger chunk overlap (${overlap}) than chunk size (${size}), should be smaller.`);
|
|
58
|
+
}
|
|
59
|
+
this.size = size;
|
|
60
|
+
this.overlap = overlap;
|
|
61
|
+
this.lengthFunction = lengthFunction;
|
|
62
|
+
this.keepSeparator = keepSeparator;
|
|
63
|
+
this.addStartIndex = addStartIndex;
|
|
64
|
+
this.stripWhitespace = stripWhitespace;
|
|
65
|
+
}
|
|
66
|
+
setAddStartIndex(value) {
|
|
67
|
+
this.addStartIndex = value;
|
|
68
|
+
}
|
|
69
|
+
createDocuments(texts, metadatas) {
|
|
70
|
+
const _metadatas = metadatas || Array(texts.length).fill({});
|
|
71
|
+
const documents = [];
|
|
72
|
+
texts.forEach((text, i) => {
|
|
73
|
+
let index = 0;
|
|
74
|
+
let previousChunkLen = 0;
|
|
75
|
+
this.splitText({ text }).forEach((chunk) => {
|
|
76
|
+
const metadata = { ..._metadatas[i] };
|
|
77
|
+
if (this.addStartIndex) {
|
|
78
|
+
const offset = index + previousChunkLen - this.overlap;
|
|
79
|
+
index = text.indexOf(chunk, Math.max(0, offset));
|
|
80
|
+
metadata.startIndex = index;
|
|
81
|
+
previousChunkLen = chunk.length;
|
|
82
|
+
}
|
|
83
|
+
documents.push(
|
|
84
|
+
new Document({
|
|
85
|
+
text: chunk,
|
|
86
|
+
metadata
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
return documents;
|
|
92
|
+
}
|
|
93
|
+
splitDocuments(documents) {
|
|
94
|
+
const texts = [];
|
|
95
|
+
const metadatas = [];
|
|
96
|
+
for (const doc of documents) {
|
|
97
|
+
texts.push(doc.text);
|
|
98
|
+
metadatas.push(doc.metadata);
|
|
99
|
+
}
|
|
100
|
+
return this.createDocuments(texts, metadatas);
|
|
101
|
+
}
|
|
102
|
+
transformDocuments(documents) {
|
|
103
|
+
const texts = [];
|
|
104
|
+
const metadatas = [];
|
|
105
|
+
for (const doc of documents) {
|
|
106
|
+
texts.push(doc.text);
|
|
107
|
+
metadatas.push(doc.metadata);
|
|
108
|
+
}
|
|
109
|
+
return this.createDocuments(texts, metadatas);
|
|
110
|
+
}
|
|
111
|
+
joinDocs(docs, separator) {
|
|
112
|
+
let text = docs.join(separator);
|
|
113
|
+
if (this.stripWhitespace) {
|
|
114
|
+
text = text.trim();
|
|
115
|
+
}
|
|
116
|
+
return text === "" ? null : text;
|
|
117
|
+
}
|
|
118
|
+
mergeSplits(splits, separator) {
|
|
119
|
+
const separatorLen = this.lengthFunction(separator);
|
|
120
|
+
const docs = [];
|
|
121
|
+
let currentDoc = [];
|
|
122
|
+
let total = 0;
|
|
123
|
+
splits.forEach((d) => {
|
|
124
|
+
const len = this.lengthFunction(d);
|
|
125
|
+
if (total + len + (currentDoc.length > 0 ? separatorLen : 0) > this.size) {
|
|
126
|
+
if (total > this.size) {
|
|
127
|
+
console.warn(`Created a chunk of size ${total}, which is longer than the specified ${this.size}`);
|
|
128
|
+
}
|
|
129
|
+
if (currentDoc.length > 0) {
|
|
130
|
+
const doc2 = this.joinDocs(currentDoc, separator);
|
|
131
|
+
if (doc2 !== null) {
|
|
132
|
+
docs.push(doc2);
|
|
133
|
+
}
|
|
134
|
+
while (total > this.overlap || total + len + (currentDoc.length > 0 ? separatorLen : 0) > this.size && total > 0) {
|
|
135
|
+
total -= this.lengthFunction(currentDoc?.[0]) + (currentDoc.length > 1 ? separatorLen : 0);
|
|
136
|
+
currentDoc = currentDoc.slice(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
currentDoc.push(d);
|
|
141
|
+
total += len + (currentDoc.length > 1 ? separatorLen : 0);
|
|
142
|
+
});
|
|
143
|
+
const doc = this.joinDocs(currentDoc, separator);
|
|
144
|
+
if (doc !== null) {
|
|
145
|
+
docs.push(doc);
|
|
146
|
+
}
|
|
147
|
+
return docs;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// src/document/transformers/character.ts
|
|
152
|
+
function splitTextWithRegex(text, separator, keepSeparator) {
|
|
153
|
+
if (!separator) {
|
|
154
|
+
return text.split("");
|
|
155
|
+
}
|
|
156
|
+
if (!keepSeparator) {
|
|
157
|
+
return text.split(new RegExp(separator)).filter((s) => s !== "");
|
|
158
|
+
}
|
|
159
|
+
if (!text) {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
const splits = text.split(new RegExp(`(${separator})`));
|
|
163
|
+
const result = [];
|
|
164
|
+
if (keepSeparator === "end") {
|
|
165
|
+
for (let i = 0; i < splits.length - 1; i += 2) {
|
|
166
|
+
if (i + 1 < splits.length) {
|
|
167
|
+
const chunk = splits[i] + (splits[i + 1] || "");
|
|
168
|
+
if (chunk) result.push(chunk);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (splits.length % 2 === 1 && splits[splits.length - 1]) {
|
|
172
|
+
result.push(splits?.[splits.length - 1]);
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
if (splits[0]) result.push(splits[0]);
|
|
176
|
+
for (let i = 1; i < splits.length - 1; i += 2) {
|
|
177
|
+
const separator2 = splits[i];
|
|
178
|
+
const text2 = splits[i + 1];
|
|
179
|
+
if (separator2 && text2) {
|
|
180
|
+
result.push(separator2 + text2);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return result.filter((s) => s !== "");
|
|
185
|
+
}
|
|
186
|
+
var CharacterTransformer = class extends TextTransformer {
|
|
187
|
+
separator;
|
|
188
|
+
isSeparatorRegex;
|
|
189
|
+
constructor({
|
|
190
|
+
separator = "\n\n",
|
|
191
|
+
isSeparatorRegex = false,
|
|
192
|
+
options = {}
|
|
193
|
+
}) {
|
|
194
|
+
super(options);
|
|
195
|
+
this.separator = separator;
|
|
196
|
+
this.isSeparatorRegex = isSeparatorRegex;
|
|
197
|
+
}
|
|
198
|
+
splitText({ text }) {
|
|
199
|
+
const separator = this.isSeparatorRegex ? this.separator : this.separator.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
200
|
+
const initialSplits = splitTextWithRegex(text, separator, this.keepSeparator);
|
|
201
|
+
const chunks = [];
|
|
202
|
+
for (const split of initialSplits) {
|
|
203
|
+
if (this.lengthFunction(split) <= this.size) {
|
|
204
|
+
chunks.push(split);
|
|
205
|
+
} else {
|
|
206
|
+
const subChunks = this.__splitChunk(split);
|
|
207
|
+
chunks.push(...subChunks);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return chunks;
|
|
211
|
+
}
|
|
212
|
+
__splitChunk(text) {
|
|
213
|
+
const chunks = [];
|
|
214
|
+
let currentChunk = "";
|
|
215
|
+
const chars = text.split("");
|
|
216
|
+
for (const char of chars) {
|
|
217
|
+
if (this.lengthFunction(currentChunk + char) <= this.size) {
|
|
218
|
+
currentChunk += char;
|
|
219
|
+
} else {
|
|
220
|
+
if (currentChunk) {
|
|
221
|
+
chunks.push(currentChunk);
|
|
222
|
+
}
|
|
223
|
+
currentChunk = char;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (currentChunk) {
|
|
227
|
+
chunks.push(currentChunk);
|
|
228
|
+
}
|
|
229
|
+
return chunks;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
var RecursiveCharacterTransformer = class _RecursiveCharacterTransformer extends TextTransformer {
|
|
233
|
+
separators;
|
|
234
|
+
isSeparatorRegex;
|
|
235
|
+
constructor({
|
|
236
|
+
separators,
|
|
237
|
+
isSeparatorRegex = false,
|
|
238
|
+
options = {}
|
|
239
|
+
}) {
|
|
240
|
+
super(options);
|
|
241
|
+
this.separators = separators || ["\n\n", "\n", " ", ""];
|
|
242
|
+
this.isSeparatorRegex = isSeparatorRegex;
|
|
243
|
+
}
|
|
244
|
+
_splitText(text, separators) {
|
|
245
|
+
const finalChunks = [];
|
|
246
|
+
let separator = separators?.[separators.length - 1];
|
|
247
|
+
let newSeparators = [];
|
|
248
|
+
for (let i = 0; i < separators.length; i++) {
|
|
249
|
+
const s = separators[i];
|
|
250
|
+
const _separator2 = this.isSeparatorRegex ? s : s?.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
251
|
+
if (s === "") {
|
|
252
|
+
separator = s;
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
if (new RegExp(_separator2).test(text)) {
|
|
256
|
+
separator = s;
|
|
257
|
+
newSeparators = separators.slice(i + 1);
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const _separator = this.isSeparatorRegex ? separator : separator?.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
262
|
+
const splits = splitTextWithRegex(text, _separator, this.keepSeparator);
|
|
263
|
+
const goodSplits = [];
|
|
264
|
+
const mergeSeparator = this.keepSeparator ? "" : separator;
|
|
265
|
+
for (const s of splits) {
|
|
266
|
+
if (this.lengthFunction(s) < this.size) {
|
|
267
|
+
goodSplits.push(s);
|
|
268
|
+
} else {
|
|
269
|
+
if (goodSplits.length > 0) {
|
|
270
|
+
const mergedText = this.mergeSplits(goodSplits, mergeSeparator);
|
|
271
|
+
finalChunks.push(...mergedText);
|
|
272
|
+
goodSplits.length = 0;
|
|
273
|
+
}
|
|
274
|
+
if (newSeparators.length === 0) {
|
|
275
|
+
finalChunks.push(s);
|
|
276
|
+
} else {
|
|
277
|
+
const otherInfo = this._splitText(s, newSeparators);
|
|
278
|
+
finalChunks.push(...otherInfo);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (goodSplits.length > 0) {
|
|
283
|
+
const mergedText = this.mergeSplits(goodSplits, mergeSeparator);
|
|
284
|
+
finalChunks.push(...mergedText);
|
|
285
|
+
}
|
|
286
|
+
return finalChunks;
|
|
287
|
+
}
|
|
288
|
+
splitText({ text }) {
|
|
289
|
+
return this._splitText(text, this.separators);
|
|
290
|
+
}
|
|
291
|
+
static fromLanguage(language, options = {}) {
|
|
292
|
+
const separators = _RecursiveCharacterTransformer.getSeparatorsForLanguage(language);
|
|
293
|
+
return new _RecursiveCharacterTransformer({ separators, isSeparatorRegex: true, options });
|
|
294
|
+
}
|
|
295
|
+
static getSeparatorsForLanguage(language) {
|
|
296
|
+
switch (language) {
|
|
297
|
+
case "markdown" /* MARKDOWN */:
|
|
298
|
+
return [
|
|
299
|
+
// First, try to split along Markdown headings (starting with level 2)
|
|
300
|
+
"\n#{1,6} ",
|
|
301
|
+
// End of code block
|
|
302
|
+
"```\n",
|
|
303
|
+
// Horizontal lines
|
|
304
|
+
"\n\\*\\*\\*+\n",
|
|
305
|
+
"\n---+\n",
|
|
306
|
+
"\n___+\n",
|
|
307
|
+
// Note that this splitter doesn't handle horizontal lines defined
|
|
308
|
+
// by *three or more* of ***, ---, or ___, but this is not handled
|
|
309
|
+
"\n\n",
|
|
310
|
+
"\n",
|
|
311
|
+
" ",
|
|
312
|
+
""
|
|
313
|
+
];
|
|
314
|
+
case "cpp" /* CPP */:
|
|
315
|
+
case "c" /* C */:
|
|
316
|
+
return [
|
|
317
|
+
"\nclass ",
|
|
318
|
+
"\nvoid ",
|
|
319
|
+
"\nint ",
|
|
320
|
+
"\nfloat ",
|
|
321
|
+
"\ndouble ",
|
|
322
|
+
"\nif ",
|
|
323
|
+
"\nfor ",
|
|
324
|
+
"\nwhile ",
|
|
325
|
+
"\nswitch ",
|
|
326
|
+
"\ncase ",
|
|
327
|
+
"\n\n",
|
|
328
|
+
"\n",
|
|
329
|
+
" ",
|
|
330
|
+
""
|
|
331
|
+
];
|
|
332
|
+
case "ts" /* TS */:
|
|
333
|
+
return [
|
|
334
|
+
"\nenum ",
|
|
335
|
+
"\ninterface ",
|
|
336
|
+
"\nnamespace ",
|
|
337
|
+
"\ntype ",
|
|
338
|
+
"\nclass ",
|
|
339
|
+
"\nfunction ",
|
|
340
|
+
"\nconst ",
|
|
341
|
+
"\nlet ",
|
|
342
|
+
"\nvar ",
|
|
343
|
+
"\nif ",
|
|
344
|
+
"\nfor ",
|
|
345
|
+
"\nwhile ",
|
|
346
|
+
"\nswitch ",
|
|
347
|
+
"\ncase ",
|
|
348
|
+
"\ndefault ",
|
|
349
|
+
"\n\n",
|
|
350
|
+
"\n",
|
|
351
|
+
" ",
|
|
352
|
+
""
|
|
353
|
+
];
|
|
354
|
+
// ... (add other language cases following the same pattern)
|
|
355
|
+
default:
|
|
356
|
+
throw new Error(`Language ${language} is not supported! Please choose from ${Object.values(Language)}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
var HTMLHeaderTransformer = class {
|
|
361
|
+
headersToSplitOn;
|
|
362
|
+
returnEachElement;
|
|
363
|
+
constructor(headersToSplitOn, returnEachElement = false) {
|
|
364
|
+
this.returnEachElement = returnEachElement;
|
|
365
|
+
this.headersToSplitOn = [...headersToSplitOn].sort();
|
|
366
|
+
}
|
|
367
|
+
splitText({ text }) {
|
|
368
|
+
const root = parse(text);
|
|
369
|
+
const headerFilter = this.headersToSplitOn.map(([header]) => header);
|
|
370
|
+
const headerMapping = Object.fromEntries(this.headersToSplitOn);
|
|
371
|
+
const elements = [];
|
|
372
|
+
const headers = root.querySelectorAll(headerFilter.join(","));
|
|
373
|
+
headers.forEach((header) => {
|
|
374
|
+
let content = "";
|
|
375
|
+
const parentNode = header.parentNode;
|
|
376
|
+
if (parentNode && parentNode.childNodes) {
|
|
377
|
+
let foundHeader = false;
|
|
378
|
+
for (const node of parentNode.childNodes) {
|
|
379
|
+
if (node === header) {
|
|
380
|
+
foundHeader = true;
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (foundHeader && node.tagName && headerFilter.includes(node.tagName.toLowerCase())) {
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
if (foundHeader) {
|
|
387
|
+
content += this.getTextContent(node) + " ";
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
elements.push({
|
|
392
|
+
url: text,
|
|
393
|
+
xpath: this.getXPath(header),
|
|
394
|
+
content: content.trim(),
|
|
395
|
+
metadata: {
|
|
396
|
+
[headerMapping?.[header.tagName.toLowerCase()]]: header.text || ""
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
return this.returnEachElement ? elements.map(
|
|
401
|
+
(el) => new Document({
|
|
402
|
+
text: el.content,
|
|
403
|
+
metadata: { ...el.metadata, xpath: el.xpath }
|
|
404
|
+
})
|
|
405
|
+
) : this.aggregateElementsToChunks(elements);
|
|
406
|
+
}
|
|
407
|
+
getXPath(element) {
|
|
408
|
+
if (!element) return "";
|
|
409
|
+
const parts = [];
|
|
410
|
+
let current = element;
|
|
411
|
+
while (current && current.tagName) {
|
|
412
|
+
let index = 1;
|
|
413
|
+
const parent = current.parentNode;
|
|
414
|
+
if (parent && parent.childNodes) {
|
|
415
|
+
for (const sibling of parent.childNodes) {
|
|
416
|
+
if (sibling === current) break;
|
|
417
|
+
if (sibling.tagName === current.tagName) {
|
|
418
|
+
index++;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
parts.unshift(`${current.tagName.toLowerCase()}[${index}]`);
|
|
423
|
+
current = current.parentNode;
|
|
424
|
+
}
|
|
425
|
+
return "/" + parts.join("/");
|
|
426
|
+
}
|
|
427
|
+
getTextContent(element) {
|
|
428
|
+
if (!element) return "";
|
|
429
|
+
if (!element.tagName) {
|
|
430
|
+
return element.text || "";
|
|
431
|
+
}
|
|
432
|
+
let content = element.text || "";
|
|
433
|
+
if (element.childNodes) {
|
|
434
|
+
for (const child of element.childNodes) {
|
|
435
|
+
const childText = this.getTextContent(child);
|
|
436
|
+
if (childText) {
|
|
437
|
+
content += " " + childText;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return content.trim();
|
|
442
|
+
}
|
|
443
|
+
aggregateElementsToChunks(elements) {
|
|
444
|
+
const aggregatedChunks = [];
|
|
445
|
+
for (const element of elements) {
|
|
446
|
+
if (aggregatedChunks.length > 0 && JSON.stringify(aggregatedChunks[aggregatedChunks.length - 1].metadata) === JSON.stringify(element.metadata)) {
|
|
447
|
+
aggregatedChunks[aggregatedChunks.length - 1].content += " \n" + element.content;
|
|
448
|
+
} else {
|
|
449
|
+
aggregatedChunks.push({ ...element });
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return aggregatedChunks.map(
|
|
453
|
+
(chunk) => new Document({
|
|
454
|
+
text: chunk.content,
|
|
455
|
+
metadata: { ...chunk.metadata, xpath: chunk.xpath }
|
|
456
|
+
})
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
createDocuments(texts, metadatas) {
|
|
460
|
+
const _metadatas = metadatas || Array(texts.length).fill({});
|
|
461
|
+
const documents = [];
|
|
462
|
+
for (let i = 0; i < texts.length; i++) {
|
|
463
|
+
const chunks = this.splitText({ text: texts[i] });
|
|
464
|
+
for (const chunk of chunks) {
|
|
465
|
+
const metadata = { ..._metadatas[i] || {} };
|
|
466
|
+
const chunkMetadata = chunk.metadata;
|
|
467
|
+
if (chunkMetadata) {
|
|
468
|
+
for (const [key, value] of Object.entries(chunkMetadata || {})) {
|
|
469
|
+
if (value === "#TITLE#") {
|
|
470
|
+
chunkMetadata[key] = metadata["Title"];
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
documents.push(
|
|
475
|
+
new Document({
|
|
476
|
+
text: chunk.text,
|
|
477
|
+
metadata: { ...metadata, ...chunkMetadata }
|
|
478
|
+
})
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return documents;
|
|
483
|
+
}
|
|
484
|
+
transformDocuments(documents) {
|
|
485
|
+
const texts = [];
|
|
486
|
+
const metadatas = [];
|
|
487
|
+
for (const doc of documents) {
|
|
488
|
+
texts.push(doc.text);
|
|
489
|
+
metadatas.push(doc.metadata);
|
|
490
|
+
}
|
|
491
|
+
return this.createDocuments(texts, metadatas);
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
var HTMLSectionTransformer = class {
|
|
495
|
+
headersToSplitOn;
|
|
496
|
+
options;
|
|
497
|
+
constructor(headersToSplitOn, options = {}) {
|
|
498
|
+
this.headersToSplitOn = Object.fromEntries(headersToSplitOn.map(([tag, name]) => [tag.toLowerCase(), name]));
|
|
499
|
+
this.options = options;
|
|
500
|
+
}
|
|
501
|
+
splitText(text) {
|
|
502
|
+
const sections = this.splitHtmlByHeaders(text);
|
|
503
|
+
return sections.map(
|
|
504
|
+
(section) => new Document({
|
|
505
|
+
text: section.content,
|
|
506
|
+
metadata: {
|
|
507
|
+
[this.headersToSplitOn[section.tagName.toLowerCase()]]: section.header,
|
|
508
|
+
xpath: section.xpath
|
|
509
|
+
}
|
|
510
|
+
})
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
getXPath(element) {
|
|
514
|
+
const parts = [];
|
|
515
|
+
let current = element;
|
|
516
|
+
while (current && current.nodeType === 1) {
|
|
517
|
+
let index = 1;
|
|
518
|
+
let sibling = current.previousSibling;
|
|
519
|
+
while (sibling) {
|
|
520
|
+
if (sibling.nodeType === 1 && sibling.tagName === current.tagName) {
|
|
521
|
+
index++;
|
|
522
|
+
}
|
|
523
|
+
sibling = sibling.previousSibling;
|
|
524
|
+
}
|
|
525
|
+
if (current.tagName) {
|
|
526
|
+
parts.unshift(`${current.tagName.toLowerCase()}[${index}]`);
|
|
527
|
+
}
|
|
528
|
+
current = current.parentNode;
|
|
529
|
+
}
|
|
530
|
+
return "/" + parts.join("/");
|
|
531
|
+
}
|
|
532
|
+
splitHtmlByHeaders(htmlDoc) {
|
|
533
|
+
const sections = [];
|
|
534
|
+
const root = parse(htmlDoc);
|
|
535
|
+
const headers = Object.keys(this.headersToSplitOn);
|
|
536
|
+
const headerElements = root.querySelectorAll(headers.join(","));
|
|
537
|
+
headerElements.forEach((headerElement, index) => {
|
|
538
|
+
const header = headerElement.text?.trim() || "";
|
|
539
|
+
const tagName = headerElement.tagName;
|
|
540
|
+
const xpath = this.getXPath(headerElement);
|
|
541
|
+
let content = "";
|
|
542
|
+
let currentElement = headerElement.nextElementSibling;
|
|
543
|
+
const nextHeader = headerElements[index + 1];
|
|
544
|
+
while (currentElement && (!nextHeader || currentElement !== nextHeader)) {
|
|
545
|
+
if (currentElement.text) {
|
|
546
|
+
content += currentElement.text.trim() + " ";
|
|
547
|
+
}
|
|
548
|
+
currentElement = currentElement.nextElementSibling;
|
|
549
|
+
}
|
|
550
|
+
content = content.trim();
|
|
551
|
+
sections.push({
|
|
552
|
+
header,
|
|
553
|
+
content,
|
|
554
|
+
tagName,
|
|
555
|
+
xpath
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
return sections;
|
|
559
|
+
}
|
|
560
|
+
async splitDocuments(documents) {
|
|
561
|
+
const texts = [];
|
|
562
|
+
const metadatas = [];
|
|
563
|
+
for (const doc of documents) {
|
|
564
|
+
texts.push(doc.text);
|
|
565
|
+
metadatas.push(doc.metadata);
|
|
566
|
+
}
|
|
567
|
+
const results = await this.createDocuments(texts, metadatas);
|
|
568
|
+
const textSplitter = new RecursiveCharacterTransformer({ options: this.options });
|
|
569
|
+
return textSplitter.splitDocuments(results);
|
|
570
|
+
}
|
|
571
|
+
createDocuments(texts, metadatas) {
|
|
572
|
+
const _metadatas = metadatas || Array(texts.length).fill({});
|
|
573
|
+
const documents = [];
|
|
574
|
+
for (let i = 0; i < texts.length; i++) {
|
|
575
|
+
const chunks = this.splitText(texts[i]);
|
|
576
|
+
for (const chunk of chunks) {
|
|
577
|
+
const metadata = { ..._metadatas[i] || {} };
|
|
578
|
+
const chunkMetadata = chunk.metadata;
|
|
579
|
+
if (chunkMetadata) {
|
|
580
|
+
for (const [key, value] of Object.entries(chunkMetadata || {})) {
|
|
581
|
+
if (value === "#TITLE#") {
|
|
582
|
+
chunkMetadata[key] = metadata["Title"];
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
documents.push(
|
|
587
|
+
new Document({
|
|
588
|
+
text: chunk.text,
|
|
589
|
+
metadata: { ...metadata, ...chunkMetadata }
|
|
590
|
+
})
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return documents;
|
|
595
|
+
}
|
|
596
|
+
transformDocuments(documents) {
|
|
597
|
+
const texts = [];
|
|
598
|
+
const metadatas = [];
|
|
599
|
+
for (const doc of documents) {
|
|
600
|
+
texts.push(doc.text);
|
|
601
|
+
metadatas.push(doc.metadata);
|
|
602
|
+
}
|
|
603
|
+
return this.createDocuments(texts, metadatas);
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
var RecursiveJsonTransformer = class _RecursiveJsonTransformer {
|
|
607
|
+
maxSize;
|
|
608
|
+
minSize;
|
|
609
|
+
constructor({ maxSize = 2e3, minSize }) {
|
|
610
|
+
this.maxSize = maxSize;
|
|
611
|
+
this.minSize = minSize ?? Math.max(maxSize - 200, 50);
|
|
612
|
+
}
|
|
613
|
+
static jsonSize(data) {
|
|
614
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
615
|
+
function getStringifiableData(obj) {
|
|
616
|
+
if (obj === null || typeof obj !== "object") {
|
|
617
|
+
return obj;
|
|
618
|
+
}
|
|
619
|
+
if (seen.has(obj)) {
|
|
620
|
+
return "[Circular]";
|
|
621
|
+
}
|
|
622
|
+
seen.add(obj);
|
|
623
|
+
if (Array.isArray(obj)) {
|
|
624
|
+
const safeArray = [];
|
|
625
|
+
for (const item of obj) {
|
|
626
|
+
safeArray.push(getStringifiableData(item));
|
|
627
|
+
}
|
|
628
|
+
return safeArray;
|
|
629
|
+
}
|
|
630
|
+
const safeObj = {};
|
|
631
|
+
for (const key in obj) {
|
|
632
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
633
|
+
safeObj[key] = getStringifiableData(obj[key]);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return safeObj;
|
|
637
|
+
}
|
|
638
|
+
const stringifiable = getStringifiableData(data);
|
|
639
|
+
return JSON.stringify(stringifiable).length;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Transform JSON data while handling circular references
|
|
643
|
+
*/
|
|
644
|
+
transform(data) {
|
|
645
|
+
const size = _RecursiveJsonTransformer.jsonSize(data);
|
|
646
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
647
|
+
function createSafeCopy(obj) {
|
|
648
|
+
if (obj === null || typeof obj !== "object") {
|
|
649
|
+
return obj;
|
|
650
|
+
}
|
|
651
|
+
if (seen.has(obj)) {
|
|
652
|
+
return "[Circular]";
|
|
653
|
+
}
|
|
654
|
+
seen.add(obj);
|
|
655
|
+
if (Array.isArray(obj)) {
|
|
656
|
+
return obj.map((item) => createSafeCopy(item));
|
|
657
|
+
}
|
|
658
|
+
const copy = {};
|
|
659
|
+
for (const key in obj) {
|
|
660
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
661
|
+
copy[key] = createSafeCopy(obj[key]);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return copy;
|
|
665
|
+
}
|
|
666
|
+
return {
|
|
667
|
+
size,
|
|
668
|
+
data: createSafeCopy(data)
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Set a value in a nested dictionary based on the given path
|
|
673
|
+
*/
|
|
674
|
+
static setNestedDict(d, path, value) {
|
|
675
|
+
let current = d;
|
|
676
|
+
for (const key of path.slice(0, -1)) {
|
|
677
|
+
current[key] = current[key] || {};
|
|
678
|
+
current = current[key];
|
|
679
|
+
}
|
|
680
|
+
current[path[path.length - 1]] = value;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Convert lists in the JSON structure to dictionaries with index-based keys
|
|
684
|
+
*/
|
|
685
|
+
listToDictPreprocessing(data) {
|
|
686
|
+
if (data && typeof data === "object") {
|
|
687
|
+
if (Array.isArray(data)) {
|
|
688
|
+
return Object.fromEntries(data.map((item, index) => [String(index), this.listToDictPreprocessing(item)]));
|
|
689
|
+
}
|
|
690
|
+
return Object.fromEntries(Object.entries(data).map(([k, v]) => [k, this.listToDictPreprocessing(v)]));
|
|
691
|
+
}
|
|
692
|
+
return data;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Split json into maximum size dictionaries while preserving structure
|
|
696
|
+
*/
|
|
697
|
+
jsonSplit({
|
|
698
|
+
data,
|
|
699
|
+
currentPath = [],
|
|
700
|
+
chunks = [{}]
|
|
701
|
+
}) {
|
|
702
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
703
|
+
for (const [key, value] of Object.entries(data)) {
|
|
704
|
+
const newPath = [...currentPath, key];
|
|
705
|
+
const chunkSize = _RecursiveJsonTransformer.jsonSize(chunks[chunks.length - 1] || {});
|
|
706
|
+
const size = _RecursiveJsonTransformer.jsonSize({ [key]: value });
|
|
707
|
+
const remaining = this.maxSize - chunkSize;
|
|
708
|
+
if (size < remaining) {
|
|
709
|
+
_RecursiveJsonTransformer.setNestedDict(chunks[chunks.length - 1] || {}, newPath, value);
|
|
710
|
+
} else {
|
|
711
|
+
if (chunkSize >= this.minSize) {
|
|
712
|
+
chunks.push({});
|
|
713
|
+
}
|
|
714
|
+
this.jsonSplit({
|
|
715
|
+
data: typeof value === "object" ? value : { [key]: value },
|
|
716
|
+
currentPath: newPath,
|
|
717
|
+
chunks
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
} else {
|
|
722
|
+
_RecursiveJsonTransformer.setNestedDict(chunks[chunks.length - 1] || {}, currentPath, data);
|
|
723
|
+
}
|
|
724
|
+
return chunks;
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Splits JSON into a list of JSON chunks
|
|
728
|
+
*/
|
|
729
|
+
splitJson({
|
|
730
|
+
jsonData,
|
|
731
|
+
convertLists = false
|
|
732
|
+
}) {
|
|
733
|
+
const processedData = convertLists ? this.listToDictPreprocessing(jsonData) : jsonData;
|
|
734
|
+
const chunks = this.jsonSplit({ data: processedData });
|
|
735
|
+
if (Object.keys(chunks[chunks.length - 1] || {}).length === 0) {
|
|
736
|
+
chunks.pop();
|
|
737
|
+
}
|
|
738
|
+
return chunks;
|
|
739
|
+
}
|
|
740
|
+
escapeNonAscii(obj) {
|
|
741
|
+
if (typeof obj === "string") {
|
|
742
|
+
return obj.replace(/[\u0080-\uffff]/g, (char) => {
|
|
743
|
+
return `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`;
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
if (typeof obj === "object" && obj !== null) {
|
|
747
|
+
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, this.escapeNonAscii(value)]));
|
|
748
|
+
}
|
|
749
|
+
return obj;
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Splits JSON into a list of JSON formatted strings
|
|
753
|
+
*/
|
|
754
|
+
splitText({
|
|
755
|
+
jsonData,
|
|
756
|
+
convertLists = false,
|
|
757
|
+
ensureAscii = true
|
|
758
|
+
}) {
|
|
759
|
+
const chunks = this.splitJson({ jsonData, convertLists });
|
|
760
|
+
if (ensureAscii) {
|
|
761
|
+
const escapedChunks = chunks.map((chunk) => this.escapeNonAscii(chunk));
|
|
762
|
+
return escapedChunks.map((chunk) => JSON.stringify(chunk));
|
|
763
|
+
}
|
|
764
|
+
return chunks.map((chunk) => JSON.stringify(chunk));
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Create documents from a list of json objects
|
|
768
|
+
*/
|
|
769
|
+
createDocuments({
|
|
770
|
+
texts,
|
|
771
|
+
convertLists = false,
|
|
772
|
+
ensureAscii = true,
|
|
773
|
+
metadatas
|
|
774
|
+
}) {
|
|
775
|
+
const _metadatas = metadatas || Array(texts.length).fill({});
|
|
776
|
+
const documents = [];
|
|
777
|
+
texts.forEach((text, i) => {
|
|
778
|
+
const chunks = this.splitText({ jsonData: JSON.parse(text), convertLists, ensureAscii });
|
|
779
|
+
chunks.forEach((chunk) => {
|
|
780
|
+
const metadata = { ..._metadatas[i] || {} };
|
|
781
|
+
documents.push(
|
|
782
|
+
new Document({
|
|
783
|
+
text: chunk,
|
|
784
|
+
metadata
|
|
785
|
+
})
|
|
786
|
+
);
|
|
787
|
+
});
|
|
788
|
+
});
|
|
789
|
+
return documents;
|
|
790
|
+
}
|
|
791
|
+
transformDocuments({
|
|
792
|
+
ensureAscii,
|
|
793
|
+
documents,
|
|
794
|
+
convertLists
|
|
795
|
+
}) {
|
|
796
|
+
const texts = [];
|
|
797
|
+
const metadatas = [];
|
|
798
|
+
for (const doc of documents) {
|
|
799
|
+
texts.push(doc.text);
|
|
800
|
+
metadatas.push(doc.metadata);
|
|
801
|
+
}
|
|
802
|
+
return this.createDocuments({
|
|
803
|
+
texts,
|
|
804
|
+
metadatas,
|
|
805
|
+
ensureAscii,
|
|
806
|
+
convertLists
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
// src/document/transformers/latex.ts
|
|
812
|
+
var LatexTransformer = class extends RecursiveCharacterTransformer {
|
|
813
|
+
constructor(options = {}) {
|
|
814
|
+
const separators = RecursiveCharacterTransformer.getSeparatorsForLanguage("latex" /* LATEX */);
|
|
815
|
+
super({ separators, isSeparatorRegex: true, options });
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
var MarkdownTransformer = class extends RecursiveCharacterTransformer {
|
|
819
|
+
constructor(options = {}) {
|
|
820
|
+
const separators = RecursiveCharacterTransformer.getSeparatorsForLanguage("markdown" /* MARKDOWN */);
|
|
821
|
+
super({ separators, isSeparatorRegex: true, options });
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
var MarkdownHeaderTransformer = class {
|
|
825
|
+
headersToSplitOn;
|
|
826
|
+
returnEachLine;
|
|
827
|
+
stripHeaders;
|
|
828
|
+
constructor(headersToSplitOn, returnEachLine = false, stripHeaders = true) {
|
|
829
|
+
this.headersToSplitOn = [...headersToSplitOn].sort((a, b) => b[0].length - a[0].length);
|
|
830
|
+
this.returnEachLine = returnEachLine;
|
|
831
|
+
this.stripHeaders = stripHeaders;
|
|
832
|
+
}
|
|
833
|
+
aggregateLinesToChunks(lines) {
|
|
834
|
+
if (this.returnEachLine) {
|
|
835
|
+
return lines.flatMap((line) => {
|
|
836
|
+
const contentLines = line.content.split("\n");
|
|
837
|
+
return contentLines.filter((l) => l.trim() !== "" || this.headersToSplitOn.some(([sep]) => l.trim().startsWith(sep))).map(
|
|
838
|
+
(l) => new Document({
|
|
839
|
+
text: l.trim(),
|
|
840
|
+
metadata: line.metadata
|
|
841
|
+
})
|
|
842
|
+
);
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
const aggregatedChunks = [];
|
|
846
|
+
for (const line of lines) {
|
|
847
|
+
if (aggregatedChunks.length > 0 && JSON.stringify(aggregatedChunks?.[aggregatedChunks.length - 1].metadata) === JSON.stringify(line.metadata)) {
|
|
848
|
+
const aggChunk = aggregatedChunks[aggregatedChunks.length - 1];
|
|
849
|
+
aggChunk.content += " \n" + line.content;
|
|
850
|
+
} else if (aggregatedChunks.length > 0 && JSON.stringify(aggregatedChunks?.[aggregatedChunks.length - 1].metadata) !== JSON.stringify(line.metadata) && Object.keys(aggregatedChunks?.[aggregatedChunks.length - 1].metadata).length < Object.keys(line.metadata).length && aggregatedChunks?.[aggregatedChunks.length - 1]?.content?.split("\n")?.slice(-1)[0][0] === "#" && !this.stripHeaders) {
|
|
851
|
+
if (aggregatedChunks && aggregatedChunks?.[aggregatedChunks.length - 1]) {
|
|
852
|
+
const aggChunk = aggregatedChunks[aggregatedChunks.length - 1];
|
|
853
|
+
if (aggChunk) {
|
|
854
|
+
aggChunk.content += " \n" + line.content;
|
|
855
|
+
aggChunk.metadata = line.metadata;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
} else {
|
|
859
|
+
aggregatedChunks.push(line);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return aggregatedChunks.map(
|
|
863
|
+
(chunk) => new Document({
|
|
864
|
+
text: chunk.content,
|
|
865
|
+
metadata: chunk.metadata
|
|
866
|
+
})
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
splitText({ text }) {
|
|
870
|
+
const lines = text.split("\n");
|
|
871
|
+
const linesWithMetadata = [];
|
|
872
|
+
let currentContent = [];
|
|
873
|
+
let currentMetadata = {};
|
|
874
|
+
const headerStack = [];
|
|
875
|
+
const initialMetadata = {};
|
|
876
|
+
let inCodeBlock = false;
|
|
877
|
+
let openingFence = "";
|
|
878
|
+
for (let i = 0; i < lines.length; i++) {
|
|
879
|
+
const line = lines[i];
|
|
880
|
+
const strippedLine = line.trim();
|
|
881
|
+
if (!inCodeBlock) {
|
|
882
|
+
if (strippedLine.startsWith("```") && strippedLine.split("```").length === 2 || strippedLine.startsWith("~~~")) {
|
|
883
|
+
inCodeBlock = true;
|
|
884
|
+
openingFence = strippedLine.startsWith("```") ? "```" : "~~~";
|
|
885
|
+
}
|
|
886
|
+
} else {
|
|
887
|
+
if (strippedLine.startsWith(openingFence)) {
|
|
888
|
+
inCodeBlock = false;
|
|
889
|
+
openingFence = "";
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
if (inCodeBlock) {
|
|
893
|
+
currentContent.push(line);
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
let headerMatched = false;
|
|
897
|
+
for (const [sep, name] of this.headersToSplitOn) {
|
|
898
|
+
if (strippedLine.startsWith(sep) && (strippedLine.length === sep.length || strippedLine[sep.length] === " ")) {
|
|
899
|
+
headerMatched = true;
|
|
900
|
+
if (currentContent.length > 0) {
|
|
901
|
+
linesWithMetadata.push({
|
|
902
|
+
content: currentContent.join("\n"),
|
|
903
|
+
metadata: { ...currentMetadata }
|
|
904
|
+
});
|
|
905
|
+
currentContent = [];
|
|
906
|
+
}
|
|
907
|
+
if (name !== null) {
|
|
908
|
+
const currentHeaderLevel = (sep.match(/#/g) || []).length;
|
|
909
|
+
while (headerStack.length > 0 && headerStack?.[headerStack.length - 1].level >= currentHeaderLevel) {
|
|
910
|
+
const poppedHeader = headerStack.pop();
|
|
911
|
+
if (poppedHeader.name in initialMetadata) {
|
|
912
|
+
delete initialMetadata[poppedHeader.name];
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
const header = {
|
|
916
|
+
level: currentHeaderLevel,
|
|
917
|
+
name,
|
|
918
|
+
data: strippedLine.slice(sep.length).trim()
|
|
919
|
+
};
|
|
920
|
+
headerStack.push(header);
|
|
921
|
+
initialMetadata[name] = header.data;
|
|
922
|
+
}
|
|
923
|
+
linesWithMetadata.push({
|
|
924
|
+
content: line,
|
|
925
|
+
metadata: { ...currentMetadata, ...initialMetadata }
|
|
926
|
+
});
|
|
927
|
+
break;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
if (!headerMatched) {
|
|
931
|
+
if (strippedLine || this.returnEachLine) {
|
|
932
|
+
currentContent.push(line);
|
|
933
|
+
if (this.returnEachLine) {
|
|
934
|
+
linesWithMetadata.push({
|
|
935
|
+
content: line,
|
|
936
|
+
metadata: { ...currentMetadata }
|
|
937
|
+
});
|
|
938
|
+
currentContent = [];
|
|
939
|
+
}
|
|
940
|
+
} else if (currentContent.length > 0) {
|
|
941
|
+
linesWithMetadata.push({
|
|
942
|
+
content: currentContent.join("\n"),
|
|
943
|
+
metadata: { ...currentMetadata }
|
|
944
|
+
});
|
|
945
|
+
currentContent = [];
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
currentMetadata = { ...initialMetadata };
|
|
949
|
+
}
|
|
950
|
+
if (currentContent.length > 0) {
|
|
951
|
+
linesWithMetadata.push({
|
|
952
|
+
content: currentContent.join("\n"),
|
|
953
|
+
metadata: currentMetadata
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
return this.aggregateLinesToChunks(linesWithMetadata);
|
|
957
|
+
}
|
|
958
|
+
createDocuments(texts, metadatas) {
|
|
959
|
+
const _metadatas = metadatas || Array(texts.length).fill({});
|
|
960
|
+
const documents = [];
|
|
961
|
+
texts.forEach((text, i) => {
|
|
962
|
+
this.splitText({ text }).forEach((chunk) => {
|
|
963
|
+
const metadata = { ..._metadatas[i], ...chunk.metadata };
|
|
964
|
+
documents.push(
|
|
965
|
+
new Document({
|
|
966
|
+
text: chunk.text,
|
|
967
|
+
metadata
|
|
968
|
+
})
|
|
969
|
+
);
|
|
970
|
+
});
|
|
971
|
+
});
|
|
972
|
+
return documents;
|
|
973
|
+
}
|
|
974
|
+
transformDocuments(documents) {
|
|
975
|
+
const texts = [];
|
|
976
|
+
const metadatas = [];
|
|
977
|
+
for (const doc of documents) {
|
|
978
|
+
texts.push(doc.text);
|
|
979
|
+
metadatas.push(doc.metadata);
|
|
980
|
+
}
|
|
981
|
+
return this.createDocuments(texts, metadatas);
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
function splitTextOnTokens({ text, tokenizer }) {
|
|
985
|
+
const splits = [];
|
|
986
|
+
const inputIds = tokenizer.encode(text);
|
|
987
|
+
let startIdx = 0;
|
|
988
|
+
let curIdx = Math.min(startIdx + tokenizer.tokensPerChunk, inputIds.length);
|
|
989
|
+
let chunkIds = inputIds.slice(startIdx, curIdx);
|
|
990
|
+
while (startIdx < inputIds.length) {
|
|
991
|
+
splits.push(tokenizer.decode(chunkIds));
|
|
992
|
+
if (curIdx === inputIds.length) {
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
995
|
+
startIdx += tokenizer.tokensPerChunk - tokenizer.overlap;
|
|
996
|
+
curIdx = Math.min(startIdx + tokenizer.tokensPerChunk, inputIds.length);
|
|
997
|
+
chunkIds = inputIds.slice(startIdx, curIdx);
|
|
998
|
+
}
|
|
999
|
+
return splits;
|
|
1000
|
+
}
|
|
1001
|
+
var TokenTransformer = class _TokenTransformer extends TextTransformer {
|
|
1002
|
+
tokenizer;
|
|
1003
|
+
allowedSpecial;
|
|
1004
|
+
disallowedSpecial;
|
|
1005
|
+
constructor({
|
|
1006
|
+
encodingName = "cl100k_base",
|
|
1007
|
+
modelName,
|
|
1008
|
+
allowedSpecial = /* @__PURE__ */ new Set(),
|
|
1009
|
+
disallowedSpecial = "all",
|
|
1010
|
+
options = {}
|
|
1011
|
+
}) {
|
|
1012
|
+
super(options);
|
|
1013
|
+
try {
|
|
1014
|
+
this.tokenizer = modelName ? encodingForModel(modelName) : getEncoding(encodingName);
|
|
1015
|
+
} catch {
|
|
1016
|
+
throw new Error("Could not load tiktoken encoding. Please install it with `npm install js-tiktoken`.");
|
|
1017
|
+
}
|
|
1018
|
+
this.allowedSpecial = allowedSpecial;
|
|
1019
|
+
this.disallowedSpecial = disallowedSpecial;
|
|
1020
|
+
}
|
|
1021
|
+
splitText({ text }) {
|
|
1022
|
+
const encode = (text2) => {
|
|
1023
|
+
const allowed = this.allowedSpecial === "all" ? "all" : Array.from(this.allowedSpecial);
|
|
1024
|
+
const disallowed = this.disallowedSpecial === "all" ? "all" : Array.from(this.disallowedSpecial);
|
|
1025
|
+
const processedText = this.stripWhitespace ? text2.trim() : text2;
|
|
1026
|
+
return Array.from(this.tokenizer.encode(processedText, allowed, disallowed));
|
|
1027
|
+
};
|
|
1028
|
+
const decode = (tokens) => {
|
|
1029
|
+
const text2 = this.tokenizer.decode(tokens);
|
|
1030
|
+
return this.stripWhitespace ? text2.trim() : text2;
|
|
1031
|
+
};
|
|
1032
|
+
const tokenizer = {
|
|
1033
|
+
overlap: this.overlap,
|
|
1034
|
+
tokensPerChunk: this.size,
|
|
1035
|
+
decode,
|
|
1036
|
+
encode
|
|
1037
|
+
};
|
|
1038
|
+
return splitTextOnTokens({ text, tokenizer });
|
|
1039
|
+
}
|
|
1040
|
+
static fromTikToken({
|
|
1041
|
+
encodingName = "cl100k_base",
|
|
1042
|
+
modelName,
|
|
1043
|
+
options = {}
|
|
1044
|
+
}) {
|
|
1045
|
+
let tokenizer;
|
|
1046
|
+
try {
|
|
1047
|
+
if (modelName) {
|
|
1048
|
+
tokenizer = encodingForModel(modelName);
|
|
1049
|
+
} else {
|
|
1050
|
+
tokenizer = getEncoding(encodingName);
|
|
1051
|
+
}
|
|
1052
|
+
} catch {
|
|
1053
|
+
throw new Error("Could not load tiktoken encoding. Please install it with `npm install js-tiktoken`.");
|
|
1054
|
+
}
|
|
1055
|
+
const tikTokenEncoder = (text) => {
|
|
1056
|
+
const allowed = options.allowedSpecial === "all" ? "all" : options.allowedSpecial ? Array.from(options.allowedSpecial) : [];
|
|
1057
|
+
const disallowed = options.disallowedSpecial === "all" ? "all" : options.disallowedSpecial ? Array.from(options.disallowedSpecial) : [];
|
|
1058
|
+
return tokenizer.encode(text, allowed, disallowed).length;
|
|
1059
|
+
};
|
|
1060
|
+
return new _TokenTransformer({
|
|
1061
|
+
encodingName,
|
|
1062
|
+
modelName,
|
|
1063
|
+
allowedSpecial: options.allowedSpecial,
|
|
1064
|
+
disallowedSpecial: options.disallowedSpecial,
|
|
1065
|
+
options: {
|
|
1066
|
+
size: options.size,
|
|
1067
|
+
overlap: options.overlap,
|
|
1068
|
+
lengthFunction: tikTokenEncoder
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
|
|
1074
|
+
// src/document/document.ts
|
|
1075
|
+
var MDocument = class _MDocument {
|
|
1076
|
+
chunks;
|
|
1077
|
+
type;
|
|
1078
|
+
// e.g., 'text', 'html', 'markdown', 'json'
|
|
1079
|
+
constructor({ docs, type }) {
|
|
1080
|
+
this.chunks = docs.map((d) => {
|
|
1081
|
+
return new Document({ text: d.text, metadata: d.metadata });
|
|
1082
|
+
});
|
|
1083
|
+
this.type = type;
|
|
1084
|
+
}
|
|
1085
|
+
async extractMetadata({ title, summary, questions, keywords }) {
|
|
1086
|
+
const transformations = [];
|
|
1087
|
+
if (typeof summary !== "undefined") {
|
|
1088
|
+
transformations.push(new SummaryExtractor(typeof summary === "boolean" ? {} : summary));
|
|
1089
|
+
}
|
|
1090
|
+
if (typeof questions !== "undefined") {
|
|
1091
|
+
transformations.push(new QuestionsAnsweredExtractor(typeof questions === "boolean" ? {} : questions));
|
|
1092
|
+
}
|
|
1093
|
+
if (typeof keywords !== "undefined") {
|
|
1094
|
+
transformations.push(new KeywordExtractor(typeof keywords === "boolean" ? {} : keywords));
|
|
1095
|
+
}
|
|
1096
|
+
if (typeof title !== "undefined") {
|
|
1097
|
+
transformations.push(new TitleExtractor(typeof title === "boolean" ? {} : title));
|
|
1098
|
+
}
|
|
1099
|
+
const pipeline = new IngestionPipeline({
|
|
1100
|
+
transformations
|
|
1101
|
+
});
|
|
1102
|
+
const nodes = await pipeline.run({
|
|
1103
|
+
documents: this.chunks
|
|
1104
|
+
});
|
|
1105
|
+
this.chunks = this.chunks.map((doc, i) => {
|
|
1106
|
+
return new Document({
|
|
1107
|
+
text: doc.text,
|
|
1108
|
+
metadata: {
|
|
1109
|
+
...doc.metadata,
|
|
1110
|
+
...nodes?.[i]?.metadata || {}
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
});
|
|
1114
|
+
return this;
|
|
1115
|
+
}
|
|
1116
|
+
static fromText(text, metadata) {
|
|
1117
|
+
return new _MDocument({
|
|
1118
|
+
docs: [
|
|
1119
|
+
{
|
|
1120
|
+
text,
|
|
1121
|
+
metadata
|
|
1122
|
+
}
|
|
1123
|
+
],
|
|
1124
|
+
type: "text"
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
static fromHTML(html, metadata) {
|
|
1128
|
+
return new _MDocument({
|
|
1129
|
+
docs: [
|
|
1130
|
+
{
|
|
1131
|
+
text: html,
|
|
1132
|
+
metadata
|
|
1133
|
+
}
|
|
1134
|
+
],
|
|
1135
|
+
type: "html"
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
static fromMarkdown(markdown, metadata) {
|
|
1139
|
+
return new _MDocument({
|
|
1140
|
+
docs: [
|
|
1141
|
+
{
|
|
1142
|
+
text: markdown,
|
|
1143
|
+
metadata
|
|
1144
|
+
}
|
|
1145
|
+
],
|
|
1146
|
+
type: "markdown"
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
static fromJSON(jsonString, metadata) {
|
|
1150
|
+
return new _MDocument({
|
|
1151
|
+
docs: [
|
|
1152
|
+
{
|
|
1153
|
+
text: jsonString,
|
|
1154
|
+
metadata
|
|
1155
|
+
}
|
|
1156
|
+
],
|
|
1157
|
+
type: "json"
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
defaultStrategy() {
|
|
1161
|
+
switch (this.type) {
|
|
1162
|
+
case "html":
|
|
1163
|
+
return "html";
|
|
1164
|
+
case "markdown":
|
|
1165
|
+
return "markdown";
|
|
1166
|
+
case "json":
|
|
1167
|
+
return "json";
|
|
1168
|
+
case "latex":
|
|
1169
|
+
return "latex";
|
|
1170
|
+
default:
|
|
1171
|
+
return "recursive";
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
async chunkBy(strategy, options) {
|
|
1175
|
+
switch (strategy) {
|
|
1176
|
+
case "recursive":
|
|
1177
|
+
await this.chunkRecursive(options);
|
|
1178
|
+
break;
|
|
1179
|
+
case "character":
|
|
1180
|
+
await this.chunkCharacter(options);
|
|
1181
|
+
break;
|
|
1182
|
+
case "token":
|
|
1183
|
+
await this.chunkToken(options);
|
|
1184
|
+
break;
|
|
1185
|
+
case "markdown":
|
|
1186
|
+
await this.chunkMarkdown(options);
|
|
1187
|
+
break;
|
|
1188
|
+
case "html":
|
|
1189
|
+
await this.chunkHTML(options);
|
|
1190
|
+
break;
|
|
1191
|
+
case "json":
|
|
1192
|
+
await this.chunkJSON(options);
|
|
1193
|
+
break;
|
|
1194
|
+
case "latex":
|
|
1195
|
+
await this.chunkLatex(options);
|
|
1196
|
+
break;
|
|
1197
|
+
default:
|
|
1198
|
+
throw new Error(`Unknown strategy: ${strategy}`);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
async chunkRecursive(options) {
|
|
1202
|
+
if (options?.language) {
|
|
1203
|
+
const rt2 = RecursiveCharacterTransformer.fromLanguage(options.language, options);
|
|
1204
|
+
const textSplit2 = rt2.transformDocuments(this.chunks);
|
|
1205
|
+
this.chunks = textSplit2;
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
const rt = new RecursiveCharacterTransformer({
|
|
1209
|
+
separators: options?.separators,
|
|
1210
|
+
isSeparatorRegex: options?.isSeparatorRegex,
|
|
1211
|
+
options
|
|
1212
|
+
});
|
|
1213
|
+
const textSplit = rt.transformDocuments(this.chunks);
|
|
1214
|
+
this.chunks = textSplit;
|
|
1215
|
+
}
|
|
1216
|
+
async chunkCharacter(options) {
|
|
1217
|
+
const rt = new CharacterTransformer({
|
|
1218
|
+
separator: options?.separator,
|
|
1219
|
+
isSeparatorRegex: options?.isSeparatorRegex,
|
|
1220
|
+
options
|
|
1221
|
+
});
|
|
1222
|
+
const textSplit = rt.transformDocuments(this.chunks);
|
|
1223
|
+
this.chunks = textSplit;
|
|
1224
|
+
}
|
|
1225
|
+
async chunkHTML(options) {
|
|
1226
|
+
if (options?.headers?.length) {
|
|
1227
|
+
const rt = new HTMLHeaderTransformer(options.headers, options?.returnEachLine);
|
|
1228
|
+
const textSplit = rt.transformDocuments(this.chunks);
|
|
1229
|
+
this.chunks = textSplit;
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
if (options?.sections?.length) {
|
|
1233
|
+
const rt = new HTMLSectionTransformer(options.sections);
|
|
1234
|
+
const textSplit = rt.transformDocuments(this.chunks);
|
|
1235
|
+
this.chunks = textSplit;
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
throw new Error("HTML chunking requires either headers or sections to be specified");
|
|
1239
|
+
}
|
|
1240
|
+
async chunkJSON(options) {
|
|
1241
|
+
if (!options?.maxSize) {
|
|
1242
|
+
throw new Error("JSON chunking requires maxSize to be specified");
|
|
1243
|
+
}
|
|
1244
|
+
const rt = new RecursiveJsonTransformer({
|
|
1245
|
+
maxSize: options?.maxSize,
|
|
1246
|
+
minSize: options?.minSize
|
|
1247
|
+
});
|
|
1248
|
+
const textSplit = rt.transformDocuments({
|
|
1249
|
+
documents: this.chunks,
|
|
1250
|
+
ensureAscii: options?.ensureAscii,
|
|
1251
|
+
convertLists: options?.convertLists
|
|
1252
|
+
});
|
|
1253
|
+
this.chunks = textSplit;
|
|
1254
|
+
}
|
|
1255
|
+
async chunkLatex(options) {
|
|
1256
|
+
const rt = new LatexTransformer(options);
|
|
1257
|
+
const textSplit = rt.transformDocuments(this.chunks);
|
|
1258
|
+
this.chunks = textSplit;
|
|
1259
|
+
}
|
|
1260
|
+
async chunkToken(options) {
|
|
1261
|
+
const rt = TokenTransformer.fromTikToken({
|
|
1262
|
+
options,
|
|
1263
|
+
encodingName: options?.encodingName,
|
|
1264
|
+
modelName: options?.modelName
|
|
1265
|
+
});
|
|
1266
|
+
const textSplit = rt.transformDocuments(this.chunks);
|
|
1267
|
+
this.chunks = textSplit;
|
|
1268
|
+
}
|
|
1269
|
+
async chunkMarkdown(options) {
|
|
1270
|
+
if (options?.headers) {
|
|
1271
|
+
const rt2 = new MarkdownHeaderTransformer(options.headers, options?.returnEachLine, options?.stripHeaders);
|
|
1272
|
+
const textSplit2 = rt2.transformDocuments(this.chunks);
|
|
1273
|
+
this.chunks = textSplit2;
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
const rt = new MarkdownTransformer(options);
|
|
1277
|
+
const textSplit = rt.transformDocuments(this.chunks);
|
|
1278
|
+
this.chunks = textSplit;
|
|
1279
|
+
}
|
|
1280
|
+
async chunk(params) {
|
|
1281
|
+
const { strategy: passedStrategy, extract, ...chunkOptions } = params || {};
|
|
1282
|
+
const strategy = passedStrategy || this.defaultStrategy();
|
|
1283
|
+
await this.chunkBy(strategy, chunkOptions);
|
|
1284
|
+
if (extract) {
|
|
1285
|
+
await this.extractMetadata(extract);
|
|
1286
|
+
}
|
|
1287
|
+
return this.chunks;
|
|
1288
|
+
}
|
|
1289
|
+
getDocs() {
|
|
1290
|
+
return this.chunks;
|
|
1291
|
+
}
|
|
1292
|
+
getText() {
|
|
1293
|
+
return this.chunks.map((doc) => doc.text);
|
|
1294
|
+
}
|
|
1295
|
+
getMetadata() {
|
|
1296
|
+
return this.chunks.map((doc) => doc.metadata);
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
var DEFAULT_WEIGHTS = {
|
|
1300
|
+
semantic: 0.4,
|
|
1301
|
+
vector: 0.4,
|
|
1302
|
+
position: 0.2
|
|
1303
|
+
};
|
|
1304
|
+
function calculatePositionScore(position, totalChunks) {
|
|
1305
|
+
return 1 - position / totalChunks;
|
|
1306
|
+
}
|
|
1307
|
+
function analyzeQueryEmbedding(embedding) {
|
|
1308
|
+
const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0));
|
|
1309
|
+
const dominantFeatures = embedding.map((value, index) => ({ value: Math.abs(value), index })).sort((a, b) => b.value - a.value).slice(0, 5).map((item) => item.index);
|
|
1310
|
+
return { magnitude, dominantFeatures };
|
|
1311
|
+
}
|
|
1312
|
+
function adjustScores(score, queryAnalysis) {
|
|
1313
|
+
const magnitudeAdjustment = queryAnalysis.magnitude > 10 ? 1.1 : 1;
|
|
1314
|
+
const featureStrengthAdjustment = queryAnalysis.magnitude > 5 ? 1.05 : 1;
|
|
1315
|
+
return score * magnitudeAdjustment * featureStrengthAdjustment;
|
|
1316
|
+
}
|
|
1317
|
+
async function rerank(results, query, model, options) {
|
|
1318
|
+
let semanticProvider;
|
|
1319
|
+
if (model.modelId === "rerank-v3.5") {
|
|
1320
|
+
semanticProvider = new CohereRelevanceScorer(model.modelId);
|
|
1321
|
+
} else {
|
|
1322
|
+
semanticProvider = new MastraAgentRelevanceScorer(model.provider, model);
|
|
1323
|
+
}
|
|
1324
|
+
const { queryEmbedding, topK = 3 } = options;
|
|
1325
|
+
const weights = {
|
|
1326
|
+
...DEFAULT_WEIGHTS,
|
|
1327
|
+
...options.weights
|
|
1328
|
+
};
|
|
1329
|
+
const totalWeights = Object.values(weights).reduce((sum, weight) => sum + weight, 0);
|
|
1330
|
+
if (totalWeights !== 1) {
|
|
1331
|
+
throw new Error("Weights must add up to 1");
|
|
1332
|
+
}
|
|
1333
|
+
const resultLength = results.length;
|
|
1334
|
+
const queryAnalysis = queryEmbedding ? analyzeQueryEmbedding(queryEmbedding) : null;
|
|
1335
|
+
const scoredResults = await Promise.all(
|
|
1336
|
+
results.map(async (result, index) => {
|
|
1337
|
+
const semanticScore = await semanticProvider.getRelevanceScore(query, result?.metadata?.text);
|
|
1338
|
+
const vectorScore = result.score;
|
|
1339
|
+
const positionScore = calculatePositionScore(index, resultLength);
|
|
1340
|
+
let finalScore = weights.semantic * semanticScore + weights.vector * vectorScore + weights.position * positionScore;
|
|
1341
|
+
if (queryAnalysis) {
|
|
1342
|
+
finalScore = adjustScores(finalScore, queryAnalysis);
|
|
1343
|
+
}
|
|
1344
|
+
return {
|
|
1345
|
+
result,
|
|
1346
|
+
score: finalScore,
|
|
1347
|
+
details: {
|
|
1348
|
+
semantic: semanticScore,
|
|
1349
|
+
vector: vectorScore,
|
|
1350
|
+
position: positionScore,
|
|
1351
|
+
...queryAnalysis && {
|
|
1352
|
+
queryAnalysis: {
|
|
1353
|
+
magnitude: queryAnalysis.magnitude,
|
|
1354
|
+
dominantFeatures: queryAnalysis.dominantFeatures
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
};
|
|
1359
|
+
})
|
|
1360
|
+
);
|
|
1361
|
+
return scoredResults.sort((a, b) => b.score - a.score).slice(0, topK);
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// src/graph-rag/index.ts
|
|
1365
|
+
var GraphRAG = class {
|
|
1366
|
+
nodes;
|
|
1367
|
+
edges;
|
|
1368
|
+
dimension;
|
|
1369
|
+
threshold;
|
|
1370
|
+
constructor(dimension = 1536, threshold = 0.7) {
|
|
1371
|
+
this.nodes = /* @__PURE__ */ new Map();
|
|
1372
|
+
this.edges = [];
|
|
1373
|
+
this.dimension = dimension;
|
|
1374
|
+
this.threshold = threshold;
|
|
1375
|
+
}
|
|
1376
|
+
// Add a node to the graph
|
|
1377
|
+
addNode(node) {
|
|
1378
|
+
if (!node.embedding) {
|
|
1379
|
+
throw new Error("Node must have an embedding");
|
|
1380
|
+
}
|
|
1381
|
+
if (node.embedding.length !== this.dimension) {
|
|
1382
|
+
throw new Error(`Embedding dimension must be ${this.dimension}`);
|
|
1383
|
+
}
|
|
1384
|
+
this.nodes.set(node.id, node);
|
|
1385
|
+
}
|
|
1386
|
+
// Add an edge between two nodes
|
|
1387
|
+
addEdge(edge) {
|
|
1388
|
+
if (!this.nodes.has(edge.source) || !this.nodes.has(edge.target)) {
|
|
1389
|
+
throw new Error("Both source and target nodes must exist");
|
|
1390
|
+
}
|
|
1391
|
+
this.edges.push(edge);
|
|
1392
|
+
this.edges.push({
|
|
1393
|
+
source: edge.target,
|
|
1394
|
+
target: edge.source,
|
|
1395
|
+
weight: edge.weight,
|
|
1396
|
+
type: edge.type
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
// Helper method to get all nodes
|
|
1400
|
+
getNodes() {
|
|
1401
|
+
return Array.from(this.nodes.values());
|
|
1402
|
+
}
|
|
1403
|
+
// Helper method to get all edges
|
|
1404
|
+
getEdges() {
|
|
1405
|
+
return this.edges;
|
|
1406
|
+
}
|
|
1407
|
+
getEdgesByType(type) {
|
|
1408
|
+
return this.edges.filter((edge) => edge.type === type);
|
|
1409
|
+
}
|
|
1410
|
+
clear() {
|
|
1411
|
+
this.nodes.clear();
|
|
1412
|
+
this.edges = [];
|
|
1413
|
+
}
|
|
1414
|
+
updateNodeContent(id, newContent) {
|
|
1415
|
+
const node = this.nodes.get(id);
|
|
1416
|
+
if (!node) {
|
|
1417
|
+
throw new Error(`Node ${id} not found`);
|
|
1418
|
+
}
|
|
1419
|
+
node.content = newContent;
|
|
1420
|
+
}
|
|
1421
|
+
// Get neighbors of a node
|
|
1422
|
+
getNeighbors(nodeId, edgeType) {
|
|
1423
|
+
return this.edges.filter((edge) => edge.source === nodeId && (!edgeType || edge.type === edgeType)).map((edge) => ({
|
|
1424
|
+
id: edge.target,
|
|
1425
|
+
weight: edge.weight
|
|
1426
|
+
})).filter((node) => node !== void 0);
|
|
1427
|
+
}
|
|
1428
|
+
// Calculate cosine similarity between two vectors
|
|
1429
|
+
cosineSimilarity(vec1, vec2) {
|
|
1430
|
+
if (!vec1 || !vec2) {
|
|
1431
|
+
throw new Error("Vectors must not be null or undefined");
|
|
1432
|
+
}
|
|
1433
|
+
const vectorLength = vec1.length;
|
|
1434
|
+
if (vectorLength !== vec2.length) {
|
|
1435
|
+
throw new Error(`Vector dimensions must match: vec1(${vec1.length}) !== vec2(${vec2.length})`);
|
|
1436
|
+
}
|
|
1437
|
+
let dotProduct = 0;
|
|
1438
|
+
let normVec1 = 0;
|
|
1439
|
+
let normVec2 = 0;
|
|
1440
|
+
for (let i = 0; i < vectorLength; i++) {
|
|
1441
|
+
const a = vec1[i];
|
|
1442
|
+
const b = vec2[i];
|
|
1443
|
+
dotProduct += a * b;
|
|
1444
|
+
normVec1 += a * a;
|
|
1445
|
+
normVec2 += b * b;
|
|
1446
|
+
}
|
|
1447
|
+
const magnitudeProduct = Math.sqrt(normVec1 * normVec2);
|
|
1448
|
+
if (magnitudeProduct === 0) {
|
|
1449
|
+
return 0;
|
|
1450
|
+
}
|
|
1451
|
+
const similarity = dotProduct / magnitudeProduct;
|
|
1452
|
+
return Math.max(-1, Math.min(1, similarity));
|
|
1453
|
+
}
|
|
1454
|
+
createGraph(chunks, embeddings) {
|
|
1455
|
+
if (!chunks?.length || !embeddings?.length) {
|
|
1456
|
+
throw new Error("Chunks and embeddings arrays must not be empty");
|
|
1457
|
+
}
|
|
1458
|
+
if (chunks.length !== embeddings.length) {
|
|
1459
|
+
throw new Error("Chunks and embeddings must have the same length");
|
|
1460
|
+
}
|
|
1461
|
+
chunks.forEach((chunk, index) => {
|
|
1462
|
+
const node = {
|
|
1463
|
+
id: index.toString(),
|
|
1464
|
+
content: chunk.text,
|
|
1465
|
+
embedding: embeddings[index]?.vector,
|
|
1466
|
+
metadata: { ...chunk.metadata }
|
|
1467
|
+
};
|
|
1468
|
+
this.addNode(node);
|
|
1469
|
+
this.nodes.set(node.id, node);
|
|
1470
|
+
});
|
|
1471
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
1472
|
+
const firstEmbedding = embeddings[i]?.vector;
|
|
1473
|
+
for (let j = i + 1; j < chunks.length; j++) {
|
|
1474
|
+
const secondEmbedding = embeddings[j]?.vector;
|
|
1475
|
+
const similarity = this.cosineSimilarity(firstEmbedding, secondEmbedding);
|
|
1476
|
+
if (similarity > this.threshold) {
|
|
1477
|
+
this.addEdge({
|
|
1478
|
+
source: i.toString(),
|
|
1479
|
+
target: j.toString(),
|
|
1480
|
+
weight: similarity,
|
|
1481
|
+
type: "semantic"
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
selectWeightedNeighbor(neighbors) {
|
|
1488
|
+
const totalWeight = neighbors.reduce((sum, n) => sum + n.weight, 0);
|
|
1489
|
+
let remainingWeight = Math.random() * totalWeight;
|
|
1490
|
+
for (const neighbor of neighbors) {
|
|
1491
|
+
remainingWeight -= neighbor.weight;
|
|
1492
|
+
if (remainingWeight <= 0) {
|
|
1493
|
+
return neighbor.id;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
return neighbors[neighbors.length - 1]?.id;
|
|
1497
|
+
}
|
|
1498
|
+
// Perform random walk with restart
|
|
1499
|
+
randomWalkWithRestart(startNodeId, steps, restartProb) {
|
|
1500
|
+
const visits = /* @__PURE__ */ new Map();
|
|
1501
|
+
let currentNodeId = startNodeId;
|
|
1502
|
+
for (let step = 0; step < steps; step++) {
|
|
1503
|
+
visits.set(currentNodeId, (visits.get(currentNodeId) || 0) + 1);
|
|
1504
|
+
if (Math.random() < restartProb) {
|
|
1505
|
+
currentNodeId = startNodeId;
|
|
1506
|
+
continue;
|
|
1507
|
+
}
|
|
1508
|
+
const neighbors = this.getNeighbors(currentNodeId);
|
|
1509
|
+
if (neighbors.length === 0) {
|
|
1510
|
+
currentNodeId = startNodeId;
|
|
1511
|
+
continue;
|
|
1512
|
+
}
|
|
1513
|
+
currentNodeId = this.selectWeightedNeighbor(neighbors);
|
|
1514
|
+
}
|
|
1515
|
+
const totalVisits = Array.from(visits.values()).reduce((a, b) => a + b, 0);
|
|
1516
|
+
const normalizedVisits = /* @__PURE__ */ new Map();
|
|
1517
|
+
for (const [nodeId, count] of visits) {
|
|
1518
|
+
normalizedVisits.set(nodeId, count / totalVisits);
|
|
1519
|
+
}
|
|
1520
|
+
return normalizedVisits;
|
|
1521
|
+
}
|
|
1522
|
+
// Retrieve relevant nodes using hybrid approach
|
|
1523
|
+
query({
|
|
1524
|
+
query,
|
|
1525
|
+
topK = 10,
|
|
1526
|
+
randomWalkSteps = 100,
|
|
1527
|
+
restartProb = 0.15
|
|
1528
|
+
}) {
|
|
1529
|
+
if (!query || query.length !== this.dimension) {
|
|
1530
|
+
throw new Error(`Query embedding must have dimension ${this.dimension}`);
|
|
1531
|
+
}
|
|
1532
|
+
if (topK < 1) {
|
|
1533
|
+
throw new Error("TopK must be greater than 0");
|
|
1534
|
+
}
|
|
1535
|
+
if (randomWalkSteps < 1) {
|
|
1536
|
+
throw new Error("Random walk steps must be greater than 0");
|
|
1537
|
+
}
|
|
1538
|
+
if (restartProb <= 0 || restartProb >= 1) {
|
|
1539
|
+
throw new Error("Restart probability must be between 0 and 1");
|
|
1540
|
+
}
|
|
1541
|
+
const similarities = Array.from(this.nodes.values()).map((node) => ({
|
|
1542
|
+
node,
|
|
1543
|
+
similarity: this.cosineSimilarity(query, node.embedding)
|
|
1544
|
+
}));
|
|
1545
|
+
similarities.sort((a, b) => b.similarity - a.similarity);
|
|
1546
|
+
const topNodes = similarities.slice(0, topK);
|
|
1547
|
+
const rerankedNodes = /* @__PURE__ */ new Map();
|
|
1548
|
+
for (const { node, similarity } of topNodes) {
|
|
1549
|
+
const walkScores = this.randomWalkWithRestart(node.id, randomWalkSteps, restartProb);
|
|
1550
|
+
for (const [nodeId, walkScore] of walkScores) {
|
|
1551
|
+
const node2 = this.nodes.get(nodeId);
|
|
1552
|
+
const existingScore = rerankedNodes.get(nodeId)?.score || 0;
|
|
1553
|
+
rerankedNodes.set(nodeId, {
|
|
1554
|
+
node: node2,
|
|
1555
|
+
score: existingScore + similarity * walkScore
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
return Array.from(rerankedNodes.values()).sort((a, b) => b.score - a.score).slice(0, topK).map((item) => ({
|
|
1560
|
+
id: item.node.id,
|
|
1561
|
+
content: item.node.content,
|
|
1562
|
+
metadata: item.node.metadata,
|
|
1563
|
+
score: item.score
|
|
1564
|
+
}));
|
|
1565
|
+
}
|
|
1566
|
+
};
|
|
1567
|
+
var createDocumentChunkerTool = ({
|
|
1568
|
+
doc,
|
|
1569
|
+
params = {
|
|
1570
|
+
strategy: "recursive",
|
|
1571
|
+
size: 512,
|
|
1572
|
+
overlap: 50,
|
|
1573
|
+
separator: "\n"
|
|
1574
|
+
}
|
|
1575
|
+
}) => {
|
|
1576
|
+
return createTool({
|
|
1577
|
+
id: `Document Chunker ${params.strategy} ${params.size}`,
|
|
1578
|
+
inputSchema: z.object({}),
|
|
1579
|
+
description: `Chunks document using ${params.strategy} strategy with size ${params.size} and ${params.overlap} overlap`,
|
|
1580
|
+
execute: async () => {
|
|
1581
|
+
const chunks = await doc.chunk(params);
|
|
1582
|
+
return {
|
|
1583
|
+
chunks
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
});
|
|
1587
|
+
};
|
|
1588
|
+
var vectorQuerySearch = async ({
|
|
1589
|
+
indexName,
|
|
1590
|
+
vectorStore,
|
|
1591
|
+
queryText,
|
|
1592
|
+
model,
|
|
1593
|
+
queryFilter = {},
|
|
1594
|
+
topK,
|
|
1595
|
+
includeVectors = false,
|
|
1596
|
+
maxRetries = 2
|
|
1597
|
+
}) => {
|
|
1598
|
+
const { embedding } = await embed({
|
|
1599
|
+
value: queryText,
|
|
1600
|
+
model,
|
|
1601
|
+
maxRetries
|
|
1602
|
+
});
|
|
1603
|
+
const results = await vectorStore.query(indexName, embedding, topK, queryFilter, includeVectors);
|
|
1604
|
+
return { results, queryEmbedding: embedding };
|
|
1605
|
+
};
|
|
1606
|
+
|
|
1607
|
+
// src/utils/default-settings.ts
|
|
1608
|
+
var defaultTopK = `You MUST generate for each query:
|
|
1609
|
+
topK: number of results to return (REQUIRED) (Default: 10)
|
|
1610
|
+
- Generate topK based on exactly what user specifies
|
|
1611
|
+
- must be a valid number
|
|
1612
|
+
|
|
1613
|
+
Notes:
|
|
1614
|
+
- If user provides a valid topK, use the topK provided
|
|
1615
|
+
- If user does not specify topK or provides an invalid topK, use default topK: 10
|
|
1616
|
+
`;
|
|
1617
|
+
var defaultFilter = `You MUST generate for each query:
|
|
1618
|
+
filter: query filter (REQUIRED) (Default: {})
|
|
1619
|
+
- Generate filter based on user's explicit query intent
|
|
1620
|
+
- Must be valid JSON string
|
|
1621
|
+
|
|
1622
|
+
Notes:
|
|
1623
|
+
- If user provides a valid filter, use the filter provided
|
|
1624
|
+
- If user does not specify filter or provides an invalid filter, use default filter: {}
|
|
1625
|
+
`;
|
|
1626
|
+
var defaultVectorQueryDescription = (vectorStoreName, indexName) => `Retrieves relevant information from ${vectorStoreName} using ${indexName} index.
|
|
1627
|
+
|
|
1628
|
+
${defaultTopK}
|
|
1629
|
+
${defaultFilter}
|
|
1630
|
+
`;
|
|
1631
|
+
var defaultGraphRagDescription = (vectorStoreName, indexName) => `Fetches and reranks the most relevant chunks using GraphRAG from the ${vectorStoreName} vector store using the ${indexName} index.
|
|
1632
|
+
|
|
1633
|
+
${defaultTopK}
|
|
1634
|
+
${defaultFilter}
|
|
1635
|
+
`;
|
|
1636
|
+
|
|
1637
|
+
// src/tools/graph-rag.ts
|
|
1638
|
+
var createGraphRAGTool = ({
|
|
1639
|
+
vectorStoreName,
|
|
1640
|
+
indexName,
|
|
1641
|
+
model,
|
|
1642
|
+
enableFilter = false,
|
|
1643
|
+
graphOptions = {
|
|
1644
|
+
dimension: 1536,
|
|
1645
|
+
randomWalkSteps: 100,
|
|
1646
|
+
restartProb: 0.15,
|
|
1647
|
+
threshold: 0.7
|
|
1648
|
+
},
|
|
1649
|
+
id,
|
|
1650
|
+
description
|
|
1651
|
+
}) => {
|
|
1652
|
+
const toolId = id || `GraphRAG ${vectorStoreName} ${indexName} Tool`;
|
|
1653
|
+
const toolDescription = description || defaultGraphRagDescription(vectorStoreName, indexName);
|
|
1654
|
+
const graphRag = new GraphRAG(graphOptions.dimension, graphOptions.threshold);
|
|
1655
|
+
let isInitialized = false;
|
|
1656
|
+
return createTool({
|
|
1657
|
+
id: toolId,
|
|
1658
|
+
inputSchema: z.object({
|
|
1659
|
+
queryText: z.string(),
|
|
1660
|
+
topK: z.number(),
|
|
1661
|
+
filter: z.string()
|
|
1662
|
+
}),
|
|
1663
|
+
outputSchema: z.object({
|
|
1664
|
+
relevantContext: z.any()
|
|
1665
|
+
}),
|
|
1666
|
+
description: toolDescription,
|
|
1667
|
+
execute: async ({ context: { queryText, topK, filter }, mastra }) => {
|
|
1668
|
+
const vectorStore = mastra?.vectors?.[vectorStoreName];
|
|
1669
|
+
if (vectorStore) {
|
|
1670
|
+
let queryFilter = {};
|
|
1671
|
+
if (enableFilter) {
|
|
1672
|
+
queryFilter = filter ? (() => {
|
|
1673
|
+
try {
|
|
1674
|
+
return JSON.parse(filter);
|
|
1675
|
+
} catch {
|
|
1676
|
+
return filter;
|
|
1677
|
+
}
|
|
1678
|
+
})() : filter;
|
|
1679
|
+
}
|
|
1680
|
+
if (mastra.logger) {
|
|
1681
|
+
mastra.logger.debug("Using this filter and topK:", { queryFilter, topK });
|
|
1682
|
+
}
|
|
1683
|
+
const { results, queryEmbedding } = await vectorQuerySearch({
|
|
1684
|
+
indexName,
|
|
1685
|
+
vectorStore,
|
|
1686
|
+
queryText,
|
|
1687
|
+
model,
|
|
1688
|
+
queryFilter: Object.keys(queryFilter || {}).length > 0 ? queryFilter : void 0,
|
|
1689
|
+
topK,
|
|
1690
|
+
includeVectors: true
|
|
1691
|
+
});
|
|
1692
|
+
if (!isInitialized) {
|
|
1693
|
+
const chunks = results.map((result) => ({
|
|
1694
|
+
text: result?.metadata?.text,
|
|
1695
|
+
metadata: result.metadata ?? {}
|
|
1696
|
+
}));
|
|
1697
|
+
const embeddings = results.map((result) => ({
|
|
1698
|
+
vector: result.vector || []
|
|
1699
|
+
}));
|
|
1700
|
+
graphRag.createGraph(chunks, embeddings);
|
|
1701
|
+
isInitialized = true;
|
|
1702
|
+
}
|
|
1703
|
+
const rerankedResults = graphRag.query({
|
|
1704
|
+
query: queryEmbedding,
|
|
1705
|
+
topK,
|
|
1706
|
+
randomWalkSteps: graphOptions.randomWalkSteps,
|
|
1707
|
+
restartProb: graphOptions.restartProb
|
|
1708
|
+
});
|
|
1709
|
+
const relevantChunks = rerankedResults.map((result) => result.content);
|
|
1710
|
+
return {
|
|
1711
|
+
relevantContext: relevantChunks
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
return {
|
|
1715
|
+
relevantContext: []
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
});
|
|
1719
|
+
};
|
|
1720
|
+
var createVectorQueryTool = ({
|
|
1721
|
+
vectorStoreName,
|
|
1722
|
+
indexName,
|
|
1723
|
+
model,
|
|
1724
|
+
enableFilter = false,
|
|
1725
|
+
reranker,
|
|
1726
|
+
id,
|
|
1727
|
+
description
|
|
1728
|
+
}) => {
|
|
1729
|
+
const toolId = id || `VectorQuery ${vectorStoreName} ${indexName} Tool`;
|
|
1730
|
+
const toolDescription = description || defaultVectorQueryDescription(vectorStoreName, indexName);
|
|
1731
|
+
return createTool({
|
|
1732
|
+
id: toolId,
|
|
1733
|
+
inputSchema: z.object({
|
|
1734
|
+
queryText: z.string(),
|
|
1735
|
+
topK: z.number(),
|
|
1736
|
+
filter: z.string()
|
|
1737
|
+
}),
|
|
1738
|
+
outputSchema: z.object({
|
|
1739
|
+
relevantContext: z.any()
|
|
1740
|
+
}),
|
|
1741
|
+
description: toolDescription,
|
|
1742
|
+
execute: async ({ context: { queryText, topK, filter }, mastra }) => {
|
|
1743
|
+
const vectorStore = mastra?.vectors?.[vectorStoreName];
|
|
1744
|
+
if (vectorStore) {
|
|
1745
|
+
let queryFilter = {};
|
|
1746
|
+
if (enableFilter) {
|
|
1747
|
+
queryFilter = filter ? (() => {
|
|
1748
|
+
try {
|
|
1749
|
+
return JSON.parse(filter);
|
|
1750
|
+
} catch {
|
|
1751
|
+
return filter;
|
|
1752
|
+
}
|
|
1753
|
+
})() : filter;
|
|
1754
|
+
}
|
|
1755
|
+
if (mastra.logger) {
|
|
1756
|
+
mastra.logger.debug("Using this filter and topK:", { queryFilter, topK });
|
|
1757
|
+
}
|
|
1758
|
+
const { results } = await vectorQuerySearch({
|
|
1759
|
+
indexName,
|
|
1760
|
+
vectorStore,
|
|
1761
|
+
queryText,
|
|
1762
|
+
model,
|
|
1763
|
+
queryFilter: Object.keys(queryFilter || {}).length > 0 ? queryFilter : void 0,
|
|
1764
|
+
topK
|
|
1765
|
+
});
|
|
1766
|
+
if (reranker) {
|
|
1767
|
+
const rerankedResults = await rerank(results, queryText, reranker.model, {
|
|
1768
|
+
...reranker.options,
|
|
1769
|
+
topK: reranker.options?.topK || topK
|
|
1770
|
+
});
|
|
1771
|
+
const relevantChunks2 = rerankedResults.map(({ result }) => result?.metadata);
|
|
1772
|
+
return { relevantContext: relevantChunks2 };
|
|
1773
|
+
}
|
|
1774
|
+
const relevantChunks = results.map((result) => result?.metadata);
|
|
1775
|
+
return {
|
|
1776
|
+
relevantContext: relevantChunks
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
return {
|
|
1780
|
+
relevantContext: []
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
});
|
|
1784
|
+
};
|
|
1785
|
+
|
|
1786
|
+
// src/utils/vector-prompts.ts
|
|
1787
|
+
var ASTRA_PROMPT = `When querying Astra, you can ONLY use the operators listed below. Any other operators will be rejected.
|
|
1788
|
+
Important: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results.
|
|
1789
|
+
If a user tries to give an explicit operator that is not supported, reject the filter entirely and let them know that the operator is not supported.
|
|
1790
|
+
|
|
1791
|
+
Basic Comparison Operators:
|
|
1792
|
+
- $eq: Exact match (default when using field: value)
|
|
1793
|
+
Example: { "category": "electronics" }
|
|
1794
|
+
- $ne: Not equal
|
|
1795
|
+
Example: { "category": { "$ne": "electronics" } }
|
|
1796
|
+
- $gt: Greater than
|
|
1797
|
+
Example: { "price": { "$gt": 100 } }
|
|
1798
|
+
- $gte: Greater than or equal
|
|
1799
|
+
Example: { "price": { "$gte": 100 } }
|
|
1800
|
+
- $lt: Less than
|
|
1801
|
+
Example: { "price": { "$lt": 100 } }
|
|
1802
|
+
- $lte: Less than or equal
|
|
1803
|
+
Example: { "price": { "$lte": 100 } }
|
|
1804
|
+
|
|
1805
|
+
Array Operators:
|
|
1806
|
+
- $in: Match any value in array
|
|
1807
|
+
Example: { "category": { "$in": ["electronics", "books"] } }
|
|
1808
|
+
- $nin: Does not match any value in array
|
|
1809
|
+
Example: { "category": { "$nin": ["electronics", "books"] } }
|
|
1810
|
+
- $all: Match all values in array
|
|
1811
|
+
Example: { "tags": { "$all": ["premium", "sale"] } }
|
|
1812
|
+
|
|
1813
|
+
Logical Operators:
|
|
1814
|
+
- $and: Logical AND (can be implicit or explicit)
|
|
1815
|
+
Implicit Example: { "price": { "$gt": 100 }, "category": "electronics" }
|
|
1816
|
+
Explicit Example: { "$and": [{ "price": { "$gt": 100 } }, { "category": "electronics" }] }
|
|
1817
|
+
- $or: Logical OR
|
|
1818
|
+
Example: { "$or": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
|
|
1819
|
+
- $not: Logical NOT
|
|
1820
|
+
Example: { "$not": { "category": "electronics" } }
|
|
1821
|
+
|
|
1822
|
+
Element Operators:
|
|
1823
|
+
- $exists: Check if field exists
|
|
1824
|
+
Example: { "rating": { "$exists": true } }
|
|
1825
|
+
|
|
1826
|
+
Special Operators:
|
|
1827
|
+
- $size: Array length check
|
|
1828
|
+
Example: { "tags": { "$size": 2 } }
|
|
1829
|
+
|
|
1830
|
+
Restrictions:
|
|
1831
|
+
- Regex patterns are not supported
|
|
1832
|
+
- Only $and, $or, and $not logical operators are supported
|
|
1833
|
+
- Nested fields are supported using dot notation
|
|
1834
|
+
- Multiple conditions on the same field are supported with both implicit and explicit $and
|
|
1835
|
+
- Empty arrays in $in/$nin will return no results
|
|
1836
|
+
- A non-empty array is required for $all operator
|
|
1837
|
+
- Only logical operators ($and, $or, $not) can be used at the top level
|
|
1838
|
+
- All other operators must be used within a field condition
|
|
1839
|
+
Valid: { "field": { "$gt": 100 } }
|
|
1840
|
+
Valid: { "$and": [...] }
|
|
1841
|
+
Invalid: { "$gt": 100 }
|
|
1842
|
+
- Logical operators must contain field conditions, not direct operators
|
|
1843
|
+
Valid: { "$and": [{ "field": { "$gt": 100 } }] }
|
|
1844
|
+
Invalid: { "$and": [{ "$gt": 100 }] }
|
|
1845
|
+
- $not operator:
|
|
1846
|
+
- Must be an object
|
|
1847
|
+
- Cannot be empty
|
|
1848
|
+
- Can be used at field level or top level
|
|
1849
|
+
- Valid: { "$not": { "field": "value" } }
|
|
1850
|
+
- Valid: { "field": { "$not": { "$eq": "value" } } }
|
|
1851
|
+
- Other logical operators ($and, $or):
|
|
1852
|
+
- Can only be used at top level or nested within other logical operators
|
|
1853
|
+
- Can not be used on a field level, or be nested inside a field
|
|
1854
|
+
- Can not be used inside an operator
|
|
1855
|
+
- Valid: { "$and": [{ "field": { "$gt": 100 } }] }
|
|
1856
|
+
- Valid: { "$or": [{ "$and": [{ "field": { "$gt": 100 } }] }] }
|
|
1857
|
+
- Invalid: { "field": { "$and": [{ "$gt": 100 }] } }
|
|
1858
|
+
- Invalid: { "field": { "$or": [{ "$gt": 100 }] } }
|
|
1859
|
+
- Invalid: { "field": { "$gt": { "$and": [{...}] } } }
|
|
1860
|
+
|
|
1861
|
+
Example Complex Query:
|
|
1862
|
+
{
|
|
1863
|
+
"$and": [
|
|
1864
|
+
{ "category": { "$in": ["electronics", "computers"] } },
|
|
1865
|
+
{ "price": { "$gte": 100, "$lte": 1000 } },
|
|
1866
|
+
{ "tags": { "$all": ["premium"] } },
|
|
1867
|
+
{ "rating": { "$exists": true, "$gt": 4 } },
|
|
1868
|
+
{ "$or": [
|
|
1869
|
+
{ "stock": { "$gt": 0 } },
|
|
1870
|
+
{ "preorder": true }
|
|
1871
|
+
]}
|
|
1872
|
+
]
|
|
1873
|
+
}`;
|
|
1874
|
+
var CHROMA_PROMPT = `When querying Chroma, you can ONLY use the operators listed below. Any other operators will be rejected.
|
|
1875
|
+
Important: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results.
|
|
1876
|
+
If a user tries to give an explicit operator that is not supported, reject the filter entirely and let them know that the operator is not supported.
|
|
1877
|
+
|
|
1878
|
+
Basic Comparison Operators:
|
|
1879
|
+
- $eq: Exact match (default when using field: value)
|
|
1880
|
+
Example: { "category": "electronics" }
|
|
1881
|
+
- $ne: Not equal
|
|
1882
|
+
Example: { "category": { "$ne": "electronics" } }
|
|
1883
|
+
- $gt: Greater than
|
|
1884
|
+
Example: { "price": { "$gt": 100 } }
|
|
1885
|
+
- $gte: Greater than or equal
|
|
1886
|
+
Example: { "price": { "$gte": 100 } }
|
|
1887
|
+
- $lt: Less than
|
|
1888
|
+
Example: { "price": { "$lt": 100 } }
|
|
1889
|
+
- $lte: Less than or equal
|
|
1890
|
+
Example: { "price": { "$lte": 100 } }
|
|
1891
|
+
|
|
1892
|
+
Array Operators:
|
|
1893
|
+
- $in: Match any value in array
|
|
1894
|
+
Example: { "category": { "$in": ["electronics", "books"] } }
|
|
1895
|
+
- $nin: Does not match any value in array
|
|
1896
|
+
Example: { "category": { "$nin": ["electronics", "books"] } }
|
|
1897
|
+
|
|
1898
|
+
Logical Operators:
|
|
1899
|
+
- $and: Logical AND
|
|
1900
|
+
Example: { "$and": [{ "price": { "$gt": 100 } }, { "category": "electronics" }] }
|
|
1901
|
+
- $or: Logical OR
|
|
1902
|
+
Example: { "$or": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
|
|
1903
|
+
|
|
1904
|
+
Restrictions:
|
|
1905
|
+
- Regex patterns are not supported
|
|
1906
|
+
- Element operators are not supported
|
|
1907
|
+
- Only $and and $or logical operators are supported
|
|
1908
|
+
- Nested fields are supported using dot notation
|
|
1909
|
+
- Multiple conditions on the same field are supported with both implicit and explicit $and
|
|
1910
|
+
- Empty arrays in $in/$nin will return no results
|
|
1911
|
+
- If multiple top-level fields exist, they're wrapped in $and
|
|
1912
|
+
- Only logical operators ($and, $or) can be used at the top level
|
|
1913
|
+
- All other operators must be used within a field condition
|
|
1914
|
+
Valid: { "field": { "$gt": 100 } }
|
|
1915
|
+
Valid: { "$and": [...] }
|
|
1916
|
+
Invalid: { "$gt": 100 }
|
|
1917
|
+
Invalid: { "$in": [...] }
|
|
1918
|
+
- Logical operators must contain field conditions, not direct operators
|
|
1919
|
+
Valid: { "$and": [{ "field": { "$gt": 100 } }] }
|
|
1920
|
+
Invalid: { "$and": [{ "$gt": 100 }] }
|
|
1921
|
+
- Logical operators ($and, $or):
|
|
1922
|
+
- Can only be used at top level or nested within other logical operators
|
|
1923
|
+
- Can not be used on a field level, or be nested inside a field
|
|
1924
|
+
- Can not be used inside an operator
|
|
1925
|
+
- Valid: { "$and": [{ "field": { "$gt": 100 } }] }
|
|
1926
|
+
- Valid: { "$or": [{ "$and": [{ "field": { "$gt": 100 } }] }] }
|
|
1927
|
+
- Invalid: { "field": { "$and": [{ "$gt": 100 }] } }
|
|
1928
|
+
- Invalid: { "field": { "$or": [{ "$gt": 100 }] } }
|
|
1929
|
+
- Invalid: { "field": { "$gt": { "$and": [{...}] } } }
|
|
1930
|
+
Example Complex Query:
|
|
1931
|
+
{
|
|
1932
|
+
"$and": [
|
|
1933
|
+
{ "category": { "$in": ["electronics", "computers"] } },
|
|
1934
|
+
{ "price": { "$gte": 100, "$lte": 1000 } },
|
|
1935
|
+
{ "$or": [
|
|
1936
|
+
{ "inStock": true },
|
|
1937
|
+
{ "preorder": true }
|
|
1938
|
+
]}
|
|
1939
|
+
]
|
|
1940
|
+
}`;
|
|
1941
|
+
var LIBSQL_PROMPT = `When querying LibSQL Vector, you can ONLY use the operators listed below. Any other operators will be rejected.
|
|
1942
|
+
Important: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results.
|
|
1943
|
+
If a user tries to give an explicit operator that is not supported, reject the filter entirely and let them know that the operator is not supported.
|
|
1944
|
+
|
|
1945
|
+
Basic Comparison Operators:
|
|
1946
|
+
- $eq: Exact match (default when using field: value)
|
|
1947
|
+
Example: { "category": "electronics" }
|
|
1948
|
+
- $ne: Not equal
|
|
1949
|
+
Example: { "category": { "$ne": "electronics" } }
|
|
1950
|
+
- $gt: Greater than
|
|
1951
|
+
Example: { "price": { "$gt": 100 } }
|
|
1952
|
+
- $gte: Greater than or equal
|
|
1953
|
+
Example: { "price": { "$gte": 100 } }
|
|
1954
|
+
- $lt: Less than
|
|
1955
|
+
Example: { "price": { "$lt": 100 } }
|
|
1956
|
+
- $lte: Less than or equal
|
|
1957
|
+
Example: { "price": { "$lte": 100 } }
|
|
1958
|
+
|
|
1959
|
+
Array Operators:
|
|
1960
|
+
- $in: Match any value in array
|
|
1961
|
+
Example: { "category": { "$in": ["electronics", "books"] } }
|
|
1962
|
+
- $nin: Does not match any value in array
|
|
1963
|
+
Example: { "category": { "$nin": ["electronics", "books"] } }
|
|
1964
|
+
- $all: Match all values in array
|
|
1965
|
+
Example: { "tags": { "$all": ["premium", "sale"] } }
|
|
1966
|
+
- $elemMatch: Match array elements that meet all specified conditions
|
|
1967
|
+
Example: { "items": { "$elemMatch": { "price": { "$gt": 100 } } } }
|
|
1968
|
+
- $contains: Check if array contains value
|
|
1969
|
+
Example: { "tags": { "$contains": "premium" } }
|
|
1970
|
+
|
|
1971
|
+
Logical Operators:
|
|
1972
|
+
- $and: Logical AND (implicit when using multiple conditions)
|
|
1973
|
+
Example: { "$and": [{ "price": { "$gt": 100 } }, { "category": "electronics" }] }
|
|
1974
|
+
- $or: Logical OR
|
|
1975
|
+
Example: { "$or": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
|
|
1976
|
+
- $not: Logical NOT
|
|
1977
|
+
Example: { "$not": { "category": "electronics" } }
|
|
1978
|
+
- $nor: Logical NOR
|
|
1979
|
+
Example: { "$nor": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
|
|
1980
|
+
|
|
1981
|
+
Element Operators:
|
|
1982
|
+
- $exists: Check if field exists
|
|
1983
|
+
Example: { "rating": { "$exists": true } }
|
|
1984
|
+
|
|
1985
|
+
Special Operators:
|
|
1986
|
+
- $size: Array length check
|
|
1987
|
+
Example: { "tags": { "$size": 2 } }
|
|
1988
|
+
|
|
1989
|
+
Restrictions:
|
|
1990
|
+
- Regex patterns are not supported
|
|
1991
|
+
- Direct RegExp patterns will throw an error
|
|
1992
|
+
- Nested fields are supported using dot notation
|
|
1993
|
+
- Multiple conditions on the same field are supported with both implicit and explicit $and
|
|
1994
|
+
- Array operations work on array fields only
|
|
1995
|
+
- Basic operators handle array values as JSON strings
|
|
1996
|
+
- Empty arrays in conditions are handled gracefully
|
|
1997
|
+
- Only logical operators ($and, $or, $not, $nor) can be used at the top level
|
|
1998
|
+
- All other operators must be used within a field condition
|
|
1999
|
+
Valid: { "field": { "$gt": 100 } }
|
|
2000
|
+
Valid: { "$and": [...] }
|
|
2001
|
+
Invalid: { "$gt": 100 }
|
|
2002
|
+
Invalid: { "$contains": "value" }
|
|
2003
|
+
- Logical operators must contain field conditions, not direct operators
|
|
2004
|
+
Valid: { "$and": [{ "field": { "$gt": 100 } }] }
|
|
2005
|
+
Invalid: { "$and": [{ "$gt": 100 }] }
|
|
2006
|
+
- $not operator:
|
|
2007
|
+
- Must be an object
|
|
2008
|
+
- Cannot be empty
|
|
2009
|
+
- Can be used at field level or top level
|
|
2010
|
+
- Valid: { "$not": { "field": "value" } }
|
|
2011
|
+
- Valid: { "field": { "$not": { "$eq": "value" } } }
|
|
2012
|
+
- Other logical operators ($and, $or, $nor):
|
|
2013
|
+
- Can only be used at top level or nested within other logical operators
|
|
2014
|
+
- Can not be used on a field level, or be nested inside a field
|
|
2015
|
+
- Can not be used inside an operator
|
|
2016
|
+
- Valid: { "$and": [{ "field": { "$gt": 100 } }] }
|
|
2017
|
+
- Valid: { "$or": [{ "$and": [{ "field": { "$gt": 100 } }] }] }
|
|
2018
|
+
- Invalid: { "field": { "$and": [{ "$gt": 100 }] } }
|
|
2019
|
+
- Invalid: { "field": { "$or": [{ "$gt": 100 }] } }
|
|
2020
|
+
- Invalid: { "field": { "$gt": { "$and": [{...}] } } }
|
|
2021
|
+
- $elemMatch requires an object with conditions
|
|
2022
|
+
Valid: { "array": { "$elemMatch": { "field": "value" } } }
|
|
2023
|
+
Invalid: { "array": { "$elemMatch": "value" } }
|
|
2024
|
+
|
|
2025
|
+
Example Complex Query:
|
|
2026
|
+
{
|
|
2027
|
+
"$and": [
|
|
2028
|
+
{ "category": { "$in": ["electronics", "computers"] } },
|
|
2029
|
+
{ "price": { "$gte": 100, "$lte": 1000 } },
|
|
2030
|
+
{ "tags": { "$all": ["premium", "sale"] } },
|
|
2031
|
+
{ "items": { "$elemMatch": { "price": { "$gt": 50 }, "inStock": true } } },
|
|
2032
|
+
{ "$or": [
|
|
2033
|
+
{ "stock": { "$gt": 0 } },
|
|
2034
|
+
{ "preorder": true }
|
|
2035
|
+
]}
|
|
2036
|
+
]
|
|
2037
|
+
}`;
|
|
2038
|
+
var PGVECTOR_PROMPT = `When querying PG Vector, you can ONLY use the operators listed below. Any other operators will be rejected.
|
|
2039
|
+
Important: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results.
|
|
2040
|
+
If a user tries to give an explicit operator that is not supported, reject the filter entirely and let them know that the operator is not supported.
|
|
2041
|
+
|
|
2042
|
+
Basic Comparison Operators:
|
|
2043
|
+
- $eq: Exact match (default when using field: value)
|
|
2044
|
+
Example: { "category": "electronics" }
|
|
2045
|
+
- $ne: Not equal
|
|
2046
|
+
Example: { "category": { "$ne": "electronics" } }
|
|
2047
|
+
- $gt: Greater than
|
|
2048
|
+
Example: { "price": { "$gt": 100 } }
|
|
2049
|
+
- $gte: Greater than or equal
|
|
2050
|
+
Example: { "price": { "$gte": 100 } }
|
|
2051
|
+
- $lt: Less than
|
|
2052
|
+
Example: { "price": { "$lt": 100 } }
|
|
2053
|
+
- $lte: Less than or equal
|
|
2054
|
+
Example: { "price": { "$lte": 100 } }
|
|
2055
|
+
|
|
2056
|
+
Array Operators:
|
|
2057
|
+
- $in: Match any value in array
|
|
2058
|
+
Example: { "category": { "$in": ["electronics", "books"] } }
|
|
2059
|
+
- $nin: Does not match any value in array
|
|
2060
|
+
Example: { "category": { "$nin": ["electronics", "books"] } }
|
|
2061
|
+
- $all: Match all values in array
|
|
2062
|
+
Example: { "tags": { "$all": ["premium", "sale"] } }
|
|
2063
|
+
- $elemMatch: Match array elements that meet all specified conditions
|
|
2064
|
+
Example: { "items": { "$elemMatch": { "price": { "$gt": 100 } } } }
|
|
2065
|
+
- $contains: Check if array contains value
|
|
2066
|
+
Example: { "tags": { "$contains": "premium" } }
|
|
2067
|
+
|
|
2068
|
+
Logical Operators:
|
|
2069
|
+
- $and: Logical AND
|
|
2070
|
+
Example: { "$and": [{ "price": { "$gt": 100 } }, { "category": "electronics" }] }
|
|
2071
|
+
- $or: Logical OR
|
|
2072
|
+
Example: { "$or": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
|
|
2073
|
+
- $not: Logical NOT
|
|
2074
|
+
Example: { "$not": { "category": "electronics" } }
|
|
2075
|
+
- $nor: Logical NOR
|
|
2076
|
+
Example: { "$nor": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
|
|
2077
|
+
|
|
2078
|
+
Element Operators:
|
|
2079
|
+
- $exists: Check if field exists
|
|
2080
|
+
Example: { "rating": { "$exists": true } }
|
|
2081
|
+
|
|
2082
|
+
Special Operators:
|
|
2083
|
+
- $size: Array length check
|
|
2084
|
+
Example: { "tags": { "$size": 2 } }
|
|
2085
|
+
- $regex: Pattern matching (PostgreSQL regex syntax)
|
|
2086
|
+
Example: { "name": { "$regex": "^iphone" } }
|
|
2087
|
+
- $options: Regex options (used with $regex)
|
|
2088
|
+
Example: { "name": { "$regex": "iphone", "$options": "i" } }
|
|
2089
|
+
|
|
2090
|
+
Restrictions:
|
|
2091
|
+
- Direct RegExp patterns are supported
|
|
2092
|
+
- Nested fields are supported using dot notation
|
|
2093
|
+
- Multiple conditions on the same field are supported with both implicit and explicit $and
|
|
2094
|
+
- Array operations work on array fields only
|
|
2095
|
+
- Regex patterns must follow PostgreSQL syntax
|
|
2096
|
+
- Empty arrays in conditions are handled gracefully
|
|
2097
|
+
- Only logical operators ($and, $or, $not, $nor) can be used at the top level
|
|
2098
|
+
- All other operators must be used within a field condition
|
|
2099
|
+
Valid: { "field": { "$gt": 100 } }
|
|
2100
|
+
Valid: { "$and": [...] }
|
|
2101
|
+
Invalid: { "$gt": 100 }
|
|
2102
|
+
Invalid: { "$regex": "pattern" }
|
|
2103
|
+
- Logical operators must contain field conditions, not direct operators
|
|
2104
|
+
Valid: { "$and": [{ "field": { "$gt": 100 } }] }
|
|
2105
|
+
Invalid: { "$and": [{ "$gt": 100 }] }
|
|
2106
|
+
- $not operator:
|
|
2107
|
+
- Must be an object
|
|
2108
|
+
- Cannot be empty
|
|
2109
|
+
- Can be used at field level or top level
|
|
2110
|
+
- Valid: { "$not": { "field": "value" } }
|
|
2111
|
+
- Valid: { "field": { "$not": { "$eq": "value" } } }
|
|
2112
|
+
- Other logical operators ($and, $or, $nor):
|
|
2113
|
+
- Can only be used at top level or nested within other logical operators
|
|
2114
|
+
- Can not be used on a field level, or be nested inside a field
|
|
2115
|
+
- Can not be used inside an operator
|
|
2116
|
+
- Valid: { "$and": [{ "field": { "$gt": 100 } }] }
|
|
2117
|
+
- Valid: { "$or": [{ "$and": [{ "field": { "$gt": 100 } }] }] }
|
|
2118
|
+
- Invalid: { "field": { "$and": [{ "$gt": 100 }] } }
|
|
2119
|
+
- Invalid: { "field": { "$or": [{ "$gt": 100 }] } }
|
|
2120
|
+
- Invalid: { "field": { "$gt": { "$and": [{...}] } } }
|
|
2121
|
+
- $elemMatch requires an object with conditions
|
|
2122
|
+
Valid: { "array": { "$elemMatch": { "field": "value" } } }
|
|
2123
|
+
Invalid: { "array": { "$elemMatch": "value" } }
|
|
2124
|
+
|
|
2125
|
+
Example Complex Query:
|
|
2126
|
+
{
|
|
2127
|
+
"$and": [
|
|
2128
|
+
{ "category": { "$in": ["electronics", "computers"] } },
|
|
2129
|
+
{ "price": { "$gte": 100, "$lte": 1000 } },
|
|
2130
|
+
{ "tags": { "$all": ["premium", "sale"] } },
|
|
2131
|
+
{ "items": { "$elemMatch": { "price": { "$gt": 50 }, "inStock": true } } },
|
|
2132
|
+
{ "$or": [
|
|
2133
|
+
{ "name": { "$regex": "^iphone", "$options": "i" } },
|
|
2134
|
+
{ "description": { "$regex": ".*apple.*" } }
|
|
2135
|
+
]}
|
|
2136
|
+
]
|
|
2137
|
+
}`;
|
|
2138
|
+
var PINECONE_PROMPT = `When querying Pinecone, you can ONLY use the operators listed below. Any other operators will be rejected.
|
|
2139
|
+
Important: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results.
|
|
2140
|
+
If a user tries to give an explicit operator that is not supported, reject the filter entirely and let them know that the operator is not supported.
|
|
2141
|
+
|
|
2142
|
+
Basic Comparison Operators:
|
|
2143
|
+
- $eq: Exact match (default when using field: value)
|
|
2144
|
+
Example: { "category": "electronics" }
|
|
2145
|
+
- $ne: Not equal
|
|
2146
|
+
Example: { "category": { "$ne": "electronics" } }
|
|
2147
|
+
- $gt: Greater than
|
|
2148
|
+
Example: { "price": { "$gt": 100 } }
|
|
2149
|
+
- $gte: Greater than or equal
|
|
2150
|
+
Example: { "price": { "$gte": 100 } }
|
|
2151
|
+
- $lt: Less than
|
|
2152
|
+
Example: { "price": { "$lt": 100 } }
|
|
2153
|
+
- $lte: Less than or equal
|
|
2154
|
+
Example: { "price": { "$lte": 100 } }
|
|
2155
|
+
|
|
2156
|
+
Array Operators:
|
|
2157
|
+
- $in: Match any value in array
|
|
2158
|
+
Example: { "category": { "$in": ["electronics", "books"] } }
|
|
2159
|
+
- $nin: Does not match any value in array
|
|
2160
|
+
Example: { "category": { "$nin": ["electronics", "books"] } }
|
|
2161
|
+
- $all: Match all values in array
|
|
2162
|
+
Example: { "tags": { "$all": ["premium", "sale"] } }
|
|
2163
|
+
|
|
2164
|
+
Logical Operators:
|
|
2165
|
+
- $and: Logical AND (can be implicit or explicit)
|
|
2166
|
+
Implicit Example: { "price": { "$gt": 100 }, "category": "electronics" }
|
|
2167
|
+
Explicit Example: { "$and": [{ "price": { "$gt": 100 } }, { "category": "electronics" }] }
|
|
2168
|
+
- $or: Logical OR
|
|
2169
|
+
Example: { "$or": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
|
|
2170
|
+
|
|
2171
|
+
Element Operators:
|
|
2172
|
+
- $exists: Check if field exists
|
|
2173
|
+
Example: { "rating": { "$exists": true } }
|
|
2174
|
+
|
|
2175
|
+
Restrictions:
|
|
2176
|
+
- Regex patterns are not supported
|
|
2177
|
+
- Only $and and $or logical operators are supported at the top level
|
|
2178
|
+
- Empty arrays in $in/$nin will return no results
|
|
2179
|
+
- A non-empty array is required for $all operator
|
|
2180
|
+
- Nested fields are supported using dot notation
|
|
2181
|
+
- Multiple conditions on the same field are supported with both implicit and explicit $and
|
|
2182
|
+
- At least one key-value pair is required in filter object
|
|
2183
|
+
- Empty objects and undefined values are treated as no filter
|
|
2184
|
+
- Invalid types in comparison operators will throw errors
|
|
2185
|
+
- All non-logical operators must be used within a field condition
|
|
2186
|
+
Valid: { "field": { "$gt": 100 } }
|
|
2187
|
+
Valid: { "$and": [...] }
|
|
2188
|
+
Invalid: { "$gt": 100 }
|
|
2189
|
+
- Logical operators must contain field conditions, not direct operators
|
|
2190
|
+
Valid: { "$and": [{ "field": { "$gt": 100 } }] }
|
|
2191
|
+
Invalid: { "$and": [{ "$gt": 100 }] }
|
|
2192
|
+
- Logical operators ($and, $or):
|
|
2193
|
+
- Can only be used at top level or nested within other logical operators
|
|
2194
|
+
- Can not be used on a field level, or be nested inside a field
|
|
2195
|
+
- Can not be used inside an operator
|
|
2196
|
+
- Valid: { "$and": [{ "field": { "$gt": 100 } }] }
|
|
2197
|
+
- Valid: { "$or": [{ "$and": [{ "field": { "$gt": 100 } }] }] }
|
|
2198
|
+
- Invalid: { "field": { "$and": [{ "$gt": 100 }] } }
|
|
2199
|
+
- Invalid: { "field": { "$or": [{ "$gt": 100 }] } }
|
|
2200
|
+
- Invalid: { "field": { "$gt": { "$and": [{...}] } } }
|
|
2201
|
+
Example Complex Query:
|
|
2202
|
+
{
|
|
2203
|
+
"$and": [
|
|
2204
|
+
{ "category": { "$in": ["electronics", "computers"] } },
|
|
2205
|
+
{ "price": { "$gte": 100, "$lte": 1000 } },
|
|
2206
|
+
{ "tags": { "$all": ["premium", "sale"] } },
|
|
2207
|
+
{ "rating": { "$exists": true, "$gt": 4 } },
|
|
2208
|
+
{ "$or": [
|
|
2209
|
+
{ "stock": { "$gt": 0 } },
|
|
2210
|
+
{ "preorder": true }
|
|
2211
|
+
]}
|
|
2212
|
+
]
|
|
2213
|
+
}`;
|
|
2214
|
+
var QDRANT_PROMPT = `When querying Qdrant, you can ONLY use the operators listed below. Any other operators will be rejected.
|
|
2215
|
+
Important: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results.
|
|
2216
|
+
If a user tries to give an explicit operator that is not supported, reject the filter entirely and let them know that the operator is not supported.
|
|
2217
|
+
|
|
2218
|
+
Basic Comparison Operators:
|
|
2219
|
+
- $eq: Exact match (default when using field: value)
|
|
2220
|
+
Example: { "category": "electronics" }
|
|
2221
|
+
- $ne: Not equal
|
|
2222
|
+
Example: { "category": { "$ne": "electronics" } }
|
|
2223
|
+
- $gt: Greater than
|
|
2224
|
+
Example: { "price": { "$gt": 100 } }
|
|
2225
|
+
- $gte: Greater than or equal
|
|
2226
|
+
Example: { "price": { "$gte": 100 } }
|
|
2227
|
+
- $lt: Less than
|
|
2228
|
+
Example: { "price": { "$lt": 100 } }
|
|
2229
|
+
- $lte: Less than or equal
|
|
2230
|
+
Example: { "price": { "$lte": 100 } }
|
|
2231
|
+
|
|
2232
|
+
Array Operators:
|
|
2233
|
+
- $in: Match any value in array
|
|
2234
|
+
Example: { "category": { "$in": ["electronics", "books"] } }
|
|
2235
|
+
- $nin: Does not match any value in array
|
|
2236
|
+
Example: { "category": { "$nin": ["electronics", "books"] } }
|
|
2237
|
+
|
|
2238
|
+
Logical Operators:
|
|
2239
|
+
- $and: Logical AND (implicit when using multiple conditions)
|
|
2240
|
+
Example: { "$and": [{ "price": { "$gt": 100 } }, { "category": "electronics" }] }
|
|
2241
|
+
- $or: Logical OR
|
|
2242
|
+
Example: { "$or": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
|
|
2243
|
+
- $not: Logical NOT
|
|
2244
|
+
Example: { "$not": { "category": "electronics" } }
|
|
2245
|
+
|
|
2246
|
+
Element Operators:
|
|
2247
|
+
- $exists: Check if field exists
|
|
2248
|
+
Example: { "rating": { "$exists": true } }
|
|
2249
|
+
|
|
2250
|
+
Special Operators:
|
|
2251
|
+
- $regex: Pattern matching
|
|
2252
|
+
Example: { "name": { "$regex": "iphone.*" } }
|
|
2253
|
+
- $count: Array length/value count
|
|
2254
|
+
Example: { "tags": { "$count": { "$gt": 2 } } }
|
|
2255
|
+
- $geo: Geographical filters (supports radius, box, polygon)
|
|
2256
|
+
Example: {
|
|
2257
|
+
"location": {
|
|
2258
|
+
"$geo": {
|
|
2259
|
+
"type": "radius",
|
|
2260
|
+
"center": { "lat": 52.5, "lon": 13.4 },
|
|
2261
|
+
"radius": 10000
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
- $hasId: Match specific document IDs
|
|
2266
|
+
Example: { "$hasId": ["doc1", "doc2"] }
|
|
2267
|
+
- $hasVector: Check vector existence
|
|
2268
|
+
Example: { "$hasVector": "" }
|
|
2269
|
+
- $datetime: RFC 3339 datetime range
|
|
2270
|
+
Example: {
|
|
2271
|
+
"created_at": {
|
|
2272
|
+
"$datetime": {
|
|
2273
|
+
"range": {
|
|
2274
|
+
"gt": "2024-01-01T00:00:00Z",
|
|
2275
|
+
"lt": "2024-12-31T23:59:59Z"
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
- $null: Check for null values
|
|
2281
|
+
Example: { "field": { "$null": true } }
|
|
2282
|
+
- $empty: Check for empty values
|
|
2283
|
+
Example: { "array": { "$empty": true } }
|
|
2284
|
+
- $nested: Nested object filters
|
|
2285
|
+
Example: {
|
|
2286
|
+
"items[]": {
|
|
2287
|
+
"$nested": {
|
|
2288
|
+
"price": { "$gt": 100 },
|
|
2289
|
+
"stock": { "$gt": 0 }
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
Restrictions:
|
|
2295
|
+
- Only logical operators ($and, $or, $not) and collection operators ($hasId, $hasVector) can be used at the top level
|
|
2296
|
+
- All other operators must be used within a field condition
|
|
2297
|
+
Valid: { "field": { "$gt": 100 } }
|
|
2298
|
+
Valid: { "$and": [...] }
|
|
2299
|
+
Valid: { "$hasId": [...] }
|
|
2300
|
+
Invalid: { "$gt": 100 }
|
|
2301
|
+
- Nested fields are supported using dot notation
|
|
2302
|
+
- Array fields with nested objects use [] suffix: "items[]"
|
|
2303
|
+
- Geo filtering requires specific format for radius, box, or polygon
|
|
2304
|
+
- Datetime values must be in RFC 3339 format
|
|
2305
|
+
- Empty arrays in conditions are handled as empty values
|
|
2306
|
+
- Null values are handled with $null operator
|
|
2307
|
+
- Empty values are handled with $empty operator
|
|
2308
|
+
- $regex uses standard regex syntax
|
|
2309
|
+
- $count can only be used with numeric comparison operators
|
|
2310
|
+
- $nested requires an object with conditions
|
|
2311
|
+
- Logical operators must contain field conditions, not direct operators
|
|
2312
|
+
Valid: { "$and": [{ "field": { "$gt": 100 } }] }
|
|
2313
|
+
Invalid: { "$and": [{ "$gt": 100 }] }
|
|
2314
|
+
- $not operator:
|
|
2315
|
+
- Must be an object
|
|
2316
|
+
- Cannot be empty
|
|
2317
|
+
- Can be used at field level or top level
|
|
2318
|
+
- Valid: { "$not": { "field": "value" } }
|
|
2319
|
+
- Valid: { "field": { "$not": { "$eq": "value" } } }
|
|
2320
|
+
- Other logical operators ($and, $or):
|
|
2321
|
+
- Can only be used at top level or nested within other logical operators
|
|
2322
|
+
- Can not be used on a field level, or be nested inside a field
|
|
2323
|
+
- Can not be used inside an operator
|
|
2324
|
+
- Valid: { "$and": [{ "field": { "$gt": 100 } }] }
|
|
2325
|
+
- Valid: { "$or": [{ "$and": [{ "field": { "$gt": 100 } }] }] }
|
|
2326
|
+
- Invalid: { "field": { "$and": [{ "$gt": 100 }] } }
|
|
2327
|
+
- Invalid: { "field": { "$or": [{ "$gt": 100 }] } }
|
|
2328
|
+
- Invalid: { "field": { "$gt": { "$and": [{...}] } } }
|
|
2329
|
+
Example Complex Query:
|
|
2330
|
+
{
|
|
2331
|
+
"$and": [
|
|
2332
|
+
{ "category": { "$in": ["electronics"] } },
|
|
2333
|
+
{ "price": { "$gt": 100 } },
|
|
2334
|
+
{ "location": {
|
|
2335
|
+
"$geo": {
|
|
2336
|
+
"type": "radius",
|
|
2337
|
+
"center": { "lat": 52.5, "lon": 13.4 },
|
|
2338
|
+
"radius": 5000
|
|
2339
|
+
}
|
|
2340
|
+
}},
|
|
2341
|
+
{ "items[]": {
|
|
2342
|
+
"$nested": {
|
|
2343
|
+
"price": { "$gt": 50 },
|
|
2344
|
+
"stock": { "$gt": 0 }
|
|
2345
|
+
}
|
|
2346
|
+
}},
|
|
2347
|
+
{ "created_at": {
|
|
2348
|
+
"$datetime": {
|
|
2349
|
+
"range": {
|
|
2350
|
+
"gt": "2024-01-01T00:00:00Z"
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
}},
|
|
2354
|
+
{ "$or": [
|
|
2355
|
+
{ "status": { "$ne": "discontinued" } },
|
|
2356
|
+
{ "clearance": true }
|
|
2357
|
+
]}
|
|
2358
|
+
]
|
|
2359
|
+
}`;
|
|
2360
|
+
var UPSTASH_PROMPT = `When querying Upstash Vector, you can ONLY use the operators listed below. Any other operators will be rejected.
|
|
2361
|
+
Important: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results.
|
|
2362
|
+
If a user tries to give an explicit operator that is not supported, reject the filter entirely and let them know that the operator is not supported.
|
|
2363
|
+
|
|
2364
|
+
Basic Comparison Operators:
|
|
2365
|
+
- $eq: Exact match (default when using field: value)
|
|
2366
|
+
Example: { "category": "electronics" } or { "category": { "$eq": "electronics" } }
|
|
2367
|
+
- $ne: Not equal
|
|
2368
|
+
Example: { "category": { "$ne": "electronics" } }
|
|
2369
|
+
- $gt: Greater than
|
|
2370
|
+
Example: { "price": { "$gt": 100 } }
|
|
2371
|
+
- $gte: Greater than or equal
|
|
2372
|
+
Example: { "price": { "$gte": 100 } }
|
|
2373
|
+
- $lt: Less than
|
|
2374
|
+
Example: { "price": { "$lt": 100 } }
|
|
2375
|
+
- $lte: Less than or equal
|
|
2376
|
+
Example: { "price": { "$lte": 100 } }
|
|
2377
|
+
|
|
2378
|
+
Array Operators:
|
|
2379
|
+
- $in: Match any value in array
|
|
2380
|
+
Example: { "category": { "$in": ["electronics", "books"] } }
|
|
2381
|
+
- $nin: Does not match any value in array
|
|
2382
|
+
Example: { "category": { "$nin": ["electronics", "books"] } }
|
|
2383
|
+
- $all: Matches all values in array
|
|
2384
|
+
Example: { "tags": { "$all": ["premium", "new"] } }
|
|
2385
|
+
|
|
2386
|
+
Logical Operators:
|
|
2387
|
+
- $and: Logical AND (implicit when using multiple conditions)
|
|
2388
|
+
Example: { "$and": [{ "price": { "$gt": 100 } }, { "category": "electronics" }] }
|
|
2389
|
+
- $or: Logical OR
|
|
2390
|
+
Example: { "$or": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
|
|
2391
|
+
- $not: Logical NOT
|
|
2392
|
+
Example: { "$not": { "category": "electronics" } }
|
|
2393
|
+
- $nor: Logical NOR
|
|
2394
|
+
Example: { "$nor": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
|
|
2395
|
+
|
|
2396
|
+
Element Operators:
|
|
2397
|
+
- $exists: Check if field exists
|
|
2398
|
+
Example: { "rating": { "$exists": true } }
|
|
2399
|
+
|
|
2400
|
+
Special Operators:
|
|
2401
|
+
- $regex: Pattern matching using glob syntax (only as operator, not direct RegExp)
|
|
2402
|
+
Example: { "name": { "$regex": "iphone*" } }
|
|
2403
|
+
- $contains: Check if array/string contains value
|
|
2404
|
+
Example: { "tags": { "$contains": "premium" } }
|
|
2405
|
+
|
|
2406
|
+
Restrictions:
|
|
2407
|
+
- Null/undefined values are not supported in any operator
|
|
2408
|
+
- Empty arrays are only supported in $in/$nin operators
|
|
2409
|
+
- Direct RegExp patterns are not supported, use $regex with glob syntax
|
|
2410
|
+
- Nested fields are supported using dot notation
|
|
2411
|
+
- Multiple conditions on same field are combined with AND
|
|
2412
|
+
- String values with quotes are automatically escaped
|
|
2413
|
+
- Only logical operators ($and, $or, $not, $nor) can be used at the top level
|
|
2414
|
+
- All other operators must be used within a field condition
|
|
2415
|
+
Valid: { "field": { "$gt": 100 } }
|
|
2416
|
+
Valid: { "$and": [...] }
|
|
2417
|
+
Invalid: { "$gt": 100 }
|
|
2418
|
+
- $regex uses glob syntax (*, ?) not standard regex patterns
|
|
2419
|
+
- $contains works on both arrays and string fields
|
|
2420
|
+
- Logical operators must contain field conditions, not direct operators
|
|
2421
|
+
Valid: { "$and": [{ "field": { "$gt": 100 } }] }
|
|
2422
|
+
Invalid: { "$and": [{ "$gt": 100 }] }
|
|
2423
|
+
- $not operator:
|
|
2424
|
+
- Must be an object
|
|
2425
|
+
- Cannot be empty
|
|
2426
|
+
- Can be used at field level or top level
|
|
2427
|
+
- Valid: { "$not": { "field": "value" } }
|
|
2428
|
+
- Valid: { "field": { "$not": { "$eq": "value" } } }
|
|
2429
|
+
- Other logical operators ($and, $or, $nor):
|
|
2430
|
+
- Can only be used at top level or nested within other logical operators
|
|
2431
|
+
- Can not be used on a field level, or be nested inside a field
|
|
2432
|
+
- Can not be used inside an operator
|
|
2433
|
+
- Valid: { "$and": [{ "field": { "$gt": 100 } }] }
|
|
2434
|
+
- Valid: { "$or": [{ "$and": [{ "field": { "$gt": 100 } }] }] }
|
|
2435
|
+
- Invalid: { "field": { "$and": [{ "$gt": 100 }] } }
|
|
2436
|
+
- Invalid: { "field": { "$or": [{ "$gt": 100 }] } }
|
|
2437
|
+
- Invalid: { "field": { "$gt": { "$and": [{...}] } } }
|
|
2438
|
+
Example Complex Query:
|
|
2439
|
+
{
|
|
2440
|
+
"$and": [
|
|
2441
|
+
{ "category": { "$in": ["electronics", "computers"] } },
|
|
2442
|
+
{ "price": { "$gt": 100, "$lt": 1000 } },
|
|
2443
|
+
{ "tags": { "$all": ["premium", "new"] } },
|
|
2444
|
+
{ "name": { "$regex": "iphone*" } },
|
|
2445
|
+
{ "description": { "$contains": "latest" } },
|
|
2446
|
+
{ "$or": [
|
|
2447
|
+
{ "brand": "Apple" },
|
|
2448
|
+
{ "rating": { "$gte": 4.5 } }
|
|
2449
|
+
]}
|
|
2450
|
+
]
|
|
2451
|
+
}`;
|
|
2452
|
+
var VECTORIZE_PROMPT = `When querying Vectorize, you can ONLY use the operators listed below. Any other operators will be rejected.
|
|
2453
|
+
Important: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results.
|
|
2454
|
+
If a user tries to give an explicit operator that is not supported, reject the filter entirely and let them know that the operator is not supported.
|
|
2455
|
+
|
|
2456
|
+
Basic Comparison Operators:
|
|
2457
|
+
- $eq: Exact match (default when using field: value)
|
|
2458
|
+
Example: { "category": "electronics" }
|
|
2459
|
+
- $ne: Not equal
|
|
2460
|
+
Example: { "category": { "$ne": "electronics" } }
|
|
2461
|
+
- $gt: Greater than
|
|
2462
|
+
Example: { "price": { "$gt": 100 } }
|
|
2463
|
+
- $gte: Greater than or equal
|
|
2464
|
+
Example: { "price": { "$gte": 100 } }
|
|
2465
|
+
- $lt: Less than
|
|
2466
|
+
Example: { "price": { "$lt": 100 } }
|
|
2467
|
+
- $lte: Less than or equal
|
|
2468
|
+
Example: { "price": { "$lte": 100 } }
|
|
2469
|
+
|
|
2470
|
+
Array Operators:
|
|
2471
|
+
- $in: Match any value in array
|
|
2472
|
+
Example: { "category": { "$in": ["electronics", "books"] } }
|
|
2473
|
+
- $nin: Does not match any value in array
|
|
2474
|
+
Example: { "category": { "$nin": ["electronics", "books"] } }
|
|
2475
|
+
|
|
2476
|
+
Restrictions:
|
|
2477
|
+
- Regex patterns are not supported
|
|
2478
|
+
- Logical operators are not supported
|
|
2479
|
+
- Element operators are not supported
|
|
2480
|
+
- Fields must have a flat structure, as nested fields are not supported
|
|
2481
|
+
- Multiple conditions on the same field are supported
|
|
2482
|
+
- Empty arrays in $in/$nin will return no results
|
|
2483
|
+
- Filter keys cannot be longer than 512 characters
|
|
2484
|
+
- Filter keys cannot contain invalid characters ($, ", empty)
|
|
2485
|
+
- Filter size is limited to prevent oversized queries
|
|
2486
|
+
- Invalid types in operators return no results instead of throwing errors
|
|
2487
|
+
- Empty objects are accepted in filters
|
|
2488
|
+
- Metadata must use flat structure with dot notation (no nested objects)
|
|
2489
|
+
- Must explicitly create metadata indexes for filterable fields (limit 10 per index)
|
|
2490
|
+
- Can only effectively filter on indexed metadata fields
|
|
2491
|
+
- Metadata values can be strings, numbers, booleans, or homogeneous arrays
|
|
2492
|
+
- No operators can be used at the top level (no logical operators supported)
|
|
2493
|
+
- All operators must be used within a field condition
|
|
2494
|
+
Valid: { "field": { "$gt": 100 } }
|
|
2495
|
+
Invalid: { "$gt": 100 }
|
|
2496
|
+
Invalid: { "$in": [...] }
|
|
2497
|
+
|
|
2498
|
+
Example Complex Query:
|
|
2499
|
+
{
|
|
2500
|
+
"category": { "$in": ["electronics", "computers"] },
|
|
2501
|
+
"price": { "$gte": 100, "$lte": 1000 },
|
|
2502
|
+
"inStock": true
|
|
2503
|
+
}`;
|
|
2504
|
+
|
|
2505
|
+
export { ASTRA_PROMPT, CHROMA_PROMPT, GraphRAG, LIBSQL_PROMPT, MDocument, PGVECTOR_PROMPT, PINECONE_PROMPT, QDRANT_PROMPT, UPSTASH_PROMPT, VECTORIZE_PROMPT, createDocumentChunkerTool, createGraphRAGTool, createVectorQueryTool, defaultFilter, defaultGraphRagDescription, defaultTopK, defaultVectorQueryDescription, rerank };
|