mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Les fichiers circulent enfin dans les deux sens. Envoi : trombone dans le composeur, glisser-deposer sur toute la fenetre, ou Ctrl+V. Le fichier est depose dans uploads/ du dossier de travail de l'agent et son chemin est annonce au modele, qui en fait ce qu'il veut avec ses outils. Rien n'est interprete au passage, donc tout type de fichier passe. Le depot n'a lieu qu'a l'ENVOI du message : retirer un fichier de la liste ne laisse aucune trace sur le disque. Transport en base64 dans du JSON et non en multipart, seule forme que le tunnel E2E dispatche : l'acces distant marche sans code specifique. Reception : le modele ecrit un lien Markdown ordinaire vers un fichier de son dossier de travail, et le clic telecharge. Il repondait jusqu'ici par un chemin serveur, inutilisable depuis un navigateur. Le clic passe par fetch + blob (la cle de pilotage voyage dans un en-tete qu'une navigation ne porterait pas, et le chemin de base change derriere le tunnel). Markdown refusant les espaces non echappes dans une cible de lien, ils sont encodes avant rendu : sans ca [x](mon rapport.pdf) ne produisait rien du tout. Perimetre du telechargement strictement limite au dossier de travail : '..', chemins absolus et liens symboliques qui en sortent sont refuses, et le fichier est toujours servi en piece jointe avec nosniff — un .html produit par le modele ne doit pas s'executer dans l'origine de l'UI, ou il lirait la cle de pilotage. Preambule du mode agent : ~2350 -> ~1770 tokens, sans perdre une regle. Le poids etait dans les schemas d'outils (le double du prompt), pas dans le prompt. Celui-ci reenumerait des outils que les schemas decrivent deja, et la regle « write, jamais echo » y figurait trois fois. Trois tests gardent le budget : ce prompt a deja regrossi ligne par ligne deux fois. Aussi : l'export mentionne les pieces jointes (un message sans texte donnait une section vide), et le decompte de contexte perd le mot « contexte » au profit du nom du modele.
66 lines
2.4 KiB
Go
66 lines
2.4 KiB
Go
package ajean
|
|
|
|
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.
|
|
const promptCharBudget = 7500 // ~1875 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)
|
|
}
|
|
}
|