@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,244 @@
|
|
|
1
|
+
import { Document } from 'llamaindex';
|
|
2
|
+
|
|
3
|
+
import { Language } from '../types';
|
|
4
|
+
|
|
5
|
+
import { RecursiveCharacterTransformer } from './character';
|
|
6
|
+
|
|
7
|
+
interface LineType {
|
|
8
|
+
metadata: Record<string, string>;
|
|
9
|
+
content: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface HeaderType {
|
|
13
|
+
level: number;
|
|
14
|
+
name: string;
|
|
15
|
+
data: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class MarkdownTransformer extends RecursiveCharacterTransformer {
|
|
19
|
+
constructor(
|
|
20
|
+
options: {
|
|
21
|
+
chunkSize?: number;
|
|
22
|
+
chunkOverlap?: number;
|
|
23
|
+
lengthFunction?: (text: string) => number;
|
|
24
|
+
keepSeparator?: boolean | 'start' | 'end';
|
|
25
|
+
addStartIndex?: boolean;
|
|
26
|
+
stripWhitespace?: boolean;
|
|
27
|
+
} = {},
|
|
28
|
+
) {
|
|
29
|
+
const separators = RecursiveCharacterTransformer.getSeparatorsForLanguage(Language.MARKDOWN);
|
|
30
|
+
super({ separators, isSeparatorRegex: true, options });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class MarkdownHeaderTransformer {
|
|
35
|
+
private headersToSplitOn: [string, string][];
|
|
36
|
+
private returnEachLine: boolean;
|
|
37
|
+
private stripHeaders: boolean;
|
|
38
|
+
|
|
39
|
+
constructor(headersToSplitOn: [string, string][], returnEachLine: boolean = false, stripHeaders: boolean = true) {
|
|
40
|
+
this.headersToSplitOn = [...headersToSplitOn].sort((a, b) => b[0].length - a[0].length);
|
|
41
|
+
this.returnEachLine = returnEachLine;
|
|
42
|
+
this.stripHeaders = stripHeaders;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private aggregateLinesToChunks(lines: LineType[]): Document[] {
|
|
46
|
+
if (this.returnEachLine) {
|
|
47
|
+
return lines.flatMap(line => {
|
|
48
|
+
const contentLines = line.content.split('\n');
|
|
49
|
+
return contentLines
|
|
50
|
+
.filter(l => l.trim() !== '' || this.headersToSplitOn.some(([sep]) => l.trim().startsWith(sep)))
|
|
51
|
+
.map(
|
|
52
|
+
l =>
|
|
53
|
+
new Document({
|
|
54
|
+
text: l.trim(),
|
|
55
|
+
metadata: line.metadata,
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const aggregatedChunks: LineType[] = [];
|
|
62
|
+
|
|
63
|
+
for (const line of lines) {
|
|
64
|
+
if (
|
|
65
|
+
aggregatedChunks.length > 0 &&
|
|
66
|
+
JSON.stringify(aggregatedChunks?.[aggregatedChunks.length - 1]!.metadata) === JSON.stringify(line.metadata)
|
|
67
|
+
) {
|
|
68
|
+
const aggChunk = aggregatedChunks[aggregatedChunks.length - 1];
|
|
69
|
+
aggChunk!.content += ' \n' + line.content;
|
|
70
|
+
} else if (
|
|
71
|
+
aggregatedChunks.length > 0 &&
|
|
72
|
+
JSON.stringify(aggregatedChunks?.[aggregatedChunks.length - 1]!.metadata) !== JSON.stringify(line.metadata) &&
|
|
73
|
+
Object.keys(aggregatedChunks?.[aggregatedChunks.length - 1]!.metadata).length <
|
|
74
|
+
Object.keys(line.metadata).length &&
|
|
75
|
+
aggregatedChunks?.[aggregatedChunks.length - 1]?.content?.split('\n')?.slice(-1)[0]![0] === '#' &&
|
|
76
|
+
!this.stripHeaders
|
|
77
|
+
) {
|
|
78
|
+
if (aggregatedChunks && aggregatedChunks?.[aggregatedChunks.length - 1]) {
|
|
79
|
+
const aggChunk = aggregatedChunks[aggregatedChunks.length - 1];
|
|
80
|
+
if (aggChunk) {
|
|
81
|
+
aggChunk.content += ' \n' + line.content;
|
|
82
|
+
aggChunk.metadata = line.metadata;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
aggregatedChunks.push(line);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return aggregatedChunks.map(
|
|
91
|
+
chunk =>
|
|
92
|
+
new Document({
|
|
93
|
+
text: chunk.content,
|
|
94
|
+
metadata: chunk.metadata,
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
splitText({ text }: { text: string }): Document[] {
|
|
100
|
+
const lines = text.split('\n');
|
|
101
|
+
const linesWithMetadata: LineType[] = [];
|
|
102
|
+
let currentContent: string[] = [];
|
|
103
|
+
let currentMetadata: Record<string, string> = {};
|
|
104
|
+
const headerStack: HeaderType[] = [];
|
|
105
|
+
const initialMetadata: Record<string, string> = {};
|
|
106
|
+
|
|
107
|
+
let inCodeBlock = false;
|
|
108
|
+
let openingFence = '';
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < lines.length; i++) {
|
|
111
|
+
const line = lines[i]!;
|
|
112
|
+
const strippedLine = line.trim();
|
|
113
|
+
|
|
114
|
+
if (!inCodeBlock) {
|
|
115
|
+
if (
|
|
116
|
+
(strippedLine.startsWith('```') && strippedLine.split('```').length === 2) ||
|
|
117
|
+
strippedLine.startsWith('~~~')
|
|
118
|
+
) {
|
|
119
|
+
inCodeBlock = true;
|
|
120
|
+
openingFence = strippedLine.startsWith('```') ? '```' : '~~~';
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
if (strippedLine.startsWith(openingFence)) {
|
|
124
|
+
inCodeBlock = false;
|
|
125
|
+
openingFence = '';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (inCodeBlock) {
|
|
130
|
+
currentContent.push(line);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let headerMatched = false;
|
|
135
|
+
for (const [sep, name] of this.headersToSplitOn) {
|
|
136
|
+
if (strippedLine.startsWith(sep) && (strippedLine.length === sep.length || strippedLine[sep.length] === ' ')) {
|
|
137
|
+
headerMatched = true;
|
|
138
|
+
|
|
139
|
+
// If we have existing content, save it before processing the header
|
|
140
|
+
if (currentContent.length > 0) {
|
|
141
|
+
linesWithMetadata.push({
|
|
142
|
+
content: currentContent.join('\n'),
|
|
143
|
+
metadata: { ...currentMetadata },
|
|
144
|
+
});
|
|
145
|
+
currentContent = [];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (name !== null) {
|
|
149
|
+
const currentHeaderLevel = (sep.match(/#/g) || []).length;
|
|
150
|
+
|
|
151
|
+
// Pop headers of lower or same level
|
|
152
|
+
while (headerStack.length > 0 && headerStack?.[headerStack.length - 1]!.level >= currentHeaderLevel) {
|
|
153
|
+
const poppedHeader = headerStack.pop()!;
|
|
154
|
+
if (poppedHeader.name in initialMetadata) {
|
|
155
|
+
delete initialMetadata[poppedHeader.name];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Push current header
|
|
160
|
+
const header: HeaderType = {
|
|
161
|
+
level: currentHeaderLevel,
|
|
162
|
+
name,
|
|
163
|
+
data: strippedLine.slice(sep.length).trim(),
|
|
164
|
+
};
|
|
165
|
+
headerStack.push(header);
|
|
166
|
+
initialMetadata[name] = header.data;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Always create a separate chunk for the header
|
|
170
|
+
linesWithMetadata.push({
|
|
171
|
+
content: line,
|
|
172
|
+
metadata: { ...currentMetadata, ...initialMetadata },
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!headerMatched) {
|
|
180
|
+
if (strippedLine || this.returnEachLine) {
|
|
181
|
+
currentContent.push(line);
|
|
182
|
+
|
|
183
|
+
if (this.returnEachLine) {
|
|
184
|
+
// In returnEachLine mode, flush each non-header line immediately
|
|
185
|
+
linesWithMetadata.push({
|
|
186
|
+
content: line,
|
|
187
|
+
metadata: { ...currentMetadata },
|
|
188
|
+
});
|
|
189
|
+
currentContent = [];
|
|
190
|
+
}
|
|
191
|
+
} else if (currentContent.length > 0) {
|
|
192
|
+
linesWithMetadata.push({
|
|
193
|
+
content: currentContent.join('\n'),
|
|
194
|
+
metadata: { ...currentMetadata },
|
|
195
|
+
});
|
|
196
|
+
currentContent = [];
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
currentMetadata = { ...initialMetadata };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Handle any remaining content
|
|
204
|
+
if (currentContent.length > 0) {
|
|
205
|
+
linesWithMetadata.push({
|
|
206
|
+
content: currentContent.join('\n'),
|
|
207
|
+
metadata: currentMetadata,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return this.aggregateLinesToChunks(linesWithMetadata);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
createDocuments(texts: string[], metadatas?: Record<string, any>[]): Document[] {
|
|
215
|
+
const _metadatas = metadatas || Array(texts.length).fill({});
|
|
216
|
+
const documents: Document[] = [];
|
|
217
|
+
|
|
218
|
+
texts.forEach((text, i) => {
|
|
219
|
+
this.splitText({ text }).forEach(chunk => {
|
|
220
|
+
const metadata = { ..._metadatas[i], ...chunk.metadata };
|
|
221
|
+
documents.push(
|
|
222
|
+
new Document({
|
|
223
|
+
text: chunk.text,
|
|
224
|
+
metadata,
|
|
225
|
+
}),
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
return documents;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
transformDocuments(documents: Document[]): Document[] {
|
|
234
|
+
const texts: string[] = [];
|
|
235
|
+
const metadatas: Record<string, any>[] = [];
|
|
236
|
+
|
|
237
|
+
for (const doc of documents) {
|
|
238
|
+
texts.push(doc.text);
|
|
239
|
+
metadatas.push(doc.metadata);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return this.createDocuments(texts, metadatas);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Document } from 'llamaindex';
|
|
2
|
+
|
|
3
|
+
import type { ChunkOptions } from '../types';
|
|
4
|
+
|
|
5
|
+
import type { Transformer } from './transformer';
|
|
6
|
+
|
|
7
|
+
export abstract class TextTransformer implements Transformer {
|
|
8
|
+
protected size: number;
|
|
9
|
+
protected overlap: number;
|
|
10
|
+
protected lengthFunction: (text: string) => number;
|
|
11
|
+
protected keepSeparator: boolean | 'start' | 'end';
|
|
12
|
+
protected addStartIndex: boolean;
|
|
13
|
+
protected stripWhitespace: boolean;
|
|
14
|
+
|
|
15
|
+
constructor({
|
|
16
|
+
size = 4000,
|
|
17
|
+
overlap = 200,
|
|
18
|
+
lengthFunction = (text: string) => text.length,
|
|
19
|
+
keepSeparator = false,
|
|
20
|
+
addStartIndex = false,
|
|
21
|
+
stripWhitespace = true,
|
|
22
|
+
}: ChunkOptions) {
|
|
23
|
+
if (overlap > size) {
|
|
24
|
+
throw new Error(`Got a larger chunk overlap (${overlap}) than chunk size ` + `(${size}), should be smaller.`);
|
|
25
|
+
}
|
|
26
|
+
this.size = size;
|
|
27
|
+
this.overlap = overlap;
|
|
28
|
+
this.lengthFunction = lengthFunction;
|
|
29
|
+
this.keepSeparator = keepSeparator;
|
|
30
|
+
this.addStartIndex = addStartIndex;
|
|
31
|
+
this.stripWhitespace = stripWhitespace;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
setAddStartIndex(value: boolean): void {
|
|
35
|
+
this.addStartIndex = value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
abstract splitText({ text }: { text: string }): string[];
|
|
39
|
+
|
|
40
|
+
createDocuments(texts: string[], metadatas?: Record<string, any>[]): Document[] {
|
|
41
|
+
const _metadatas = metadatas || Array(texts.length).fill({});
|
|
42
|
+
const documents: Document[] = [];
|
|
43
|
+
|
|
44
|
+
texts.forEach((text, i) => {
|
|
45
|
+
let index = 0;
|
|
46
|
+
let previousChunkLen = 0;
|
|
47
|
+
|
|
48
|
+
this.splitText({ text }).forEach(chunk => {
|
|
49
|
+
const metadata = { ..._metadatas[i] };
|
|
50
|
+
if (this.addStartIndex) {
|
|
51
|
+
const offset = index + previousChunkLen - this.overlap;
|
|
52
|
+
index = text.indexOf(chunk, Math.max(0, offset));
|
|
53
|
+
metadata.startIndex = index;
|
|
54
|
+
previousChunkLen = chunk.length;
|
|
55
|
+
}
|
|
56
|
+
documents.push(
|
|
57
|
+
new Document({
|
|
58
|
+
text: chunk,
|
|
59
|
+
metadata,
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return documents;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
splitDocuments(documents: Document[]): Document[] {
|
|
69
|
+
const texts: string[] = [];
|
|
70
|
+
const metadatas: Record<string, any>[] = [];
|
|
71
|
+
for (const doc of documents) {
|
|
72
|
+
texts.push(doc.text);
|
|
73
|
+
metadatas.push(doc.metadata);
|
|
74
|
+
}
|
|
75
|
+
return this.createDocuments(texts, metadatas);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
transformDocuments(documents: Document[]): Document[] {
|
|
79
|
+
const texts: string[] = [];
|
|
80
|
+
const metadatas: Record<string, any>[] = [];
|
|
81
|
+
|
|
82
|
+
for (const doc of documents) {
|
|
83
|
+
texts.push(doc.text);
|
|
84
|
+
metadatas.push(doc.metadata);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return this.createDocuments(texts, metadatas);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
protected joinDocs(docs: string[], separator: string): string | null {
|
|
91
|
+
let text = docs.join(separator);
|
|
92
|
+
if (this.stripWhitespace) {
|
|
93
|
+
text = text.trim();
|
|
94
|
+
}
|
|
95
|
+
return text === '' ? null : text;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
protected mergeSplits(splits: string[], separator: string): string[] {
|
|
99
|
+
const separatorLen = this.lengthFunction(separator);
|
|
100
|
+
const docs: string[] = [];
|
|
101
|
+
let currentDoc: string[] = [];
|
|
102
|
+
let total = 0;
|
|
103
|
+
|
|
104
|
+
splits.forEach(d => {
|
|
105
|
+
const len = this.lengthFunction(d);
|
|
106
|
+
if (total + len + (currentDoc.length > 0 ? separatorLen : 0) > this.size) {
|
|
107
|
+
if (total > this.size) {
|
|
108
|
+
console.warn(`Created a chunk of size ${total}, ` + `which is longer than the specified ${this.size}`);
|
|
109
|
+
}
|
|
110
|
+
if (currentDoc.length > 0) {
|
|
111
|
+
const doc = this.joinDocs(currentDoc, separator);
|
|
112
|
+
if (doc !== null) {
|
|
113
|
+
docs.push(doc);
|
|
114
|
+
}
|
|
115
|
+
while (
|
|
116
|
+
total > this.overlap ||
|
|
117
|
+
(total + len + (currentDoc.length > 0 ? separatorLen : 0) > this.size && total > 0)
|
|
118
|
+
) {
|
|
119
|
+
total -= this.lengthFunction(currentDoc?.[0]!) + (currentDoc.length > 1 ? separatorLen : 0);
|
|
120
|
+
currentDoc = currentDoc.slice(1);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
currentDoc.push(d);
|
|
125
|
+
total += len + (currentDoc.length > 1 ? separatorLen : 0);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const doc = this.joinDocs(currentDoc, separator);
|
|
129
|
+
if (doc !== null) {
|
|
130
|
+
docs.push(doc);
|
|
131
|
+
}
|
|
132
|
+
return docs;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { TiktokenModel, TiktokenEncoding, Tiktoken } from 'js-tiktoken';
|
|
2
|
+
import { encodingForModel, getEncoding } from 'js-tiktoken';
|
|
3
|
+
|
|
4
|
+
import { TextTransformer } from './text';
|
|
5
|
+
|
|
6
|
+
interface Tokenizer {
|
|
7
|
+
overlap: number;
|
|
8
|
+
tokensPerChunk: number;
|
|
9
|
+
decode: (tokens: number[]) => string;
|
|
10
|
+
encode: (text: string) => number[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function splitTextOnTokens({ text, tokenizer }: { text: string; tokenizer: Tokenizer }): string[] {
|
|
14
|
+
const splits: string[] = [];
|
|
15
|
+
const inputIds = tokenizer.encode(text);
|
|
16
|
+
let startIdx = 0;
|
|
17
|
+
let curIdx = Math.min(startIdx + tokenizer.tokensPerChunk, inputIds.length);
|
|
18
|
+
let chunkIds = inputIds.slice(startIdx, curIdx);
|
|
19
|
+
|
|
20
|
+
while (startIdx < inputIds.length) {
|
|
21
|
+
splits.push(tokenizer.decode(chunkIds));
|
|
22
|
+
if (curIdx === inputIds.length) {
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
startIdx += tokenizer.tokensPerChunk - tokenizer.overlap;
|
|
26
|
+
curIdx = Math.min(startIdx + tokenizer.tokensPerChunk, inputIds.length);
|
|
27
|
+
chunkIds = inputIds.slice(startIdx, curIdx);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return splits;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class TokenTransformer extends TextTransformer {
|
|
34
|
+
private tokenizer: Tiktoken;
|
|
35
|
+
private allowedSpecial: Set<string> | 'all';
|
|
36
|
+
private disallowedSpecial: Set<string> | 'all';
|
|
37
|
+
|
|
38
|
+
constructor({
|
|
39
|
+
encodingName = 'cl100k_base',
|
|
40
|
+
modelName,
|
|
41
|
+
allowedSpecial = new Set(),
|
|
42
|
+
disallowedSpecial = 'all',
|
|
43
|
+
options = {},
|
|
44
|
+
}: {
|
|
45
|
+
encodingName: TiktokenEncoding;
|
|
46
|
+
modelName?: TiktokenModel;
|
|
47
|
+
allowedSpecial?: Set<string> | 'all';
|
|
48
|
+
disallowedSpecial?: Set<string> | 'all';
|
|
49
|
+
options: {
|
|
50
|
+
size?: number;
|
|
51
|
+
overlap?: number;
|
|
52
|
+
lengthFunction?: (text: string) => number;
|
|
53
|
+
keepSeparator?: boolean | 'start' | 'end';
|
|
54
|
+
addStartIndex?: boolean;
|
|
55
|
+
stripWhitespace?: boolean;
|
|
56
|
+
};
|
|
57
|
+
}) {
|
|
58
|
+
super(options);
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
this.tokenizer = modelName ? encodingForModel(modelName) : getEncoding(encodingName);
|
|
62
|
+
} catch {
|
|
63
|
+
throw new Error('Could not load tiktoken encoding. ' + 'Please install it with `npm install js-tiktoken`.');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.allowedSpecial = allowedSpecial;
|
|
67
|
+
this.disallowedSpecial = disallowedSpecial;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
splitText({ text }: { text: string }): string[] {
|
|
71
|
+
const encode = (text: string): number[] => {
|
|
72
|
+
const allowed = this.allowedSpecial === 'all' ? 'all' : Array.from(this.allowedSpecial);
|
|
73
|
+
|
|
74
|
+
const disallowed = this.disallowedSpecial === 'all' ? 'all' : Array.from(this.disallowedSpecial);
|
|
75
|
+
|
|
76
|
+
// If stripWhitespace is enabled, trim the text before encoding
|
|
77
|
+
const processedText = this.stripWhitespace ? text.trim() : text;
|
|
78
|
+
return Array.from(this.tokenizer.encode(processedText, allowed, disallowed));
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const decode = (tokens: number[]): string => {
|
|
82
|
+
const text = this.tokenizer.decode(tokens);
|
|
83
|
+
return this.stripWhitespace ? text.trim() : text;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const tokenizer: Tokenizer = {
|
|
87
|
+
overlap: this.overlap,
|
|
88
|
+
tokensPerChunk: this.size,
|
|
89
|
+
decode,
|
|
90
|
+
encode,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return splitTextOnTokens({ text, tokenizer });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static fromTikToken({
|
|
97
|
+
encodingName = 'cl100k_base',
|
|
98
|
+
modelName,
|
|
99
|
+
options = {},
|
|
100
|
+
}: {
|
|
101
|
+
encodingName?: TiktokenEncoding;
|
|
102
|
+
modelName?: TiktokenModel;
|
|
103
|
+
options?: {
|
|
104
|
+
size?: number;
|
|
105
|
+
overlap?: number;
|
|
106
|
+
allowedSpecial?: Set<string> | 'all';
|
|
107
|
+
disallowedSpecial?: Set<string> | 'all';
|
|
108
|
+
};
|
|
109
|
+
}): TokenTransformer {
|
|
110
|
+
let tokenizer: Tiktoken;
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
if (modelName) {
|
|
114
|
+
tokenizer = encodingForModel(modelName);
|
|
115
|
+
} else {
|
|
116
|
+
tokenizer = getEncoding(encodingName);
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
throw new Error('Could not load tiktoken encoding. ' + 'Please install it with `npm install js-tiktoken`.');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const tikTokenEncoder = (text: string): number => {
|
|
123
|
+
const allowed =
|
|
124
|
+
options.allowedSpecial === 'all' ? 'all' : options.allowedSpecial ? Array.from(options.allowedSpecial) : [];
|
|
125
|
+
|
|
126
|
+
const disallowed =
|
|
127
|
+
options.disallowedSpecial === 'all'
|
|
128
|
+
? 'all'
|
|
129
|
+
: options.disallowedSpecial
|
|
130
|
+
? Array.from(options.disallowedSpecial)
|
|
131
|
+
: [];
|
|
132
|
+
|
|
133
|
+
return tokenizer.encode(text, allowed, disallowed).length;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return new TokenTransformer({
|
|
137
|
+
encodingName,
|
|
138
|
+
modelName,
|
|
139
|
+
allowedSpecial: options.allowedSpecial,
|
|
140
|
+
disallowedSpecial: options.disallowedSpecial,
|
|
141
|
+
options: {
|
|
142
|
+
size: options.size,
|
|
143
|
+
overlap: options.overlap,
|
|
144
|
+
lengthFunction: tikTokenEncoder,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { TiktokenEncoding, TiktokenModel } from 'js-tiktoken';
|
|
2
|
+
import type {
|
|
3
|
+
LLM,
|
|
4
|
+
TitleCombinePrompt,
|
|
5
|
+
TitleExtractorPrompt,
|
|
6
|
+
SummaryPrompt,
|
|
7
|
+
QuestionExtractPrompt,
|
|
8
|
+
KeywordExtractPrompt,
|
|
9
|
+
} from 'llamaindex';
|
|
10
|
+
|
|
11
|
+
export enum Language {
|
|
12
|
+
CPP = 'cpp',
|
|
13
|
+
GO = 'go',
|
|
14
|
+
JAVA = 'java',
|
|
15
|
+
KOTLIN = 'kotlin',
|
|
16
|
+
JS = 'js',
|
|
17
|
+
TS = 'ts',
|
|
18
|
+
PHP = 'php',
|
|
19
|
+
PROTO = 'proto',
|
|
20
|
+
PYTHON = 'python',
|
|
21
|
+
RST = 'rst',
|
|
22
|
+
RUBY = 'ruby',
|
|
23
|
+
RUST = 'rust',
|
|
24
|
+
SCALA = 'scala',
|
|
25
|
+
SWIFT = 'swift',
|
|
26
|
+
MARKDOWN = 'markdown',
|
|
27
|
+
LATEX = 'latex',
|
|
28
|
+
HTML = 'html',
|
|
29
|
+
SOL = 'sol',
|
|
30
|
+
CSHARP = 'csharp',
|
|
31
|
+
COBOL = 'cobol',
|
|
32
|
+
C = 'c',
|
|
33
|
+
LUA = 'lua',
|
|
34
|
+
PERL = 'perl',
|
|
35
|
+
HASKELL = 'haskell',
|
|
36
|
+
ELIXIR = 'elixir',
|
|
37
|
+
POWERSHELL = 'powershell',
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type ExtractParams = {
|
|
41
|
+
title?: TitleExtractorsArgs | boolean;
|
|
42
|
+
summary?: SummaryExtractArgs | boolean;
|
|
43
|
+
questions?: QuestionAnswerExtractArgs | boolean;
|
|
44
|
+
keywords?: boolean | Record<string, any>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type ChunkOptions = {
|
|
48
|
+
headers?: [string, string][];
|
|
49
|
+
returnEachLine?: boolean;
|
|
50
|
+
sections?: [string, string][];
|
|
51
|
+
separator?: string;
|
|
52
|
+
separators?: string[];
|
|
53
|
+
isSeparatorRegex?: boolean;
|
|
54
|
+
size?: number;
|
|
55
|
+
maxSize?: number;
|
|
56
|
+
minSize?: number;
|
|
57
|
+
overlap?: number;
|
|
58
|
+
lengthFunction?: (text: string) => number;
|
|
59
|
+
keepSeparator?: boolean | 'start' | 'end';
|
|
60
|
+
addStartIndex?: boolean;
|
|
61
|
+
stripWhitespace?: boolean;
|
|
62
|
+
language?: Language;
|
|
63
|
+
ensureAscii?: boolean;
|
|
64
|
+
convertLists?: boolean;
|
|
65
|
+
encodingName?: TiktokenEncoding;
|
|
66
|
+
modelName?: TiktokenModel;
|
|
67
|
+
allowedSpecial?: Set<string> | 'all';
|
|
68
|
+
disallowedSpecial?: Set<string> | 'all';
|
|
69
|
+
stripHeaders?: boolean;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type TitleExtractorsArgs = {
|
|
73
|
+
llm?: LLM;
|
|
74
|
+
nodes?: number;
|
|
75
|
+
nodeTemplate?: TitleExtractorPrompt['template'];
|
|
76
|
+
combineTemplate?: TitleCombinePrompt['template'];
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type SummaryExtractArgs = {
|
|
80
|
+
llm?: LLM;
|
|
81
|
+
summaries?: string[];
|
|
82
|
+
promptTemplate?: SummaryPrompt['template'];
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export type QuestionAnswerExtractArgs = {
|
|
86
|
+
llm?: LLM;
|
|
87
|
+
questions?: number;
|
|
88
|
+
promptTemplate?: QuestionExtractPrompt['template'];
|
|
89
|
+
embeddingOnly?: boolean;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export type KeywordExtractArgs = {
|
|
93
|
+
llm?: LLM;
|
|
94
|
+
keywords?: number;
|
|
95
|
+
promptTemplate?: KeywordExtractPrompt['template'];
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export type ChunkStrategy = 'recursive' | 'character' | 'token' | 'markdown' | 'html' | 'json' | 'latex';
|
|
99
|
+
|
|
100
|
+
export interface ChunkParams extends ChunkOptions {
|
|
101
|
+
strategy?: ChunkStrategy;
|
|
102
|
+
extract?: ExtractParams;
|
|
103
|
+
}
|