( chatHistory: ChatHistoryItem[], model: ModelConfig, llmApi: BaseLlmApi, options?: CompactionOptions, )
| 51 | * @returns The compacted history with compaction index |
| 52 | */ |
| 53 | export async function compactChatHistory( |
| 54 | chatHistory: ChatHistoryItem[], |
| 55 | model: ModelConfig, |
| 56 | llmApi: BaseLlmApi, |
| 57 | options?: CompactionOptions, |
| 58 | ): Promise<CompactionResult> { |
| 59 | const { callbacks, abortController, systemMessageTokens = 0 } = options || {}; |
| 60 | // Create a prompt to summarize the conversation |
| 61 | const compactionPrompt: ChatHistoryItem = { |
| 62 | message: { |
| 63 | role: "user" as const, |
| 64 | content: COMPACTION_PROMPT, |
| 65 | }, |
| 66 | contextItems: [], |
| 67 | }; |
| 68 | |
| 69 | // Check if the history with compaction prompt is too long, prune if necessary |
| 70 | let historyToUse = chatHistory; |
| 71 | let historyForCompaction = [...historyToUse, compactionPrompt]; |
| 72 | |
| 73 | const contextLimit = getModelContextLimit(model); |
| 74 | const maxTokens = getModelMaxTokens(model); |
| 75 | |
| 76 | // Check if system message is already in the history to avoid double-counting |
| 77 | const hasSystemMessageInHistory = chatHistory.some( |
| 78 | (item) => item.message.role === "system", |
| 79 | ); |
| 80 | |
| 81 | // Account for system message (if not already in history) AND compaction prompt |
| 82 | const systemMessageReservation = hasSystemMessageInHistory |
| 83 | ? 0 |
| 84 | : systemMessageTokens; |
| 85 | |
| 86 | const availableForInput = |
| 87 | contextLimit - |
| 88 | maxTokens - |
| 89 | systemMessageReservation - |
| 90 | COMPACTION_PROMPT_TOKENS; |
| 91 | |
| 92 | // Check if we need to prune to fit within context |
| 93 | while ( |
| 94 | countChatHistoryTokens(historyForCompaction, model) > availableForInput && |
| 95 | historyToUse.length > 0 |
| 96 | ) { |
| 97 | logger.debug("Compaction history too long, pruning last message", { |
| 98 | tokenCount: countChatHistoryTokens(historyForCompaction, model), |
| 99 | availableForInput, |
| 100 | historyLength: historyToUse.length, |
| 101 | }); |
| 102 | const prunedHistory = pruneLastMessage(historyToUse); |
| 103 | |
| 104 | // Break if pruning didn't change the history (prevents infinite loop) |
| 105 | if (prunedHistory.length === historyToUse.length) { |
| 106 | logger.warn( |
| 107 | "Cannot prune history further while maintaining valid conversation structure", |
| 108 | ); |
| 109 | break; |
| 110 | } |
no test coverage detected