From c1d3757f23b4c1d1eb45017c46fb76f2627244d1 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Sun, 4 Oct 2026 09:49:06 +0200 Subject: [PATCH] =?UTF-8?q?Cache=20:=20KEEP=5FTURN=5FIMAGES,=20relecture?= =?UTF-8?q?=20=E2=80=94=20budget=20plein=20v=C3=A9rifi=C3=A9=20avant=20de?= =?UTF-8?q?=20ranger=20l'image?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le relais rangeait l'image sur disque avant de regarder si le budget des images gardées était déjà plein : un fichier écrit pour rien, élagué 24 h plus tard. - budget vérifié d'abord ; plein, l'image reste éphémère sans être rangée - README : sur un modèle hybride, le gain dépend des points de reprise du moteur après une image, à vérifier dans la télémétrie avant d'adopter la clé - test : budget plein, rien d'écrit dans chatimg Co-Authored-By: Claude Opus 5.5 --- README.md | 7 ++++-- internal/loki/chat_keep_images.go | 30 +++++++++++++++----------- internal/loki/chat_keep_images_test.go | 15 +++++++++++++ 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 02c40d7..708158e 100644 --- a/README.md +++ b/README.md @@ -452,8 +452,11 @@ Ajoutées par ce fork : encore nécessaire ; rouvrir la discussion les retire aussi, comme les pièces jointes. Discussion seulement (ni tâche, ni sous-agent, ni vérification). Un preset externe n'est concerné que si sa vision est déclarée, et l'API - refacture alors ces images à chaque tour. Sans la clé, la requête est - identique à l'octet près. + refacture alors ces images à chaque tour. Le gain suppose que le moteur + reprenne son cache au-delà d'une image ; sur un modèle hybride (Qwen3.5/3.6), + dont les points de reprise ne suivent pas toujours une image, à vérifier dans + la télémétrie (`lost` du tour suivant) avant de l'adopter. Sans la clé, la + requête est identique à l'octet près. - **Rappel de budget dans le résultat d'outil** (clé `NUDGE_IN_TOOL`, **off** par défaut, **zone grise**, `loki config set NUDGE_IN_TOOL on`) : après un grand nombre d'appels d'outils dans un même tour, Loki rappelle au modèle de diff --git a/internal/loki/chat_keep_images.go b/internal/loki/chat_keep_images.go index 9511a17..a1e5d8a 100644 --- a/internal/loki/chat_keep_images.go +++ b/internal/loki/chat_keep_images.go @@ -228,19 +228,25 @@ func (k *keepImages) relay(messages, extra []Message, m Message, from int) ([]Me return append(messages, m), extra } m.ImgRelay = true - if !k.stopped { - if r, w, ok := refRelayImage(m); ok && keptImageTokens(messages) < keepImgBudget() { - if len(k.pend) == 0 { - k.pendReq, k.pendBase, k.pendFrom = k.reqN+1, k.lastAfter, from - } - messages = append(messages, r) - extra = append(extra, r) - k.pend = append(k.pend, keepPend{msgIdx: len(messages) - 1, extraIdx: len(extra) - 1, weight: w}) - return messages, extra - } - k.stopped = true + if k.stopped { + return append(messages, m), extra } - return append(messages, m), extra + // Budget déjà plein : inutile de ranger le fichier. + r, w, ok := Message{}, 0, false + if keptImageTokens(messages) < keepImgBudget() { + r, w, ok = refRelayImage(m) + } + if !ok { + k.stopped = true + return append(messages, m), extra + } + if len(k.pend) == 0 { + k.pendReq, k.pendBase, k.pendFrom = k.reqN+1, k.lastAfter, from + } + messages = append(messages, r) + extra = append(extra, r) + k.pend = append(k.pend, keepPend{msgIdx: len(messages) - 1, extraIdx: len(extra) - 1, weight: w}) + return messages, extra } // sending : une requête part avec n messages. diff --git a/internal/loki/chat_keep_images_test.go b/internal/loki/chat_keep_images_test.go index 8a203f5..590f644 100644 --- a/internal/loki/chat_keep_images_test.go +++ b/internal/loki/chat_keep_images_test.go @@ -526,3 +526,18 @@ func TestKeepImagesRetireesEnCoursDeTour(t *testing.T) { t.Fatalf("extra après publication : %+v", extra) } } + +// Budget déjà plein : l'image suivante reste éphémère sans même être rangée. +func TestKeepImagesBudgetPleinAvantRangement(t *testing.T) { + testHome(t) + k := &keepImages{} + msgs := []Message{um("q"), keptRelay(keepImgBudget())} + img := seeImageMessage("x.png", imageURLPart([]byte("png"), "image/png")) + got, extra := k.relay(msgs, nil, img, 1) + if len(got) != 3 || len(extra) != 0 || !k.stopped || len(k.pend) != 0 || !got[2].ImgRelay { + t.Fatalf("relais au budget plein : %+v / %+v", got, k) + } + if ents, _ := os.ReadDir(chatImgDir()); len(ents) != 0 { + t.Fatal("image rangée alors que le budget était plein") + } +}