--- type: workflow title: Suivre une conversation et restaurer la réponse description: Comment piighost garde le même jeton pour une valeur sur toute une conversation, restaure la réponse du modèle, traite les valeurs apportées par l'assistant et les jetons inventés, applique une correction humaine et efface une conversation. tags: [thread, conversation-memory, deanonymize, provenance, invented-placeholder, hitl, erasure, streaming] sources: - id: openwiki-source-a4810bc908328d4c6013f381 resource: repo://src/piighost/components/placeholder/base.py - id: openwiki-source-219ef8159700bea2d8181beb resource: repo://src/piighost/components/placeholder/streaming.py - id: openwiki-source-beb42dcd927067e197327036 resource: repo://src/piighost/conversation_memory/base.py - id: openwiki-source-19f6f6edbb7533ba166dd534 resource: repo://src/piighost/conversation_memory/memory.py - id: openwiki-source-32946ba53121de4a935726de resource: repo://src/piighost/conversation_memory/redis_backend.py - id: openwiki-source-60f405cf9fd8c0cba8a61889 resource: repo://src/piighost/integrations/_deidentify.py - id: openwiki-source-07566b3f03a831d37fa4fbce resource: repo://src/piighost/pipeline/thread.py generated: { by: "claude-code", at: "2026-10-02T18:00:00.000Z" } --- # Suivre une conversation et restaurer la réponse ## En bref - Dans une conversation, une même valeur garde le même jeton du premier au dernier message. - Deux conversations sont isolées. Le même nom peut porter le même numéro dans les deux, mais elles ne partagent rien. - Chaque appel nomme sa conversation. Un appel sans identifiant est refusé, au lieu de tomber dans une conversation partagée. Seule la commande `piighost anonymize` se rabat sur la conversation `default`. - Une valeur citée d'abord par l'assistant reste en clair, même si l'utilisateur la reprend ensuite. - Effacer une conversation supprime sa mémoire. Les jetons de cette conversation ne sont plus restaurés ensuite. Besoins couverts, décrits dans [Besoins par profil](../needs-by-profile.md) : - Responsable conformité : DPO-6 - Développeur : DEV-2, DEV-3, DEV-8, DEV-10, DEV-11 - Exploitant : OPS-2, OPS-7 - Utilisateur de l'application : USER-1, USER-2, USER-6 Les termes sont définis dans le [glossaire](../glossary.md). Le traitement d'un message isolé est décrit dans [Protéger un message avant l'envoi au modèle](protect-a-message.md). ## Pour le métier `piighost` n'a pas d'écran. Une conversation est désignée par un identifiant que l'application transmet à chaque message. Ce que vous pouvez constater, c'est le texte reçu par le modèle et la réponse affichée. ### Qui intervient | Acteur | Rôle | |---|---| | L'utilisateur final | écrit en clair, lit la réponse restaurée, peut corriger un repérage | | L'application | nomme la conversation à chaque appel | | `piighost` | garde en mémoire les valeurs de chaque conversation | | Le modèle | ne reçoit et n'écrit que des jetons | | Le DPO | demande l'effacement d'une conversation | ### Le trajet d'un message dans une conversation ```mermaid flowchart TD A["Message de l'utilisateur, avec l'identifiant de la conversation"] --> B["Repérage des valeurs"] B --> C["Mémoire de la conversation"] C --> D{"Valeur déjà vue ?"} D -- oui --> E["Même jeton qu'avant"] D -- non --> F["Numéro suivant"] E --> G["Le modèle répond avec des jetons"] F --> G G --> H["Restauration avec les valeurs de la conversation"] C -. effacement .-> I["Mémoire vidée"] ``` ### Exemple de conversation | Tour | Auteur | Texte écrit | Texte vu par le modèle | |---|---|---|---| | 1 | utilisateur | Je suis Claire Dubois. | Je suis `<>`. | | 2 | utilisateur | Mon collègue Marc Petit et Claire Dubois. | Mon collègue `<>` et `<>`. | | 3 | assistant | Le siège est à Lyon. | Le siège est à Lyon. | | 4 | utilisateur | Je vais à Lyon voir Marc Petit. | Je vais à Lyon voir `<>`. | Si le modèle répond « Bonjour `<>`, saluez `<>`. », l'utilisateur lit « Bonjour Claire Dubois, saluez Marc Petit. » **Comment vérifier** : demandez à l'équipe technique la correspondance des jetons de la conversation. Chaque jeton doit désigner une seule personne. ### Corriger un repérage L'utilisateur, ou l'application en son nom, peut corriger les valeurs d'un message avant l'envoi, par exemple ajouter un nom oublié, ou rendre lisible un terme masqué à tort. Si votre application a un écran de correction, faites-le dans cet écran. Sinon, demandez-le à l'équipe technique. Elle applique la correction par `anonymize_corrected` (ou la route `/v1/anonymize/corrected` du serveur). 1. Repérez le message à corriger. 2. Ajoutez la valeur oubliée avec son type, ou retirez la valeur masquée à tort. 3. Renvoyez le message corrigé. > [!WARNING] > Corriger un message ancien peut changer les numéros de toute la conversation (BR-CONV-07). Une réponse déjà écrite par le modèle peut alors être restaurée avec le nom d'une autre personne. Corrigez de préférence le dernier message. **Comment vérifier** : la valeur ajoutée part sous forme de jeton, la valeur retirée part en clair, et les autres messages ne changent pas. ### Effacer une conversation Cas typique : une personne exerce son droit à l'effacement. 1. Identifiez la conversation par son identifiant. 2. Demandez son effacement à l'équipe technique, ou appelez la route d'effacement du serveur. 3. Notez le compte rendu, c'est-à-dire le nombre de messages et de valeurs supprimés. Sur un serveur qui tourne en plusieurs instances, l'effacement vide le stockage et le cache de jetons de l'instance qui reçoit la demande. Les autres instances gardent une copie des valeurs dans leur propre cache, jusqu'à ce qu'elle en soit chassée ou que la durée de vie de ce cache expire (BR-STO-06). Si vous effacez des conversations sur demande, faites régler cette durée de vie par l'équipe technique. **Comment vérifier** : restaurez un ancien jeton de cette conversation. Il doit rester tel quel, par exemple « Bonjour `<>` ». Sur un serveur à plusieurs instances, ce contrôle ne prouve pas que la copie des autres instances a disparu. Attendez la fin de la durée de vie du cache, ou faites redémarrer ces instances. ### Règles à connaître **BR-CONV-01.** Quand une valeur réapparaît dans un message suivant de la même conversation, alors elle reprend son jeton. Une nouvelle valeur du même type reçoit le numéro suivant. **BR-CONV-02.** Quand la même valeur apparaît dans deux conversations différentes, alors chacune a sa propre numérotation, et un jeton ne se restaure que dans sa conversation. Par exemple, « Marc Petit » est `<>` dans une conversation et `<>` dans une autre. Restaurer `<>` dans une troisième conversation vide le laisse tel quel. **BR-CONV-03.** Quand un appel ne nomme aucune conversation, alors il est refusé, avant tout envoi au modèle. Une application dont les conversations n'ont pas besoin d'être séparées nomme elle-même la conversation `default`. La raison est que sans identifiant, tous les utilisateurs partageraient leurs jetons. La commande `piighost anonymize` fait exception. Elle sert à essayer un texte isolé, et se rabat sur la conversation `default` quand aucun identifiant ne lui est donné. **BR-CONV-04.** Quand l'assistant cite une valeur avant l'utilisateur, alors elle reste en clair pour toute la conversation, même si l'utilisateur la reprend ensuite (tour 4, « Lyon »). La raison est que le modèle connaît déjà cette valeur, et la masquer lui retirerait une connaissance utile. Les deux autres réglages sont de la masquer comme une valeur de l'utilisateur, ou de ne pas analyser les messages de l'assistant. Une valeur apportée d'abord par l'utilisateur reste masquée, même si l'assistant la répète. **BR-CONV-05.** Quand la réponse du modèle contient un jeton de la conversation, alors il est remplacé par la vraie valeur, même dans un texte que `piighost` n'a jamais protégé. **BR-CONV-06.** Quand la réponse contient un jeton au bon format que `piighost` n'a jamais émis, alors la restauration est refusée par défaut, avec l'erreur `Deanonymized text holds tokens the pipeline never issued: ['<>']`. Ce jeton a été inventé par le modèle ou injecté par un texte. Les deux autres choix sont de le garder ou de le retirer. | Réglage | « Bonjour `<>` et `<>`. » devient | |---|---| | Refuser (par défaut) | erreur, rien n'est affiché | | Retirer | « Bonjour Claire Dubois et . » | | Garder | « Bonjour Claire Dubois et `<>`. » | Un jeton dont la casse ou le numéro a changé (`<>`, `<>`) compte comme inventé. Un jeton aux délimiteurs abîmés (`<< PERSON:1 >>`) n'est pas reconnu du tout et reste tel quel. **BR-CONV-07.** Quand une personne corrige à la main les valeurs d'un message, alors sa correction remplace le repérage automatique de ce message seulement. La liste à masquer (`deny_list` dans la configuration) et la liste à laisser en clair (`allow_list`) s'appliquent encore. Par exemple, retirer « Claire Dubois » du tour 1 le laisse en clair au tour 1, et il reste masqué au tour 2. Les numéros peuvent alors changer pour toute la conversation. Après ce retrait, `<>` désigne Marc Petit et `<>` Claire Dubois. **BR-CONV-08.** Quand un message identique est renvoyé dans la même conversation, alors son repérage n'est pas refait. Le résultat enregistré la première fois est réutilisé. **BR-CONV-09.** Quand une conversation est effacée, alors toute sa mémoire est supprimée, et l'effacement rend le nombre de messages et de valeurs supprimés. Par exemple, il rend `Forgotten(messages=4, detections=5)` pour la conversation ci-dessus. Ensuite, `<>` n'est plus restauré et reste tel quel. **BR-CONV-10.** Quand plusieurs instances du service partagent la même mémoire Redis, alors elles donnent le même jeton à la même valeur d'une conversation, et chacune restaure les jetons émis par l'autre. **BR-CONV-11.** Quand la mémoire est gardée dans le processus, alors, sauf autre réglage, elle garde au plus 10 000 conversations et oublie chacune un jour après son dernier message. Une conversation oubliée ne restaure plus ses jetons. La raison est qu'un serveur qui tourne des semaines ne doit pas garder toutes les valeurs qu'il a vues. Pour une réponse affichée pendant qu'elle arrive, voir [Afficher une réponse au fil de l'eau](show-a-streamed-reply.md). ### Ce que voit l'utilisateur final Une conversation lisible, avec les vraies valeurs. Une ancienne réponse peut toutefois montrer des jetons si l'application la restaure une seconde fois. C'est le cas après un effacement, ou après un jour d'inactivité quand la mémoire est gardée dans le processus. ### Questions fréquentes **L'appel s'arrête avec `No thread_id in the LangGraph config`.** L'application ne nomme pas la conversation (BR-CONV-03). Faites-lui passer un identifiant, ou `default` si les conversations n'ont pas besoin d'être séparées. **Une réponse a affiché le nom d'une autre personne.** Un message ancien a probablement été corrigé à la main (BR-CONV-07), ou la mémoire d'un message a expiré (voir Pièges). Vérifiez l'historique de la conversation. **« Lyon » n'est pas masqué alors que l'utilisateur l'a écrit.** L'assistant l'avait cité avant lui (BR-CONV-04). Pour forcer le masquage, voir [Imposer une liste à masquer et une liste à laisser en clair](impose-a-whitelist-and-blacklist.md). **La réponse s'arrête avec `Deanonymized text holds tokens the pipeline never issued`.** Le modèle a écrit un jeton inconnu (BR-CONV-06). Il a souvent recopié un jeton d'une autre conversation ou d'un document. **La réponse montre `<>` après une longue pause.** La mémoire en processus a oublié la conversation après un jour d'inactivité (BR-CONV-11), ou la conversation a été effacée (BR-CONV-09). ## Pour les développeurs Le guide technique construit un pipeline de conversation pas à pas dans [Pipeline conversationnel](../../../docs/fr/getting-started/conversation.md). ### Où vivent les règles | Règle | Emplacement | |---|---| | BR-CONV-01, BR-CONV-07 | `src/piighost/pipeline/thread.py:289-339` (`_thread_tokens` sur l'union des détections), `components/placeholder/base.py:145-156` | | BR-CONV-02 | `conversation_memory/memory.py` (stockage par `thread_id`), `pipeline/thread.py:219-231` (`deanonymize`) | | BR-CONV-03 | `integrations/langchain/middleware.py:47-66` (`_thread_id`), `integrations/claude_code/hooks.py:101-105`, `piighost-api` (`app.py`, `thread_id` requis sur les routes de conversation) | | BR-CONV-04 | `pipeline/thread.py:322-329`, `conversation_memory/memory.py` (`get_provenance`), `integrations/langchain/middleware.py:370` (`_message_role`) | | BR-CONV-05 | `pipeline/thread.py:219-231` (`deanonymize`) | | BR-CONV-06 | `integrations/_deidentify.py:133-155` (`_handle_invented`) | | BR-CONV-07 | `pipeline/thread.py:190-217` (`anonymize_corrected`), rendu par message lignes 159-178 | | BR-CONV-08 | `pipeline/thread.py:264-287` (`_detect`) | | BR-CONV-09 | `pipeline/thread.py:244-262` (`forget_thread`) | | BR-CONV-10 | `conversation_memory/redis_backend.py` | | BR-CONV-11 | `conversation_memory/memory.py:13-25` (`DEFAULT_MAX_THREADS`, `DEFAULT_TTL`), `config/models/memory.py` (`InMemoryConfig`) | Composants liés : `ThreadAnonymizationPipeline`, `AnyConversationMemory` (`remember`, `get_detections`, `get_provenance`, `forget`), `MessageRole`, `Forgotten`, `thread_token_map`, `TextDeidentifier`, `DEFAULT_THREAD_ID` (`conversation_memory/base.py:31`), `MissingThreadIdError`, `InventedPlaceholderStrategy`, `EntityCreateByAssistantStrategy`. Mécanique : les jetons sont attribués sur l'union des détections de tous les messages, dans l'ordre de première apparition. Le rendu ne remplace que les positions du message courant, car les détections de messages différents partagent le même espace de positions. ### Corriger un message à la main 1. Construisez le jeu corrigé de `Detection` pour le texte exact du message. 2. Appelez `await pipeline.anonymize_corrected(texte, thread_id, détections)`. 3. Si le message corrigé n'est pas le dernier, relancez les tours suivants (BR-CONV-07). #### Vérifier ```bash uv run pytest tests/pipeline/test_thread.py tests/pipeline/test_thread_hitl.py tests/acceptance ``` Après la correction, `await pipeline.thread_token_map(thread_id)` doit montrer la correspondance attendue jeton par jeton. ### Pièges - **Aucune intégration ne retombe sur `default`.** Le middleware LangChain et les hooks Claude Code lèvent `MissingThreadIdError`. Le serveur répond 400. Seule la commande `piighost anonymize` garde `--thread-id default`, pour une commande isolée. - **La numérotation dépend de l'ordre de l'union.** Tout ce qui retire un message ancien de l'union décale la numérotation. C'est le cas d'une correction (BR-CONV-07), mais aussi de l'expiration d'un message Redis avec `ttl`, que `_read_all` retire de l'union (`conversation_memory/redis_backend.py:200-228`). - **La mémoire en processus oublie en silence.** Une conversation évincée ou expirée (BR-CONV-11) ne lève rien. Ses jetons restent tels quels à la restauration. `max_threads=None` et `ttl=None` lèvent les bornes. - **La provenance porte sur la clé de valeur** (`value_key`), donc sur toutes les graphies d'une valeur. - **Le cache de jetons est mémorisé par processus** (256 cartes au plus, `_TOKEN_MEMO_MAX`). Voir [Stocker les conversations](../operations/storage-and-encryption.md) pour l'effet sur l'effacement en multi-processus, et `token_memo_ttl` pour le borner. - **`anonymize_corrected` ne résout pas les chevauchements** et ne relance pas l'expansion. Le jeu corrigé doit être propre. Il passe seulement par la liste à masquer et la liste à laisser en clair. ### Tests | Test | Couvre | |---|---| | `tests/pipeline/test_thread.py` | Jeton stable, numéro suivant, isolement des conversations, cache, effacement et mémo, provenance assistant, contrôle final, durée du mémo | | `tests/pipeline/test_thread_hitl.py` | Ajout et retrait par correction, correction locale au message, correction remplacée | | `tests/integrations/langchain/test_middleware.py` (`TestThreadId`), `tests/integrations/test_claude_code_hooks.py` | Refus d'un appel sans identifiant (AT-DEV-10-1, AT-DEV-10-2) | | `tests/conversation_memory/test_in_memory.py` (`TestBounding`) | Bornes par défaut de la mémoire en processus (AT-OPS-7-1) | | `tests/acceptance/test_dev.py`, `test_dpo.py`, `test_ops.py` | Restauration limitée à sa conversation (AT-DEV-3-2), effacement (AT-DPO-6-1), deux instances sur un même Redis (AT-OPS-2-1) | | `tests/integrations/test_deidentify.py` | Jetons inventés |