Lot 16 : pilotage du moteur tenu par la façade (LOKI_ENGINE=rust)

La façade sert /api/start, /api/stop, /api/restart, /api/vram/unload et
/api/vram/reload quand LOKI_ENGINE=rust (expérimental ; le Go reste le
défaut). Elle lance elle-même « loki-rs engine <action> », le superviseur
que le Go appelait déjà en conteneur, avec l'environnement qu'il lui
passait et de quoi joindre l'API interne du Go ; sa dernière ligne
d'erreur devient l'erreur rendue. Le contrôle d'avant démarrage
(preflightEngine : preset externe, Strata, BIN, MODEL, tranches) et le
bilan de VRAM du déchargement (gpuSettle) sont portés.

La façade ne tient ce lot qu'en conteneur : sous systemd ou avec
LOKI_ENGINE_SUPERVISOR=go, loki-rs ne passe pas la variable au Go.

Restent au Go derrière son API interne (facade_engine.go) : le verrou de
l'optimiseur avant tout démarrage (/internal/v1/service/prestart), la
réponse en cours quand il tient la conversation
(/internal/v1/chat/generating) et l'arrêt de la dictée quand il la tient
(/internal/v1/dictate/stop).

Parité : parity_engine_test.go tire du Go dix bilans de VRAM, vingt-six
installations passées au préflight, les messages du superviseur et dix
réponses de route, rejoués par tests/enginectl.rs ; démarrage, arrêt,
redémarrage, déchargement chiffré et rechargement vérifiés de bout en bout
avec un faux superviseur et un faux nvidia-smi ; TestFacadeMoteur côté Go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
This commit is contained in:
Claude committed 2026-10-15 18:33:31 +00:00
1 parent a50b8f3cc6
commit 2da2efc1fd
19 files changed
+2225 -2

No files matched your search

+75
View File
@@ -570,3 +570,78 @@ func TestFacadePresets(t *testing.T) {
t.Fatalf("reçu par la façade : %d appels", n)
}
}
// Le pilotage du moteur (LOKI_ENGINE=rust, lot 16) part à la façade, et l'API
// interne lui rend ce qui reste ici : verrou de l'optimiseur avant un
// démarrage, génération en cours, arrêt de la dictée.
func TestFacadeMoteur(t *testing.T) {
testHome(t)
f := demarrerFacade(t)
t.Setenv(facadeChatEnv, "")
local := func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "go") }
for p := range facadeEngineRoutes {
w := httptest.NewRecorder()
withFacadeChat(p, local)(w, httptest.NewRequest("POST", p, nil))
if w.Body.String() != "go" {
t.Fatalf("%s servi ailleurs sans LOKI_ENGINE : %q", p, w.Body.String())
}
}
t.Setenv(facadeEngineEnv, "rust")
for p := range facadeEngineRoutes {
w := httptest.NewRecorder()
withFacadeChat(p, local)(w, httptest.NewRequest("POST", p, strings.NewReader("{}")))
if w.Body.String() != f.reply {
t.Fatalf("%s : réponse %q", p, w.Body.String())
}
}
if n := len(f.recu()); n != len(facadeEngineRoutes) {
t.Fatalf("reçu par la façade : %d appels", n)
}
// Avant un démarrage : le verrou, puis le ménage.
appels := 0
old := enginePrestart
t.Cleanup(func() { enginePrestart = old })
enginePrestart = func() error {
appels++
if appels == 2 {
return errTuneBusy
}
return nil
}
for i, want := range []string{`{"error":""}`, `{"error":"` + errTuneBusy.Error() + `"}`} {
w := httptest.NewRecorder()
handleInternalServicePrestart(w, httptest.NewRequest("POST", "/internal/v1/service/prestart", nil))
if got := strings.TrimSpace(w.Body.String()); w.Code != 200 || got != want {
t.Fatalf("prestart %d : %d %s", i, w.Code, got)
}
}
w := httptest.NewRecorder()
handleInternalServicePrestart(w, httptest.NewRequest("GET", "/internal/v1/service/prestart", nil))
if w.Code != 405 || appels != 2 {
t.Fatalf("prestart en GET : %d (%d appels)", w.Code, appels)
}
// La génération en cours de la conversation tenue ici.
for _, gen := range []bool{false, true} {
conv.mu.Lock()
conv.Generating = gen
conv.mu.Unlock()
w := httptest.NewRecorder()
handleInternalChatGenerating(w, httptest.NewRequest("GET", "/internal/v1/chat/generating", nil))
var out struct{ Generating bool }
if err := json.Unmarshal(w.Body.Bytes(), &out); err != nil || out.Generating != gen {
t.Fatalf("generating=%v : %s", gen, w.Body.String())
}
}
conv.mu.Lock()
conv.Generating = false
conv.mu.Unlock()
// L'arrêt de la dictée (rien ne tourne : immédiat).
w = httptest.NewRecorder()
handleInternalDictateStop(w, httptest.NewRequest("POST", "/internal/v1/dictate/stop", nil))
if w.Code != 204 {
t.Fatalf("arrêt de la dictée : %d", w.Code)
}
}