Merge pull request #2014 from badgerbees/fix/context-pruning-guards

fix(agent): include SystemParts in token estimation and add reasoning guards
This commit is contained in:
Mauro 2026-03-31 13:30:00 +02:00 committed by GitHub
commit 848f9dd2e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 40 additions and 5 deletions

View file

@ -90,13 +90,28 @@ func findSafeBoundary(history []providers.Message, targetIndex int) int {
// including Content, ReasoningContent, ToolCalls arguments, ToolCallID // including Content, ReasoningContent, ToolCalls arguments, ToolCallID
// metadata, and Media items. Uses a heuristic of 2.5 characters per token. // metadata, and Media items. Uses a heuristic of 2.5 characters per token.
func estimateMessageTokens(msg providers.Message) int { func estimateMessageTokens(msg providers.Message) int {
chars := utf8.RuneCountInString(msg.Content) contentChars := utf8.RuneCountInString(msg.Content)
// ReasoningContent (extended thinking / chain-of-thought) can be // SystemParts are structured system blocks used for cache-aware adapters.
// substantial and is stored in session history via AddFullMessage. // They carry the same content as Content, but in multiple blocks.
if msg.ReasoningContent != "" { // We estimate them as an alternative representation, not additive.
chars += utf8.RuneCountInString(msg.ReasoningContent) systemPartsChars := 0
if len(msg.SystemParts) > 0 {
for _, part := range msg.SystemParts {
systemPartsChars += utf8.RuneCountInString(part.Text)
} }
// Per-part overhead for JSON structure (type, text, cache_control).
const perPartOverhead = 20
systemPartsChars += len(msg.SystemParts) * perPartOverhead
}
// Use the larger of the two representations to stay conservative.
chars := contentChars
if systemPartsChars > chars {
chars = systemPartsChars
}
chars += utf8.RuneCountInString(msg.ReasoningContent)
for _, tc := range msg.ToolCalls { for _, tc := range msg.ToolCalls {
chars += len(tc.ID) + len(tc.Type) chars += len(tc.ID) + len(tc.Type)

View file

@ -529,6 +529,26 @@ func TestEstimateMessageTokens_MediaItems(t *testing.T) {
} }
} }
func TestEstimateMessageTokens_SystemParts(t *testing.T) {
plain := providers.Message{Role: "system", Content: "instructions"}
withParts := providers.Message{
Role: "system",
Content: "instructions",
SystemParts: []providers.ContentBlock{
{Type: "text", Text: "some more system context"},
{Type: "text", Text: "even more cached blocks"},
},
}
plainTokens := estimateMessageTokens(plain)
partsTokens := estimateMessageTokens(withParts)
if partsTokens <= plainTokens {
t.Errorf("system message with SystemParts (%d) should exceed plain message (%d)",
partsTokens, plainTokens)
}
}
// --- estimateToolDefsTokens tests --- // --- estimateToolDefsTokens tests ---
func TestEstimateToolDefsTokens(t *testing.T) { func TestEstimateToolDefsTokens(t *testing.T) {