mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-17 20:26:35 +02:00
main a reçu en parallèle la télémétrie du tour tenu par la façade (/internal/v1/turn/perf), les tickets « en vol » du moteur et la conversation tenue par la façade par défaut (LOKI_CHAT=go pour revenir au Go). Le lot 29 faisait la même chose par /internal/v1/perf/record : un seul chemin est gardé, celui de main. - TurnHost::perf_record (main) remplace TurnHost::perf ; la nature est posée par l'hôte (verify pour la passe isolée seulement, main pour les corrections du builder, comme le Go). - Les résumés de compaction passent par le même point d'entrée, nature compact : chat/perf.rs ne garde que le relevé de la réponse entière (perfRecFromWire) et l'envoi. - La sonde du gabarit reste à la façade (lot 23) : /internal/v1/turn/perf ne la lance plus côté Go. - facade_perf.go retiré ; ses cas (perte, intercalé, alerte, preset externe, incomplète, GET) rejoignent web_internal_turnperf_test.go. - README : LOKI_CHAT défaut Rust, avec ce que les lots 19, 23, 29 et 30 y ajoutent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
125 lines
5.1 KiB
Go
125 lines
5.1 KiB
Go
package loki
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"testing"
|
|
)
|
|
|
|
// La complétion d'un tour tenu par Rust arrive dans l'anneau de télémétrie,
|
|
// comme celle de la boucle Go : /api/perf/summary la compte.
|
|
func TestTurnPerfDansLAnneau(t *testing.T) {
|
|
testHome(t)
|
|
t.Setenv(internalTokenEnv, jetonTest)
|
|
avant := perfSummarize(perfLog.snapshot())
|
|
n0 := 0
|
|
if k := avant.Kinds[perfMain]; k != nil {
|
|
n0 = k.Completions
|
|
}
|
|
body := `{"kind":"main","conv":"c-perf","iter":0,"complete":true,"external":false,
|
|
"stats":{"prompt_tokens":120,"prompt_tokens_total":120,"gen_tokens":8,"cache_tokens":0},
|
|
"prefix":[1,2,3]}`
|
|
w := appelOAIInterne(t, "POST", "/internal/v1/turn/perf", body)
|
|
if w.Code != 200 {
|
|
t.Fatalf("code %d : %s", w.Code, w.Body.String())
|
|
}
|
|
var got map[string]any
|
|
if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil || got["kind"] != "main" {
|
|
t.Fatalf("réponse : %s", w.Body.String())
|
|
}
|
|
apres := perfSummarize(perfLog.snapshot())
|
|
if k := apres.Kinds[perfMain]; k == nil || k.Completions != n0+1 {
|
|
t.Fatalf("complétion non rangée : %+v", apres.Kinds[perfMain])
|
|
}
|
|
if w := appelOAIInterne(t, "POST", "/internal/v1/turn/perf", "{"); w.Code != 400 {
|
|
t.Fatalf("corps illisible : %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// Perte de cache calculée contre la complétion précédente de la même
|
|
// discussion, ce qui s'est intercalé, alerte au seuil du preset actif ; un
|
|
// preset externe ou une complétion incomplète ne la signalent jamais.
|
|
func TestTurnPerfPerteEtAlerte(t *testing.T) {
|
|
testHome(t)
|
|
t.Setenv(internalTokenEnv, jetonTest)
|
|
post := func(body string) (int, map[string]any) {
|
|
w := appelOAIInterne(t, "POST", "/internal/v1/turn/perf", body)
|
|
var out map[string]any
|
|
_ = json.Unmarshal(w.Body.Bytes(), &out)
|
|
return w.Code, out
|
|
}
|
|
code, out := post(`{"kind":"main","conv":"fp1","iter":0,"complete":true,
|
|
"stats":{"prompt_tokens":5000,"prompt_tokens_total":5000,"cache_tokens":0},"prefix":[11,22]}`)
|
|
if code != 200 || out["lost"] != nil || out["lost_after"] != "" {
|
|
t.Fatalf("première : %d %v", code, out)
|
|
}
|
|
// 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(`{"kind":"main","conv":"fp1","iter":1,"complete":true,
|
|
"stats":{"prompt_tokens":5900,"prompt_tokens_total":6000,"cache_tokens":100},"prefix":[11,22,33]}`)
|
|
if code != 200 || out["lost"] != float64(4900) || out["lost_after"] != perfTask || out["lost_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(`{"kind":"main","conv":"fp1","iter":2,"complete":true,"external":true,
|
|
"stats":{"prompt_tokens_total":7000,"cache_tokens":100},"prefix":[11,22,33,44]}`)
|
|
if code != 200 || out["lost"] != float64(5900) || out["lost_alert"] != false {
|
|
t.Fatalf("externe : %d %v", code, out)
|
|
}
|
|
// Incomplète : ses empreintes sont ignorées, ni perte ni alerte.
|
|
code, out = post(`{"kind":"main","conv":"fp1","iter":3,"complete":false,
|
|
"stats":{"cache_tokens":0},"prefix":[11,22,33,44,55]}`)
|
|
if code != 200 || out["lost"] != nil || out["lost_alert"] != false {
|
|
t.Fatalf("incomplète : %d %v", code, out)
|
|
}
|
|
if recs := perfLog.snapshot(); recs[len(recs)-1].Complete {
|
|
t.Fatalf("incomplète rangée complète : %+v", recs[len(recs)-1])
|
|
}
|
|
// Résumé de compaction : sa nature est gardée.
|
|
code, out = post(`{"kind":"compact","conv":"fp1","complete":true,"stats":{"prompt_tokens_total":900}}`)
|
|
if code != 200 || out["kind"] != perfCompact {
|
|
t.Fatalf("compaction : %d %v", code, out)
|
|
}
|
|
// Sans nature : un tour.
|
|
if _, out = post(`{"conv":"fp2","complete":true,"stats":{}}`); out["kind"] != perfMain {
|
|
t.Fatalf("sans nature : %v", out)
|
|
}
|
|
if w := appelOAIInterne(t, "GET", "/internal/v1/turn/perf", ""); w.Code != 405 {
|
|
t.Fatalf("GET : %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// Une étape de tour acceptée par le moteur : le slot porte la discussion
|
|
// (engineSlotHolds), comme engineMarkMain dans la boucle Go.
|
|
func TestOAIServedMarqueLeSlot(t *testing.T) {
|
|
testHome(t)
|
|
t.Setenv(internalTokenEnv, jetonTest)
|
|
w := appelOAIInterne(t, "POST", "/internal/v1/oai/begin", `{"main_conv":"c-slot"}`)
|
|
var b struct {
|
|
Ticket uint64 `json:"ticket"`
|
|
}
|
|
if err := json.Unmarshal(w.Body.Bytes(), &b); err != nil || b.Ticket == 0 {
|
|
t.Fatalf("begin : %s", w.Body.String())
|
|
}
|
|
if engineSlotHolds("c-slot") {
|
|
t.Fatal("slot marqué avant que le moteur ait accepté la requête")
|
|
}
|
|
tk := fmt.Sprintf(`{"ticket":%d}`, b.Ticket)
|
|
if w := appelOAIInterne(t, "POST", "/internal/v1/oai/served", tk); w.Code != 204 {
|
|
t.Fatalf("served : %d", w.Code)
|
|
}
|
|
if !engineSlotHolds("c-slot") {
|
|
t.Fatal("slot non marqué après served")
|
|
}
|
|
if w := appelOAIInterne(t, "POST", "/internal/v1/oai/end", tk); w.Code != 204 {
|
|
t.Fatalf("end : %d", w.Code)
|
|
}
|
|
if w := appelOAIInterne(t, "POST", "/internal/v1/oai/served", tk); w.Code != 404 {
|
|
t.Fatalf("served après end : %d", w.Code)
|
|
}
|
|
}
|