When you develop AI Agent, especially with n8n. You want to send message to Discord with Webhook. But it end up with error when LLM produced output more than 2000 characters as a limitation of Discord Webhook.
LLM Limitation
Solving by let LLM trying to split the message is not accurate, since 1 Token != 1 characters. then It will end-up with Multi-turn request which not efficient as possible with many attempt which wasted time and context windows(even with Context windows caching).
Tackle the problem with Tools calling
Since LLM is not accurate on counting, e.g. strawberry problem. Using Tools calling. It can help.
Simple Tools to count string length
Then I start with a simple tools as one liner to return string length to let Agent to split text and try again
return query.length
But this is not sufficient enough as
- Still Multi-turn effort as time consume. Even LLM know the length, but LLM cannot split perfectly
- Wasted Token (even with LLM context caching)
Efficient ways
Then I looking forward to create a single tools to let LLM calling tools split string chunks to array and let n8n workflow continue the rest.
I also create a reusable with Executed by another Workflow
Trigger to perform sending Discord Webhook
Here is a JS Snipplet https://gist.github.com/meddlesome/b1e35ad133c726e0691b89c307245af5
// Input: Single string from previous node
const inputText = $input.first().json.text; // Adjust field name as needed
// Configuration
const MAX_LENGTH = 2000;
const SPLIT_PATTERNS = ['\n\n', '\n', '. ', ' ']; // Priority order for splitting
function splitText(text, maxLength) {
if (text.length <= maxLength) {
return [text];
}
const chunks = [];
let remainingText = text;
while (remainingText.length > 0) {
if (remainingText.length <= maxLength) {
chunks.push(remainingText);
break;
}
let chunk = remainingText.substring(0, maxLength);
let splitIndex = -1;
// Find best split point
for (const pattern of SPLIT_PATTERNS) {
const lastIndex = chunk.lastIndexOf(pattern);
if (lastIndex > maxLength * 0.5) { // Don't split too early
splitIndex = lastIndex + pattern.length;
break;
}
}
// If no good split point found, force split at max length
if (splitIndex === -1) {
splitIndex = maxLength;
}
chunks.push(remainingText.substring(0, splitIndex).trim());
remainingText = remainingText.substring(splitIndex).trim();
}
return chunks;
}
// Process the text
const textChunks = splitText(inputText, MAX_LENGTH);
// Return array of chunks
return textChunks.map((chunk, index) => ({
json: {
chunk: chunk,
index: index,
totalChunks: textChunks.length,
length: chunk.length
}
}));
It end-up with single turn and reducing time over 50% !
Top comments (0)