@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
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { Document } from 'llamaindex';
|
|
2
|
+
import { parse } from 'node-html-better-parser';
|
|
3
|
+
|
|
4
|
+
import { RecursiveCharacterTransformer } from './character';
|
|
5
|
+
|
|
6
|
+
interface ElementType {
|
|
7
|
+
url: string;
|
|
8
|
+
xpath: string;
|
|
9
|
+
content: string;
|
|
10
|
+
metadata: Record<string, string>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class HTMLHeaderTransformer {
|
|
14
|
+
private headersToSplitOn: [string, string][];
|
|
15
|
+
private returnEachElement: boolean;
|
|
16
|
+
|
|
17
|
+
constructor(headersToSplitOn: [string, string][], returnEachElement: boolean = false) {
|
|
18
|
+
this.returnEachElement = returnEachElement;
|
|
19
|
+
this.headersToSplitOn = [...headersToSplitOn].sort();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
splitText({ text }: { text: string }): Document[] {
|
|
23
|
+
const root = parse(text);
|
|
24
|
+
|
|
25
|
+
const headerFilter = this.headersToSplitOn.map(([header]) => header);
|
|
26
|
+
const headerMapping = Object.fromEntries(this.headersToSplitOn);
|
|
27
|
+
|
|
28
|
+
const elements: ElementType[] = [];
|
|
29
|
+
const headers = root.querySelectorAll(headerFilter.join(','));
|
|
30
|
+
|
|
31
|
+
headers.forEach(header => {
|
|
32
|
+
let content = '';
|
|
33
|
+
const parentNode = header.parentNode;
|
|
34
|
+
|
|
35
|
+
if (parentNode && parentNode.childNodes) {
|
|
36
|
+
let foundHeader = false;
|
|
37
|
+
for (const node of parentNode.childNodes) {
|
|
38
|
+
// Start collecting content after we find our header
|
|
39
|
+
if (node === header) {
|
|
40
|
+
foundHeader = true;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// If we found our header and hit another header, stop
|
|
45
|
+
// @ts-expect-error - node.tagName is not defined on type Node
|
|
46
|
+
if (foundHeader && node.tagName && headerFilter.includes(node.tagName.toLowerCase())) {
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Collect content between headers
|
|
51
|
+
if (foundHeader) {
|
|
52
|
+
content += this.getTextContent(node) + ' ';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
elements.push({
|
|
58
|
+
url: text,
|
|
59
|
+
xpath: this.getXPath(header),
|
|
60
|
+
content: content.trim(),
|
|
61
|
+
metadata: {
|
|
62
|
+
[headerMapping?.[header.tagName.toLowerCase()]!]: header.text || '',
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return this.returnEachElement
|
|
68
|
+
? elements.map(
|
|
69
|
+
el =>
|
|
70
|
+
new Document({
|
|
71
|
+
text: el.content,
|
|
72
|
+
metadata: { ...el.metadata, xpath: el.xpath },
|
|
73
|
+
}),
|
|
74
|
+
)
|
|
75
|
+
: this.aggregateElementsToChunks(elements);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private getXPath(element: any): string {
|
|
79
|
+
if (!element) return '';
|
|
80
|
+
|
|
81
|
+
const parts: string[] = [];
|
|
82
|
+
let current = element;
|
|
83
|
+
|
|
84
|
+
while (current && current.tagName) {
|
|
85
|
+
let index = 1;
|
|
86
|
+
const parent = current.parentNode;
|
|
87
|
+
|
|
88
|
+
if (parent && parent.childNodes) {
|
|
89
|
+
// Count preceding siblings with same tag
|
|
90
|
+
for (const sibling of parent.childNodes) {
|
|
91
|
+
if (sibling === current) break;
|
|
92
|
+
if (sibling.tagName === current.tagName) {
|
|
93
|
+
index++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
parts.unshift(`${current.tagName.toLowerCase()}[${index}]`);
|
|
99
|
+
current = current.parentNode;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return '/' + parts.join('/');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private getTextContent(element: any): string {
|
|
106
|
+
if (!element) return '';
|
|
107
|
+
|
|
108
|
+
// For text nodes, return their content
|
|
109
|
+
if (!element.tagName) {
|
|
110
|
+
return element.text || '';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// For element nodes, combine their text with children's text
|
|
114
|
+
let content = element.text || '';
|
|
115
|
+
|
|
116
|
+
if (element.childNodes) {
|
|
117
|
+
for (const child of element.childNodes) {
|
|
118
|
+
const childText = this.getTextContent(child);
|
|
119
|
+
if (childText) {
|
|
120
|
+
content += ' ' + childText;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return content.trim();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private aggregateElementsToChunks(elements: ElementType[]): Document[] {
|
|
129
|
+
const aggregatedChunks: ElementType[] = [];
|
|
130
|
+
|
|
131
|
+
for (const element of elements) {
|
|
132
|
+
if (
|
|
133
|
+
aggregatedChunks.length > 0 &&
|
|
134
|
+
JSON.stringify(aggregatedChunks[aggregatedChunks.length - 1]!.metadata) === JSON.stringify(element.metadata)
|
|
135
|
+
) {
|
|
136
|
+
// If the last element has the same metadata, append content
|
|
137
|
+
aggregatedChunks[aggregatedChunks.length - 1]!.content += ' \n' + element.content;
|
|
138
|
+
} else {
|
|
139
|
+
// Otherwise, add as new element
|
|
140
|
+
aggregatedChunks.push({ ...element });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return aggregatedChunks.map(
|
|
145
|
+
chunk =>
|
|
146
|
+
new Document({
|
|
147
|
+
text: chunk.content,
|
|
148
|
+
metadata: { ...chunk.metadata, xpath: chunk.xpath },
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
createDocuments(texts: string[], metadatas?: Record<string, any>[]): Document[] {
|
|
154
|
+
const _metadatas = metadatas || Array(texts.length).fill({});
|
|
155
|
+
const documents: Document[] = [];
|
|
156
|
+
|
|
157
|
+
for (let i = 0; i < texts.length; i++) {
|
|
158
|
+
const chunks = this.splitText({ text: texts[i]! });
|
|
159
|
+
for (const chunk of chunks) {
|
|
160
|
+
const metadata = { ...(_metadatas[i] || {}) };
|
|
161
|
+
const chunkMetadata = chunk.metadata;
|
|
162
|
+
|
|
163
|
+
if (chunkMetadata) {
|
|
164
|
+
for (const [key, value] of Object.entries(chunkMetadata || {})) {
|
|
165
|
+
if (value === '#TITLE#') {
|
|
166
|
+
chunkMetadata[key] = metadata['Title'];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
documents.push(
|
|
172
|
+
new Document({
|
|
173
|
+
text: chunk.text!,
|
|
174
|
+
metadata: { ...metadata, ...chunkMetadata },
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return documents;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
transformDocuments(documents: Document[]): Document[] {
|
|
184
|
+
const texts: string[] = [];
|
|
185
|
+
const metadatas: Record<string, any>[] = [];
|
|
186
|
+
|
|
187
|
+
for (const doc of documents) {
|
|
188
|
+
texts.push(doc.text);
|
|
189
|
+
metadatas.push(doc.metadata);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return this.createDocuments(texts, metadatas);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export class HTMLSectionTransformer {
|
|
197
|
+
private headersToSplitOn: Record<string, string>;
|
|
198
|
+
private options: Record<string, any>;
|
|
199
|
+
|
|
200
|
+
constructor(headersToSplitOn: [string, string][], options: Record<string, any> = {}) {
|
|
201
|
+
this.headersToSplitOn = Object.fromEntries(headersToSplitOn.map(([tag, name]) => [tag.toLowerCase(), name]));
|
|
202
|
+
this.options = options;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
splitText(text: string): Document[] {
|
|
206
|
+
const sections = this.splitHtmlByHeaders(text);
|
|
207
|
+
|
|
208
|
+
return sections.map(
|
|
209
|
+
section =>
|
|
210
|
+
new Document({
|
|
211
|
+
text: section.content,
|
|
212
|
+
metadata: {
|
|
213
|
+
[this.headersToSplitOn[section.tagName.toLowerCase()]!]: section.header,
|
|
214
|
+
xpath: section.xpath,
|
|
215
|
+
},
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private getXPath(element: any): string {
|
|
221
|
+
const parts: string[] = [];
|
|
222
|
+
let current = element;
|
|
223
|
+
|
|
224
|
+
while (current && current.nodeType === 1) {
|
|
225
|
+
let index = 1;
|
|
226
|
+
let sibling = current.previousSibling;
|
|
227
|
+
|
|
228
|
+
while (sibling) {
|
|
229
|
+
if (sibling.nodeType === 1 && sibling.tagName === current.tagName) {
|
|
230
|
+
index++;
|
|
231
|
+
}
|
|
232
|
+
sibling = sibling.previousSibling;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (current.tagName) {
|
|
236
|
+
parts.unshift(`${current.tagName.toLowerCase()}[${index}]`);
|
|
237
|
+
}
|
|
238
|
+
current = current.parentNode;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return '/' + parts.join('/');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private splitHtmlByHeaders(htmlDoc: string): Array<{
|
|
245
|
+
header: string;
|
|
246
|
+
content: string;
|
|
247
|
+
tagName: string;
|
|
248
|
+
xpath: string;
|
|
249
|
+
}> {
|
|
250
|
+
const sections: Array<{
|
|
251
|
+
header: string;
|
|
252
|
+
content: string;
|
|
253
|
+
tagName: string;
|
|
254
|
+
xpath: string;
|
|
255
|
+
}> = [];
|
|
256
|
+
|
|
257
|
+
const root = parse(htmlDoc);
|
|
258
|
+
const headers = Object.keys(this.headersToSplitOn);
|
|
259
|
+
const headerElements = root.querySelectorAll(headers.join(','));
|
|
260
|
+
|
|
261
|
+
headerElements.forEach((headerElement, index) => {
|
|
262
|
+
const header = headerElement.text?.trim() || '';
|
|
263
|
+
const tagName = headerElement.tagName;
|
|
264
|
+
const xpath = this.getXPath(headerElement);
|
|
265
|
+
let content = '';
|
|
266
|
+
|
|
267
|
+
// @ts-expect-error - nextElementSibling is not defined on type Element
|
|
268
|
+
let currentElement = headerElement.nextElementSibling;
|
|
269
|
+
const nextHeader = headerElements[index + 1];
|
|
270
|
+
|
|
271
|
+
while (currentElement && (!nextHeader || currentElement !== nextHeader)) {
|
|
272
|
+
if (currentElement.text) {
|
|
273
|
+
content += currentElement.text.trim() + ' ';
|
|
274
|
+
}
|
|
275
|
+
currentElement = currentElement.nextElementSibling;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
content = content.trim();
|
|
279
|
+
sections.push({
|
|
280
|
+
header,
|
|
281
|
+
content,
|
|
282
|
+
tagName,
|
|
283
|
+
xpath,
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
return sections;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async splitDocuments(documents: Document[]): Promise<Document[]> {
|
|
291
|
+
const texts: string[] = [];
|
|
292
|
+
const metadatas: Record<string, any>[] = [];
|
|
293
|
+
|
|
294
|
+
for (const doc of documents) {
|
|
295
|
+
texts.push(doc.text);
|
|
296
|
+
metadatas.push(doc.metadata);
|
|
297
|
+
}
|
|
298
|
+
const results = await this.createDocuments(texts, metadatas);
|
|
299
|
+
const textSplitter = new RecursiveCharacterTransformer({ options: this.options });
|
|
300
|
+
|
|
301
|
+
return textSplitter.splitDocuments(results);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
createDocuments(texts: string[], metadatas?: Record<string, any>[]): Document[] {
|
|
305
|
+
const _metadatas = metadatas || Array(texts.length).fill({});
|
|
306
|
+
const documents: Document[] = [];
|
|
307
|
+
|
|
308
|
+
for (let i = 0; i < texts.length; i++) {
|
|
309
|
+
const chunks = this.splitText(texts[i]!);
|
|
310
|
+
for (const chunk of chunks) {
|
|
311
|
+
const metadata = { ...(_metadatas[i] || {}) };
|
|
312
|
+
|
|
313
|
+
const chunkMetadata = chunk.metadata;
|
|
314
|
+
|
|
315
|
+
if (chunkMetadata) {
|
|
316
|
+
for (const [key, value] of Object.entries(chunkMetadata || {})) {
|
|
317
|
+
if (value === '#TITLE#') {
|
|
318
|
+
chunkMetadata[key] = metadata['Title'];
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
documents.push(
|
|
324
|
+
new Document({
|
|
325
|
+
text: chunk.text!,
|
|
326
|
+
metadata: { ...metadata, ...chunkMetadata },
|
|
327
|
+
}),
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return documents;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
transformDocuments(documents: Document[]): Document[] {
|
|
336
|
+
const texts: string[] = [];
|
|
337
|
+
const metadatas: Record<string, any>[] = [];
|
|
338
|
+
|
|
339
|
+
for (const doc of documents) {
|
|
340
|
+
texts.push(doc.text);
|
|
341
|
+
metadatas.push(doc.metadata);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return this.createDocuments(texts, metadatas);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { Document } from 'llamaindex';
|
|
2
|
+
|
|
3
|
+
export class RecursiveJsonTransformer {
|
|
4
|
+
private maxSize: number;
|
|
5
|
+
private minSize: number;
|
|
6
|
+
|
|
7
|
+
constructor({ maxSize = 2000, minSize }: { maxSize: number; minSize?: number }) {
|
|
8
|
+
this.maxSize = maxSize;
|
|
9
|
+
this.minSize = minSize ?? Math.max(maxSize - 200, 50);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
private static jsonSize(data: Record<string, any>): number {
|
|
13
|
+
const seen = new WeakSet();
|
|
14
|
+
|
|
15
|
+
function getStringifiableData(obj: any): any {
|
|
16
|
+
if (obj === null || typeof obj !== 'object') {
|
|
17
|
+
return obj;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (seen.has(obj)) {
|
|
21
|
+
return '[Circular]';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
seen.add(obj);
|
|
25
|
+
|
|
26
|
+
if (Array.isArray(obj)) {
|
|
27
|
+
const safeArray = [];
|
|
28
|
+
for (const item of obj) {
|
|
29
|
+
safeArray.push(getStringifiableData(item));
|
|
30
|
+
}
|
|
31
|
+
return safeArray;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const safeObj: Record<string, any> = {};
|
|
35
|
+
for (const key in obj) {
|
|
36
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
37
|
+
safeObj[key] = getStringifiableData(obj[key]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return safeObj;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const stringifiable = getStringifiableData(data);
|
|
44
|
+
return JSON.stringify(stringifiable).length;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Transform JSON data while handling circular references
|
|
49
|
+
*/
|
|
50
|
+
public transform(data: Record<string, any>): Record<string, any> {
|
|
51
|
+
const size = RecursiveJsonTransformer.jsonSize(data);
|
|
52
|
+
|
|
53
|
+
const seen = new WeakSet();
|
|
54
|
+
|
|
55
|
+
function createSafeCopy(obj: any): any {
|
|
56
|
+
if (obj === null || typeof obj !== 'object') {
|
|
57
|
+
return obj;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (seen.has(obj)) {
|
|
61
|
+
return '[Circular]';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
seen.add(obj);
|
|
65
|
+
|
|
66
|
+
if (Array.isArray(obj)) {
|
|
67
|
+
return obj.map(item => createSafeCopy(item));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const copy: Record<string, any> = {};
|
|
71
|
+
for (const key in obj) {
|
|
72
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
73
|
+
copy[key] = createSafeCopy(obj[key]);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return copy;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
size,
|
|
81
|
+
data: createSafeCopy(data),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Set a value in a nested dictionary based on the given path
|
|
87
|
+
*/
|
|
88
|
+
private static setNestedDict(d: Record<string, any>, path: string[], value: any): void {
|
|
89
|
+
let current = d;
|
|
90
|
+
for (const key of path.slice(0, -1)) {
|
|
91
|
+
current[key] = current[key] || {};
|
|
92
|
+
current = current[key];
|
|
93
|
+
}
|
|
94
|
+
current[path[path.length - 1]!] = value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Convert lists in the JSON structure to dictionaries with index-based keys
|
|
99
|
+
*/
|
|
100
|
+
private listToDictPreprocessing(data: any): any {
|
|
101
|
+
if (data && typeof data === 'object') {
|
|
102
|
+
if (Array.isArray(data)) {
|
|
103
|
+
return Object.fromEntries(data.map((item, index) => [String(index), this.listToDictPreprocessing(item)]));
|
|
104
|
+
}
|
|
105
|
+
return Object.fromEntries(Object.entries(data).map(([k, v]) => [k, this.listToDictPreprocessing(v)]));
|
|
106
|
+
}
|
|
107
|
+
return data;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Split json into maximum size dictionaries while preserving structure
|
|
112
|
+
*/
|
|
113
|
+
private jsonSplit({
|
|
114
|
+
data,
|
|
115
|
+
currentPath = [],
|
|
116
|
+
chunks = [{}],
|
|
117
|
+
}: {
|
|
118
|
+
data: Record<string, any>;
|
|
119
|
+
currentPath?: string[];
|
|
120
|
+
chunks?: Record<string, any>[];
|
|
121
|
+
}): Record<string, any>[] {
|
|
122
|
+
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
|
123
|
+
for (const [key, value] of Object.entries(data)) {
|
|
124
|
+
const newPath = [...currentPath, key];
|
|
125
|
+
const chunkSize = RecursiveJsonTransformer.jsonSize(chunks[chunks.length - 1] || {});
|
|
126
|
+
const size = RecursiveJsonTransformer.jsonSize({ [key]: value });
|
|
127
|
+
const remaining = this.maxSize - chunkSize;
|
|
128
|
+
|
|
129
|
+
if (size < remaining) {
|
|
130
|
+
// Add item to current chunk
|
|
131
|
+
RecursiveJsonTransformer.setNestedDict(chunks[chunks.length - 1] || {}, newPath, value);
|
|
132
|
+
} else {
|
|
133
|
+
if (chunkSize >= this.minSize) {
|
|
134
|
+
// Chunk is big enough, start a new chunk
|
|
135
|
+
chunks.push({});
|
|
136
|
+
}
|
|
137
|
+
// Iterate
|
|
138
|
+
this.jsonSplit({
|
|
139
|
+
data: typeof value === 'object' ? value : { [key]: value },
|
|
140
|
+
currentPath: newPath,
|
|
141
|
+
chunks,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
RecursiveJsonTransformer.setNestedDict(chunks[chunks.length - 1] || {}, currentPath, data);
|
|
147
|
+
}
|
|
148
|
+
return chunks;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Splits JSON into a list of JSON chunks
|
|
153
|
+
*/
|
|
154
|
+
splitJson({
|
|
155
|
+
jsonData,
|
|
156
|
+
convertLists = false,
|
|
157
|
+
}: {
|
|
158
|
+
jsonData: Record<string, any>;
|
|
159
|
+
convertLists?: boolean;
|
|
160
|
+
}): Record<string, any>[] {
|
|
161
|
+
const processedData = convertLists ? this.listToDictPreprocessing(jsonData) : jsonData;
|
|
162
|
+
|
|
163
|
+
const chunks = this.jsonSplit({ data: processedData });
|
|
164
|
+
|
|
165
|
+
if (Object.keys(chunks[chunks.length - 1] || {}).length === 0) {
|
|
166
|
+
chunks.pop();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return chunks;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private escapeNonAscii(obj: any): any {
|
|
173
|
+
if (typeof obj === 'string') {
|
|
174
|
+
return obj.replace(/[\u0080-\uffff]/g, char => {
|
|
175
|
+
return `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
180
|
+
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, this.escapeNonAscii(value)]));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return obj;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Splits JSON into a list of JSON formatted strings
|
|
187
|
+
*/
|
|
188
|
+
splitText({
|
|
189
|
+
jsonData,
|
|
190
|
+
convertLists = false,
|
|
191
|
+
ensureAscii = true,
|
|
192
|
+
}: {
|
|
193
|
+
jsonData: Record<string, any>;
|
|
194
|
+
convertLists?: boolean;
|
|
195
|
+
ensureAscii?: boolean;
|
|
196
|
+
}): string[] {
|
|
197
|
+
const chunks = this.splitJson({ jsonData, convertLists });
|
|
198
|
+
|
|
199
|
+
if (ensureAscii) {
|
|
200
|
+
const escapedChunks = chunks.map(chunk => this.escapeNonAscii(chunk));
|
|
201
|
+
return escapedChunks.map(chunk => JSON.stringify(chunk));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return chunks.map(chunk => JSON.stringify(chunk));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Create documents from a list of json objects
|
|
209
|
+
*/
|
|
210
|
+
createDocuments({
|
|
211
|
+
texts,
|
|
212
|
+
convertLists = false,
|
|
213
|
+
ensureAscii = true,
|
|
214
|
+
metadatas,
|
|
215
|
+
}: {
|
|
216
|
+
texts: string[];
|
|
217
|
+
convertLists?: boolean;
|
|
218
|
+
ensureAscii?: boolean;
|
|
219
|
+
metadatas?: Record<string, any>[];
|
|
220
|
+
}): Document[] {
|
|
221
|
+
const _metadatas = metadatas || Array(texts.length).fill({});
|
|
222
|
+
const documents: Document[] = [];
|
|
223
|
+
|
|
224
|
+
texts.forEach((text, i) => {
|
|
225
|
+
const chunks = this.splitText({ jsonData: JSON.parse(text), convertLists, ensureAscii });
|
|
226
|
+
chunks.forEach(chunk => {
|
|
227
|
+
const metadata = { ...(_metadatas[i] || {}) };
|
|
228
|
+
documents.push(
|
|
229
|
+
new Document({
|
|
230
|
+
text: chunk,
|
|
231
|
+
metadata,
|
|
232
|
+
}),
|
|
233
|
+
);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
return documents;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
transformDocuments({
|
|
241
|
+
ensureAscii,
|
|
242
|
+
documents,
|
|
243
|
+
convertLists,
|
|
244
|
+
}: {
|
|
245
|
+
ensureAscii?: boolean;
|
|
246
|
+
convertLists?: boolean;
|
|
247
|
+
documents: Document[];
|
|
248
|
+
}): Document[] {
|
|
249
|
+
const texts: string[] = [];
|
|
250
|
+
const metadatas: Record<string, any>[] = [];
|
|
251
|
+
|
|
252
|
+
for (const doc of documents) {
|
|
253
|
+
texts.push(doc.text);
|
|
254
|
+
metadatas.push(doc.metadata);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return this.createDocuments({
|
|
258
|
+
texts,
|
|
259
|
+
metadatas,
|
|
260
|
+
|
|
261
|
+
ensureAscii,
|
|
262
|
+
convertLists,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Language } from '../types';
|
|
2
|
+
|
|
3
|
+
import { RecursiveCharacterTransformer } from './character';
|
|
4
|
+
|
|
5
|
+
export class LatexTransformer extends RecursiveCharacterTransformer {
|
|
6
|
+
constructor(
|
|
7
|
+
options: {
|
|
8
|
+
size?: number;
|
|
9
|
+
overlap?: number;
|
|
10
|
+
lengthFunction?: (text: string) => number;
|
|
11
|
+
keepSeparator?: boolean | 'start' | 'end';
|
|
12
|
+
addStartIndex?: boolean;
|
|
13
|
+
stripWhitespace?: boolean;
|
|
14
|
+
} = {},
|
|
15
|
+
) {
|
|
16
|
+
const separators = RecursiveCharacterTransformer.getSeparatorsForLanguage(Language.LATEX);
|
|
17
|
+
super({ separators, isSeparatorRegex: true, options });
|
|
18
|
+
}
|
|
19
|
+
}
|