Jelajahi Sumber

Mémoire : surnoms reconnus même hors de la première ligne de la fiche

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Johnounet 2 hari lalu
induk
melakukan
67e5b4e632
3 mengubah file dengan 29 tambahan dan 9 penghapusan
  1. 14 9
      bot/memory.py
  2. 4 0
      changelog.txt
  3. 11 0
      tests/test_memory.py

+ 14 - 9
bot/memory.py

@@ -19,8 +19,9 @@ logger = logging.getLogger("chatbot.memory")
 
 
 SERVER_FILE = "serveur.md"
 SERVER_FILE = "serveur.md"
 MEMBER_FILE = re.compile(r"^membres/(\d+)\.md$")
 MEMBER_FILE = re.compile(r"^membres/(\d+)\.md$")
-# Première ligne d'une fiche : les surnoms du membre, repris dans la liste des membres.
-NICKNAMES_LINE = re.compile(r"^surnoms\s*:\s*(.*)$", re.IGNORECASE)
+# Ligne d'une fiche (la première, en principe) avec les surnoms du membre, repris dans la liste des
+# membres. Une puce en tête (« - Surnoms : … ») est acceptée.
+NICKNAMES_LINE = re.compile(r"^(?:[-*•]\s*)?surnoms\s*:\s*(.*)$", re.IGNORECASE)
 # Surnoms repris dans la liste : peu nombreux et courts (comme un pseudo Discord), pour qu'une
 # Surnoms repris dans la liste : peu nombreux et courts (comme un pseudo Discord), pour qu'une
 # ligne « Surnoms » ne puisse pas glisser un long texte dans le prompt système.
 # ligne « Surnoms » ne puisse pas glisser un long texte dans le prompt système.
 MAX_NICKNAMES = 5
 MAX_NICKNAMES = 5
@@ -111,17 +112,21 @@ class MemoryStore:
         return f"membres/{member_id}.md"
         return f"membres/{member_id}.md"
 
 
     def nicknames(self, member_id: int | str) -> list[str]:
     def nicknames(self, member_id: int | str) -> list[str]:
-        """Surnoms notés sur la première ligne de la fiche (« Surnoms : pnt, le Pent »), les
-        MAX_NICKNAMES premiers ; ceux de plus de MAX_NICKNAME_LENGTH caractères sont ignorés."""
+        """Surnoms notés dans la fiche (« Surnoms : pnt, le Pent »), les MAX_NICKNAMES premiers ; ceux
+        de plus de MAX_NICKNAME_LENGTH caractères sont ignorés."""
         return self._nicknames(self.read(self.member_file(member_id)))
         return self._nicknames(self.read(self.member_file(member_id)))
 
 
     @staticmethod
     @staticmethod
     def _nicknames(content: str) -> list[str]:
     def _nicknames(content: str) -> list[str]:
-        match = NICKNAMES_LINE.match(content.partition("\n")[0].strip())
-        if match is None:
-            return []
-        names = [name.strip() for name in re.split(r"[,;]", match.group(1)) if name.strip()]
-        return [name for name in names if len(name) <= MAX_NICKNAME_LENGTH][:MAX_NICKNAMES]
+        # Toutes les lignes « Surnoms », pas seulement la première de la fiche : une ligne ajoutée à la
+        # fin avec « ajouter » ou une fiche modifiée à la main ne doit pas être ignorée sans rien dire.
+        names: dict[str, str] = {}
+        for line in content.splitlines():
+            if match := NICKNAMES_LINE.match(line.strip()):
+                for name in re.split(r"[,;]", match.group(1)):
+                    if 0 < len(name := name.strip()) <= MAX_NICKNAME_LENGTH:
+                        names.setdefault(name.casefold(), name)
+        return list(names.values())[:MAX_NICKNAMES]
 
 
     def index(self) -> dict[str, list[str]]:
     def index(self) -> dict[str, list[str]]:
         """Identifiants des membres dont la fiche n'est pas vide, avec leurs surnoms (voir _index)."""
         """Identifiants des membres dont la fiche n'est pas vide, avec leurs surnoms (voir _index)."""

+ 4 - 0
changelog.txt

@@ -69,6 +69,10 @@ Corrections
   réparation, au lieu de repartir d'un registre vide qui réactivait la
   réparation, au lieu de repartir d'un registre vide qui réactivait la
   mémoire de ceux qui l'avaient désactivée. Le fichier n'est plus mis
   mémoire de ceux qui l'avaient désactivée. Le fichier n'est plus mis
   de côté ni réécrit ; le log et /memoire signalent la panne.
   de côté ni réécrit ; le log et /memoire signalent la panne.
+- Les surnoms d'un membre sont reconnus même si la ligne « Surnoms : … »
+  n'est pas la première de sa fiche (ajoutée à la fin, ou fiche
+  modifiée à la main), avec ou sans puce ; plusieurs lignes « Surnoms »
+  sont regroupées. Avant, elles étaient ignorées sans rien dire.
 
 
 Divers
 Divers
 - Le token Discord et les clés Anthropic et Steam n'apparaissent plus
 - Le token Discord et les clés Anthropic et Steam n'apparaissent plus

+ 11 - 0
tests/test_memory.py

@@ -47,6 +47,17 @@ class NicknamesTest(MemoryTestCase):
         self.assertEqual(nicknames, names[:MAX_NICKNAMES])
         self.assertEqual(nicknames, names[:MAX_NICKNAMES])
         self.assertNotIn("alexandrins", self.store.system_context())
         self.assertNotIn("alexandrins", self.store.system_context())
 
 
+    async def test_line_found_anywhere(self):
+        fiche = f"membres/{MEMBER_ID}.md"
+        self.write(fiche, "habite à Lyon")
+        await self.tool(fichier=fiche, texte="- Surnoms : Bobby, le Grand B")
+        self.assertEqual(self.store.nicknames(MEMBER_ID), ["Bobby", "le Grand B"])
+        self.assertIn("Bobby", self.store.system_context())
+
+    async def test_several_lines_merged(self):
+        self.write(f"membres/{MEMBER_ID}.md", "Surnoms : Bobby\nhabite à Lyon\nsurnoms : bobby ; B")
+        self.assertEqual(self.store.nicknames(MEMBER_ID), ["Bobby", "B"])
+
     async def test_short_nickname_kept(self):
     async def test_short_nickname_kept(self):
         name = "x" * MAX_NICKNAME_LENGTH
         name = "x" * MAX_NICKNAME_LENGTH
         self.write(f"membres/{MEMBER_ID}.md", f"Surnoms : {name}")
         self.write(f"membres/{MEMBER_ID}.md", f"Surnoms : {name}")