Files
Loki/internal/loki/web_internal_turnperf_test.go
T
MichaelandClaude Opus 5.5 5edcad50a5 Conversation tenue par Rust : télémétrie et requêtes en vol comme le Go
Trois choses manquaient au tour tenu par la façade, actives même sans
clé opt-in, et qu'il fallait avant d'en faire le défaut :

- la télémétrie de chaque complétion (perfRecord) : /api/perf/summary et
  l'alerte « cache perdu » posée sur les stats finales d'une étape. La
  boucle Rust la remet au Go (/internal/v1/turn/perf) au même moment que
  la boucle Go, avec ses empreintes de messages ; le Go range, calcule la
  perte et rend nature, jetons perdus et alerte, posés sur la copie finale
  des stats ;
- la sonde du gabarit, lancée dans les mêmes conditions (requête de forme
  normale du moteur local) ;
- le comptage des requêtes en vol : chaque requête au moteur local ouvre
  un ticket (/internal/v1/oai/begin), marqué servi à la réponse 200
  (engineServed, engineMarkMain pour une étape de la discussion), rendu en
  fin de lecture du flux. Le slot de la discussion n'est ainsi ni effacé
  pendant une requête ni pris pour un autre.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:04:05 +02:00

68 lines
2.3 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)
}
}
// 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)
}
}