From ea5d0eed4fd3bacc0ca03b4d8c23563a7ee6a454 Mon Sep 17 00:00:00 2001 From: nathaninline Date: Thu, 23 Jul 2026 17:28:12 +0200 Subject: [PATCH] Compaction : budget de queue base sur la conversation, pas la fenetre (fix "rien a compacter") Bug : tailBudget = 0.25 x fenetre. Une conversation de 25k tokens dans une fenetre de 64k gardait 16k en queue -> torse minuscule (~4k) -> reduction < 20% -> refusee -> "rien a compacter" a 68%. Corrige : tailBudget = 0.25 x taille estimee de la conversation -> on garde ~25% des tours recents et on compacte les ~75% du debut, quelle que soit la fenetre. Aussi : la jauge post-compaction ajoute le surcout fixe non compactable (prompt systeme injecte + schemas d'outils = contexte reel - estimation messages), pour afficher une valeur realiste au lieu de chuter trop bas puis resauter. --- internal/jean/chat_compact.go | 7 ++++++- internal/jean/chat_conversation.go | 17 +++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/internal/jean/chat_compact.go b/internal/jean/chat_compact.go index 0810f51..18736be 100644 --- a/internal/jean/chat_compact.go +++ b/internal/jean/chat_compact.go @@ -164,7 +164,12 @@ func compactBounds(msgs []Message, tailBudget int) (head, tailStart int) { } func compactMessages(ctx context.Context, msgs []Message, caps Caps) ([]Message, bool) { - tailBudget := int(float64(ctxWindow()) * compactTailFrac) + // Budget de queue = fraction de la CONVERSATION (pas de la fenêtre). Le lier à + // la fenêtre était le bug : une conversation de 25k tokens dans une fenêtre de + // 64k gardait 16k (0.25×64k) en queue → torse minuscule → réduction < 20% → + // refusée. Lié à la conversation, on garde toujours ~25% des tours récents et + // on compacte les ~75% du début, quelle que soit la taille de la fenêtre. + tailBudget := int(float64(estimateTokens(msgs)) * compactTailFrac) head, tailStart := compactBounds(msgs, tailBudget) // Rien à compacter : le torse [head, tailStart) est vide. diff --git a/internal/jean/chat_conversation.go b/internal/jean/chat_conversation.go index 5b09679..f467104 100644 --- a/internal/jean/chat_conversation.go +++ b/internal/jean/chat_conversation.go @@ -197,8 +197,16 @@ func (c *Conversation) generate(ctx context.Context, caps Caps, temperature floa c.appendDelta(epoch, map[string]any{"compacting": true}) } if compacted, changed := MaybeCompact(ctx, msgs, caps, ctxUsed); changed { + // Surcoût fixe non compactable (prompt système injecté + schémas d'outils + + // gabarit) = contexte réel mesuré − estimation des messages. On le rajoute à + // l'estimation post-compaction pour que la jauge affiche une valeur réaliste + // (sinon elle chute trop bas puis resaute au tour suivant). + overhead := ctxUsed - estimateTokens(msgs) + if overhead < 0 { + overhead = 0 + } + est := estimateTokens(compacted) + overhead msgs = compacted - est := estimateTokens(compacted) c.mu.Lock() if c.epoch == epoch { c.Messages = compacted @@ -284,6 +292,7 @@ func (c *Conversation) CompactNow() error { ctx, cancel := context.WithCancel(context.Background()) c.cancel = cancel msgs := append([]Message(nil), c.Messages...) + lastReal := c.CtxUsed // dernier contexte réel mesuré, pour estimer le surcoût fixe epoch := c.epoch c.mu.Unlock() @@ -301,7 +310,11 @@ func (c *Conversation) CompactNow() error { c.appendDelta(epoch, map[string]any{"compact_noop": true}) return } - est := estimateTokens(compacted) + overhead := lastReal - estimateTokens(msgs) + if overhead < 0 { + overhead = 0 + } + est := estimateTokens(compacted) + overhead // + surcoût fixe (système+outils) pour une jauge réaliste c.mu.Lock() if c.epoch == epoch { c.Messages = compacted