mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-19 21:18:20 +02:00
Lot 8 : téléchargements de modèles tenus par la façade (LOKI_DOWNLOADS=rust)
La façade Rust mène les téléchargements de .gguf quand LOKI_DOWNLOADS=rust (expérimental ; le Go reste le défaut) : sonde, lancement, suivi, annulation, avec les mêmes contrats et la même mécanique que le Go (tranches rapatriées en entier, plages d'octets parallèles, reprise, place disque vérifiée sur le total, jeton réservé aux hôtes Hugging Face, refus traduits, .part vidés au démarrage). Côté Go : facade_downloads.go remet ces routes à la façade quand elles arrivent par le tunnel du relais, et le Go ne vide plus les .part qu'elle mène. Parité : journal des fonctions pures tiré du Go (parity_dl_test.go) et rejoué en Rust ; téléchargements de bout en bout contre un faux Hugging Face (tests/downloads.rs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
This commit is contained in:
20 files changed
+2820
-17
No files matched your search
@@ -26,6 +26,9 @@ pub struct Ctx {
|
||||
pub engine: engine::Paths,
|
||||
/// Conversation tenue par la façade (`LOKI_CHAT=rust`) ; `None` : au Go.
|
||||
pub chat: Option<std::sync::Arc<crate::chat::generate::ChatService>>,
|
||||
/// Téléchargements de modèles tenus par la façade (`LOKI_DOWNLOADS=rust`) ;
|
||||
/// `None` : au Go.
|
||||
pub downloads: Option<std::sync::Arc<crate::downloads::Downloads>>,
|
||||
/// Jeton de l'API interne : une requête qui le porte vient du Go.
|
||||
pub internal_token: String,
|
||||
}
|
||||
|
||||
@@ -208,8 +208,9 @@ impl EngineHttp {
|
||||
}
|
||||
}
|
||||
|
||||
/// Fournisseur cryptographique de rustls (ring), posé une fois.
|
||||
fn install_crypto() {
|
||||
/// Fournisseur cryptographique de rustls (ring), posé une fois (tout client
|
||||
/// reqwest de la façade le demande avant d'être construit).
|
||||
pub fn install_crypto() {
|
||||
#[cfg(feature = "tls")]
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -15,6 +15,7 @@ pub mod auth;
|
||||
pub mod chat;
|
||||
pub mod chatapi;
|
||||
pub mod child;
|
||||
pub mod downloads;
|
||||
pub mod engine;
|
||||
pub mod engstatus;
|
||||
pub mod gguf;
|
||||
|
||||
@@ -118,9 +118,17 @@ fn serve() -> ExitCode {
|
||||
// sait (il remet alors à la façade le trafic de chat du tunnel) et
|
||||
// connaît son adresse locale.
|
||||
let chat_rust = chat_held_by_facade();
|
||||
// LOKI_DOWNLOADS=rust : les téléchargements de modèles aussi (le Go
|
||||
// remet alors leurs routes du tunnel et ne vide plus les .part).
|
||||
let downloads_rust = downloads_held_by_facade();
|
||||
let mut env = vec![(auth::INTERNAL_ENV.to_string(), token.clone())];
|
||||
if chat_rust {
|
||||
env.push(("LOKI_CHAT".into(), "rust".into()));
|
||||
}
|
||||
if downloads_rust {
|
||||
env.push(("LOKI_DOWNLOADS".into(), "rust".into()));
|
||||
}
|
||||
if chat_rust || downloads_rust {
|
||||
env.push(("LOKI_FACADE_ADDR".into(), format!("127.0.0.1:{web_port}")));
|
||||
}
|
||||
let sup = child::Supervisor::spawn(go_bin, go_port, env);
|
||||
@@ -153,6 +161,11 @@ fn serve() -> ExitCode {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let downloads = if downloads_rust {
|
||||
Some(start_downloads(backend, &token, &engine_paths.home).await)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], web_port));
|
||||
let listener = match tokio::net::TcpListener::bind(addr).await {
|
||||
@@ -170,6 +183,7 @@ fn serve() -> ExitCode {
|
||||
host_env: auth::HostEnv::from_env(),
|
||||
engine: engine_paths,
|
||||
chat,
|
||||
downloads,
|
||||
};
|
||||
let res = proxy::serve(listener, cfg, shutdown_signal()).await;
|
||||
eprintln!("[loki-rs] arrêt demandé — arrêt du serveur Go");
|
||||
@@ -189,6 +203,37 @@ fn chat_held_by_facade() -> bool {
|
||||
std::env::var("LOKI_CHAT").is_ok_and(|v| v.trim().eq_ignore_ascii_case("rust"))
|
||||
}
|
||||
|
||||
/// `LOKI_DOWNLOADS=rust` : téléchargements de modèles tenus par la façade.
|
||||
fn downloads_held_by_facade() -> bool {
|
||||
std::env::var("LOKI_DOWNLOADS").is_ok_and(|v| v.trim().eq_ignore_ascii_case("rust"))
|
||||
}
|
||||
|
||||
/// Les `.part` d'un téléchargement tué ne reprendront jamais : vidés au
|
||||
/// démarrage (le Go ne le fait plus quand la façade les tient).
|
||||
async fn start_downloads(
|
||||
backend: SocketAddr,
|
||||
token: &str,
|
||||
home: &std::path::Path,
|
||||
) -> std::sync::Arc<loki_rs::downloads::Downloads> {
|
||||
use loki_rs::{downloads, gguf, store};
|
||||
let st = store::Store::new(backend, token.to_string());
|
||||
let stored = gguf::parse_stored_model_dirs(
|
||||
st.get(store::BK_STATE, gguf::STATE_MODEL_DIRS_KEY)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.as_deref(),
|
||||
);
|
||||
let dirs = gguf::model_dirs(
|
||||
home,
|
||||
&stored,
|
||||
std::env::var(gguf::MODEL_DIRS_ENV).ok().as_deref(),
|
||||
);
|
||||
let _ = tokio::task::spawn_blocking(move || downloads::clean_stale_part_files(&dirs)).await;
|
||||
eprintln!("[loki-rs] téléchargements de modèles tenus par la façade (LOKI_DOWNLOADS=rust)");
|
||||
std::sync::Arc::new(downloads::Downloads::new())
|
||||
}
|
||||
|
||||
/// Le Go garde-t-il la conversation ? (quelques essais : il vient de démarrer)
|
||||
async fn go_keeps_chat(backend: SocketAddr, token: &str) -> Result<Option<String>, String> {
|
||||
let store = loki_rs::store::Store::new(backend, token.to_string());
|
||||
|
||||
@@ -63,6 +63,9 @@ pub struct Config {
|
||||
/// Conversation tenue par la façade (`LOKI_CHAT=rust`) : ses routes sont
|
||||
/// servies ici ; `None` : relayées au Go.
|
||||
pub chat: Option<Arc<crate::chat::generate::ChatService>>,
|
||||
/// Téléchargements de modèles tenus par la façade (`LOKI_DOWNLOADS=rust`) :
|
||||
/// leurs routes sont servies ici ; `None` : relayées au Go.
|
||||
pub downloads: Option<Arc<crate::downloads::Downloads>>,
|
||||
}
|
||||
|
||||
struct Proxy {
|
||||
@@ -90,6 +93,7 @@ pub async fn serve(
|
||||
host_env: cfg.host_env,
|
||||
engine: cfg.engine,
|
||||
chat: cfg.chat,
|
||||
downloads: cfg.downloads,
|
||||
internal_token: cfg.internal_token.clone(),
|
||||
},
|
||||
});
|
||||
@@ -129,8 +133,10 @@ pub async fn serve(
|
||||
impl Proxy {
|
||||
async fn handle(&self, mut req: Request<Incoming>) -> Response<Body> {
|
||||
// Le Go (et lui seul : jeton tiré à chaque démarrage) remet à la
|
||||
// façade le trafic de chat qui lui arrive sans passer par elle.
|
||||
let from_go = self.ctx.chat.is_some() && self.ctx.from_go(req.headers());
|
||||
// façade le trafic de chat et de téléchargements qui lui arrive sans
|
||||
// passer par elle.
|
||||
let from_go = (self.ctx.chat.is_some() || self.ctx.downloads.is_some())
|
||||
&& self.ctx.from_go(req.headers());
|
||||
// Jamais de confiance accordée sur la foi d'un en-tête du client :
|
||||
// ni « je viens du tunnel », ni « je suis la façade ».
|
||||
req.headers_mut().remove(VIA_TUNNEL);
|
||||
@@ -163,6 +169,11 @@ impl Proxy {
|
||||
{
|
||||
return chatapi::serve(&self.ctx, chat, req, from_go).await;
|
||||
}
|
||||
if let Some(dl) = &self.ctx.downloads
|
||||
&& crate::downloads::handles(path)
|
||||
{
|
||||
return crate::downloads::serve(&self.ctx, dl, req, from_go).await;
|
||||
}
|
||||
self.forward(req).await
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user