mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-17 20:26:35 +02:00
Lot 29 : télémétrie des complétions de la façade
Quand la façade tient la conversation (LOKI_CHAT=rust), ses complétions rejoignent l'anneau de télémétrie du Go (perf_log.go) au lieu de passer inaperçues : /api/perf/summary les compte, l'optimiseur en tire ses poids, et l'interface reçoit à nouveau la perte de cache. - chat/perf.rs : perfRecFromStats, perfRecFromWire (lu sans échec possible, mêmes tolérances que perfNum, timings d'un autre type présents mais vides comme le Go) et perfPrefix (FNV-1a cumulé) ; - la boucle du tour range chaque complétion lue, numérotée dans le tour, avec ses empreintes si elle est complète ; une complétion coupée par un stop est rangée incomplète ; la dernière statistique porte la nature, la perte, ce qui s'est intercalé et l'alerte ; - natures : main (tour), verify (vérification du mode Code), compact (résumé de compaction, rangé dès que la réponse se lit) ; - le Go (/internal/v1/perf/record) range le relevé comme les siens et calcule l'alerte au seuil du preset actif, jamais pour un preset externe. Tests : relevés et empreintes (chat/perf.rs), télémétrie de la boucle (complète, coupée), du vrai hôte et des résumés (tests/lot6.rs), TestFacadePerf 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:
15 files changed
+731
-16
No files matched your search
@@ -0,0 +1,37 @@
|
||||
package loki
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// facade_perf.go — la télémétrie des complétions de la façade (lot 29 de la
|
||||
// migration). Quand la façade tient la conversation (LOKI_CHAT=rust), ses
|
||||
// complétions — tours, vérifications, résumés de compaction — rejoignent
|
||||
// l'anneau du Go (perf_log.go) : /api/perf/summary les compte, la perte de
|
||||
// cache se calcule contre la complétion précédente de la même discussion, et
|
||||
// l'alerte suit le seuil du preset actif (perfLostAlertAt), comme pour un
|
||||
// tour du Go.
|
||||
|
||||
// handleInternalPerfRecord : POST {rec, prefix, external} → {seq, lost,
|
||||
// lost_after, alert}. rec : une complétion (perfRec, sans at ni seq) ;
|
||||
// prefix : les empreintes cumulées des messages envoyés (perfPrefix), vide
|
||||
// pour une complétion incomplète ; external : preset externe (jamais
|
||||
// d'alerte).
|
||||
func handleInternalPerfRecord(w http.ResponseWriter, r *http.Request) {
|
||||
var in struct {
|
||||
Rec perfRec `json:"rec"`
|
||||
Prefix []uint64 `json:"prefix"`
|
||||
External bool `json:"external"`
|
||||
}
|
||||
if !readInternalJSON(w, r, &in) {
|
||||
return
|
||||
}
|
||||
in.Rec.At, in.Rec.Seq, in.Rec.Lost, in.Rec.LostAfter = time.Time{}, 0, nil, ""
|
||||
rec := perfRecord(in.Rec, in.Prefix)
|
||||
alert := false
|
||||
if rec.Complete && !in.External && ((rec.Lost != nil && *rec.Lost > 0) || (rec.Cached != nil && *rec.Cached == 0)) {
|
||||
alert = perfAlert(rec, perfLostAlertAt(ReadConfig()))
|
||||
}
|
||||
sendJSON(w, http.StatusOK, map[string]any{"seq": rec.Seq, "lost": rec.Lost, "lost_after": rec.LostAfter, "alert": alert})
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package loki
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// La télémétrie des complétions de la façade rejoint l'anneau du Go : perte
|
||||
// de cache calculée contre la complétion précédente de la même discussion,
|
||||
// ce qui s'est intercalé, et l'alerte au seuil du preset actif.
|
||||
func TestFacadePerf(t *testing.T) {
|
||||
testHome(t)
|
||||
post := func(body string) (int, map[string]any) {
|
||||
rec := httptest.NewRecorder()
|
||||
handleInternalPerfRecord(rec, httptest.NewRequest("POST", "/internal/v1/perf/record", strings.NewReader(body)))
|
||||
var out map[string]any
|
||||
_ = json.Unmarshal(rec.Body.Bytes(), &out)
|
||||
return rec.Code, out
|
||||
}
|
||||
// Première complétion : rien à comparer. Ce que la façade enverrait en
|
||||
// trop (at, seq, lost) est ignoré.
|
||||
code, out := post(`{"rec":{"kind":"main","conv":"fp1","iter":0,"complete":true,"total":5000,"cached":0,"new":5000,
|
||||
"seq":99,"lost":7,"lost_after":"x"},"prefix":[11,22]}`)
|
||||
if code != 200 || out["lost"] != nil || out["lost_after"] != "" || out["alert"] != false {
|
||||
t.Fatalf("première : %d %v", code, out)
|
||||
}
|
||||
seq := out["seq"].(float64)
|
||||
if seq == 99 || seq <= 0 {
|
||||
t.Fatalf("seq : %v", seq)
|
||||
}
|
||||
// Une tâche passe entre deux.
|
||||
perfRecord(perfRec{Kind: perfTask, Conv: "t", Complete: true, Total: 10}, nil)
|
||||
// La suivante prolonge la première, mais n'en reprend que 100 jetons.
|
||||
code, out = post(`{"rec":{"kind":"main","conv":"fp1","iter":1,"complete":true,"total":6000,"cached":100,"new":5900},"prefix":[11,22,33]}`)
|
||||
if code != 200 || out["lost"] != float64(4900) || out["lost_after"] != perfTask || out["alert"] != true {
|
||||
t.Fatalf("perte : %d %v", code, out)
|
||||
}
|
||||
if recs := perfLog.snapshot(); recs[len(recs)-1].Conv != "fp1" || recs[len(recs)-1].Iter != 1 || recs[len(recs)-1].New != 5900 {
|
||||
t.Fatalf("relevé rangé : %+v", recs[len(recs)-1])
|
||||
}
|
||||
// Preset externe : la perte est rangée, jamais signalée.
|
||||
code, out = post(`{"rec":{"kind":"main","conv":"fp1","iter":2,"complete":true,"total":7000,"cached":100},"prefix":[11,22,33,44],"external":true}`)
|
||||
if code != 200 || out["lost"] != float64(5900) || out["alert"] != false {
|
||||
t.Fatalf("externe : %d %v", code, out)
|
||||
}
|
||||
// Incomplète : ni perte ni alerte.
|
||||
code, out = post(`{"rec":{"kind":"main","conv":"fp1","iter":3,"complete":false,"cached":0}}`)
|
||||
if code != 200 || out["lost"] != nil || out["alert"] != false {
|
||||
t.Fatalf("incomplète : %d %v", code, out)
|
||||
}
|
||||
if code, _ := post(`{"rec":`); code != 400 {
|
||||
t.Fatalf("corps illisible : %d", code)
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
handleInternalPerfRecord(rec, httptest.NewRequest("GET", "/internal/v1/perf/record", nil))
|
||||
if rec.Code != 405 {
|
||||
t.Fatalf("GET : %d", rec.Code)
|
||||
}
|
||||
}
|
||||
@@ -135,6 +135,8 @@ func withInternalAPI(next http.Handler) http.Handler {
|
||||
handleInternalBenchPerf(w, r)
|
||||
case "/internal/v1/tune/context":
|
||||
handleInternalTuneContext(w, r)
|
||||
case "/internal/v1/perf/record":
|
||||
handleInternalPerfRecord(w, r)
|
||||
case "/internal/v1/tasks/run":
|
||||
handleInternalTaskRun(w, r)
|
||||
case "/internal/v1/push/send":
|
||||
|
||||
Reference in new issue
Block a user