Intégration LangChain v1¶
Cette page présente l'intégration complète de PIIGhost dans un agent LangGraph, basée sur l'exemple disponible dans examples/graph/.
Installation¶
Pour utiliser le middleware LangChain, installez les dépendances supplémentaires :
Dépendance optionnelle
PIIAnonymizationMiddleware importe langchain au moment de son instanciation. Si langchain n'est pas installé, une ImportError explicite est levée avec le message "You must install piighost[langchain] for use middleware".
Structure de l'intégration¶
GLiNER2 model
└── GlinerDetector
└── Anonymizer
└── AnonymizationPipeline
└── PIIAnonymizationMiddleware
└── create_agent(middleware=[...])
Exemple complet¶
from dotenv import load_dotenv
from gliner2 import GLiNER2
from langchain.agents import create_agent
from langchain_core.tools import tool
from piighost.anonymizer import Anonymizer, GlinerDetector
from piighost.middleware import PIIAnonymizationMiddleware
from piighost.pipeline import AnonymizationPipeline
load_dotenv()
# ---------------------------------------------------------------------------
# 1. Définir les outils de l'agent
# ---------------------------------------------------------------------------
@tool
def send_email(to: str, subject: str, body: str) -> str:
"""Envoie un email à l'adresse donnée.
Args:
to: Adresse email du destinataire.
subject: Objet de l'email.
body: Corps du message.
Returns:
Confirmation d'envoi.
"""
return f"Email envoyé à {to}."
@tool
def get_weather(country_or_city: str) -> str:
"""Retourne la météo actuelle pour un lieu donné.
Args:
country_or_city: Nom de la ville ou du pays.
Returns:
Résumé météo.
"""
return f"Il fait 22°C et ensoleillé à {country_or_city}."
# ---------------------------------------------------------------------------
# 2. Configurer le system prompt pour les placeholders
# ---------------------------------------------------------------------------
system_prompt = """\
Tu es un assistant utile. Certaines entrées peuvent contenir des placeholders \
anonymisés qui remplacent des valeurs réelles pour des raisons de confidentialité.
Règles :
1. Traite chaque placeholder comme s'il était la vraie valeur. Ne commente jamais \
son format, ne dis pas que c'est un token, ne demande pas à l'utilisateur de le révéler.
2. Les placeholders peuvent être passés directement aux outils. Cela préserve la \
confidentialité de l'utilisateur tout en permettant aux outils de fonctionner.
3. Si l'utilisateur demande un détail spécifique sur un placeholder \
(ex: "quelle est la première lettre ?"), réponds brièvement : "Je ne peux pas \
répondre à cette question car les données ont été anonymisées pour protéger vos \
informations personnelles."
"""
# ---------------------------------------------------------------------------
# 3. Initialiser la stack d'anonymisation
# ---------------------------------------------------------------------------
# Charger le modèle GLiNER2 (téléchargement HuggingFace ~500 Mo à la première exécution)
extractor = GLiNER2.from_pretrained("fastino/gliner2-multi-v1")
detector = GlinerDetector(model=extractor, threshold=0.5, flat_ner=True)
anonymizer = Anonymizer(detector=detector)
pipeline = AnonymizationPipeline(
anonymizer=anonymizer,
labels=["PERSON", "LOCATION"],
)
middleware = PIIAnonymizationMiddleware(pipeline=pipeline)
# ---------------------------------------------------------------------------
# 4. Créer l'agent LangGraph avec le middleware
# ---------------------------------------------------------------------------
graph = create_agent(
model="openai:gpt-4o-mini",
system_prompt=system_prompt,
tools=[send_email, get_weather],
middleware=[middleware],
)
Comment fonctionne le middleware¶
Le PIIAnonymizationMiddleware intercepte chaque tour de l'agent en trois points :
abefore_model avant le LLM¶
Utilisateur : "Envoie un email à Patrick à Paris"
↓
Middleware : détection NER sur HumanMessage
→ "Envoie un email à <<PERSON_1>> à <<LOCATION_1>>"
↓
LLM voit : "Envoie un email à <<PERSON_1>> à <<LOCATION_1>>"
awrap_tool_call autour des outils¶
LLM appelle : send_email(to="<<PERSON_1>>", subject="...", body="...")
↓
Middleware : désanonymise les args
→ send_email(to="Patrick", subject="...", body="...")
↓
Outil reçoit : to="Patrick" ← vraie valeur
↓
Outil retourne: "Email envoyé à Patrick."
↓
Middleware : reanonymise la réponse
→ "Email envoyé à <<PERSON_1>>."
↓
LLM voit : "Email envoyé à <<PERSON_1>>."
aafter_model après le LLM¶
LLM répond : "C'est fait ! Email envoyé à <<PERSON_1>>."
↓
Middleware : désanonymise tous les messages
→ "C'est fait ! Email envoyé à Patrick."
↓
Utilisateur : "C'est fait ! Email envoyé à Patrick."
Utiliser l'agent¶
import asyncio
async def main():
response = await graph.ainvoke({
"messages": [{"role": "user", "content": "Envoie un email à Patrick à Paris"}]
})
print(response["messages"][-1].content)
# C'est fait ! Email envoyé à Patrick.
asyncio.run(main())
Avec Langfuse (observabilité)¶
L'exemple complet inclut l'intégration Langfuse pour tracer les appels LLM :
from langfuse import get_client
from langfuse.langchain import CallbackHandler
langfuse = get_client()
langfuse_handler = CallbackHandler()
graph = create_agent(
model="openai:gpt-4o-mini",
system_prompt=system_prompt,
tools=[send_email, get_weather],
middleware=[middleware],
callbacks=[langfuse_handler], # (1)!
)
- Les callbacks Langfuse s'ajoutent à
create_agent. Toutes les interactions LLM sont tracées avec les textes anonymisés (le traçage ne voit jamais de données personnelles).
Déploiement avec Aegra¶
L'exemple examples/graph/ est conçu pour être déployé avec Aegra (alternative auto-hébergée à LangSmith).
Fichier aegra.json :
{
"graph": "./src/graph/graph.py:graph",
"http": "./src/graph/app.py:app",
"ttl": {
"interval_minutes": 60,
"default_minutes": 20160
}
}
# Démarrer le serveur de développement (graph + FastAPI sur le port 8000)
uv run aegra dev
# Stack complète avec PostgreSQL
docker compose up --build
Variables d'environnement¶
Copiez .env.example en .env et renseignez :