Files
Loki/internal/loki/facade_backends.go
T
Claude 2c926a8274 Lot 22 : backends llama.cpp et job des moteurs tenus par la façade (LOKI_BACKENDS=rust)
La façade (rust/loki-rs/src/backends) sert le statut du moteur llama.cpp
(dépôt git, binaire, moteur de l'image, précompilé, moteur téléchargé,
plan de build et conseil), la vérification git, la dernière release et
l'installation des binaires précompilés (extraction avec liens, VERSION,
ménage), la bascule fast/opt, les backends custom et leur désinstallation,
et les GPU vus par un moteur (caches mémoire et disque).

Elle détient LE job des moteurs (state/build_job + loki-build.log, repris
au démarrage). Les jobs que le Go fait encore tourner (compilation depuis
les sources, moteur de l'image, Strata) lui rapportent chaque étape par
/internal/facade/v1/build-job/* ; le Go garde le garde de l'optimiseur et
ne restaure plus de job quand la façade tient le lot.

Correction du Go : prebuiltVersionFormat lisait le format comme 3e champ
du marqueur VERSION ; hors CUDA Windows (« b300  fmt2 ») il n'y en a que
deux, et chaque vérification retéléchargeait la release au lieu de dire
« déjà à jour ». Le port suit.

Parité : parity_backends_test.go (Linux) → tests/parity_backends, rejoué
par tests/backends.rs (fausses machines dans le PATH, faux GitHub, dépôt
git reproductible) ; TestFacadeBackends pour les étapes transmises.
Mutations : 55 tuées sur 57, deux inobservables sous Linux (cudart
Windows, garde d'un avertissement).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
2026-10-15 20:58:28 +00:00

155 lines
4.6 KiB
Go

package loki
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"strconv"
"strings"
"time"
)
// facade_backends.go — backends llama.cpp tenus par la façade Rust
// (LOKI_BACKENDS=rust, lot 22 de la migration ; expérimental, le Go reste le
// défaut) : statut, vérification git, binaires précompilés, bascule fast/opt,
// suivi du job, backends custom et GPU vus par un moteur
// (rust/loki-rs/src/backends).
//
// La façade détient alors LE job des moteurs. Ceux que le Go fait encore
// tourner — compilation depuis les sources (install, update, install-custom,
// que la façade lui relaie), moteur de l'image, Strata — lui rapportent chaque
// étape (/internal/facade/v1/build-job/*) : lcAppend, lcPhase, lcFail, lcDone,
// startLcJob et lcJobSnapshot passent par elle, et le Go ne restaure plus de
// job au démarrage (la façade s'en charge).
const facadeBackendsEnv = "LOKI_BACKENDS"
// facadeBackends : la façade tient-elle les backends et le job ?
func facadeBackends() bool {
return strings.EqualFold(strings.TrimSpace(os.Getenv(facadeBackendsEnv)), "rust") &&
facadeAddr() != "" && internalToken() != ""
}
// facadeBackendsRoutes : les routes que la façade sert alors (backends/mod.rs).
var facadeBackendsRoutes = map[string]bool{
"/api/llamacpp": true,
"/api/llamacpp/check": true,
"/api/llamacpp/prebuilt": true,
"/api/llamacpp/prebuilt/check": true,
"/api/llamacpp/use": true,
"/api/llamacpp/job": true,
"/api/llamacpp/uninstall-custom": true,
"/api/backends": true,
"/api/backends/custom": true,
"/api/backends/devices": true,
}
// facadeJobClient : appels courts (une ligne de journal, une phase).
var facadeJobClient = &http.Client{Timeout: 30 * time.Second}
// facadeJobCall envoie une étape du job à la façade. out peut être nil.
// Une réponse 409 rend l'erreur que la façade explique (job déjà en cours).
func facadeJobCall(op string, body map[string]any, out any) error {
b, err := json.Marshal(body)
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodPost, "http://"+facadeAddr()+"/internal/facade/v1/build-job/"+op, bytes.NewReader(b))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set(internalTokenHeader, internalToken())
resp, err := facadeJobClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusConflict {
var e struct {
Error string `json:"error"`
}
_ = json.NewDecoder(resp.Body).Decode(&e)
if e.Error == "" {
e.Error = resp.Status
}
return errors.New(e.Error)
}
if resp.StatusCode >= 300 {
return fmt.Errorf("façade : %s", resp.Status)
}
if out != nil {
return json.NewDecoder(resp.Body).Decode(out)
}
return nil
}
// facadeJobNote : une étape dont l'échec ne doit pas arrêter le job (le
// journal perd une ligne, rien de plus) — tracée sur la sortie d'erreur.
func facadeJobNote(op string, body map[string]any) {
if err := facadeJobCall(op, body, nil); err != nil {
fmt.Fprintf(os.Stderr, "[job] étape %s non transmise à la façade : %v\n", op, err)
}
}
// facadeJobStart démarre le job côté façade, puis son corps ici.
func facadeJobStart(action string, run func()) error {
if err := facadeJobCall("start", map[string]any{"action": action}, nil); err != nil {
return err
}
setBuildSink(lcAppend)
go func() {
defer func() {
setBuildSink(nil)
facadeJobNote("end", map[string]any{})
}()
run()
}()
return nil
}
// facadeJobSnapshot : la vue du job tenue par la façade.
func facadeJobSnapshot(from int, withLines bool) map[string]any {
lines := "0"
if withLines {
lines = "1"
}
req, err := http.NewRequest(http.MethodGet, "http://"+facadeAddr()+
"/internal/facade/v1/build-job/snapshot?from="+strconv.Itoa(from)+"&lines="+lines, nil)
if err != nil {
return map[string]any{"exists": false}
}
req.Header.Set(internalTokenHeader, internalToken())
resp, err := facadeJobClient.Do(req)
if err != nil {
return map[string]any{"exists": false}
}
defer resp.Body.Close()
var out map[string]any
if resp.StatusCode != http.StatusOK || json.NewDecoder(resp.Body).Decode(&out) != nil {
return map[string]any{"exists": false}
}
return out
}
// lcSetCommit note le commit d'avant ("old") ou d'après ("new") une mise à
// jour.
func lcSetCommit(which, commit string) {
if facadeBackends() {
facadeJobNote("commits", map[string]any{which: commit})
return
}
lcMu.Lock()
if lcCur != nil {
if which == "old" {
lcCur.OldCommit = commit
} else {
lcCur.NewCommit = commit
}
}
lcMu.Unlock()
}