mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-14 10:48:35 +02:00
Contexte : corrections de relecture — la garde de marge ne compacte plus en boucle, et le preset externe compte comme avant
La garde de marge de génération réclamait 1,2 × la plus longue génération récente, sans plafond. Une étape de 40 k sur une fenêtre de 65 k (ou de 20 k sur 32 k, sans budget de raisonnement) demandait plus de place que l'historique fraîchement compacté n'en laisse : la garde repartait à chaque étape, et chaque fois résumait le résumé précédent. - Garde : jamais avant la moitié de la fenêtre. Au-delà, le rejeu sur « length » reste le filet. - Preset externe : le complément « messages arrivés depuis la mesure » ne s'applique plus. Son CtxUsed garde le raisonnement, qui couvrait déjà ce complément ; l'ajouter le faisait compacter plus tôt qu'avant. - Journal [ctx] : l'estimation d'avant compaction (début de tour, en tour) n'est plus comparée au compte d'une requête compactée, ce qui donnait de faux écarts. - Tests : génération énorme juste après compaction, seuil de 50 % pour toutes les fenêtres, comptage externe contre local. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d6023ef8a0
commit
26fc348506
4 files changed
+59
-11
No files matched your search
@@ -234,12 +234,18 @@ func compactNeeded(msgs []Message, used, peakGen int) bool {
|
||||
// fenêtre pour les petites), plus une petite marge. Fonction pure, testable.
|
||||
// Plancher et marge sont choisis pour ne JAMAIS devancer le seuil de 75 % à eux
|
||||
// seuls : seule une génération longue avance la compaction.
|
||||
//
|
||||
// Et jamais avant la MOITIÉ de la fenêtre, si longue que soit la génération :
|
||||
// une étape de 40 k sur 65 k (ou de 20 k sur 32 k) réclamait sinon une place
|
||||
// que l'historique compacté lui-même — prompt système, outils, résumé, queue —
|
||||
// ne laisse pas. La garde repartait alors à CHAQUE étape, résumé de résumé, le
|
||||
// fil perdu pour rien. Au-delà, c'est le rejeu sur « length » qui rattrape.
|
||||
func genHeadroomShort(used, peakGen, window int) bool {
|
||||
if used <= 0 || peakGen <= 0 || window <= 0 {
|
||||
return false
|
||||
}
|
||||
need := max(peakGen*6/5, min(8192, window/8)) + min(1024, window/32)
|
||||
return used+need > window
|
||||
return used+min(need, window/2) > window
|
||||
}
|
||||
|
||||
// logCtx trace un fait du comptage de contexte (fenêtre pleine, relance après
|
||||
|
||||
@@ -752,27 +752,35 @@ func (c *Conversation) generate(ctx context.Context, caps Caps, temperature floa
|
||||
// sous-agents, vérification) est rattaché à la discussion active.
|
||||
ctx = withPerf(ctx, perfMain, getStr(bkChat, ckActive))
|
||||
|
||||
// llama-server local seulement : le preset externe garde le seul seuil, et
|
||||
// ses complétions ne nourrissent pas la garde de marge (compactNeeded).
|
||||
local := !externalActive()
|
||||
// Snapshot de la vue modèle.
|
||||
c.mu.Lock()
|
||||
msgs := append([]Message(nil), c.Messages...)
|
||||
// Dernier compte du moteur + ce qu'il n'a pas encore vu (ce message-ci, la
|
||||
// file). Sans ce complément, seul le raisonnement compté à tort masquait le
|
||||
// manque ; maintenant qu'il est retiré, le nouveau message doit compter.
|
||||
ctxUsed := ctxPending(c.CtxUsed, c.ctxUsedLen, msgs)
|
||||
ctxUsed := c.ctxNowLocked(msgs, local)
|
||||
peak := c.genPeak
|
||||
c.genPeak = 0 // recompté pendant ce tour
|
||||
c.mu.Unlock()
|
||||
// llama-server local seulement : le preset externe garde le seul seuil, et
|
||||
// ses complétions ne nourrissent pas la garde de marge (compactNeeded).
|
||||
local := !externalActive()
|
||||
|
||||
// Compaction proactive (façon Hermes) sur la vue MODÈLE uniquement ; le journal
|
||||
// d'affichage garde le fil complet. Le résumé est un appel modèle non streamé :
|
||||
// il bloque plusieurs secondes AVANT que la vraie réponse commence, d'où la
|
||||
// bannière de progression émise par compactAndPublish.
|
||||
// Estimation de début de tour, comparée au premier compte réel du moteur
|
||||
// (journal [ctx], seulement au-delà de 2 % d'écart).
|
||||
startEst := ctxUsed
|
||||
if compactNeeded(msgs, ctxUsed, peak) {
|
||||
if out, changed := c.compactAndPublish(ctx, epoch, "début-tour", msgs, ctxUsed, caps); changed {
|
||||
msgs = out
|
||||
// Le chiffre d'avant compaction ne dit plus rien de la requête qui
|
||||
// part : on compare celui que la compaction vient de poser.
|
||||
c.mu.Lock()
|
||||
startEst = c.CtxUsed
|
||||
c.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -809,9 +817,6 @@ func (c *Conversation) generate(ctx context.Context, caps Caps, temperature floa
|
||||
// l'usage est arrivé ; sinon on retombe sur l'estimation (celle qui pilote
|
||||
// déjà la compaction), approximative mais jamais absente.
|
||||
sawUsage := false
|
||||
// Estimation de début de tour, comparée au premier compte réel du moteur
|
||||
// (journal [ctx], seulement au-delà de 2 % d'écart).
|
||||
startEst := ctxUsed
|
||||
turnPeak := 0
|
||||
// asked : le tour s'est terminé sur une question à l'utilisateur (outil ask) —
|
||||
// pas de vérification du mode Code avant sa réponse.
|
||||
@@ -923,7 +928,7 @@ func (c *Conversation) generate(ctx context.Context, caps Caps, temperature floa
|
||||
c.genPeak = max(c.genPeak, turnPeak)
|
||||
}
|
||||
msgs = append([]Message(nil), c.Messages...)
|
||||
ctxUsed = ctxPending(c.CtxUsed, c.ctxUsedLen, msgs)
|
||||
ctxUsed = c.ctxNowLocked(msgs, local)
|
||||
peak = c.genPeak
|
||||
stale := c.epoch != epoch
|
||||
c.mu.Unlock()
|
||||
@@ -950,7 +955,7 @@ func (c *Conversation) generate(ctx context.Context, caps Caps, temperature floa
|
||||
c.codeVerifyLoop(ctx, caps, temperature, epoch, asked)
|
||||
c.mu.Lock()
|
||||
msgs = append([]Message(nil), c.Messages...)
|
||||
ctxUsed = ctxPending(c.CtxUsed, c.ctxUsedLen, msgs)
|
||||
ctxUsed = c.ctxNowLocked(msgs, local)
|
||||
peak = c.genPeak
|
||||
stale = c.epoch != epoch
|
||||
c.mu.Unlock()
|
||||
@@ -973,6 +978,18 @@ func (c *Conversation) generate(ctx context.Context, caps Caps, temperature floa
|
||||
c.compactAndPublish(context.Background(), epoch, "fin-tour", msgs, ctxUsed, caps)
|
||||
}
|
||||
|
||||
// ctxNowLocked : contexte à juger pour compacter, c.mu tenu. llama-server
|
||||
// local : le dernier compte plus les messages arrivés depuis (ctxPending). Preset
|
||||
// externe : le dernier compte seul, comme avant — son CtxUsed garde encore le
|
||||
// raisonnement (ctxAfter n'y retire rien), qui couvre déjà ce complément ; l'y
|
||||
// ajouter le ferait compacter plus tôt qu'avant.
|
||||
func (c *Conversation) ctxNowLocked(msgs []Message, local bool) int {
|
||||
if !local {
|
||||
return c.CtxUsed
|
||||
}
|
||||
return ctxPending(c.CtxUsed, c.ctxUsedLen, msgs)
|
||||
}
|
||||
|
||||
// CompactNow force une compaction du contexte MAINTENANT, sans attendre le seuil
|
||||
// (bouton « compacter » de l'UI). Détaché comme la génération : émet la bannière
|
||||
// de progression, résume les anciens tours, remplace le torse et persiste. Les
|
||||
@@ -982,6 +999,7 @@ func (c *Conversation) CompactNow() error {
|
||||
if !healthCheck() {
|
||||
return errModelLoading
|
||||
}
|
||||
local := !externalActive()
|
||||
c.mu.Lock()
|
||||
if c.Generating {
|
||||
c.mu.Unlock()
|
||||
@@ -991,7 +1009,7 @@ func (c *Conversation) CompactNow() error {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
c.cancel = cancel
|
||||
msgs := append([]Message(nil), c.Messages...)
|
||||
lastReal := ctxPending(c.CtxUsed, c.ctxUsedLen, msgs) // dernier contexte réel mesuré, pour estimer le surcoût fixe
|
||||
lastReal := c.ctxNowLocked(msgs, local) // dernier contexte réel mesuré, pour estimer le surcoût fixe
|
||||
epoch := c.epoch
|
||||
c.mu.Unlock()
|
||||
|
||||
|
||||
@@ -47,6 +47,8 @@ func TestGenHeadroomShort(t *testing.T) {
|
||||
{"65k, courte : plancher 8k + marge, sous 84 %", 55000, 2000, 65536, false, true},
|
||||
{"65k, longue : 1,2×15k ne tient plus", 47000, 15000, 65536, true, false},
|
||||
{"65k, longue mais la place y est", 40000, 15000, 65536, false, false},
|
||||
{"65k, énorme (45k) mais historique tout juste compacté", 20000, 45000, 65536, false, false},
|
||||
{"65k, énorme (45k) au-delà de la moitié", 33000, 45000, 65536, true, false},
|
||||
{"131k, courte : rien avant le seuil", 98000, 4000, 131072, false, false},
|
||||
{"131k, très longue génération", 95000, 30000, 131072, true, false},
|
||||
{"8k, plancher ramené à 1/8", 6000, 0, 8192, false, false},
|
||||
@@ -67,6 +69,14 @@ func TestGenHeadroomShort(t *testing.T) {
|
||||
if genHeadroomShort(below, 1, w) {
|
||||
t.Errorf("fenêtre %d : la garde devance le seuil sans génération longue", w)
|
||||
}
|
||||
// Génération aussi longue que la fenêtre : jamais avant la moitié, sinon
|
||||
// l'historique fraîchement compacté la redéclenche à chaque étape.
|
||||
if genHeadroomShort(w/2, w, w) {
|
||||
t.Errorf("fenêtre %d : la garde part à 50 %% (compaction en boucle)", w)
|
||||
}
|
||||
if !genHeadroomShort(w/2+1, w, w) {
|
||||
t.Errorf("fenêtre %d : la garde ne part pas au-delà de 50 %% avec une génération énorme", w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,3 +302,16 @@ func TestPasseIsoleeNeTouchePasLeContexte(t *testing.T) {
|
||||
t.Fatalf("passe de correction : genPeak = %d, attendu 800", c.genPeak)
|
||||
}
|
||||
}
|
||||
|
||||
// Preset externe : le compte mesuré seul, comme avant ; local : complété des
|
||||
// messages arrivés depuis la mesure.
|
||||
func TestCtxNowLockedExternal(t *testing.T) {
|
||||
msgs := []Message{um("bonjour"), am("salut"), um(strings.Repeat("x", 4000))}
|
||||
c := &Conversation{CtxUsed: 30000, ctxUsedLen: 2}
|
||||
if got := c.ctxNowLocked(msgs, false); got != 30000 {
|
||||
t.Errorf("externe : %d, attendu le compte mesuré seul (30000)", got)
|
||||
}
|
||||
if got, want := c.ctxNowLocked(msgs, true), 30000+estimateTokens(msgs[2:]); got != want {
|
||||
t.Errorf("local : %d, attendu %d", got, want)
|
||||
}
|
||||
}
|
||||
@@ -2038,6 +2038,7 @@ func runChat(ctx context.Context, messages []Message, temperature float64, caps
|
||||
cb(StreamEvent{Compacting: &no})
|
||||
logCompact("en-tour", used, messages, c, changed)
|
||||
if changed {
|
||||
lastEst = 0 // estimation d'avant compaction : plus rien à comparer
|
||||
messages = c
|
||||
// La nouvelle base contient déjà tout ce tour : on la publie et on
|
||||
// repart d'un `extra` vide, sinon l'appelant la ré-empilerait avec
|
||||
|
||||
Reference in new issue
Block a user