Lot 8 : toutes les routes des modèles tenues par la façade (LOKI_MODELS=rust)

La variable LOKI_DOWNLOADS devient LOKI_MODELS et couvre, en plus des
téléchargements, la liste du sélecteur (avec qui référence chaque .gguf),
la suppression (refus si en service, 409 avec les presets sans force,
tranches emportées), les dossiers de modèles et le catalogue Hugging Face
(recherche, fichiers d'un dépôt avec verdict mémoire, jeton vérifié puis
enregistré, cache vidé à son changement).

Côté Go, facade_models.go remet ces routes à la façade quand elles
arrivent par le tunnel du relais.

Parité : parity_models_test.go tire du Go les fonctions pures du catalogue
et un scénario sur disque de 20 appels (liste, dossiers, suppressions),
rejoués à l'identique par tests/models.rs ; catalogue et routes testés de
bout en bout contre un faux Hugging Face.

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 13:25:45 +00:00
1 parent bbea5fcd3f
commit a33e5ccffe
22 files changed
+3395 -187

No files matched your search

@@ -0,0 +1,101 @@
# Lot 8 — routes des modèles tenues par la façade
## Contexte
Les modèles `.gguf` vivaient dans le Go :
- la liste du sélecteur (`handleModels`, `backend_models_refs.go`) ;
- les dossiers de modèles (`backend_model_dirs.go`) ;
- la suppression (`deleteModelFile`) ;
- les téléchargements (`backend_models.go`, `backend_models_shards.go`) ;
- le catalogue Hugging Face (`backend_hf.go`, `web_hf.go`, `backend_catalog.go`).
Lot 8 : la façade tient tout cela quand `LOKI_MODELS=rust` (expérimental ;
sans la variable rien ne change, tout part au Go).
## Ce que sert la façade
| Route | Module | Rôle |
|---|---|---|
| `GET /api/models` | `models.rs` | `.gguf` des dossiers déclarés (tranches 2..N exclues), taille de la famille, tranches manquantes, qui les référence (presets, configuration en service) |
| `POST /api/models/delete` | `models.rs` | refus si en service ; 409 avec la liste des presets sans `force` ; le modèle part avec toutes ses tranches, octets libérés rendus |
| `GET\|POST /api/models/dirs` | `models.rs` | dossiers de modèles, nombre de modèles, espace libre ; ajout (dossier existant) et retrait |
| `POST /api/models/download/probe` | `downloads.rs` | taille du modèle (toutes ses tranches), espace libre, « ça tient ? », ou « déjà là » avec la valeur de `MODEL=` |
| `POST /api/models/download` | `downloads.rs` | lance en arrière-plan ; 409 si déjà en cours ; « déjà là » si toutes les tranches sont présentes |
| `GET /api/models/download/status` | `downloads.rs` | état de chaque téléchargement de ce lancement |
| `POST /api/models/download/cancel` | `downloads.rs` | annule ; ce que le téléchargement a écrit est effacé |
| `GET /api/hf/search` | `hf.rs` | dépôts GGUF, matériel local joint |
| `GET /api/hf/files` | `hf.rs` | modèles, projecteurs et poids MTP d'un dépôt, verdict mémoire de chaque modèle (contexte du preset actif, projecteur du dépôt) |
| `GET\|POST /api/hf/token` | `hf.rs` | jeton vérifié auprès de Hugging Face avant d'être enregistré, jamais rendu en clair |
Mêmes contrats (corps, codes, messages) et même mécanique que le Go, entre
autres :
- téléchargement :
- lien de page Hugging Face (`/blob/`) converti en lien direct ;
- modèle en tranches rapatrié en entier ;
- place disque vérifiée sur le total, avec 256 Mio de marge ; jamais de
refus sur FUSE, overlayfs, NFS, SMB, Ceph ou GlusterFS ;
- plages d'octets en parallèle (`LOKI_DL_CONNS`) et reprise d'une
connexion coupée ;
- jeton envoyé aux seuls hôtes Hugging Face ;
- catalogue : réponses mises en cache (60 s pour une recherche, 10 min pour un
dépôt), cache vidé à l'enregistrement d'un jeton ;
- `.part` orphelins vidés au démarrage — par la façade : le Go ne le fait
plus, il effacerait un téléchargement qu'elle mène.
## Côté Go (`facade_models.go`)
Le tunnel du relais et la redistribution E2E arrivent au Go sans passer par
la façade : `withFacadeChat` lui remet ces routes, authentifiées par le
jeton interne, comme celles du chat. Façade injoignable : 502 « modèles
indisponibles : … ».
La façade transmet `LOKI_MODELS=rust` et son adresse au Go qu'elle lance.
## Écarts assumés
- Une source qui ignore les plages n'est plus lue en entier pendant la sonde.
- Erreurs de transport (réseau, TLS) au format de reqwest.
- Message d'un corps JSON illisible : celui de serde (l'interface n'en envoie
jamais).
- Hôte d'un lien collé en majuscules converti comme les autres.
- Espace libre et mémoire vive inconnus hors Unix/Linux (la façade n'est
livrée que pour Linux).
- `/api/models/download/status` trié par date de début.
- Nombres flottants du matériel écrits `24.0` (le Go écrit `24`) : même
valeur JSON.
## Parité
Journaux tirés du Go et rejoués en Rust (`LOKI_PARITY_UPDATE=1` pour
régénérer) :
- `internal/loki/parity_dl_test.go` → `tests/parity_models/download.json` →
`tests/downloads.rs` : lien normalisé, famille de tranches,
`Content-Range`, messages de refus, hôtes et dépôts Hugging Face, dossier
de destination ;
- `internal/loki/parity_models_test.go` → `tests/parity_models/models.json` →
`tests/models.rs` : quantification déduite du nom, découpage
d'`EXTRA_ARGS`, jeton masqué, `gated`, classement d'un dépôt, choix du
projecteur, verdicts mémoire, puis un scénario sur disque de 20 appels
(liste, dossiers, suppressions avec et sans `force`, modèle en service,
tranches, chemins refusés) rejoué à l'identique, fichiers restants et
dossiers enregistrés compris.
Deux mutations (`replacen` → `replace` sur `/blob/`, `--mmproj` ignoré)
font échouer les rejeux.
De bout en bout :
- téléchargements contre un faux Hugging Face : trois connexions sur des
plages disjointes, source sans plages, tranches dont une présente, dépôt
verrouillé, panne pendant la deuxième tranche, annulation et 409 ;
- catalogue contre un faux Hugging Face : recherche, cache, taille LFS,
dépôt verrouillé ou absent, jeton refusé ou accepté ;
- toutes les routes par la façade (clé de pilotage, remise du Go, base du Go
écrite), et laissées au Go sans la variable.
## Reste
- Essai réel sur une installation avant d'en faire le défaut.
@@ -1,76 +0,0 @@
# Lot 8 — téléchargements de modèles tenus par la façade
## Contexte
Les téléchargements de `.gguf` (Hugging Face ou lien direct) vivaient dans le
Go : `backend_models.go`, `backend_models_shards.go`, `backend_model_dirs.go`,
`backend_hf.go`. Lot 8 : la façade les tient quand `LOKI_DOWNLOADS=rust`
(expérimental ; sans la variable rien ne change, tout part au Go).
## Ce que sert la façade (`rust/loki-rs/src/downloads.rs`)
| Route | Rôle |
|---|---|
| `POST /api/models/download/probe` | taille du modèle (toutes ses tranches), espace libre, « ça tient ? », ou « déjà là » avec la valeur de `MODEL=` |
| `POST /api/models/download` | lance en arrière-plan ; 409 si déjà en cours ; « déjà là » si toutes les tranches sont présentes |
| `GET /api/models/download/status` | état de chaque téléchargement de ce lancement |
| `POST /api/models/download/cancel` | annule ; ce que le téléchargement a écrit est effacé |
Mêmes contrats (corps, codes, messages) et même mécanique que le Go :
- lien de page Hugging Face (`/blob/`) converti en lien direct ;
- modèle en tranches rapatrié en entier (la première le désigne) ;
- tranches déjà là non retéléchargées ;
- place disque vérifiée sur le total, avec 256 Mio de marge ; jamais de refus
sur FUSE, overlayfs, NFS, SMB, Ceph ou GlusterFS, où l'espace annoncé
approxime ;
- jusqu'à `LOKI_DL_CONNS` connexions (8 par défaut, 16 au plus) par tranche
quand la source honore les plages, 16 Mio par connexion au moins ;
- connexion coupée reprise où elle en était (4 essais) ;
- progression sur l'ensemble, vitesse lissée ;
- jeton Hugging Face (réglage, sinon `HF_TOKEN`) envoyé aux seuls hôtes
Hugging Face ;
- refus de la source traduits en phrase qui dit quoi faire (dépôt verrouillé,
jeton absent ou insuffisant, fichier absent, quota…) ;
- `.part` orphelins vidés au démarrage — par la façade : le Go ne le fait
plus, il effacerait un téléchargement qu'elle mène.
## Côté Go (`facade_downloads.go`)
Le tunnel du relais et la redistribution E2E arrivent au Go sans passer par
la façade : `withFacadeChat` lui remet les quatre routes, authentifiées par le
jeton interne, comme celles du chat. Façade injoignable : 502
« téléchargements indisponibles : … ».
La façade transmet `LOKI_DOWNLOADS=rust` et son adresse au Go qu'elle lance.
## Écarts assumés
- Une source qui ignore les plages (200 à la sonde) n'est plus lue en entier
pendant la sonde.
- Erreur de transport (réseau, TLS) au format de reqwest.
- Hôte d'un lien collé en majuscules converti comme les autres.
- Espace libre inconnu hors Unix : rien n'est refusé (comme une mesure ratée
côté Go).
- `/status` trié par date de début (le Go rendait l'ordre d'une map).
## Parité
`internal/loki/parity_dl_test.go` tire du Go le journal des fonctions pures
(lien normalisé, famille de tranches, `Content-Range`, messages de refus,
hôtes et dépôts Hugging Face, dossier de destination), rangé dans
`rust/loki-rs/tests/parity_models/download.json` ; `tests/downloads.rs` le
rejoue (`LOKI_PARITY_UPDATE=1` pour régénérer). Une mutation
(`replacen` → `replace` sur `/blob/`) fait échouer le rejeu.
`tests/downloads.rs` mène aussi le téléchargement de bout en bout contre un
faux Hugging Face : trois connexions sur des plages disjointes, source sans
plages, tranches avec une déjà présente, dépôt verrouillé refusé dès la sonde,
panne en cours de deuxième tranche, annulation, routes par la façade (clé de
pilotage, remise du Go), routes laissées au Go sans la variable.
## Reste
- Les autres routes des modèles (`/api/models`, suppression, dossiers, liste
Hugging Face, vision) restent au Go.
- Essai réel sur une installation avant d'en faire le défaut.