Skip to content

Commit e09b7c9

Browse files
authored
Merge pull request #236 from FellouAI/develop
chore: Optimize agent compress
2 parents d249b82 + 588b30d commit e09b7c9

File tree

8 files changed

+136
-22
lines changed

8 files changed

+136
-22
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@eko-ai/eko",
3-
"version": "3.0.7",
3+
"version": "3.0.8",
44
"description": "Empowering language to transform human words into action.",
55
"workspaces": [
66
"packages/eko-core",

packages/eko-core/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@eko-ai/eko",
3-
"version": "3.0.7",
3+
"version": "3.0.8",
44
"description": "Empowering language to transform human words into action.",
55
"main": "dist/index.cjs.js",
66
"module": "dist/index.esm.js",
@@ -24,8 +24,8 @@
2424
},
2525
"scripts": {
2626
"build": "rollup -c",
27-
"test": "jest",
28-
"docs": "typedoc"
27+
"docs": "typedoc",
28+
"test": "npx jest test/core/eko.test.ts"
2929
},
3030
"author": "FellouAI",
3131
"license": "MIT",

packages/eko-core/src/agent/llm.ts

Lines changed: 125 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,25 @@ import { RetryLanguageModel } from "../llm";
55
import { AgentContext } from "../core/context";
66
import { uuidv4, sleep, toFile, getMimeType } from "../common/utils";
77
import {
8-
LLMRequest,
9-
StreamCallbackMessage,
10-
StreamCallback,
11-
HumanCallback,
12-
StreamResult,
138
Tool,
9+
LLMRequest,
1410
ToolResult,
1511
DialogueTool,
12+
StreamResult,
13+
HumanCallback,
14+
StreamCallback,
15+
StreamCallbackMessage,
1616
} from "../types";
1717
import {
18-
LanguageModelV2FunctionTool,
1918
LanguageModelV2Prompt,
20-
LanguageModelV2StreamPart,
2119
LanguageModelV2TextPart,
22-
LanguageModelV2ToolCallPart,
20+
SharedV2ProviderOptions,
2321
LanguageModelV2ToolChoice,
24-
LanguageModelV2ToolResultOutput,
22+
LanguageModelV2StreamPart,
23+
LanguageModelV2ToolCallPart,
24+
LanguageModelV2FunctionTool,
2525
LanguageModelV2ToolResultPart,
26-
SharedV2ProviderOptions,
26+
LanguageModelV2ToolResultOutput,
2727
} from "@ai-sdk/provider";
2828

2929
export function defaultLLMProviderOptions(): SharedV2ProviderOptions {
@@ -90,7 +90,10 @@ export function convertToolResult(
9090
type: "error-text",
9191
value: "Error",
9292
};
93-
} else if (toolResult.content.length == 1 && toolResult.content[0].type == "text") {
93+
} else if (
94+
toolResult.content.length == 1 &&
95+
toolResult.content[0].type == "text"
96+
) {
9497
let text = toolResult.content[0].text;
9598
result = {
9699
type: "text",
@@ -188,7 +191,11 @@ export async function callAgentLLM(
188191
requestHandler?: (request: LLMRequest) => void
189192
): Promise<Array<LanguageModelV2TextPart | LanguageModelV2ToolCallPart>> {
190193
await agentContext.context.checkAborted();
191-
if (messages.length >= config.compressThreshold && !noCompress) {
194+
if (
195+
!noCompress &&
196+
(messages.length >= config.compressThreshold || (messages.length >= 10 && estimatePromptTokens(messages, tools) >= config.compressTokensThreshold))
197+
) {
198+
// Compress messages
192199
await memory.compressAgentMessages(agentContext, rlm, messages, tools);
193200
}
194201
if (!toolChoice) {
@@ -221,8 +228,7 @@ export async function callAgentLLM(
221228
let thinkStreamId = uuidv4();
222229
let textStreamDone = false;
223230
const toolParts: LanguageModelV2ToolCallPart[] = [];
224-
let reader: ReadableStreamDefaultReader<LanguageModelV2StreamPart> | null =
225-
null;
231+
let reader: ReadableStreamDefaultReader<LanguageModelV2StreamPart> | null = null;
226232
try {
227233
agentChain.agentRequest = request;
228234
context.currentStepControllers.add(stepController);
@@ -509,6 +515,9 @@ export async function callAgentLLM(
509515
await context.checkAborted();
510516
if (retryNum < config.maxRetryNum) {
511517
await sleep(300 * (retryNum + 1) * (retryNum + 1));
518+
if ((e + "").indexOf("is too long") > -1) {
519+
await memory.compressAgentMessages(agentContext, rlm, messages, tools);
520+
}
512521
return callAgentLLM(
513522
agentContext,
514523
rlm,
@@ -534,6 +543,108 @@ export async function callAgentLLM(
534543
: toolParts;
535544
}
536545

546+
export function estimatePromptTokens(
547+
messages: LanguageModelV2Prompt,
548+
tools?: LanguageModelV2FunctionTool[]
549+
) {
550+
let tokens = messages.reduce((total, message) => {
551+
if (message.role == "system") {
552+
return total + estimateTokens(message.content);
553+
} else if (message.role == "user") {
554+
return (
555+
total +
556+
estimateTokens(
557+
message.content
558+
.filter((part) => part.type == "text")
559+
.map((part) => part.text)
560+
.join("\n")
561+
)
562+
);
563+
} else if (message.role == "assistant") {
564+
return (
565+
total +
566+
estimateTokens(
567+
message.content
568+
.map((part) => {
569+
if (part.type == "text") {
570+
return part.text;
571+
} else if (part.type == "reasoning") {
572+
return part.text;
573+
} else if (part.type == "tool-call") {
574+
return part.toolName + JSON.stringify(part.input || {});
575+
} else if (part.type == "tool-result") {
576+
return part.toolName + JSON.stringify(part.output || {});
577+
}
578+
return "";
579+
})
580+
.join("")
581+
)
582+
);
583+
} else if (message.role == "tool") {
584+
return (
585+
total +
586+
estimateTokens(
587+
message.content
588+
.map((part) => part.toolName + JSON.stringify(part.output || {}))
589+
.join("")
590+
)
591+
);
592+
}
593+
return total;
594+
}, 0);
595+
if (tools) {
596+
tokens += tools.reduce((total, tool) => {
597+
return total + estimateTokens(JSON.stringify(tool));
598+
}, 0);
599+
}
600+
return tokens;
601+
}
602+
603+
export function estimateTokens(text: string) {
604+
if (!text) {
605+
return 0;
606+
}
607+
let tokenCount = 0;
608+
for (let i = 0; i < text.length; i++) {
609+
const char = text[i];
610+
const code = char.charCodeAt(0);
611+
if (
612+
(code >= 0x4e00 && code <= 0x9fff) ||
613+
(code >= 0x3400 && code <= 0x4dbf) ||
614+
(code >= 0x3040 && code <= 0x309f) ||
615+
(code >= 0x30a0 && code <= 0x30ff) ||
616+
(code >= 0xac00 && code <= 0xd7af)
617+
) {
618+
tokenCount += 2;
619+
} else if (/\s/.test(char)) {
620+
continue;
621+
} else if (/[a-zA-Z]/.test(char)) {
622+
let word = "";
623+
while (i < text.length && /[a-zA-Z]/.test(text[i])) {
624+
word += text[i];
625+
i++;
626+
}
627+
i--;
628+
if (word.length <= 4) {
629+
tokenCount += 1;
630+
} else {
631+
tokenCount += Math.ceil(word.length / 4);
632+
}
633+
} else if (/\d/.test(char)) {
634+
let number = "";
635+
while (i < text.length && /\d/.test(text[i])) {
636+
number += text[i];
637+
i++;
638+
}
639+
i--;
640+
tokenCount += Math.max(1, Math.ceil(number.length / 3));
641+
} else {
642+
tokenCount += 1;
643+
}
644+
}
645+
return Math.max(1, tokenCount);
646+
}
647+
537648
function appendUserConversation(
538649
agentContext: AgentContext,
539650
messages: LanguageModelV2Prompt

packages/eko-core/src/config/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ type GlobalConfig = {
66
maxRetryNum: number;
77
agentParallel: boolean;
88
compressThreshold: number; // Dialogue context compression threshold (message count)
9+
compressTokensThreshold: number; // Dialogue context compression threshold (token count)
910
largeTextLength: number;
1011
fileTextMaxLength: number;
1112
maxDialogueImgFileNum: number;
@@ -23,6 +24,7 @@ const config: GlobalConfig = {
2324
maxRetryNum: 3,
2425
agentParallel: false,
2526
compressThreshold: 80,
27+
compressTokensThreshold: 100000,
2628
largeTextLength: 5000,
2729
fileTextMaxLength: 20000,
2830
maxDialogueImgFileNum: 1,

packages/eko-core/src/prompt/agent.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Agent } from "../agent";
21
import config from "../config";
2+
import { Agent } from "../agent";
33
import Context from "../core/context";
44
import { sub } from "../common/utils";
55
import { WorkflowAgent, Tool } from "../types";
@@ -37,6 +37,7 @@ During the task execution process, you can use the \`${human_interact}\` tool to
3737
- When performing dangerous operations such as deleting files, confirmation from humans is required.
3838
- When encountering obstacles while accessing websites, such as requiring user login, captcha verification, QR code scanning, or human verification, you need to request manual assistance.
3939
- Please do not use the \`${human_interact}\` tool frequently.
40+
- The \`${human_interact}\` tool does not support parallel calls.
4041
`;
4142

4243
const VARIABLE_PROMPT = `

packages/eko-extension/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@eko-ai/eko-extension",
3-
"version": "3.0.7",
3+
"version": "3.0.8",
44
"description": "Empowering language to transform human words into action.",
55
"main": "dist/index.cjs.js",
66
"module": "dist/index.esm.js",

packages/eko-nodejs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@eko-ai/eko-nodejs",
3-
"version": "3.0.7",
3+
"version": "3.0.8",
44
"description": "Empowering language to transform human words into action.",
55
"main": "dist/index.cjs.js",
66
"module": "dist/index.esm.js",

packages/eko-web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@eko-ai/eko-web",
3-
"version": "3.0.7",
3+
"version": "3.0.8",
44
"description": "Empowering language to transform human words into action.",
55
"main": "dist/index.cjs.js",
66
"module": "dist/index.esm.js",

0 commit comments

Comments
 (0)