mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-19 13:08:15 +02:00
Trois apports repris d'AJEAN 0.12.9 → 0.13.5, adaptés au fork. MÉMOIRE LONGUE DE LA CONVERSATION (chat_recall.go) Le compactage résumait, donc perdait. Chaque gros bloc du torse est désormais ARCHIVÉ verbatim sous un identifiant court (r7…) dans bbolt AVANT d'être résumé ; le résumé cite l'id, et le modèle le rappelle avec recall(id) ou le retrouve par mots-clés avec recall_search. Le contexte reste plat, l'archive grossit sur disque. En mode code, un fichier lu ou un diff produit tôt dans la session n'est plus perdu au compactage suivant. Au passage : garde-fou anti-résumé-dégénéré, budget de résumé indexé sur la fenêtre, queue ramenée à 20 % (compacter plus large ne coûte plus de perte), et fin de l'épinglage du 1er message user — le modèle répondait à l'ancienne demande au lieu de continuer la tâche en cours. PROJETS (projects.go) Un projet cloisonne une mémoire, ses discussions et ses trackers. La couture est memoryDir(), qui pointe sur le projet actif : tout le code mémoire en hérite sans le savoir. Migration automatique au premier démarrage (memory/*.md → memory/generale/, discussions et tâches orphelines rattachées). Une tâche planifiée vise un projet et l'exécution le force, pour qu'une veille n'écrive pas dans la mémoire du chantier affiché à l'écran. TRACKERS (tracker.go) 3e type de mémoire : les données datées qui s'accumulent. On ne les lit jamais en entier — consultation par niveaux (vue d'ensemble → année → mois → événements), et la dernière valeur de chaque tracker est donnée d'emblée au modèle, qui répond sans appeler l'outil. Aussi : index MEMORY.md tenu par le CODE et injecté en tête de conversation avec la description du projet (le modèle ne peut plus le désynchroniser) ; prompt système rattaché au PRESET et non plus global — stocké en base, pas dans le .env, qui ne saurait pas porter un texte multiligne. Deux correctifs du lot précédent voyagent ici, faute de pouvoir séparer les fichiers : msgText signale la présence d'une image au compactage, et le garde-fou « pensé sans agir » passe à deux relances (nudgeCount/maxNudges). Le paquet UI est regénéré dans le commit suivant, qui touche les mêmes sources. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
78 lines
3.1 KiB
Go
78 lines
3.1 KiB
Go
package loki
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Budget du préambule envoyé À CHAQUE TOUR : prompt système + schémas des outils.
|
|
// Il est payé sur toute la conversation et sort du contexte utile.
|
|
//
|
|
// Ce test existe pour empêcher le regonflement. Le prompt a déjà été raccourci
|
|
// une fois pour une raison de comportement (un préambule verbeux fait
|
|
// sur-raisonner les modèles à reasoning, qui finissent leur tour sans appeler
|
|
// d'outil), puis une seconde fois pour le coût. Les deux fois, il avait
|
|
// regrossi ligne par ligne, chacune paraissant anodine.
|
|
//
|
|
// Approximation 1 tok ≈ 4 caractères : suffisante pour une alerte, et sans
|
|
// dépendance à un tokenizer.
|
|
//
|
|
// Le budget n'a été relevé que pour de VRAIS outils nouveaux, jamais pour des
|
|
// lignes de prompt — et chaque palier dit lequel :
|
|
//
|
|
// 7500 → 7900 : recall + recall_search (chat_recall.go), ~720 car. Le compactage
|
|
// cesse d'être destructif : le modèle ne relance plus les lectures
|
|
// et recherches déjà faites. Il récupère bien plus qu'il ne coûte.
|
|
// 7900 → 8600 : tracker (tracker.go), ~620 car. Un seul schéma pour les quatre
|
|
// actions — quatre outils auraient coûté quatre fois ça.
|
|
//
|
|
// Descriptions déjà réduites au strict nécessaire dans les deux cas. La consigne
|
|
// d'origine tient pour la suite : ce budget ne se relève pas pour du prompt.
|
|
const promptCharBudget = 8600 // ~2150 tokens, tout allumé
|
|
|
|
func TestSystemPromptStaysLean(t *testing.T) {
|
|
caps := Caps{Agent: true, Internet: true, Mem: MemAlways}
|
|
sp := baseSystemPrompt(caps)
|
|
tb, err := json.Marshal(EnabledTools(caps))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
total := len(sp) + len(tb)
|
|
t.Logf("prompt %d car (~%d tok) + outils %d car (~%d tok) = ~%d tok",
|
|
len(sp), len(sp)/4, len(tb), len(tb)/4, total/4)
|
|
if total > promptCharBudget {
|
|
t.Fatalf("préambule à %d car (~%d tok), budget %d car (~%d tok).\n"+
|
|
"Avant de relever le budget : les schémas d'outils pèsent le double du prompt,\n"+
|
|
"et une consigne écrite dans un schéma n'a pas à être répétée dans le prompt.",
|
|
total, total/4, promptCharBudget, promptCharBudget/4)
|
|
}
|
|
}
|
|
|
|
// Le prompt ne doit pas redevenir un catalogue d'outils : leurs schémas partent
|
|
// dans la MÊME requête et les décrivent déjà un par un.
|
|
func TestSystemPromptDoesNotRelistTools(t *testing.T) {
|
|
sp := baseSystemPrompt(Caps{Agent: true, Internet: true, Mem: MemAlways})
|
|
for _, line := range strings.Split(sp, "\n") {
|
|
l := strings.TrimSpace(line)
|
|
if !strings.HasPrefix(l, "- ") {
|
|
continue
|
|
}
|
|
// Une puce qui commence par un nom d'outil = un catalogue qui revient.
|
|
for _, tool := range []string{"bash ", "write ", "edit ", "mem_", "web_"} {
|
|
if strings.HasPrefix(l[2:], tool) {
|
|
t.Fatalf("le prompt réénumère un outil : %q", l)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sans agent ni mémoire, aucun préambule : un modèle de chat simple à qui on
|
|
// ordonne d'appeler des outils invente des appels textuels qui fuient dans la
|
|
// réponse.
|
|
func TestSystemPromptEmptyWithoutTools(t *testing.T) {
|
|
if sp := baseSystemPrompt(Caps{Mem: MemOff}); sp != "" {
|
|
t.Fatalf("préambule non vide sans outils : %q", sp)
|
|
}
|
|
}
|