Raisonnement : corrections de relecture du lot 2 — un refus de gabarit reconnu sans faux positif

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 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5.5 committed 2026-10-04 12:32:20 +02:00
1 parent 5f00b6f041
commit 568f344273
2 files changed
+32 -2

No files matched your search

+10 -2
View File
@@ -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
+22
View File
@@ -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: <tool_call>{\"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)
}
}
}