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