mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-17 04:10:04 +02:00
PREWARM ne garde plus la conversation au Go. La façade tient l'historique : elle assemble et envoie la requête du prochain tour ; le Go garde le compteur des requêtes en vol et ce qui l'annule. Go (facade_prewarm.go) : - /internal/v1/prewarm/check : moteur libre d'un vrai travail, loin de minuit, en bonne santé, slot visé ; - /internal/v1/prewarm/begin : rechargement SLOT_PERSIST puis inscription comme LA requête en vol ; son annulation est relayée à la façade ; - /internal/v1/prewarm/end : place rendue, cache repris noté ; - /internal/v1/oai/begin : prewarm_keep pour l'étape qui le prolonge ; - fin de tâche planifiée : le déclencheur part à la façade. Rust (chat/prewarm.rs) : - assemblage par les mêmes fonctions que le tour, sentinelle « . », un seul jeton, non streamé ; abstention (capacités inconnues, tour en cours, discussion vide, compaction attendue, assemblage inattendu) ; - déclencheurs : fin de tour, fin de tâche, changement de discussion ou de projet (full : après 3 s) ; - le tour garde le préchauffage qu'il prolonge (empreintes des messages et des options de rendu) et coupe tout de suite les autres ; - télémétrie kind=prewarm. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
151 lines
4.8 KiB
Go
151 lines
4.8 KiB
Go
package loki
|
|
|
|
import (
|
|
"net/http"
|
|
"sync"
|
|
)
|
|
|
|
// facade_prewarm.go — PREWARM quand la façade tient la conversation (lot 33 de
|
|
// la migration). La façade assemble la requête du prochain tour (elle tient
|
|
// l'historique) et l'envoie ; le Go garde ce qui décide si elle peut partir
|
|
// et ce qui l'annule :
|
|
//
|
|
// - /internal/v1/prewarm/check : le moteur est-il libre d'un vrai travail
|
|
// (travail annexe, bench, optimiseur), loin de minuit, en bonne santé, et
|
|
// sur quel slot viser (un seul slot, ou celui de la discussion avec
|
|
// SIDE_SLOT) ;
|
|
// - /internal/v1/prewarm/begin : recharge l'état SLOT_PERSIST de la
|
|
// discussion, puis inscrit le préchauffage comme LA requête en vol
|
|
// (prewarmBegin). Toute requête de Loki qui part ensuite l'annule — sauf
|
|
// un tour de la façade qui le prolonge (/internal/v1/oai/begin,
|
|
// prewarm_keep) : l'annulation est relayée à la façade, qui coupe sa
|
|
// connexion au moteur ;
|
|
// - /internal/v1/prewarm/end : la fin, avec ce que le moteur a repris du
|
|
// cache (noteEnginePrompt) ;
|
|
// - après une tâche planifiée, le déclencheur part à la façade
|
|
// (prewarmKick).
|
|
|
|
// Préchauffages de la façade inscrits ici : id → fin.
|
|
var facadePrewarms = struct {
|
|
mu sync.Mutex
|
|
next uint64
|
|
m map[uint64]func()
|
|
}{m: map[uint64]func(){}}
|
|
|
|
// handleInternalPrewarmCheck : POST {} → {"ok":true,"slot":-1|0} ou
|
|
// {"ok":false,"why":"…"}.
|
|
func handleInternalPrewarmCheck(w http.ResponseWriter, r *http.Request) {
|
|
var in struct{}
|
|
if !readInternalJSON(w, r, &in) {
|
|
return
|
|
}
|
|
refuse := func(why string) {
|
|
sendJSON(w, http.StatusOK, map[string]any{"ok": false, "why": why})
|
|
}
|
|
if prewarmMode(ReadConfig()) == "" {
|
|
refuse("clé absente")
|
|
return
|
|
}
|
|
ep := resolveChatEndpoint()
|
|
if ep.External {
|
|
refuse("preset externe")
|
|
return
|
|
}
|
|
if sideJobs.Load() > 0 || benchRunning() || tuneGuard() != nil {
|
|
refuse("moteur occupé")
|
|
return
|
|
}
|
|
if prewarmNearMidnight(prewarmNow()) {
|
|
refuse("minuit proche")
|
|
return
|
|
}
|
|
if !healthCheck() {
|
|
refuse("moteur indisponible")
|
|
return
|
|
}
|
|
// Un seul slot, ou les deux de SIDE_SLOT : le préchauffage vise alors le
|
|
// slot de la discussion (id_slot 0), celui où partira le vrai tour.
|
|
slot := -1
|
|
if n := prewarmSlots(ep); n != 1 {
|
|
if slot = engineSlotFor(ep, true); n != 2 || slot != 0 {
|
|
refuse("plusieurs slots")
|
|
return
|
|
}
|
|
}
|
|
sendJSON(w, http.StatusOK, map[string]any{"ok": true, "slot": slot})
|
|
}
|
|
|
|
// handleInternalPrewarmBegin : POST {"conv","n","prefix","opts"} →
|
|
// {"ok":true,"id":n} ou {"ok":false} (une requête de Loki est en vol, ou un
|
|
// autre préchauffage). n : messages du préfixe préchauffé (sans la
|
|
// sentinelle) ; prefix : son empreinte cumulée (perfPrefix) ; opts : celle
|
|
// des champs qui façonnent le rendu.
|
|
func handleInternalPrewarmBegin(w http.ResponseWriter, r *http.Request) {
|
|
var in struct {
|
|
Conv string `json:"conv"`
|
|
N int `json:"n"`
|
|
Prefix uint64 `json:"prefix"`
|
|
Opts uint64 `json:"opts"`
|
|
}
|
|
if !readInternalJSON(w, r, &in) {
|
|
return
|
|
}
|
|
// SLOT_PERSIST : l'état gardé de la discussion d'abord — le préchauffage
|
|
// le prolonge au lieu de tout recalculer.
|
|
slotPersistRestore(resolveChatEndpoint(), in.Conv)
|
|
facadePrewarms.mu.Lock()
|
|
facadePrewarms.next++
|
|
id := facadePrewarms.next
|
|
facadePrewarms.mu.Unlock()
|
|
run := &prewarmRun{n: in.N, prefix: in.Prefix, opts: in.Opts, done: make(chan struct{})}
|
|
// Annulé par une autre requête : la façade coupe sa connexion.
|
|
run.cancel = func() {
|
|
go func() { _ = facadePost("/internal/facade/v1/chat/prewarm/cancel", map[string]any{"id": id}) }()
|
|
}
|
|
end, ok := prewarmBegin(run)
|
|
if !ok {
|
|
sendJSON(w, http.StatusOK, map[string]any{"ok": false})
|
|
return
|
|
}
|
|
facadePrewarms.mu.Lock()
|
|
facadePrewarms.m[id] = func() {
|
|
end()
|
|
close(run.done)
|
|
}
|
|
facadePrewarms.mu.Unlock()
|
|
sendJSON(w, http.StatusOK, map[string]any{"ok": true, "id": id})
|
|
}
|
|
|
|
// handleInternalPrewarmEnd : POST {"id","total","cached"} → 204. total : la
|
|
// taille du prompt préchauffé (0 : inconnue) ; cached : ce que le moteur a
|
|
// repris de son cache (absent : inconnu).
|
|
func handleInternalPrewarmEnd(w http.ResponseWriter, r *http.Request) {
|
|
var in struct {
|
|
ID uint64 `json:"id"`
|
|
Total int `json:"total"`
|
|
Cached *int `json:"cached"`
|
|
}
|
|
if !readInternalJSON(w, r, &in) {
|
|
return
|
|
}
|
|
facadePrewarms.mu.Lock()
|
|
end := facadePrewarms.m[in.ID]
|
|
delete(facadePrewarms.m, in.ID)
|
|
facadePrewarms.mu.Unlock()
|
|
if end == nil {
|
|
sendJSON(w, http.StatusNotFound, map[string]any{"error": "préchauffage inconnu"})
|
|
return
|
|
}
|
|
end()
|
|
// Après une tâche, c'est lui qui retrouve (ou non) la conversation dans le
|
|
// cache RAM : la vérification de llm_slots.go se fait ici.
|
|
if in.Total > 0 {
|
|
cached := 0
|
|
if in.Cached != nil {
|
|
cached = *in.Cached
|
|
}
|
|
noteEnginePrompt(in.Total, cached, in.Cached != nil)
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|