Passe de qualité : contexte explosé par les captures, sonde vision, courses

Le journal moteur de production a livré la vraie cause première des 500 en
cascade : « request (55407 tokens) exceeds the available context size
(32768) ». Le message multimodal qui relaie une capture d'écran au modèle
était PERSISTÉ dans l'historique ; son base64 (des dizaines de milliers de
tokens) repartait à chaque tour, et la conversation dépassait
définitivement la fenêtre — plus aucun tour ne passait, et l'exception
Jinja du rattrapage (corrigée au commit précédent) masquait tout.

- L'image devient ÉPHÉMÈRE : jointe au tour en cours, jamais à l'historique.
  Le modèle la regarde maintenant ; sa description textuelle, elle, reste.
- stripImageParts guérit les conversations déjà empoisonnées au chargement
  et à la bascule : parties image retirées, texte aplati.
- engineSeesImages : l'image n'est envoyée que si llama-server DÉCLARE la
  vision (/props, modalities.vision, cache 10 s). La clé MMPROJ ne suffit
  pas — projecteur d'un autre modèle ou modèle sans vision (gpt-oss), le
  gabarit sérialise le base64 en texte. La description de l'outil suit le
  même état : ne jamais promettre une image qui n'arrivera pas.

Deux défauts trouvés à l'analyse, au passage :
- renderBody reconstruit le DOM à chaque delta du streaming : une image déjà
  affichée était RE-TÉLÉCHARGÉE à chaque token arrivé après elle. Cache de
  blobs par URL, une seule requête par capture.
- les opérations de discussions (création, bascule, renommage, suppression)
  entrelaçaient leurs lectures-écritures d'index sous requêtes simultanées :
  sérialisées par un verrou dédié.

Tests : historique guéri (aplati sans l'image), transmission conditionnée à
la sonde, description alignée ; suite complète, vet, staticcheck verts.
This commit is contained in:
Loki committed 2026-08-15 14:43:41 +00:00
1 parent 73196a1ff6
commit 4854ae3784
7 files changed
+172 -14

No files matched your search

+8 -1
View File
@@ -1101,11 +1101,18 @@ func runChat(ctx context.Context, messages []Message, temperature float64, caps
// texte, donc sans ce relais le modèle recevait le chemin du fichier
// et rien d'autre — il annonçait alors à l'utilisateur qu'il ne
// voyait pas l'image, alors que le projecteur était bien chargé.
//
// ÉPHÉMÈRE : l'image va dans `messages` (le tour en cours) mais PAS
// dans `extra` (l'historique persistant). Un base64 de capture pèse
// des dizaines de milliers de tokens ; persisté, il était renvoyé à
// CHAQUE tour suivant et la conversation dépassait définitivement le
// contexte (vu en production : requêtes de 55 000 tokens pour une
// fenêtre de 32 768, plus aucun tour ne passait). Le modèle regarde
// l'image MAINTENANT et sa description textuelle, elle, reste.
if tc.Function.Name == "web_screenshot" {
if rel := capturedRelPath(result); rel != "" {
if imgMsg, ok := screenshotImageMessage(rel); ok {
messages = append(messages, imgMsg)
extra = append(extra, imgMsg)
}
}
}