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

Plafonds des autres réglages

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Johnounet 22 цаг өмнө
parent
commit
0515e8bbe7
4 өөрчлөгдсөн 34 нэмэгдсэн , 10 устгасан
  1. 5 4
      .env.exemple
  2. 2 0
      CHANGELOG.md
  3. 13 6
      bot/config.py
  4. 14 0
      tests/test_config.py

+ 5 - 4
.env.exemple

@@ -16,6 +16,7 @@ CLAUDE_MODEL=claude-sonnet-5-5
 # fichier plein (MEMORY_SERVER_LIMIT) après la réflexion : 4000 sans réflexion, 6000 avec EFFORT=low
 # ou medium, 8000 avec high ou par défaut, 12000 avec xhigh ou max (pour 5000 caractères). Le bot
 # refuse de démarrer sous le minimum, et prévient dans le message « En ligne » sous la valeur conseillée.
+# Maximum : 64000.
 MAX_TOKENS=6000
 # Réflexion avant de répondre : off (rapide) ou on (plus réfléchi, plus lent et plus cher).
 # Sur claude-opus-5-5, la réflexion ne peut pas être coupée : règle plutôt EFFORT=low.
@@ -32,7 +33,7 @@ API_TIMEOUT=90
 # Recherche web et lecture de liens : off ou on (payant : voir PRICE_WEB_SEARCH_PER_1000).
 # Avec on, prévois un API_TIMEOUT d'au moins 90 secondes (40 de plus que sans la recherche).
 WEB_SEARCH=off
-# Nombre maximal de recherches (et de pages lues) par message
+# Nombre maximal de recherches (et de pages lues) par message, de 1 à 10
 WEB_MAX_USES=3
 
 # --- Mémoire -------------------------------------------------------------
@@ -55,13 +56,13 @@ MEMORY_DIR=
 # sont relues à chaque message : plus elles sont longues, plus chaque message coûte.
 MEMORY_MEMBER_LIMIT=3000
 MEMORY_SERVER_LIMIT=5000
-# Lectures et écritures maximales dans la mémoire pour une même réponse
+# Lectures et écritures maximales dans la mémoire pour une même réponse, de 1 à 20
 MEMORY_MAX_OPERATIONS=5
 
 # --- Images et PDF -------------------------------------------------------
 # Taille max d'une image en octets
 MAX_IMAGE_SIZE=25000000
-# Pages max d'un PDF (0 = PDF refusés). Claude lit le texte et l'image de chaque page :
+# Pages max d'un PDF, de 0 (PDF refusés) à 100 (limite de l'API). Claude lit le texte et l'image de chaque page :
 # compter souvent plusieurs milliers de tokens par page.
 PDF_MAX_PAGES=20
 
@@ -77,7 +78,7 @@ PRESENCE=on
 PRESENCE_WEIGHTS=anime=20,musique=20,pensee=10,jeu=20,en_ligne=20,ne_pas_deranger=5,absent=5
 # Heures pendant lesquelles le bot est absent (de 2 h à 6 h), ou off
 PRESENCE_NIGHT=2-6
-# Pour tester : 10 = statuts 10 fois plus courts. Laisse 1 en temps normal.
+# Pour tester : 10 = statuts 10 fois plus courts (100 au maximum). Laisse 1 en temps normal.
 PRESENCE_SPEED=1
 # Playlists Deezer où piocher les musiques. Vide = Top Worldwide, Top France et Top Japan.
 DEEZER_PLAYLISTS=

+ 2 - 0
CHANGELOG.md

@@ -26,6 +26,8 @@ Les versions les plus récentes sont en haut. Chaque entrée commence par un ré
 
 - **`CONTEXT_MESSAGE_LIMIT` plafonné à 100.** Aucune limite n'empêchait d'y mettre 2000 : chaque mention demandait alors une vingtaine d'appels à Discord et envoyait environ 100 000 tokens à Claude (plusieurs dizaines de centimes par mention, ou un refus « prompt is too long »). Au-delà de 100, le bot refuse de démarrer avec un message « Erreur de configuration » clair.
 
+- **Les autres réglages ont aussi un plafond.** Une valeur trop grande n'était découverte qu'au premier message, avec un « Je suis mal configuré » sans explication (`MAX_TOKENS=200000`, `PDF_MAX_PAGES=300` alors que l'API refuse les PDF de plus de 100 pages), ou passait sans erreur mais coûtait cher (`WEB_MAX_USES=50`) ou saturait Discord (`PRESENCE_SPEED=100000`). Le bot refuse maintenant de démarrer avec un message « Erreur de configuration » clair au-delà de 64000 pour `MAX_TOKENS`, 10 pour `WEB_MAX_USES`, 20 pour `MEMORY_MAX_OPERATIONS`, 100 pour `PDF_MAX_PAGES` et 100 pour `PRESENCE_SPEED`. Les bornes sont indiquées dans `.env.exemple`.
+
 - **Un dossier absent est signalé au démarrage.** Si le dossier de `LOG_FILE`, de `HISTORY_FILE` ou de `MEMORY_DIR` n'existe pas (volume Docker mal monté, faute de frappe) ou n'est pas accessible en écriture, le bot refuse de démarrer avec un message « Erreur de configuration » clair, au lieu d'une trace Python (`LOG_FILE`) ou de tourner sans jamais rien sauvegarder (`HISTORY_FILE`). L'historique est aussi enregistré comme les autres fichiers : envoyé jusqu'au disque avant de remplacer l'ancien, pour ne pas finir vide après une coupure de courant.
 
 ---

+ 13 - 6
bot/config.py

@@ -55,7 +55,8 @@ def _get_int(name: str, default: int | None = None, required: bool = False, mini
     return value
 
 
-def _get_float(name: str, default: float | None = None, minimum: float | None = None) -> float | None:
+def _get_float(name: str, default: float | None = None, minimum: float | None = None,
+               maximum: float | None = None) -> float | None:
     raw = _get_str(name)
     if raw is None:
         return default
@@ -67,6 +68,8 @@ def _get_float(name: str, default: float | None = None, minimum: float | None =
         raise ConfigError(f"La variable {name} doit être un nombre (valeur actuelle : {raw!r}).")
     if minimum is not None and value < minimum:
         raise ConfigError(f"La variable {name} doit être supérieure ou égale à {minimum:g} (valeur actuelle : {value:g}).")
+    if maximum is not None and value > maximum:
+        raise ConfigError(f"La variable {name} ne peut pas dépasser {maximum:g} (valeur actuelle : {value:g}).")
     return value
 
 
@@ -232,7 +235,8 @@ def load_settings() -> Settings:
     presence_raw = (_get_str("PRESENCE", default="on") or "on").lower()
     if presence_raw not in ("on", "off"):
         raise ConfigError(f"PRESENCE doit valoir « on » ou « off » (valeur actuelle : {presence_raw!r}).")
-    presence_speed = _get_float("PRESENCE_SPEED", default=1.0)
+    # Au-delà, le statut change trop souvent (écriture de presence_state.json, limites de débit de Discord).
+    presence_speed = _get_float("PRESENCE_SPEED", default=1.0, maximum=100)
     if presence_speed <= 0:
         raise ConfigError(f"PRESENCE_SPEED doit être supérieur à 0 (valeur actuelle : {presence_speed}).")
 
@@ -243,12 +247,14 @@ def load_settings() -> Settings:
         model=model,
         # Une limite haute ne coûte que si elle est atteinte ; trop basse, elle coupe la réécriture
         # d'un fichier de mémoire plein (voir memory_max_tokens).
-        max_tokens=_get_int("MAX_TOKENS", default=4000, minimum=1),
+        # Au-delà de 64000, l'API refuse la requête (limite de sortie des modèles).
+        max_tokens=_get_int("MAX_TOKENS", default=4000, minimum=1, maximum=64000),
         thinking=thinking,
         effort=effort,
         api_timeout=_get_float("API_TIMEOUT", default=60.0, minimum=1),
         web_search=web_search_raw == "on",
-        web_max_uses=_get_int("WEB_MAX_USES", default=3, minimum=1),
+        # Chaque recherche est facturée : au-delà de 10, une seule question peut coûter cher.
+        web_max_uses=_get_int("WEB_MAX_USES", default=3, minimum=1, maximum=10),
         max_history_length=_get_int("MAX_HISTORY_LENGTH", default=10, minimum=2),
         # Lus à chaque mention puis envoyés à Claude : au-delà, chaque mention coûte cher (ou dépasse la limite).
         context_message_limit=_get_int("CONTEXT_MESSAGE_LIMIT", default=20, minimum=0, maximum=100),
@@ -259,11 +265,12 @@ def load_settings() -> Settings:
         # Chaque fiche jointe est relue à chaque message : plus elle est longue, plus il coûte.
         memory_member_limit=memory_limits["MEMORY_MEMBER_LIMIT"],
         memory_server_limit=memory_limits["MEMORY_SERVER_LIMIT"],
-        memory_max_operations=_get_int("MEMORY_MAX_OPERATIONS", default=5, minimum=1),
+        memory_max_operations=_get_int("MEMORY_MAX_OPERATIONS", default=5, minimum=1, maximum=20),
         # Taille maximale acceptée depuis Discord ; les grosses images sont réduites avant l'envoi à Claude.
         max_image_size=_get_int("MAX_IMAGE_SIZE", default=25_000_000, minimum=1),
         # Chaque page coûte son texte et son image (souvent plusieurs milliers de tokens).
-        pdf_max_pages=_get_int("PDF_MAX_PAGES", default=20, minimum=0),
+        # L'API refuse les PDF de plus de 100 pages.
+        pdf_max_pages=_get_int("PDF_MAX_PAGES", default=20, minimum=0, maximum=100),
         presence=presence_raw == "on",
         presence_speed=presence_speed,
         presence_weights=_get_presence_weights(),

+ 14 - 0
tests/test_config.py

@@ -78,5 +78,19 @@ class OutputPathsTest(unittest.TestCase):
         self.assertIn("Impossible d'écrire dans le dossier de LOG_FILE", str(ctx.exception))
 
 
+class MaximumTest(unittest.TestCase):
+    def test_values_above_maximum_refused(self):
+        for name, value in (("MAX_TOKENS", "64001"), ("WEB_MAX_USES", "11"), ("MEMORY_MAX_OPERATIONS", "21"),
+                            ("PDF_MAX_PAGES", "101"), ("PRESENCE_SPEED", "100.5")):
+            with self.subTest(name), self.assertRaises(ConfigError) as ctx:
+                settings(MEMORY="off", **{name: value})
+            self.assertIn(f"{name} ne peut pas dépasser", str(ctx.exception))
+
+    def test_maximum_accepted(self):
+        s = settings(MEMORY="off", MAX_TOKENS="64000", WEB_MAX_USES="10", MEMORY_MAX_OPERATIONS="20",
+                     PDF_MAX_PAGES="100", PRESENCE_SPEED="100")
+        self.assertEqual((s.max_tokens, s.pdf_max_pages, s.presence_speed), (64000, 100, 100))
+
+
 if __name__ == "__main__":
     unittest.main()