1
0
Эх сурвалжийг харах

Réflexion écrite : nouvel essai dès qu'une fuite est détectée

Le reste d'une réponse qui contient de la réflexion entre balises peut en
contenir aussi sous une autre forme (JSON, bloc de code) : le bot
redemande une réponse, et ne garde la version nettoyée qu'en dernier
recours. Les consignes ajoutées à la personnalité suivent les
recommandations d'Anthropic (plus d'interdiction de commenter les
recherches, consigne générale contre les balises internes). Le log de
démarrage indique les réglages réellement utilisés, et chaque fuite les
types de blocs reçus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Johnounet 5 өдөр өмнө
parent
commit
8b224f707e

+ 21 - 9
bot/claude_client.py

@@ -44,6 +44,10 @@ GROUP_NOTE = (
     "naturellement, sans signaler qu'ils ont été regroupés."
 )
 
+# Ajouté lui aussi automatiquement. Formulation générique recommandée par Anthropic : nommer les
+# balises de réflexion, ou interdire de réfléchir, rend au contraire leur apparition plus probable.
+OUTPUT_NOTE = "\n\nN'inclus pas de balises XML internes ou système dans ta réponse."
+
 # Ajouté quand WEB_SEARCH=on.
 WEB_NOTE = (
     "\n\nTu as accès à Internet (recherche web et lecture de pages). "
@@ -51,7 +55,10 @@ WEB_NOTE = (
     "que tu ne connais pas, ou un lien partagé dans la conversation ; sinon réponds directement. "
     "Garde des réponses courtes ; quand c'est utile, nomme simplement ta source dans ta phrase "
     "(par exemple « selon Famitsu »), sans recopier d'adresse. "
-    "Ne commente pas tes recherches (pas de « je vais vérifier », « je regarde le résultat »…) : donne directement la réponse. "
+    # Le texte écrit avant un appel d'outil n'est jamais affiché (voir _final_text). L'interdire
+    # pousse au contraire le modèle à écrire ses appels d'outils sous forme de texte.
+    "Quand tu utilises un outil, tu peux d'abord dire une courte phrase. "
+    "Si aucun outil ne permet de faire ce qu'on te demande, dis-le au lieu de deviner. "
     "Dans les messages précédents, la mention « [recherche web : …] » signifie que tu as bien cherché sur Internet "
     "pour y répondre : ta réponse d'alors s'appuyait sur de vrais résultats, même si tu ne les vois plus. "
     "Ne prétends donc jamais avoir inventé une réponse marquée ainsi."
@@ -156,7 +163,7 @@ class ClaudeClient:
         messages = list(history) + [{"role": "user", "content": self.build_user_content(parts)}]
         has_image = any(image is not None for _, image in parts)
 
-        system = self.personality() + MEMORY_NOTE + GROUP_NOTE
+        system = self.personality() + MEMORY_NOTE + GROUP_NOTE + OUTPUT_NOTE
         if self.settings.web_search:
             system += WEB_NOTE
 
@@ -192,12 +199,15 @@ class ClaudeClient:
         responses = await self._answer(params, messages, has_image)
         text, leaked = self._final_text(responses)
         web_used = self._log_usage(responses)
-        if leaked and not text:
-            # Réponse faite uniquement de réflexion écrite : on redemande une fois.
-            logger.warning("Réponse sans texte (réflexion écrite dans la réponse) : nouvel essai.")
-            responses = await self._answer(params, messages, has_image)
-            text, _ = self._final_text(responses)
-            web_used = self._log_usage(responses) or web_used
+        if leaked:
+            # Le reste du texte peut contenir de la réflexion sous une autre forme (JSON, bloc
+            # de code…) : on redemande une fois, et on garde la version nettoyée en dernier recours.
+            logger.warning("Réflexion écrite dans la réponse : nouvel essai.")
+            retry = await self._answer(params, messages, has_image)
+            retry_text, retry_leaked = self._final_text(retry)
+            web_used = self._log_usage(retry) or web_used
+            if retry_text and (not retry_leaked or not text):
+                responses, text = retry, retry_text
 
         stop_reason = responses[-1].stop_reason
         if stop_reason == "max_tokens":
@@ -258,7 +268,9 @@ class ClaudeClient:
                     parts = []
         text, removed = LEAKED_REASONING.subn("", "".join(parts))
         if removed:
-            logger.warning("Réflexion écrite retirée de la réponse (%s balise(s)).", removed)
+            blocks = ", ".join(block.type for response in responses for block in response.content)
+            logger.warning("Réflexion écrite retirée de la réponse (%s balise(s) ; blocs reçus : %s).",
+                           removed, blocks)
             text = re.sub(r"\n{3,}", "\n\n", text)
         return text.strip(), bool(removed)
 

+ 4 - 0
bot/discord_bot.py

@@ -83,6 +83,10 @@ class ChatBot(commands.Bot):
         self.tree.add_command(reset_command)
         synced = await self.tree.sync()
         logger.info("%d commande(s) slash synchronisée(s).", len(synced))
+        s = self.settings
+        logger.info("Modèle %s — réflexion : %s, effort : %s, recherche web : %s",
+                    s.model, "on" if s.thinking else "off", s.effort or "par défaut",
+                    "on" if s.web_search else "off")
 
     async def on_ready(self) -> None:
         logger.info("Connecté en tant que %s (version %s)", self.user, VERSION)

+ 8 - 2
changelog.txt

@@ -11,8 +11,14 @@ Corrections
   le vocal.
 - Réflexion affichée : quand le modèle écrit sa réflexion dans sa réponse
   (faux appel d'outil <invoke name="think">… ou raisonnement entre balises
-  <reasoning>…), elle est retirée du message envoyé et de la mémoire. Si la
-  réponse ne contenait que ça, le bot redemande une réponse.
+  <reasoning>…), le bot redemande une réponse. Si la nouvelle réponse en
+  contient aussi, la réflexion est retirée du message envoyé et de la mémoire.
+  - Les consignes ajoutées automatiquement à la personnalité suivent les
+    recommandations d'Anthropic contre ces fuites : plus d'interdiction de
+    commenter les recherches web (ce commentaire n'est de toute façon jamais
+    affiché), et une consigne générale contre les balises internes.
+  - Au démarrage, le log indique le modèle, la réflexion, l'effort et la
+    recherche web réellement utilisés.
 - Logs : chaque requête HTTP vers l'API d'Anthropic n'est plus journalisée
   (le SDK 2.x passe par httpx2, qui n'était pas rendu silencieux).