Skip to content

Commit 899a7d7

Browse files
authored
Merge pull request #5310 from 0x23d11/fix-4947
perf(llm): Optimize pruneLines functions in countTokens
2 parents b402553 + 5543927 commit 899a7d7

File tree

2 files changed

+103
-9
lines changed

2 files changed

+103
-9
lines changed

core/llm/countTokens.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,39 @@ describe.skip("pruneLinesFromTop", () => {
4646
const pruned = pruneLinesFromTop(prompt, 5, "gpt-4");
4747
expect(pruned.split("\n").length).toBeLessThan(prompt.split("\n").length);
4848
});
49+
50+
it("should return the original prompt if it's within max tokens", () => {
51+
const prompt = "Line 1\nLine 2";
52+
const pruned = pruneLinesFromTop(prompt, 10, "gpt-4");
53+
expect(pruned).toEqual(prompt);
54+
});
55+
56+
it("should return an empty string if maxTokens is 0", () => {
57+
const prompt = "Line 1\nLine 2\nLine 3\nLine 4";
58+
const pruned = pruneLinesFromTop(prompt, 0, "gpt-4");
59+
expect(pruned).toEqual("");
60+
});
61+
62+
it("should handle an empty prompt string", () => {
63+
const prompt = "";
64+
const pruned = pruneLinesFromTop(prompt, 5, "gpt-4");
65+
expect(pruned).toEqual("");
66+
});
67+
68+
it("should handle a prompt with a single line that exceeds maxTokens", () => {
69+
const prompt =
70+
"This is a single long line that will exceed the token limit";
71+
const pruned = pruneLinesFromTop(prompt, 5, "gpt-4");
72+
73+
expect(pruned).toEqual("");
74+
});
75+
76+
it("should correctly prune when all lines together exceed maxTokens but individual lines do not", () => {
77+
const prompt = "L1\nL2\nL3\nL4";
78+
79+
const pruned = pruneLinesFromTop(prompt, 5, "gpt-4");
80+
expect(pruned).toEqual("L3\nL4");
81+
});
4982
});
5083

5184
describe.skip("pruneLinesFromBottom", () => {
@@ -54,6 +87,39 @@ describe.skip("pruneLinesFromBottom", () => {
5487
const pruned = pruneLinesFromBottom(prompt, 5, "gpt-4");
5588
expect(pruned.split("\n").length).toBeLessThan(prompt.split("\n").length);
5689
});
90+
91+
it("should return the original prompt if it's within max tokens", () => {
92+
const prompt = "Line 1\nLine 2";
93+
const pruned = pruneLinesFromBottom(prompt, 10, "gpt-4");
94+
expect(pruned).toEqual(prompt);
95+
});
96+
97+
it("should return an empty string if maxTokens is 0", () => {
98+
const prompt = "Line 1\nLine 2\nLine 3\nLine 4";
99+
const pruned = pruneLinesFromBottom(prompt, 0, "gpt-4");
100+
expect(pruned).toEqual("");
101+
});
102+
103+
it("should handle an empty prompt string", () => {
104+
const prompt = "";
105+
const pruned = pruneLinesFromBottom(prompt, 5, "gpt-4");
106+
expect(pruned).toEqual("");
107+
});
108+
109+
it("should handle a prompt with a single line that exceeds maxTokens", () => {
110+
const prompt =
111+
"This is a single long line that will exceed the token limit";
112+
const pruned = pruneLinesFromBottom(prompt, 5, "gpt-4");
113+
114+
expect(pruned).toEqual("");
115+
});
116+
117+
it("should correctly prune when all lines together exceed maxTokens but individual lines do not", () => {
118+
const prompt = "L1\nL2\nL3\nL4";
119+
120+
const pruned = pruneLinesFromBottom(prompt, 5, "gpt-4");
121+
expect(pruned).toEqual("L1\nL2");
122+
});
57123
});
58124

59125
describe.skip("pruneRawPromptFromTop", () => {

core/llm/countTokens.ts

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -215,27 +215,55 @@ function pruneLinesFromTop(
215215
maxTokens: number,
216216
modelName: string,
217217
): string {
218-
let totalTokens = countTokens(prompt, modelName);
219218
const lines = prompt.split("\n");
220-
while (totalTokens > maxTokens && lines.length > 0) {
221-
totalTokens -= countTokens(lines.shift()!, modelName);
219+
// Preprocess tokens for all lines and cache them.
220+
const lineTokens = lines.map((line) => countTokens(line, modelName));
221+
let totalTokens = lineTokens.reduce((sum, tokens) => sum + tokens, 0);
222+
let start = 0;
223+
let currentLines = lines.length;
224+
225+
// Calculate initial token count including newlines
226+
totalTokens += Math.max(0, currentLines - 1); // Add tokens for joining newlines
227+
228+
// Using indexes instead of array modifications.
229+
// Remove lines from the top until the token count is within the limit.
230+
while (totalTokens > maxTokens && start < currentLines) {
231+
totalTokens -= lineTokens[start];
232+
// Decrement token count for the removed line and its preceding/joining newline (if not the last line)
233+
if (currentLines - start > 1) {
234+
totalTokens--;
235+
}
236+
start++;
222237
}
223238

224-
return lines.join("\n");
239+
return lines.slice(start).join("\n");
225240
}
226241

227242
function pruneLinesFromBottom(
228243
prompt: string,
229244
maxTokens: number,
230245
modelName: string,
231246
): string {
232-
let totalTokens = countTokens(prompt, modelName);
233247
const lines = prompt.split("\n");
234-
while (totalTokens > maxTokens && lines.length > 0) {
235-
totalTokens -= countTokens(lines.pop()!, modelName);
248+
const lineTokens = lines.map((line) => countTokens(line, modelName));
249+
let totalTokens = lineTokens.reduce((sum, tokens) => sum + tokens, 0);
250+
let end = lines.length;
251+
252+
// Calculate initial token count including newlines
253+
totalTokens += Math.max(0, end - 1); // Add tokens for joining newlines
254+
255+
// Reverse traversal to avoid array modification
256+
// Remove lines from the bottom until the token count is within the limit.
257+
while (totalTokens > maxTokens && end > 0) {
258+
end--;
259+
totalTokens -= lineTokens[end];
260+
// Decrement token count for the removed line and its following/joining newline (if not the first line)
261+
if (end > 0) {
262+
totalTokens--;
263+
}
236264
}
237265

238-
return lines.join("\n");
266+
return lines.slice(0, end).join("\n");
239267
}
240268

241269
function pruneStringFromBottom(
@@ -452,5 +480,5 @@ export {
452480
pruneLinesFromTop,
453481
pruneRawPromptFromTop,
454482
pruneStringFromBottom,
455-
pruneStringFromTop,
483+
pruneStringFromTop
456484
};

0 commit comments

Comments
 (0)