Cache : KEEP_TURN_IMAGES garde les images d'outils et NUDGE_IN_TOOL range le rappel de budget, en opt-in

Une capture ou une image vue par see_image ne vivait que le temps du tour :
au tour suivant elle manquait à l'historique, le préfixe en cache divergeait
à elle et toute la boucle d'outils qui suivait était recalculée. Sur Qwen3.5,
le rappel de budget en message user faisait de même pour le tour en cours :
le nouveau message devient la « dernière question » et tout le tour est rendu
autrement. Deux clés, off par défaut et étiquetées zone grise ; sans elles,
requêtes identiques à l'octet près (testé).

- KEEP_TURN_IMAGES : image gardée par référence (chatimg, chiffrée si la
  mémoire l'est), relue à l'identique avant d'être gardée, vision active
  revérifiée à chaque tour ; preset externe seulement s'il déclare la vision
- coût mesuré par le moteur (écart de prompt_tokens moins le texte ajouté) ;
  image non mesurable = éphémère comme avant ; total sous 10 % de la fenêtre,
  la première qui dépasse et les suivantes du tour restent éphémères
- compaction, réduction forcée et début ou milieu de tour au seuil retirent
  ces images d'abord (légende et imageLostMarker gardés), le résumé ne suit
  que s'il reste nécessaire ; clé retirée ou vision perdue : retirées au tour
- relais marqué ImgRelay (persisté, retiré à l'envoi) : ni demande réinjectée
  ni preuve de demande servie, fil rouvert compris ; une image non gardée
  n'entre plus dans l'historique par une compaction en cours de tour
- discussion seulement (ni tâche, ni sous-agent, ni vérification) ; fichier
  d'image rafraîchi à chaque usage, l'élagage de 24 h ne le prend pas
- NUDGE_IN_TOOL : rappel de budget au bout du dernier résultat d'outil, même
  message persisté ; moteur local, sonde de gabarit « préfixe instable » pour
  ce modèle ; sinon, ou forme inattendue, message à part comme avant

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5.5 committed 2026-10-04 09:46:01 +02:00
1 parent b0c53e32af
commit f74f64ed90
9 files changed
+1153 -5

No files matched your search

+56
View File
@@ -55,6 +55,11 @@ func isLokiInjected(m Message) bool {
if m.Role != "user" {
return false
}
// Relais d'image d'un outil gardé dans l'historique (KEEP_TURN_IMAGES) :
// une image montrée par loki, pas une demande. Jamais posé sans la clé.
if m.ImgRelay {
return true
}
s, ok := m.Content.(string)
if !ok {
return false
@@ -156,3 +161,54 @@ func withTrailingHint(msgs []Message, hint string) []Message {
// Dernier message d'une autre forme (cas théorique) : un message à part.
return append(out, Message{Role: "user", Content: lokiNotePrefix + hint})
}
// nudgeInToolOn : NUDGE_IN_TOOL (opt-in, off par défaut, ZONE GRISE). Sur un
// gabarit dont le rendu d'un tour d'outil change dès qu'un message `user`
// s'ajoute (Qwen3.5 : le nouveau message devient la « dernière question », et
// tous les messages assistant du tour sont rendus autrement), le rappel de
// budget en message à part fait recalculer toute la boucle d'outils du tour.
// Posé au bout du dernier résultat d'outil, il ne coûte que lui-même.
//
// Le prix : une consigne glissée dans une sortie d'outil, que les modèles
// entraînés contre l'injection de prompt peuvent suivre moins bien — d'où
// l'opt-in, à n'activer qu'après avoir comparé l'obéissance aux rappels.
//
// Moteur local seulement, et seulement si la sonde de gabarit
// (chat_tplprobe.go) a conclu « préfixe instable » pour CE modèle : « inconnu »
// ou un autre modèle laissent le message à part.
func nudgeInToolOn(cfg map[string]string, ep chatEndpoint) bool {
switch strings.ToLower(strings.TrimSpace(cfg["NUDGE_IN_TOOL"])) {
case "on", "1", "true", "yes", "oui":
default:
return false
}
if ep.External {
return false
}
r, ok := tplCapsCurrent()
return ok && r.PrefixStable == tplNo && r.Model != "" && r.Model == reasoningEchoModel(cfg)
}
// nudgeIntoTool ajoute le rappel au bout du dernier résultat d'outil, dans la
// vue du modèle ET dans l'historique — le même message, modifié des deux côtés
// à l'identique, pour que le tour suivant rejoue les mêmes octets. Ce résultat
// n'est pas encore parti au moteur : rien de ce qu'il a en cache ne bouge.
// ok=false (dernier message d'une autre forme, historique réécrit entre-temps
// par une compaction) : l'appelant passe par appendNudge. Copies, jamais de
// modification en place.
func nudgeIntoTool(messages, extra []Message, text string) ([]Message, []Message, bool) {
n, e := len(messages), len(extra)
if n == 0 || e == 0 {
return messages, extra, false
}
last, kept := messages[n-1], extra[e-1]
s, ok := last.Content.(string)
ks, kok := kept.Content.(string)
if !ok || !kok || s != ks || last.Role != "tool" || kept.Role != "tool" || last.ToolCallID != kept.ToolCallID {
return messages, extra, false
}
last.Content = s + "\n\n" + text
messages = append(messages[:n-1:n-1], last)
extra = append(extra[:e-1:e-1], last)
return messages, extra, true
}