Cache : les rappels de loki et la relance après un 500 ne recalculent plus la conversation

Le rappel de budget d'outils et la relance « pensé sans agir » partaient au
moteur sans entrer dans l'historique : au tour suivant, le fil divergeait
juste avant eux et toute la boucle d'outils qui suivait était recalculée.
Le modèle relisait en prime une histoire qu'il n'avait jamais vue. La
relance après un 500 (appel d'outil mal parsé) retirait les outils et
modifiait le message système : tout le prompt repartait de zéro, deux fois.

- Rappels persistés tels qu'envoyés (appendNudge), sauf derrière un autre
  message user : éphémères là, pour ne pas casser à chaque tour les gabarits
  à alternance stricte.
- Ils restent reconnaissables (isLokiInjected) : la compaction ne les
  réinjecte pas comme demande en cours et ne les prend pas pour la preuve
  qu'une demande a été servie ; la réduction forcée coupe d'abord aux vraies
  demandes ; le vérificateur, l'export, le titre et le résumeur les sautent
  ou les présentent comme note du système.
- Relance après un 500 sur le llama-server local : mêmes outils, même
  message 0, tool_choice « none » et la consigne au bout du dernier message,
  jamais persistée. Nouveau refus, appel tenté malgré tout ou réponse vide :
  repli une fois sur le chemin historique. Preset externe inchangé.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5.5 committed 2026-10-04 01:16:48 +02:00
1 parent 72ba8e0fb6
commit 38b488515a
9 files changed
+640 -28

No files matched your search

+8 -7
View File
@@ -99,10 +99,11 @@ func TestStatutTransitoireEstRejoueEtAboutit(t *testing.T) {
// échecs déterministes ; attendre puis rejouer à l'identique ne ferait que
// retarder l'erreur de plusieurs secondes.
//
// Le tour peut malgré tout repartir UNE fois : le filet de compaction en vol,
// antérieur à ce fichier, retente le tour avec un historique résumé (le 500 est
// souvent un dépassement de contexte). C'est un rejeu SÉMANTIQUE — il change la
// requête et n'attend pas. La preuve qu'aucune reprise réseau n'a eu lieu est
// Le tour peut malgré tout repartir : le filet de compaction en vol, antérieur
// à ce fichier, retente le tour avec un historique résumé (le 500 est souvent
// un dépassement de contexte), et la relance d'un appel d'outil mal formé
// essaie tool_choice « none » puis, refusée elle aussi, le retrait des outils.
// Ce sont des rejeux SÉMANTIQUES — ils changent la requête et n'attendent pas. La preuve qu'aucune reprise réseau n'a eu lieu est
// donc le CHRONO : trois reprises coûteraient au bas mot 0,8 + 1,6 s d'attente.
func TestErreur500NestPasRejoueeParLeReseau(t *testing.T) {
testHome(t)
@@ -119,9 +120,9 @@ func TestErreur500NestPasRejoueeParLeReseau(t *testing.T) {
if elapsed >= llmNetWait {
t.Fatalf("rendu la main en %v : une attente de reprise réseau a eu lieu sur un 500", elapsed)
}
// Borne haute large : ce qui compte est qu'on ne soit pas allé au bout du
// budget réseau (1 + 3 = 4 appels).
if n := calls.Load(); n > 2 {
// Borne haute : l'appel, la relance « none » et le repli sans outils. Trois
// reprises réseau en ajouteraient au moins trois.
if n := calls.Load(); n > 3 {
t.Fatalf("%d appels au moteur sur un 500 : le budget de reprise réseau a été consommé", n)
}
}