Files
Loki/internal/loki/facade_perf_test.go
T
Claude 8a61879bfd 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
2026-10-16 01:17:49 +00:00

62 lines
2.6 KiB
Go

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)
}
}