UNPKG

@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

100 lines 4.64 kB
/** * Stage 3: Structured LLM Summarization * * Uses the structured 10-section prompt to summarize older messages * while preserving recent ones. */ import { randomUUID } from "crypto"; import { generateSummary } from "../../utils/conversationMemory.js"; import { estimateTokens } from "../../utils/tokenEstimation.js"; import { logger } from "../../utils/logger.js"; /** * Find the split index using token counting — walk backward from the end, * accumulating token counts until we've reserved `targetRecentTokens` worth * of recent content. Everything before the split index gets summarized. */ function findSplitIndexByTokens(messages, targetRecentTokens, provider) { let recentTokens = 0; let splitIndex = messages.length; for (let i = messages.length - 1; i >= 0; i--) { const content = typeof messages[i].content === "string" ? messages[i].content : JSON.stringify(messages[i].content); const msgTokens = estimateTokens(content, provider); if (recentTokens + msgTokens > targetRecentTokens) { splitIndex = i + 1; break; } recentTokens += msgTokens; } // Ensure at least one message is summarized return Math.max(1, splitIndex); } export async function summarizeMessages(messages, config) { const keepRecentRatio = config?.keepRecentRatio ?? 0.3; if (messages.length <= 4) { return { summarized: false, messages }; } // Determine split point: prefer token-based when a target budget is available, // fall back to message-count-based split for backward compatibility. let splitIndex; if (config?.targetTokens && config.targetTokens > 0) { // Keep `keepRecentRatio` fraction of the target budget as recent context const targetRecentTokens = Math.floor(config.targetTokens * keepRecentRatio); // NOTE: config.provider is the summarization provider, not the generation // provider. Ideally we'd use the generation/budget provider for accurate // token estimation, but SummarizeConfig doesn't carry a separate // budgetProvider field. This is a known design limitation. splitIndex = findSplitIndexByTokens(messages, targetRecentTokens, config.provider); } else { // Legacy: message-count-based split const keepCount = Math.max(4, Math.ceil(messages.length * keepRecentRatio)); splitIndex = messages.length - keepCount; } // Clamp so at least the last message is always preserved (never summarize everything) splitIndex = Math.min(splitIndex, messages.length - 1); if (splitIndex <= 0) { return { summarized: false, messages }; } const messagesToSummarize = messages.slice(0, splitIndex); const recentMessages = messages.slice(splitIndex); // Find previous summary if exists const previousSummary = messagesToSummarize.find((m) => m.metadata?.isSummary)?.content; // Build effective memory config: use provided memoryConfig, or construct from provider/model const effectiveMemoryConfig = config?.memoryConfig ? { ...config.memoryConfig } : {}; // Fill in summarization provider/model from compactor config if not already set if (!effectiveMemoryConfig.summarizationProvider && config?.provider) { effectiveMemoryConfig.summarizationProvider = config.provider; } if (!effectiveMemoryConfig.summarizationModel && config?.model) { effectiveMemoryConfig.summarizationModel = config.model; } // Only skip if there's genuinely no provider available if (!effectiveMemoryConfig.summarizationProvider && !effectiveMemoryConfig.summarizationModel) { logger.debug("[ContextCompactor] Stage 3 skipped: no summarization provider or model available"); return { summarized: false, messages }; } const summaryText = await generateSummary(messagesToSummarize, effectiveMemoryConfig, "[ContextCompactor]", previousSummary); if (!summaryText) { return { summarized: false, messages }; } const summaryMessage = { id: `summary-${randomUUID()}`, role: "user", content: `[Previous conversation summary]:\n\n${summaryText}`, timestamp: new Date().toISOString(), metadata: { isSummary: true, summarizesFrom: messagesToSummarize[0]?.id, summarizesTo: messagesToSummarize[messagesToSummarize.length - 1]?.id, }, }; return { summarized: true, messages: [summaryMessage, ...recentMessages], summaryText, }; } //# sourceMappingURL=structuredSummarizer.js.map