· Hernán Pérez Rodal · Engineering  · 6 min read

Agentic Compliance System avec LangGraph : patterns qui fonctionnent en production

Tous les multi-agent patterns ne survivent pas aux domaines régulés. Nous partageons l'architecture d'agents que nous utilisons chez Darwin, pourquoi, et quels anti-patterns nous évitons.

Tous les multi-agent patterns ne survivent pas aux domaines régulés. Nous partageons l'architecture d'agents que nous utilisons chez Darwin, pourquoi, et quels anti-patterns nous évitons.

TL;DR — Les tutoriels d’agents parlent d‘“autonomous AI” comme si c’était un superpouvoir magique. En production régulée, l’autonomie sans guardrails est un cauchemar juridique. Chez Darwin nous faisons tourner un système multi-agent avec LangGraph, et la valeur n’est pas dans l’autonomie — elle est dans l’orchestration déterministe de composants LLM-powered.

Le problème

Darwin traite des milliers de cas de compliance par jour sur des données de traçabilité alimentaire. Chaque cas exige :

  1. Interpréter une question réglementaire (texte libre de l’utilisateur ou du système)
  2. La décomposer en sous-requêtes (réglementaire, opérationnelle, de preuve)
  3. Déléguer chaque sous-requête à l’agent approprié
  4. Synthétiser les résultats en une réponse avec gap analysis + risk scoring
  5. Valider avant de présenter — surtout les données numériques

Essayer de faire que un seul LLM fasse tout end-to-end avec un prompt géant échoue en production. Les modèles se perdent, hallucinent des données, perdent le contexte intermédiaire. Quand l’output va à un audit FDA, “il s’est trompé un peu” n’est pas acceptable.

Le pattern que nous utilisons : supervisor + workers spécialisés

L’architecture :

           [Supervisor]

   ┌──────────┼──────────┬──────────┐
   │          │          │          │
 [Research] [Validation] [Analytics] [Reporting]
  • Supervisor — décide quels agents invoquer, dans quel ordre, et quand s’arrêter
  • Research Agent — cherche le contexte réglementaire + événements de traçabilité (utilise le RAG hybride que j’ai décrit ici)
  • Validation Agent — croise des règles métier déterministes avec les découvertes du Research
  • Analytics Agent — exécute des queries structurées + anomaly detection sur les événements
  • Reporting Agent — synthétise le tout en un output consommable (rapport PDF, API JSON, vue UI)

Le supervisor est la pièce critique. C’est lui qui décide, pas les workers.

Pourquoi LangGraph et rien d’autre

Nous avons évalué plusieurs options avant de choisir LangGraph :

FrameworkPourquoi non / pourquoi oui
LangChain Agents (AgentExecutor)Trop magique. Difficile à débugger en production.
CrewAIBon pour les prototypes. Abstraction trop haute pour des patterns spécifiques.
AutoGenFort sur les conversations multi-agent. Moins idéal pour des flux déterministes.
Custom state machineConsidéré. Pour notre cas, LangGraph donne 80 % avec 20 % du code.
LangGraphState machine explicite + checkpoints + streaming. Debuggable, production-grade.

Le point clé : LangGraph n’est pas “un framework d’agents”. C’est une state machine avec des superpouvoirs pour les LLM calls. Cette différence mentale change la façon de l’utiliser.

Patterns qui fonctionnent

1. Supervisor déterministe, pas autonome

Notre supervisor NE demande PAS au LLM “qu’est-ce que je fais maintenant ?” à chaque step. C’est de l’autonomie magique et c’est un anti-pattern.

Ce qu’il fait :

def supervisor_node(state: ComplianceState) -> str:
    """Decide next node based on state — deterministic rules first, LLM second."""
    if not state.query_classified:
        return "classify_query"
    if state.classification == "regulatory" and not state.regulatory_context:
        return "research"
    if state.has_numerical_claims and not state.validated:
        return "validate"
    if state.ready_for_report:
        return "report"
    return "END"

Le LLM n’entre que dans les nodes individuels (classify, research, validate). Le routing est du code. Ça évite qu’un LLM “décide” de sortir du circuit un step de validation critique.

2. Checkpointing agressif

Chaque step émet un checkpoint. Si quelque chose échoue, on ne ré-exécute pas tout — on reprend depuis le dernier état bon. Critique quand un cas touche 5-10 nodes et chaque LLM call coûte du temps+$.

from langgraph.checkpoint.postgres import PostgresSaver

graph = builder.compile(
    checkpointer=PostgresSaver(conn_string=DATABASE_URL),
    interrupt_before=["report"],  # Pause avant le rapport final
)

3. Human-in-the-loop configurable

Certains cas sont auto-approve (routines), d’autres require review (high-stakes). Nous utilisons interrupt_before dans LangGraph pour faire une pause avant les nodes pour lesquels nous avons décidé qu’une intervention humaine est nécessaire.

L’opérateur revoit, approuve ou édite, et le graph continue depuis le checkpoint. Sans perdre de travail.

4. Tool use avec validation structurée

Quand un agent appelle une tool (query DB, recherche de document, calcul de métrique), tout output est validé contre un schéma Pydantic avant de revenir au LLM. Si la tool retourne quelque chose d’inattendu, l’erreur est gérée explicitement — on ne la passe pas au LLM qui va inventer.

class TraceabilityQueryOutput(BaseModel):
    events: list[CriticalTrackingEvent]
    total_count: int
    date_range: DateRange

# Tool response validated. If fails → retry or escalate, NO LLM hallucination.

5. Observabilité end-to-end

Chaque node émet du tracing avec OpenTelemetry :

  • Input state
  • LLM prompt + response
  • Token usage + cost
  • Latency
  • Tool calls (avec inputs/outputs)

Sans ça, débugger un agent system est impossible. Avec ça, on trouve pourquoi un cas a mal tourné en minutes.

Anti-patterns que nous évitons

❌ Autonomie totale du supervisor. “Le LLM décide quand s’arrêter” = runaway loops + coûts inattendus. Notre supervisor a max iterations par node et timeout par cas.

❌ Conversations agent-to-agent longues. Les tutoriels montrent N agents “discutant”. En production, chaque échange est $$$ et latence. Nous utilisons un message unique par agent, pas de conversation.

❌ Shared memory global sans structure. Tous les agents écrivant dans un dict partagé → race conditions + hallucination sur ce qu’est l’état actuel. Nous utilisons state pydantic-typed avec mutations explicites.

❌ LLM-as-everything. Beaucoup de tutoriels utilisent le LLM pour des choses qu’une règle déterministe résout. Règle générale : si tu peux l’écrire en code, écris-le en code. Le LLM seulement pour ce qui exige vraiment du raisonnement en langage naturel.

Ce qui n’a pas marché

V0 : un seul agent avec tool use — nous avons essayé avec un seul Claude agent avec accès à toutes les tools. Ça a échoué parce que l’agent oubliait parfois des validations critiques quand le prompt devenait long. Nous avons refactorisé en supervisor + workers.

Streaming continu dans l’UI — nous voulions streamer les décisions du supervisor à l’UI en temps réel. C’est devenu confus pour l’utilisateur (il voyait “research → validate → research → validate” sans savoir pourquoi). Nous sommes passés à montrer seulement les milestones principaux et cacher l’orchestration interne.

Ce qui a marché

Supervisor déterministe — zero runaway loops depuis que nous l’avons implémenté.

Checkpointing en PostgreSQL — nous pouvons reprendre des cases restés à moitié par chute d’infra ou quotas d’API.

Validation agent avec règles + LLM — combinaison de règles hardcoded + LLM-as-judge pour les edge cases donne une meilleure précision que l’un ou l’autre seul.

Metrics par agent — nous savons exactement quel agent est le goulot d’étranglement en latence ou coût. Ça nous a permis d’optimiser sélectivement (ex : swapper le Reporting Agent vers un modèle plus petit sans toucher Research).

Lessons learned

  1. Agentic ≠ autonomous — les meilleurs patterns sont déterministes avec des LLM calls sélectifs
  2. LangGraph est une state machine, pas un framework d’agents — pensez-le ainsi et tout simplifie
  3. Checkpointing agressif dès le jour 0 — le coût de l’ajouter après est élevé
  4. Validez les tool outputs avec des schémas — ne passez jamais au LLM quelque chose de non validé
  5. Tracing + cost metrics par node — sans ça il n’y a pas d’itération réelle

Et maintenant ?

Nous explorons des sub-graphs réutilisables — notre Validation Agent est suffisamment générique pour l’utiliser dans d’autres flux (pas seulement compliance). LangGraph supporte la composabilité de graphs, et ça ouvre la porte à un catalogue interne de patterns que nous pouvons remixer.

Si vous construisez des AI agents en production et ça vous surprend que votre système est plus lent/plus cher/plus instable que votre demo — c’est probablement un manque d’orchestration déterministe. Commencez par là.


Vous construisez quelque chose de similaire ? Parlons-en — nous sommes intéressés à partager architecture et patterns.

Compartir:
Back to Blog

Related Posts

View All Posts »