Optimiseur : « loki tune » refuse un moteur qui n'est pas celui de son LOKI_HOME

En ligne de commande, « loki tune » et le processus web avec des LOKI_HOME
différents (sudo qui retire un LOKI_HOME exporté, variable posée dans un seul
shell) ne se voient pas : le verrou tombait dans un dossier que l'interface ne
lit pas, l'arrêt du « vrai » moteur visait celui d'une autre configuration, et
l'essai chargeait à côté d'un moteur bien vivant — VRAM saturée, mesures
fausses, relance possible par l'interface en pleine mesure.

- Avant de commencer, la CLI regarde ce qui répond sur le port de son
  LOKI_HOME : un llama-server (/health 200, 503 ou 401) alors que ce dossier
  dit son moteur arrêté, un 401 sur /props avec sa clé d'API, ou un
  model_path qui n'est pas son MODEL (même fichier vérifié par os.SameFile)
  — refus, avec LOKI_HOME en clair et la marche à suivre (relancer avec le
  bon LOKI_HOME, ou le bouton « Optimiser… »).
- Tout ce qui ne conclut pas laisse passer : rien n'écoute, autre service
  (404), /props absent d'un moteur ancien, chemin illisible d'ici. Le
  processus web n'est pas concerné : il fait foi pour son moteur.
- Test : faux moteurs httptest pour chaque cas, refus et passages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5.5 committed 2026-10-04 14:41:53 +02:00
1 parent a325e98dad
commit ac40a2556a
3 files changed
+128 -1

No files matched your search

+60
View File
@@ -4,6 +4,8 @@ import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
@@ -897,3 +899,61 @@ func TestTuneRecoverWatch(t *testing.T) {
t.Error("verrou périmé non retiré")
}
}
// « loki tune » d'un autre LOKI_HOME que le processus web (sudo) : le moteur
// qui répond sur le port de ce dossier n'est pas le sien — refus en clair.
// Tout ce qui ne conclut pas laisse passer.
func TestTuneForeignEngine(t *testing.T) {
home := testHome(t)
ours := filepath.Join(home, "ours.gguf")
other := filepath.Join(home, "other.gguf")
for _, p := range []string{ours, other} {
if err := os.WriteFile(p, []byte("GGUF"), 0o644); err != nil {
t.Fatal(err)
}
}
engine := func(health, props int, model string) *httptest.Server {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/health":
w.WriteHeader(health)
case "/props":
w.WriteHeader(props)
if props == http.StatusOK {
_ = json.NewEncoder(w).Encode(map[string]any{"model_path": model})
}
default:
w.WriteHeader(http.StatusNotFound)
}
}))
t.Cleanup(srv.Close)
return srv
}
closed := httptest.NewServer(http.NotFoundHandler())
closed.Close()
ctx := context.Background()
for _, c := range []struct {
name string
base string
active bool
refused bool
}{
{"rien n'écoute", closed.URL, false, false},
{"autre service (404) sur le port, moteur arrêté", engine(404, 404, "").URL, false, false},
{"moteur qui répond, ce dossier le dit arrêté", engine(200, 200, ours).URL, false, true},
{"moteur en chargement (503), ce dossier le dit arrêté", engine(503, 503, "").URL, false, true},
{"notre moteur", engine(200, 200, ours).URL, true, false},
{"un autre MODEL", engine(200, 200, other).URL, true, true},
{"clé d'API refusée", engine(200, 401, "").URL, true, true},
{"/props absent (moteur ancien)", engine(200, 404, "").URL, true, false},
{"chemin illisible d'ici", engine(200, 200, filepath.Join(home, "absent.gguf")).URL, true, false},
} {
err := tuneForeignEngine(ctx, c.base, c.active, nil, ours)
if (err != nil) != c.refused {
t.Errorf("%s : %v", c.name, err)
}
if err != nil && !strings.Contains(err.Error(), "LOKI_HOME="+home) {
t.Errorf("%s : message sans LOKI_HOME : %v", c.name, err)
}
}
}