@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
146 lines • 6.72 kB
JavaScript
/**
* File Content Summarization Pipeline
*
* Provides utilities to detect when attached file content exceeds the
* model's available context budget and to plan / build prompts for
* LLM-driven summarization of the largest files.
*
* Design rationale:
* - Files are the #1 cause of context overflow when users attach
* multiple large documents (PDFs, spreadsheets, source code).
* - Rather than blindly truncating, we ask an LLM to produce a
* *context-aware* summary that retains the information most
* relevant to the user's actual question.
* - The caller (FileSummarizationService) is responsible for the
* actual LLM calls; this module is pure computation + types.
*/
import { getAvailableInputTokens } from "../constants/contextWindows.js";
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
/** Fraction of the context window reserved for non-file content overhead */
export const NON_FILE_RESERVE = 0.15;
/** Minimum tokens a single file can be allocated in the plan */
export const MIN_PER_FILE_TOKENS = 500;
/** Maximum tokens a single file can be allocated in the plan */
export const MAX_PER_FILE_TOKENS = 4000;
/**
* Files with fewer estimated tokens than this threshold are never
* summarized — they're already small enough to include verbatim.
*/
export const FILE_SUMMARIZATION_THRESHOLD = 1000;
// ---------------------------------------------------------------------------
// Functions
// ---------------------------------------------------------------------------
/**
* Check whether the attached files push the total input token count
* beyond the model's available context window.
*
* When the total exceeds the budget, we calculate how many tokens are
* available for files (after accounting for system prompt, conversation
* history, current prompt, and tool definitions) and divide that
* equally across all files to derive a per-file budget.
*/
export function shouldSummarizeFiles(params) {
const { provider, model, systemPromptTokens, conversationHistoryTokens, currentPromptTokens, toolDefinitionTokens, fileTokens, fileCount = 1, maxTokens, threshold = 0.8, minTokensPerFile = MIN_PER_FILE_TOKENS, maxTokensPerFile = MAX_PER_FILE_TOKENS, } = params;
const availableInputTokens = getAvailableInputTokens(provider, model, maxTokens);
const nonFileTokens = systemPromptTokens +
conversationHistoryTokens +
currentPromptTokens +
toolDefinitionTokens;
const totalEstimatedTokens = nonFileTokens + fileTokens;
// Budget for files = available input minus non-file content minus a reserve
const reserveTokens = Math.ceil(availableInputTokens * NON_FILE_RESERVE);
const availableBudgetForFiles = Math.max(0, availableInputTokens - nonFileTokens - reserveTokens);
const usageRatio = availableInputTokens > 0 ? totalEstimatedTokens / availableInputTokens : 1;
const needsSummarization = usageRatio >= threshold || fileTokens > availableBudgetForFiles;
let perFileBudget;
if (needsSummarization && fileCount > 0) {
const rawBudget = Math.floor(availableBudgetForFiles / fileCount);
perFileBudget = Math.max(minTokensPerFile, Math.min(maxTokensPerFile, rawBudget));
}
return {
needsSummarization,
totalEstimatedTokens,
availableInputTokens,
availableBudgetForFiles,
perFileBudget,
};
}
/**
* Build the LLM prompt used to summarize a single file's content.
*
* The prompt is *context-aware*: it includes the user's original question
* so the LLM can prioritise the most relevant parts of the file.
*/
export function buildFileSummarizationPrompt(params) {
const { fileName, fileType, fileContent, userPrompt, targetTokens } = params;
return [
`You are a document summarization assistant. Your task is to summarize the following ${fileType} file in a way that preserves the most important information relevant to the user's question.`,
``,
`## User's Question`,
`${userPrompt}`,
``,
`## File: ${fileName} (${fileType})`,
``,
`${fileContent}`,
``,
`## Instructions`,
`1. Produce a concise summary of the file content above.`,
`2. Focus on information that is most relevant to the user's question.`,
`3. Preserve key data points, names, numbers, and relationships.`,
`4. Target approximately ${targetTokens} tokens in your summary.`,
`5. If the file contains structured data (tables, lists), preserve the structure in a compact form.`,
`6. Start your summary directly — do not include preamble like "Here is a summary".`,
].join("\n");
}
/**
* Decide which files need summarization and how much budget each gets.
*
* Strategy:
* 1. Sort files largest-first.
* 2. Walk through the list, marking the largest files for summarization
* until the cumulative saved tokens bring us under budget.
* 3. Files below `FILE_SUMMARIZATION_THRESHOLD` are never summarized.
*/
export function planFileSummarization(files, params) {
const checkResult = shouldSummarizeFiles({
...params,
fileCount: files.length,
});
// If no summarization needed, keep everything
if (!checkResult.needsSummarization) {
return files.map((file) => ({ file, action: "keep" }));
}
// Sort largest first (descending by estimatedTokens)
const sorted = [...files].sort((a, b) => b.estimatedTokens - a.estimatedTokens);
const perFileBudget = checkResult.perFileBudget ?? MAX_PER_FILE_TOKENS;
// Calculate how many tokens we need to save
const totalFileTokens = files.reduce((sum, f) => sum + f.estimatedTokens, 0);
const tokensToSave = Math.max(0, totalFileTokens - checkResult.availableBudgetForFiles);
let savedSoFar = 0;
const plan = [];
for (const file of sorted) {
// Never summarize tiny files
if (file.estimatedTokens < FILE_SUMMARIZATION_THRESHOLD ||
savedSoFar >= tokensToSave) {
plan.push({ file, action: "keep" });
}
else {
const savingsFromThisFile = file.estimatedTokens - perFileBudget;
if (savingsFromThisFile > 0) {
plan.push({
file,
action: "summarize",
targetTokens: perFileBudget,
});
savedSoFar += savingsFromThisFile;
}
else {
plan.push({ file, action: "keep" });
}
}
}
return plan;
}
//# sourceMappingURL=fileSummarizer.js.map