Architecture multi-agents pour un assistant industriel
Faire évoluer l'assistant conversationnel d'une startup IA en pre-seed vers une architecture agentique : passage du POC à la production, spécification de la décomposition du monolithe, puis implémentation de référence Plan-and-Execute avec garde-fous, human-in-the-loop et optimisation mesurée des coûts d'inférence.
Le contexte
Le même assistant de maintenance industrielle que le pipeline RAG : un produit conversationnel construit sur un graphe LangGraph à un niveau (routeur et profils de réponse). Ma mission de founding engineer couvrait les deux horizons : faire passer ce système du POC à la production, et concevoir l’architecture agentique qui porte la vision produit à long terme. J’ai transformé la vision produit en solution technique, de la spécification de l’architecture (décomposition du monolithe, plan de migration, contrat d’API) jusqu’à l’implémentation du code. Cette fiche décrit l’architecture et les mesures.
Du POC à la production
- Chain-of-thought et streaming temps réel : j’ai introduit le chain-of-thought balisé pour améliorer l’accuracy (le modèle structure sa pensée, ses actions d’outils et sa réponse finale en balises XML), et intégré le streaming de bout en bout pour la transparence du raisonnement dans l’UI et une bien meilleure UX de latence : un parser en machine à états émet ces balises en deltas au fil des chunks Bedrock, borné en mémoire, avec fallback sur le contenu complet en cas de flux malformé. Durci après incident contre les boucles infinies silencieuses, avec test de régression.
- Anti-hallucination testée : le modèle inventait des URLs d’images ; validation, retry et contraintes de prompt, livrés avec 79 % de couverture de test.
- Des tokens en moins au bon endroit : retrait des tokens d’image de l’appel de l’orchestrateur, les images ne sont attachées qu’aux nœuds qui en ont besoin.
- Prompt-as-code : 13 chaînes de production migrées en objets Python typés et composables (couches réutilisables, variables validées à l’injection), testées comme du code avec une gate de couverture à 95 % en CI : ordre canonique des couches, séparation system/user et parties statiques positionnées pour le prompt caching y sont vérifiés par des tests unitaires. Le tout versionné vers un hub avec tags d’environnement mobiles et tags de version immuables.
- Observabilité LangSmith de bout en bout : chaque exécution est tracée et archivée avec ses métadonnées utiles (nœud, environnement, release, thread), la même trace servant au débogage en production et aux campagnes d’évaluation qui la rejouent et la jugent.
L’architecture cible
- Plan-and-Execute : un orchestrateur, point de décision unique, planifie et dispatche les tâches en fan-out parallèle avec barrière de synchronisation à la ré-entrée.
- Sous-graphes ReAct isolés : chaque sous-agent (recherche, perception, diagnostic) est un sous-graphe compilé avec son worker, son critic de vérification et son état propre, sans accès à l’état parent.
- Un seul outil visible du modèle :
agent_callavec découverte progressive des skills, chaque capacité étant un fichier markdown, avec workflow, contraintes et exemples d’appels d’outils pour un cas d’usage précis, et un script. Le contexte du modèle reste dynamique et minimal, il ne charge que la compétence utile, au bénéfice direct du coût, de la latence et de la fiabilité. Ajouter une compétence ne touche ni au worker ni au prompt. - Le contexte traité comme une interface : le contenu récupéré est injecté avec un balisage XML sémantique pour que le modèle sépare proprement les sources et les idées, et les agents comme les skills sont nommés avec une distance sémantique suffisante entre eux pour éviter les confusions de sélection au routage.
- Human-in-the-loop auditable : interruption et reprise natives du graphe, à double persistance, dans le checkpoint pour le replay et dans la table métier pour la conformité, avec plafond de cycles et mutation de l’arbre d’hypothèses à la reprise.
- Des budgets partout : itérations de plan, appels d’outils par agent, retries, cycles HITL et timeout global sont tous bornés en configuration déclarative.
Sûreté et streaming
Dans ce domaine, la sûreté a deux visages : protéger le chatbot (injection, jailbreak, contenus interdits) et protéger les opérateurs, car une réponse de maintenance approximative peut créer un risque physique. Les deux sont traités en profondeur :
- Guardrail d’entrée à deux étages : un pré-filtre regex à coût nul court-circuite les cas évidents, un classifieur LLM dédié ne traite que le reste.
- Vérification de sortie graduée : un verdict d’ajustement injecte une contrainte de sûreté dans le synthétiseur plutôt que de refuser, et le protocole
content_retractedpermet de retirer un contenu déjà streamé quand un garde-fou bloque après coup. - Sandbox d’exécution de code à trois couches : validation AST, builtins vidés, limites de ressources.
- Prévention du risque industriel : le contexte de maintenance expose à des dangers physiques (produits chimiques, charge électrique, pression, pièces en rotation), chaque réponse embarque donc une prévention des risques robuste et détaillée, construite à partir des consignes de sécurité des documentations, et une classe de requêtes interdites est testée en évaluation pour vérifier que le système refuse ce qu’il doit refuser.
- Contrat SSE writer-first : 17 types d’événements typés émis directement par les nœuds, le service de streaming reste un tuyau transparent ; ajouter un événement, c’est une ligne dans un nœud, pas une évolution d’API.
Les coûts, mesurés
- Routing de modèles par classe de tâche : le modèle frontier uniquement pour la planification et la synthèse finale, un modèle rapide pour les workers et critics, des modèles safeguard spécialisés pour les guardrails. Une requête utilisateur déclenche 5 à 12 appels LLM.
- Prompt caching Bedrock à placement adaptatif : stratégie de cache différente pour les boucles ReAct et pour le replan de l’orchestrateur, avec gating par modèle pour ne jamais payer le surcoût d’écriture sans lecture.
- Mesuré en conditions réelles plutôt qu’affirmé : 51,7 % de réduction de coût et 49 % de latence en moins au deuxième tour de conversation, seuil de rentabilité dérivé (13,9 % de ratio de lecture), et audit de cacheabilité chaîne par chaîne excluant celles sous le minimum de 1 024 tokens.
L’évaluation en continu
Le graphe de production était lui-même sous surveillance comportementale : 240 conversations synthétiques jouées contre le service réel dockerisé, en flux SSE, avec la trajectoire de raisonnement reconstruite et jugée, pas seulement la réponse finale. Résultats : 99,6 % de routage correct, 96,6 % sans hallucination, avec des juges LLM hiérarchisés selon la difficulté du jugement et un jeu de test auto-dédupliqué incluant une classe adversariale pour vérifier les refus.
Conçu pour être hérité
Une mission de founding engineer se juge aussi à ce qu’elle laisse derrière elle :
- Des règles d’architecture exécutables, versionnées dans les repos : conventions, frontières de couches et anti-patterns nommés (CLAUDE.md, rules, docs), écrits pour que les développeurs comme les agents de code IA héritent des mêmes contraintes et que les contributions s’imbriquent sans dériver.
- Des agents de revue spécialisés versionnés avec le code : qualité des prompts (structure XML, séparation statique/dynamique pour le caching, seuils de tokens) et écriture de tests, mes propres standards de revue automatisés au service de l’équipe.
- Un outillage standardisé : uv comme gestionnaire de paquets unique, pre-commit avec lint, format et typage strict, conventional commits sur l’ensemble des contributions.
Ce que le projet démontre
Une architecture écrite avant d’être codée, puis tenue : frontières de couches vérifiables, budgets bornés, coûts d’inférence traités comme une donnée mesurée et non comme une fatalité, et la sûreté résolue au niveau du protocole plutôt qu’en sacrifiant l’expérience de streaming. C’est le cœur de ce que je construis : des systèmes agentiques dont on peut expliquer chaque décision, chaque token et chaque échec.