mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Contexte : corrections de relecture du lot 2 — seul un bloc posé par Loki est retiré, jamais un texte tapé
Les blocs <context_update> de PROJ_SNAPSHOT étaient reconnus à leur seul préfixe de texte, et le nettoyage tournait AUSSI sans la clé (requête, historique persisté, titre, tâche du vérificateur, export, résumé). Un message de l'utilisateur qui commençait par cette balise — un journal de requête collé par qui travaille sur Loki — perdait son début en silence : le défaut n'était plus identique à l'octet. - Message.CtxUpd marque le message où Loki a posé un bloc (prependCtxUpdate), comme ImgRelay pour les relais d'image ; seuls les messages marqués sont nettoyés, et la marque tombe avec le bloc. - La marque ne quitte jamais Loki : retirée à l'envoi avec celle des relais. - Sans la clé, aucun message n'est marqué : rien n'est retouché. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
39487f9449
commit
5f00b6f041
4 files changed
+63
-8
No files matched your search
@@ -117,13 +117,15 @@ func logKeptImagesDropped(phase string, freed int) {
|
||||
func stripRelayTags(msgs []Message) []Message {
|
||||
var out []Message
|
||||
for i, m := range msgs {
|
||||
if !m.ImgRelay {
|
||||
// La marque des blocs <context_update> (PROJ_SNAPSHOT) non plus ne
|
||||
// quitte jamais Loki.
|
||||
if !m.ImgRelay && !m.CtxUpd {
|
||||
continue
|
||||
}
|
||||
if out == nil {
|
||||
out = append([]Message(nil), msgs...)
|
||||
}
|
||||
m.ImgRelay = false
|
||||
m.ImgRelay, m.CtxUpd = false, false
|
||||
out[i] = m
|
||||
}
|
||||
if out == nil {
|
||||
|
||||
@@ -367,10 +367,20 @@ func stripCtxUpdText(s string) (string, bool) {
|
||||
|
||||
// withoutCtxUpdate : le message sans son bloc de tête, ok=false s'il n'en avait
|
||||
// pas (message rendu tel quel). Contenu texte ou multimodal ([]any, []map).
|
||||
// Seulement un message que Loki a marqué (CtxUpd) : sans la clé, aucun ne
|
||||
// l'est, et un texte tapé par l'utilisateur n'est jamais retouché.
|
||||
func withoutCtxUpdate(m Message) (Message, bool) {
|
||||
if m.Role != "user" {
|
||||
if m.Role != "user" || !m.CtxUpd {
|
||||
return m, false
|
||||
}
|
||||
m, ok := withoutCtxUpdateText(m)
|
||||
if ok {
|
||||
m.CtxUpd = false
|
||||
}
|
||||
return m, ok
|
||||
}
|
||||
|
||||
func withoutCtxUpdateText(m Message) (Message, bool) {
|
||||
switch c := m.Content.(type) {
|
||||
case string:
|
||||
if s, ok := stripCtxUpdText(c); ok {
|
||||
@@ -463,6 +473,7 @@ func prependCtxUpdate(m Message, block string) (Message, bool) {
|
||||
default:
|
||||
return m, false
|
||||
}
|
||||
m.CtxUpd = true
|
||||
return m, true
|
||||
}
|
||||
|
||||
|
||||
@@ -247,7 +247,9 @@ func TestProjSnapCompactionRafraichit(t *testing.T) {
|
||||
c.Messages = append(c.Messages, am("étape "+strings.Repeat("détail ", 300)), um("continue "+strings.Repeat("x", 600)))
|
||||
}
|
||||
// Le message avec la mise à jour en dernier : il tombe dans la queue gardée.
|
||||
c.Messages = append(c.Messages, am("vu"), um(ctxUpdOpen+` at="x">`+"\n~ poids — 72.4 kg\n"+ctxUpdClose+"\n\nencore"))
|
||||
upd := um(ctxUpdOpen + ` at="x">` + "\n~ poids — 72.4 kg\n" + ctxUpdClose + "\n\nencore")
|
||||
upd.CtxUpd = true
|
||||
c.Messages = append(c.Messages, am("vu"), upd)
|
||||
if _, changed := c.compactAndPublish(context.Background(), c.epoch, "test", append([]Message(nil), c.Messages...), 50000, caps, compactOpts{}); !changed {
|
||||
t.Fatal("compaction sans effet")
|
||||
}
|
||||
@@ -511,7 +513,9 @@ func TestProjSnapRetireDesLecteurs(t *testing.T) {
|
||||
testHome(t)
|
||||
blk := ctxUpdOpen + ` at="t">` + "\n+ [A](a.md)\n" + ctxUpdClose + "\n\n"
|
||||
c := newTestConv()
|
||||
c.Messages = []Message{um(blk + "corrige le build"), am("ok")}
|
||||
first := um(blk + "corrige le build")
|
||||
first.CtxUpd = true
|
||||
c.Messages = []Message{first, am("ok")}
|
||||
if got := c.lastUserText(); got != "corrige le build" {
|
||||
t.Fatalf("tâche du vérificateur : %q", got)
|
||||
}
|
||||
@@ -523,8 +527,8 @@ func TestProjSnapRetireDesLecteurs(t *testing.T) {
|
||||
t.Fatalf("export : %v %s", err, out)
|
||||
}
|
||||
multi := []Message{
|
||||
{Role: "user", Content: []any{map[string]any{"type": "text", "text": blk}, map[string]any{"type": "text", "text": "vois l'image"}}},
|
||||
{Role: "user", Content: []map[string]any{{"type": "text", "text": blk + "suite"}}},
|
||||
{Role: "user", CtxUpd: true, Content: []any{map[string]any{"type": "text", "text": blk}, map[string]any{"type": "text", "text": "vois l'image"}}},
|
||||
{Role: "user", CtxUpd: true, Content: []map[string]any{{"type": "text", "text": blk + "suite"}}},
|
||||
}
|
||||
got := stripContextUpdates(multi)
|
||||
if hasContextUpdates(got) || len(got[0].Content.([]any)) != 1 || got[1].Content.([]map[string]any)[0]["text"] != "suite" {
|
||||
@@ -534,7 +538,7 @@ func TestProjSnapRetireDesLecteurs(t *testing.T) {
|
||||
t.Fatal("l'original ne doit pas être modifié")
|
||||
}
|
||||
// Un texte qui en parle plus loin n'est pas touché.
|
||||
plain := []Message{um("explique " + blk)}
|
||||
plain := []Message{{Role: "user", CtxUpd: true, Content: "explique " + blk}}
|
||||
if s := stripContextUpdates(plain); msgText(s[0]) != msgText(plain[0]) {
|
||||
t.Fatal("bloc hors tête retiré")
|
||||
}
|
||||
@@ -588,3 +592,36 @@ func TestReplaceProjectHead(t *testing.T) {
|
||||
t.Fatalf("%+v", out)
|
||||
}
|
||||
}
|
||||
|
||||
// Corrections de relecture du lot 2 : seul un bloc posé par Loki (marqué) est
|
||||
// retiré. Sans la clé, un message TAPÉ qui commence par la même balise reste
|
||||
// tel quel partout — historique, envoi, titre — et la marque ne part jamais
|
||||
// au moteur.
|
||||
func TestProjSnapTexteTapeIntact(t *testing.T) {
|
||||
testHome(t)
|
||||
typed := um(ctxUpdOpen + ` at="x">` + "\ncollé depuis un journal\n" + ctxUpdClose + "\n\nqu'est-ce que c'est ?")
|
||||
c := newTestConv()
|
||||
c.Messages = []Message{typed}
|
||||
if got := stripContextUpdates(c.Messages); msgText(got[0]) != msgText(typed) {
|
||||
t.Fatal("texte tapé retouché")
|
||||
}
|
||||
sent, _, _ := c.turnView(Caps{Agent: true}, c.epoch, append([]Message(nil), c.Messages...), true)
|
||||
if msgText(sent[len(sent)-1]) != msgText(typed) || msgText(c.Messages[0]) != msgText(typed) {
|
||||
t.Fatalf("texte tapé retouché à l'envoi ou dans l'historique : %q", msgText(sent[len(sent)-1]))
|
||||
}
|
||||
if got := convSummary(c.Messages); !strings.Contains(got, "collé depuis un journal") {
|
||||
t.Fatalf("titre : %q", got)
|
||||
}
|
||||
marked, ok := prependCtxUpdate(um("suite"), ctxUpdOpen+` at="t">`+"\n+ x\n"+ctxUpdClose+"\n\n")
|
||||
if !ok || !marked.CtxUpd {
|
||||
t.Fatal("bloc posé sans marque")
|
||||
}
|
||||
wire, _ := wireMessages([]Message{marked}, false, echoPolicy{})
|
||||
b, _ := json.Marshal(wire)
|
||||
if strings.Contains(string(b), "ctx_upd") {
|
||||
t.Fatalf("marque envoyée au moteur : %s", b)
|
||||
}
|
||||
if s, ok := withoutCtxUpdate(marked); !ok || s.CtxUpd || msgText(s) != "suite" {
|
||||
t.Fatalf("bloc marqué non retiré : %+v", s)
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,11 @@ type Message struct {
|
||||
// relais et non comme une demande (isLokiInjected) ; retiré à l'envoi
|
||||
// (wireMessages). Faux : JSON inchangé.
|
||||
ImgRelay bool `json:"img_relay,omitempty"`
|
||||
// CtxUpd : Loki a posé un bloc <context_update> en tête de ce message
|
||||
// utilisateur (PROJ_SNAPSHOT, chat_projsnap.go) — jamais sans la clé. Seuls
|
||||
// les messages marqués sont nettoyés : un texte TAPÉ qui commencerait par la
|
||||
// même balise reste tel quel. Persisté, retiré à l'envoi (wireMessages).
|
||||
CtxUpd bool `json:"ctx_upd,omitempty"`
|
||||
// imgTokens : coût mesuré par le moteur de l'image d'un relais GARDÉ dans
|
||||
// l'historique (KEEP_TURN_IMAGES) ; 0 = image non gardée, ou déjà retirée.
|
||||
// En mémoire seulement : un fil rechargé a de toute façon perdu ses images
|
||||
|
||||
Reference in new issue
Block a user