From 568f34427306395a7d3540c74fd02727647cad6e Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Sun, 4 Oct 2026 12:32:20 +0200 Subject: [PATCH] =?UTF-8?q?Raisonnement=20:=20corrections=20de=20relecture?= =?UTF-8?q?=20du=20lot=202=20=E2=80=94=20un=20refus=20de=20gabarit=20recon?= =?UTF-8?q?nu=20sans=20faux=20positif?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avec REASONING_ECHO, tout 400/500 dont le corps contenait « thinking » passait pour un refus du raisonnement renvoyé, et ce test passe avant le filet des outils. Une erreur d'analyse d'appel d'outil cite le texte du modèle : s'il y écrivait « thinking », le tour était rejoué sans raisonnement et, si la relance passait par chance, la clé était coupée pour ce modèle jusqu'au redémarrage. - « Failed to parse input … » (appel d'outil mal formé) n'est jamais un refus du gabarit : le filet des outils s'en charge, comme sans la clé. - « thinking » ne compte que dans une erreur du gabarit (jinja, template) ; raise_exception et les messages invalides restent reconnus. Co-Authored-By: Claude Opus 5.5 --- internal/loki/llm_reasoning_echo.go | 12 ++++++++++-- internal/loki/llm_reasoning_echo_test.go | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/internal/loki/llm_reasoning_echo.go b/internal/loki/llm_reasoning_echo.go index 668e09d..d71862a 100644 --- a/internal/loki/llm_reasoning_echo.go +++ b/internal/loki/llm_reasoning_echo.go @@ -244,12 +244,20 @@ func echoTemplateError(status int, body string) bool { return false } low := strings.ToLower(body) - for _, k := range []string{"raise_exception", "thinking", "expected 'content' or 'tool_calls'", "failed to parse messages"} { + // Appel d'outil mal formé (« Failed to parse input at pos N: … ») : le + // texte du modèle y est cité, il peut dire « thinking » sans que le gabarit + // y soit pour rien. Le filet des outils s'en charge, pas celui-ci — sinon + // une relance chanceuse ferait couper la clé pour ce modèle à tort. + if strings.Contains(low, "failed to parse input") { + return false + } + for _, k := range []string{"raise_exception", "expected 'content' or 'tool_calls'", "failed to parse messages"} { if strings.Contains(low, k) { return true } } - return false + // « thinking » seul ne dit rien : seulement dans une erreur du gabarit. + return strings.Contains(low, "thinking") && (strings.Contains(low, "jinja") || strings.Contains(low, "template")) } // ReasoningEcho : raisonnement séparé de la complétion qui porte la réponse diff --git a/internal/loki/llm_reasoning_echo_test.go b/internal/loki/llm_reasoning_echo_test.go index e085c54..5fe0317 100644 --- a/internal/loki/llm_reasoning_echo_test.go +++ b/internal/loki/llm_reasoning_echo_test.go @@ -594,3 +594,25 @@ func TestBuilderAppliqueNewHistory(t *testing.T) { t.Fatalf("consigne présente %d fois, dernier message %q", fix, msgText(c.Messages[len(c.Messages)-1])) } } + +// Corrections de relecture du lot 2 : un refus de gabarit est reconnu, une +// erreur d'analyse d'appel d'outil qui cite le mot « thinking » ne l'est pas. +func TestEchoTemplateErrorEtroit(t *testing.T) { + cases := []struct { + status int + body string + want bool + }{ + {500, `{"error":{"message":"Jinja Exception: raise_exception('Cannot pass both content and thinking')"}}`, true}, + {500, `{"error":{"message":"Error in template: thinking blocks not allowed here"}}`, true}, + {400, `{"error":{"message":"Expected 'content' or 'tool_calls'"}}`, true}, + {500, `{"error":{"message":"Failed to parse input at pos 12: {\"thinking\": 1}"}}`, false}, + {500, `{"error":{"message":"the model kept thinking forever"}}`, false}, + {502, `{"error":{"message":"raise_exception"}}`, false}, + } + for _, c := range cases { + if got := echoTemplateError(c.status, c.body); got != c.want { + t.Errorf("%d %s : %v", c.status, c.body, got) + } + } +}