--- icon: lucide/bot tags: - Pydantic AI --- # Intégration Pydantic AI Vous voulez un agent Pydantic AI où le modèle ne voit jamais que des jetons, jamais les vrais noms de la conversation, et où une valeur garde le même jeton d'un tour à l'autre. Cette page assemble cet agent de bout en bout avec un détecteur GLiNER2, un `ThreadAnonymizationPipeline`, et `pii_hooks`, la capability qui dé-identifie autour du modèle. La capability couvre les messages, le prompt utilisateur et les réponses du modèle lui-même. Elle couvre aussi la frontière des outils, c'est-à-dire les appels d'outils et leurs résultats. Sous la stratégie par défaut, un outil reçoit les vraies valeurs pendant que le modèle continue de travailler sur des jetons. !!! note "Prérequis" `piighost` installé avec les extras pydantic-ai et gliner2, `pip install "piighost[pydantic-ai,gliner2]"`, plus une clé OpenAI dans `OPENAI_API_KEY`. La première exécution télécharge les poids de GLiNER2, environ 500 Mo. ## 1. Construire le pipeline sur un détecteur GLiNER2 `Gliner2Detector` enrobe un modèle GLiNER2. Passez l'identifiant du modèle sous forme de chaîne et il se charge à la construction. Passez `labels` pour lui indiquer quels types d'entités interroger. Seul le détecteur est requis, car le pipeline de conversation fournit par défaut son linker, son anonymiseur, et un stockage de conversation en mémoire. L'anonymiseur par défaut émet le jeton délimité `<>`{ .placeholder } que `pii_hooks` sait retrouver. ```python from piighost.components.detector.ner import Gliner2Detector from piighost.pipeline import ThreadAnonymizationPipeline detector = Gliner2Detector( model="fastino/gliner2-multi-v1", labels=["PERSON", "LOCATION"], threshold=0.5, ) pipeline = ThreadAnonymizationPipeline(detector) ``` ## 2. Attacher la capability à l'agent `pii_hooks` prend le pipeline et un identifiant de conversation, puis renvoie une capability Pydantic AI. Enregistrez-la avec `capabilities=[...]`. Chaque identifiant de conversation a ses propres jetons. Une valeur garde donc un seul jeton pour toute la conversation. C'est une chaîne fixe ici. Passez un appelable sur le contexte d'exécution, par exemple `lambda ctx: ctx.deps.thread_id`, pour le lire à chaque exécution. ```python from pydantic_ai import Agent from piighost.integrations.pydantic_ai import pii_hooks hooks = pii_hooks(pipeline, thread_id="thread-42") agent = Agent("openai:gpt-5.6-terra", capabilities=[hooks]) ``` ## 3. Lancer un tour La capability dé-identifie le prompt avant que le modèle ne le lise, et restaure la réponse pour l'affichage. Le modèle travaille donc sur `<>`{ .placeholder } pendant que vous lisez `Patrick`{ .pii }. ```python import asyncio async def main() -> None: result = await agent.run("Where does Patrick live?") print(result.output) asyncio.run(main()) ``` La réponse est restaurée pour l'affichage. Sa formulation dépend du modèle, par exemple : ```text I have no address for Patrick. ``` ## Qui voit quoi `GLiNER2` repère `Patrick`{ .pii } comme `PERSON` dans le message entrant. À partir de là, la capability remplace dans un sens avant l'appel au modèle, et dans l'autre sens après : - `before_model_request` fait passer chaque texte utilisateur et assistant par `pipeline.anonymize`. Le modèle reçoit donc `Where does <> live?`. Ce hook réécrit aussi les textes de l'assistant. Une valeur restaurée pour l'affichage à un tour précédent est donc dé-identifiée à nouveau avant l'appel suivant, et ne réapparaît jamais en clair dans l'historique. - `after_model_request` fait passer la réponse par `pipeline.deanonymize`, vous lisez donc la vraie valeur. Le `thread_id` garde `<>`{ .placeholder } lié à `Patrick`{ .pii } à chaque tour. ## Les jetons que le modèle invente Après la restauration, chaque jeton émis est revenu à sa valeur. Un texte qui a encore la forme d'un jeton a donc été inventé par le modèle, par hallucination ou par injection de prompt. `pii_hooks` prend un `invented_strategy` qui décide de ce qui se passe alors. `RAISE` le refuse. C'est le défaut, qui bloque plutôt que de laisser passer. `KEEP` le laisse. `DROP` le retire. ```python from piighost.integrations.langchain import InventedPlaceholderStrategy hooks = pii_hooks( pipeline, thread_id="thread-42", invented_strategy=InventedPlaceholderStrategy.DROP, ) ``` ## Appels d'outils `pii_hooks` traite aussi la frontière des outils. `tool_strategy` la pilote, avec le même enum que le middleware LangChain. Sous `FULL`, le défaut, les arguments d'un appel d'outil sont restaurés avant l'exécution. Un outil qui a besoin de `Patrick`{ .pii } le reçoit donc, et non `<>`{ .placeholder }. Le résultat texte de l'outil est dé-identifié à nouveau avant que le modèle ne le lise, et le modèle continue donc de voir des jetons. `INPUT` ne restaure que les arguments, `OUTPUT` ne dé-identifie à nouveau que le résultat, et `PASSTHROUGH` ne touche à rien. ```python from piighost.integrations.langchain import ToolCallStrategy hooks = pii_hooks(pipeline, thread_id="thread-42", tool_strategy=ToolCallStrategy.FULL) ``` ## Valeurs de l'assistant Toute valeur n'est pas une donnée confidentielle de l'utilisateur. Le modèle introduit parfois lui-même une valeur tirée de sa connaissance du monde. La dé-identifier cacherait cette valeur au modèle au tour suivant, sans rien protéger côté utilisateur. `assistant_strategy` décide du sort d'une valeur introduite par l'assistant, avec encore le même enum que le middleware. Sous `PRESERVE`, le défaut, la valeur reste en clair. Le modèle garde donc sa propre connaissance, et seule une valeur utilisateur connue est dé-identifiée. `ANONYMIZE` la dé-identifie quand même. `IGNORE` saute entièrement les messages de l'assistant, et le détecteur ne tourne donc pas sur eux. ```python from piighost.integrations.langchain import EntityCreateByAssistantStrategy hooks = pii_hooks( pipeline, thread_id="thread-42", assistant_strategy=EntityCreateByAssistantStrategy.ANONYMIZE, ) ``` ## Voir aussi - Pour comparer avec le middleware d'agent LangChain, voyez l'[intégration LangChain](langchain.md). - Pour remplacer GLiNER2 par spaCy, un pack de regex, ou votre propre détecteur, voyez [Étendre piighost](../extending.md). - Les scripts exécutables sont dans `examples/pydantic_ai/base.py` (messages) et `examples/pydantic_ai/tools.py` (un outil).