Revert "Enable separate config for truncation for models without context caching"

This commit is contained in:
Matt Rubens 2025-02-07 01:07:06 -05:00 committed by GitHub
parent 003d4e43a6
commit 3ae121687c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 42 additions and 111 deletions

View file

@ -53,7 +53,7 @@ import { AssistantMessageContent, parseAssistantMessage, ToolParamName, ToolUseN
import { formatResponse } from "./prompts/responses"
import { SYSTEM_PROMPT } from "./prompts/system"
import { modes, defaultModeSlug, getModeBySlug } from "../shared/modes"
import { truncateConversationIfNeeded } from "./sliding-window"
import { truncateHalfConversation } from "./sliding-window"
import { ClineProvider, GlobalFileNames } from "./webview/ClineProvider"
import { detectCodeOmission } from "../integrations/editor/detect-omission"
import { BrowserSession } from "../services/browser/BrowserSession"
@ -876,25 +876,18 @@ export class Cline {
// If the previous API request's total token usage is close to the context window, truncate the conversation history to free up space for the new request
if (previousApiReqIndex >= 0) {
const previousRequest = this.clineMessages[previousApiReqIndex]?.text
if (!previousRequest) return
const {
tokensIn = 0,
tokensOut = 0,
cacheWrites = 0,
cacheReads = 0,
}: ClineApiReqInfo = JSON.parse(previousRequest)
const totalTokens = tokensIn + tokensOut + cacheWrites + cacheReads
const trimmedMessages = truncateConversationIfNeeded(
this.apiConversationHistory,
totalTokens,
this.api.getModel().info,
)
if (trimmedMessages !== this.apiConversationHistory) {
await this.overwriteApiConversationHistory(trimmedMessages)
const previousRequest = this.clineMessages[previousApiReqIndex]
if (previousRequest && previousRequest.text) {
const { tokensIn, tokensOut, cacheWrites, cacheReads }: ClineApiReqInfo = JSON.parse(
previousRequest.text,
)
const totalTokens = (tokensIn || 0) + (tokensOut || 0) + (cacheWrites || 0) + (cacheReads || 0)
const contextWindow = this.api.getModel().info.contextWindow || 128_000
const maxAllowedSize = Math.max(contextWindow - 40_000, contextWindow * 0.8)
if (totalTokens >= maxAllowedSize) {
const truncatedMessages = truncateHalfConversation(this.apiConversationHistory)
await this.overwriteApiConversationHistory(truncatedMessages)
}
}
}

View file

@ -1,97 +1,26 @@
import { Anthropic } from "@anthropic-ai/sdk"
import { ModelInfo } from "../../shared/api"
/**
* Truncates a conversation by removing a fraction of the messages.
*
* The first message is always retained, and a specified fraction (rounded to an even number)
* of messages from the beginning (excluding the first) is removed.
*
* @param {Anthropic.Messages.MessageParam[]} messages - The conversation messages.
* @param {number} fracToRemove - The fraction (between 0 and 1) of messages (excluding the first) to remove.
* @returns {Anthropic.Messages.MessageParam[]} The truncated conversation messages.
*/
export function truncateConversation(
/*
We can't implement a dynamically updating sliding window as it would break prompt cache
every time. To maintain the benefits of caching, we need to keep conversation history
static. This operation should be performed as infrequently as possible. If a user reaches
a 200k context, we can assume that the first half is likely irrelevant to their current task.
Therefore, this function should only be called when absolutely necessary to fit within
context limits, not as a continuous process.
*/
export function truncateHalfConversation(
messages: Anthropic.Messages.MessageParam[],
fracToRemove: number,
): Anthropic.Messages.MessageParam[] {
// API expects messages to be in user-assistant order, and tool use messages must be followed by tool results. We need to maintain this structure while truncating.
// Always keep the first Task message (this includes the project's file structure in environment_details)
const truncatedMessages = [messages[0]]
const rawMessagesToRemove = Math.floor((messages.length - 1) * fracToRemove)
const messagesToRemove = rawMessagesToRemove - (rawMessagesToRemove % 2)
const remainingMessages = messages.slice(messagesToRemove + 1)
// Remove half of user-assistant pairs
const messagesToRemove = Math.floor(messages.length / 4) * 2 // has to be even number
const remainingMessages = messages.slice(messagesToRemove + 1) // has to start with assistant message since tool result cannot follow assistant message with no tool use
truncatedMessages.push(...remainingMessages)
return truncatedMessages
}
/**
* Conditionally truncates the conversation messages if the total token count exceeds the model's limit.
*
* Depending on whether the model supports prompt caching, different maximum token thresholds
* and truncation fractions are used. If the current total tokens exceed the threshold,
* the conversation is truncated using the appropriate fraction.
*
* @param {Anthropic.Messages.MessageParam[]} messages - The conversation messages.
* @param {number} totalTokens - The total number of tokens in the conversation.
* @param {ModelInfo} modelInfo - Model metadata including context window size and prompt cache support.
* @returns {Anthropic.Messages.MessageParam[]} The original or truncated conversation messages.
*/
export function truncateConversationIfNeeded(
messages: Anthropic.Messages.MessageParam[],
totalTokens: number,
modelInfo: ModelInfo,
): Anthropic.Messages.MessageParam[] {
if (modelInfo.supportsPromptCache) {
return totalTokens < getMaxTokensForPromptCachingModels(modelInfo)
? messages
: truncateConversation(messages, getTruncFractionForPromptCachingModels(modelInfo))
} else {
return totalTokens < getMaxTokensForNonPromptCachingModels(modelInfo)
? messages
: truncateConversation(messages, getTruncFractionForNonPromptCachingModels(modelInfo))
}
}
/**
* Calculates the maximum allowed tokens for models that support prompt caching.
*
* The maximum is computed as the greater of (contextWindow - 40000) and 80% of the contextWindow.
*
* @param {ModelInfo} modelInfo - The model information containing the context window size.
* @returns {number} The maximum number of tokens allowed for prompt caching models.
*/
function getMaxTokensForPromptCachingModels(modelInfo: ModelInfo): number {
return Math.max(modelInfo.contextWindow - 40_000, modelInfo.contextWindow * 0.8)
}
/**
* Provides the fraction of messages to remove for models that support prompt caching.
*
* @param {ModelInfo} modelInfo - The model information (unused in current implementation).
* @returns {number} The truncation fraction for prompt caching models (fixed at 0.5).
*/
function getTruncFractionForPromptCachingModels(modelInfo: ModelInfo): number {
return 0.5
}
/**
* Calculates the maximum allowed tokens for models that do not support prompt caching.
*
* The maximum is computed as the greater of (contextWindow - 40000) and 80% of the contextWindow.
*
* @param {ModelInfo} modelInfo - The model information containing the context window size.
* @returns {number} The maximum number of tokens allowed for non-prompt caching models.
*/
function getMaxTokensForNonPromptCachingModels(modelInfo: ModelInfo): number {
return Math.max(modelInfo.contextWindow - 40_000, modelInfo.contextWindow * 0.8)
}
/**
* Provides the fraction of messages to remove for models that do not support prompt caching.
*
* @param {ModelInfo} modelInfo - The model information.
* @returns {number} The truncation fraction for non-prompt caching models (fixed at 0.1).
*/
function getTruncFractionForNonPromptCachingModels(modelInfo: ModelInfo): number {
return Math.min(40_000 / modelInfo.contextWindow, 0.2)
}

View file

@ -1,7 +1,11 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
@ -17,8 +21,13 @@
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
"@/*": [
"./src/*"
]
}
},
"include": ["src", "../src/shared"]
"include": [
"src",
"../src/shared",
]
}