mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-17 20:26:35 +02:00
Les deux outils qui rappellent un bloc archivé par la compaction ne passent plus par /tool/exec du Go : src/recall.rs porte toolRecall et toolRecallSearch (id tolérant comme recallIDToSeq, mots-clés en minuscules rune par rune comme strings.ToLower, score libellé double et plus récent d'abord, limites, extraits et textes du Go). L'archive reste au Go, seul à tenir la séquence de la base, qui la sert à la façade (GET /internal/v1/recall/block?seq=N et /internal/v1/recall/blocks, blocs chiffrés sous une mémoire verrouillée exclus) ; ses propres outils passent par les mêmes lectures. Go injoignable : l'appel passe par lui. Parité : journal de 41 appels sur une archive de 35 blocs tiré du Go (parity_recall_test.go → tests/parity_recall/recall.json), rejoué par tests/recall.rs ; un tour de la façade qui rappelle et cherche (tests/lot6.rs) ; TestRecallInterneLecture côté Go. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
144 lines
5.3 KiB
Go
144 lines
5.3 KiB
Go
package loki
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// parity_recall_test.go — journal de référence des outils recall et
|
|
// recall_search (lot 46 de la migration Rust) : une archive variée (accents,
|
|
// casse Unicode, contenus longs, beaucoup de blocs), puis les appels de
|
|
// l'agent, des ids tolérés aux limites de recherche. Rangé dans
|
|
// rust/loki-rs/tests/parity_recall/recall.json avec les blocs tels que
|
|
// l'archive les sert ; tests/recall.rs le rejoue contre le port.
|
|
//
|
|
// LOKI_PARITY_UPDATE=1 go test ./internal/loki -run TestPariteRecall
|
|
|
|
const parityRecallFile = "../../rust/loki-rs/tests/parity_recall/recall.json"
|
|
|
|
type parityRecallStep struct {
|
|
Name string `json:"name"`
|
|
Tool string `json:"tool"`
|
|
Args map[string]any `json:"args"`
|
|
Result string `json:"result"`
|
|
}
|
|
|
|
func TestPariteRecall(t *testing.T) {
|
|
testHome(t)
|
|
arch := func(role, tool, content string) {
|
|
t.Helper()
|
|
if _, err := archiveRecallBlock(recallLabel(role, tool, content), role, content); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
arch("tool", "web_read", "Recette du gratin dauphinois\n"+strings.Repeat("pommes de terre, crème, ail ", 30))
|
|
arch("tool", "web_read", "Horaires SNCF\nle train de 14h12 part quai 3, le TRAIN suivant à 15h")
|
|
arch("tool", "bash", "\n\n $ ls -la\na.go b.go\tc.go")
|
|
arch("assistant", "", "Voici le résumé de l'ÉTÉ : plage, soleil, Été indien.")
|
|
arch("user", "", "İSTANBUL et ΣΟΦΙΑ en majuscules")
|
|
arch("tool", "", "outil sans nom : train de marchandises")
|
|
arch("tool", "read_file", strings.Repeat("é", 200)+" fin")
|
|
arch("tool", "web_read", "<html><b>balises</b> & entités & \"guillemets\"</html>")
|
|
arch("tool", "bash", "")
|
|
arch("tool", "mem_search", strings.Repeat("très longue première ligne sans fin ", 5)+"\nsuite")
|
|
for i := 0; i < 25; i++ {
|
|
arch("tool", "bash", fmt.Sprintf("lot %d de la migration : sortie numéro %d", i, i))
|
|
}
|
|
|
|
var steps []parityRecallStep
|
|
run := func(name, tool string, args map[string]any) {
|
|
var r string
|
|
if tool == "recall" {
|
|
r = toolRecall(args)
|
|
} else {
|
|
r = toolRecallSearch(args)
|
|
}
|
|
steps = append(steps, parityRecallStep{Name: name, Tool: tool, Args: args, Result: r})
|
|
}
|
|
id := func(v any) map[string]any { return map[string]any{"id": v} }
|
|
q := func(v any, kv ...any) map[string]any {
|
|
m := map[string]any{"query": v}
|
|
for i := 0; i+1 < len(kv); i += 2 {
|
|
m[kv[i].(string)] = kv[i+1]
|
|
}
|
|
return m
|
|
}
|
|
run("sans id", "recall", map[string]any{})
|
|
run("id non texte", "recall", id(7.0))
|
|
run("id blanc", "recall", id(" "))
|
|
run("r1", "recall", id("r1"))
|
|
run("R2 majuscule", "recall", id("R2"))
|
|
run("espaces", "recall", id(" 3 "))
|
|
run("sans préfixe", "recall", id("4"))
|
|
run("espace après le préfixe", "recall", id("r 5"))
|
|
run("deux préfixes", "recall", id("rR6"))
|
|
run("préfixes à l'envers", "recall", id("Rr6"))
|
|
run("zéros de tête", "recall", id("r007"))
|
|
run("contenu long", "recall", id("r7"))
|
|
run("caractères spéciaux", "recall", id("r8"))
|
|
run("bloc vide", "recall", id("r9"))
|
|
run("mal formé", "recall", id("rr"))
|
|
run("négatif", "recall", id("r-1"))
|
|
run("signe plus", "recall", id("+1"))
|
|
run("zéro", "recall", id("r0"))
|
|
run("absent", "recall", id("r999"))
|
|
run("démesuré", "recall", id("r99999999999999999999"))
|
|
run("guillemets dans l'id", "recall", id(`r"9\`))
|
|
|
|
run("sans requête", "recall_search", map[string]any{})
|
|
run("requête non texte", "recall_search", q(3.0))
|
|
run("requête blanche", "recall_search", q(" "))
|
|
run("mots trop courts", "recall_search", q("a b ! ?"))
|
|
run("train horaires", "recall_search", q("train horaires"))
|
|
run("casse", "recall_search", q("TRAIN"))
|
|
run("accents majuscules", "recall_search", q("ÉTÉ"))
|
|
run("i turc", "recall_search", q("İstanbul"))
|
|
run("sigma", "recall_search", q("σοφια"))
|
|
run("souligné coupé", "recall_search", q("web_read"))
|
|
run("extrait borné", "recall_search", q("fin éé"))
|
|
run("première ligne longue", "recall_search", q("suite"))
|
|
run("limite 1", "recall_search", q("train", "limit", 1.0))
|
|
run("limite tronquée", "recall_search", q("train", "limit", 2.7))
|
|
run("limite négative", "recall_search", q("lot", "limit", -3.0))
|
|
run("défaut", "recall_search", q("migration"))
|
|
run("vingt au plus", "recall_search", q("lot migration", "limit", 50.0))
|
|
run("limite texte ignorée", "recall_search", q("lot", "limit", "3"))
|
|
run("rien ne correspond", "recall_search", q("motabsolumentabsent"))
|
|
run("ponctuation", "recall_search", q("«entités»,&"))
|
|
|
|
blocks := recallAll()
|
|
for i := range blocks {
|
|
blocks[i].Created = 0
|
|
}
|
|
var buf bytes.Buffer
|
|
enc := json.NewEncoder(&buf)
|
|
enc.SetEscapeHTML(false)
|
|
enc.SetIndent("", " ")
|
|
if err := enc.Encode(map[string]any{"blocks": blocks, "steps": steps}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := buf.Bytes()
|
|
if os.Getenv("LOKI_PARITY_UPDATE") == "1" {
|
|
if err := os.MkdirAll(filepath.Dir(parityRecallFile), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(parityRecallFile, got, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return
|
|
}
|
|
want, err := os.ReadFile(parityRecallFile)
|
|
if err != nil {
|
|
t.Fatalf("journal de référence absent (%v) : LOKI_PARITY_UPDATE=1 pour le créer", err)
|
|
}
|
|
if !bytes.Equal(got, want) {
|
|
t.Errorf("recall ne produit plus le journal rangé dans %s.\n"+
|
|
"LOKI_PARITY_UPDATE=1 pour le régénérer si le changement est voulu.", parityRecallFile)
|
|
}
|
|
}
|