Files
Loki/internal/loki/chat_images_test.go
T
MichaelandClaude Opus 5.5 f7a4ee71e7 Chat : les images ne sont plus réécrites en base64 à chaque tour
Repris d'AJEAN 0.15.7 (chat_images.go et son test), adapté.

Une image jointe (ou une capture de web_screenshot) vivait en base64 dans
les messages de la conversation, et persist() réécrit la conversation
ENTIÈRE à chaque fin de tour : une photo de téléphone, c'étaient des Mo
remis sur disque à chaque réponse. Elle est désormais rangée une fois sous
$LOKI_HOME/chatimg/<empreinte>.<ext> (hors du dossier de travail, que l'IA
peut vider) ; l'historique n'en garde que « loki-img:<nom> ». Juste avant
l'envoi, expandImageRefs remet les octets exacts (cache mémoire borné à
64 Mo) : pour le modèle, rien ne change.

DEUX ÉCARTS AVEC L'AMONT
- Copie à l'écriture : l'amont remplace l'URL en place. Chez nous, persist
  peut tourner pendant qu'un runChat sérialise ces mêmes maps (compactage
  en vol, bascule de projet) — « concurrent map read and map write », fatal.
  Les parties modifiées sont donc recopiées.
- Le rechargement reste éphémère : stripImageParts retire toujours les
  images d'une conversation rouverte (garde-fou contre un modèle sans vision
  qui lirait le base64 comme du texte). L'amont, lui, les garde ; on
  pourra le suivre plus tard si l'on veut.

Pas de migration des archives : une conversation rouverte est allégée par
stripImageParts, comme avant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:05:34 +02:00

47 lines
1.5 KiB
Go

package loki
import (
"encoding/base64"
"encoding/json"
"strings"
"testing"
)
// Une image en base64 part sur disque et revient identique à l'envoi au modèle ;
// l'historique ne garde qu'une référence de quelques octets.
func TestImageRefsRoundTrip(t *testing.T) {
testHome(t)
raw := []byte(strings.Repeat("\x89PNG-fake-image-bytes", 5000))
url := "data:image/png;base64," + base64.StdEncoding.EncodeToString(raw)
msgs := []Message{
{Role: "user", Content: []map[string]any{
{"type": "text", "text": "regarde"},
{"type": "image_url", "image_url": map[string]any{"url": url}},
}},
{Role: "assistant", Content: "vu"},
}
// Relu depuis le JSON (forme []any), comme après un redémarrage.
var reread []Message
b, _ := json.Marshal(msgs)
_ = json.Unmarshal(b, &reread)
for _, set := range [][]Message{msgs, reread} {
if !hasInlineImages(set) || !refImagesInMessages(set) || hasInlineImages(set) {
t.Fatal("l'image aurait dû être rangée par référence")
}
js, _ := json.Marshal(set)
if len(js) > 500 || !strings.Contains(string(js), imgRefScheme) {
t.Fatalf("historique encore lourd : %d octets", len(js))
}
out := expandImageRefs(set)
_, got := partImageURL(contentParts(out[0].Content)[1])
if got != url {
t.Fatal("l'image renvoyée au modèle diffère de l'originale")
}
// L'historique lui-même n'est pas modifié par l'expansion.
if _, u := partImageURL(contentParts(set[0].Content)[1]); !strings.HasPrefix(u, imgRefScheme) {
t.Fatal("expandImageRefs ne doit pas toucher l'historique")
}
}
}