- Contexte
- System prompt
- System tools
- MCP tools
- Memory files
- Skills
- Messages
- Les capacités globales
- L'architecture logicielle
- La compaction du contexte
- Compaction par suppression
- Compaction par résumé
- Compaction par sélection
- Toon (token oriented object notation)
- Caveman
- RAG (retrieval augmented generation)
- Rationalisation des tools
- La réduction du nombre de tools
- Le Tools Batching
- L'architecture Supervisor / sub-agent
- Synthèse
- Avis personnel
Il y a depuis quelques mois de plus en plus d'outils et de techniques pour développer des applications à l'aide d'agents IA. J'ai donc voulu faire le point sur les différents outils et techniques que j'ai pu croiser, et partager mes retours d'expérience.
Le contexte peut être vu comme une mémoire volatile (comme la RAM d'un ordinateur), qui est utilisée pour stocker l'identité de l'agent, les outils à sa disposition, les conversations passées, etc. C'est dans ce contexte que l'agent va puiser les informations nécessaires pour répondre aux questions ou accomplir les tâches qui lui sont demandées.
Par exemple voici les détails du contexte ci-dessus :
C'est le prompt système principal de l'agent. Il contient les instructions de base, la personnalité de l'IA, les règles à respecter, les formats de réponse, etc.
Ce contexte se situe généralement dans le fichier AGENTS.md ou dans un fichier system_prompt.md.
C'est la liste des outils système disponibles pour l'agent (exemple : lire un fichier, faire une requête HTTP, utiliser Git, etc).
Ce contexte se situe généralement dans le répertoire .opencode/tools/ ou dans ~/.opencode/tools/ (pour les outils globaux).
C'est la liste des serveurs MCP (Model Context Protocol) disponibles pour l'agent (exemple: mcp jira, mcp github, etc).
Ce contexte se situe généralement dans le répertoire .opencode/mcp/ ou dans ~/.opencode/mcp/ (pour les outils globaux).
Ce sont les fichiers de mémoire de l'agent, qui contiennent les conversations passées, les actions effectuées, les résultats obtenus, etc.
Ce contexte se situe généralement dans le répertoire .opencode/memory/ ou dans des fichiers comme NOTES.md, CONTEXT.md.
Ce sont les compétences de l'agent, c'est à dire les tâches qu'il est capable d'accomplir (exemple : faire une recherche sur Google, écrire du code, etc).
Ce contexte se situe généralement dans le répertoire .opencode/skills/ ou dans des fichiers comme SKILLS.md.
Ce sont les messages échangés entre l'agent et l'utilisateur, ou entre l'agent et les outils. Ils contiennent les questions posées, les réponses données, les actions effectuées, etc.
Il est important de noter que le contexte est limité en taille et que plus celui-ci se remplit, plus l'agent produit des réponses de moins bonne qualité et donc fait apparaitre plusieurs problèmes :
- Perte de précision
- augmentation des hallucinations
- oublie des instructions système
- raisonnement de moins bonne qualité
- augmentation du cout en token
- augmentation du temps de réponse
Certains modèles ont des fenêtres de contexte de plus de 500k tokens, ce qui semble énorme mais dans la pratique, beaucoup de modèles voient leurs performances se dégrader bien avant d’atteindre leur fenêtre de contexte maximale, parfois autour de 50–60 % de la fenêtre annoncée.
En pratique, limiter les informations inutiles dans le contexte (context rot) est souvent l’un des moyens les plus efficaces pour préserver les performances d’un agent.
Les capacités globales sont les outils, les compétences, les serveurs MCP, etc, qui sont disponibles pour tous les agents. Ils se situent généralement dans les répertoires au niveau de l'utilisateur (~/.opencode/).
Dans la pratique, il est souvent préférable de limiter les capacités globales autant que possible, car elles sont partagées entre tous les agents. Même lorsque les capacités globales ne sont jamais utilisées, leurs descriptions et leurs schémas sont souvent injectés dans le contexte. Cela va avoir plusieurs conséquences néfastes :
- Augmentation inutile de la taille du contexte avec tout ce que cela implique (cf. chapitre contexte)
- Augmentation du bruit décisionnel.
J’utilise ici le terme de bruit décisionnel pour désigner le fait que plus un agent a de capacités à sa disposition, plus il a de choix à faire pour décider quelle capacité utiliser pour accomplir une tâche.
Par exemple imaginons un agent qui possède les compétences suivantes :
search_codegrep_codefind_in_filessemantic_search_code
Toutes ces compétences accomplissent à peu près la même chose : trouver du code dans un projet. Donc maintenant imaginons que l'utilisateur demande :
Trouve moi la class
UserServicedans le projet.
l'agent va devoir faire un choix entre les 4 compétences pour accomplir la tâche. Pour ce faire il va donc :
- Comparer les outils
- Comprendre les différences entre les outils
- Choisir lequel est le plus adapté à la tâche
- Parfois hésiter ou utiliser le mauvais outil
Les conséquences de ce bruit décisionnel sont :
- Augmentation du temps de réponse
- Possibilité d'erreur dans le choix de la capacité
- Augmentation du coût en token
- Diminution de la performance globale de l'agent
- Difficulté à suivre les instructions système
A noter que le bruit décisionnel ne vient pas uniquement des capacités globales, mais aussi :
- Des instructions système trop vagues / nombreuses / contradictoires
- De prompts d'entrée trop longs ou trop complexes
- D'une architecture de code mal organisée ou trop complexe
- D'un rag qui retourne trop de résultats non pertinents
- D'un historique de conversation trop long ou non pertinent
- etc
L’architecture logicielle semble avoir un impact important sur les performances des agents IA.
Plus une architecture est claire, modulaire et prévisible, plus il devient facile pour un agent de :
- Comprendre le système dans lequel il évolue
- Trouver les informations dont il a besoin pour accomplir une tâche
- Retrouver les informations pertinentes
- D'accomplir des modifications sans charger une quantité d'informations inutile dans le contexte
A l'inverse, une architecture mal organisée, non modulaire ou trop complexe va rendre la tâche de l'agent plus difficile, et donc impacter négativement ses performances.
Ce point est particulièrement visible avec les architectures logicielles utilisant fortement :
- Les interfaces
- L'inversion de dépendance
- Les responsabilités explicites
- Les frontières métier claires
Pour citer quelques exemples d'architecture qui possèdent ces caractéristiques, on peut retrouver :
- L'architecture hexagonale
- La clean architecture
- L'architecture orientée domaine (DDD)
- L'architecture orientée microservices
- etc...
Exemple concret :
Imaginons une application e-commerce dans laquelle nous souhaitons exposer un nouveau usecase permettant à un utilisateur de consulter l'historique de ses commandes.
Dans certaines architectures où les responsabilités techniques et métier sont fortement mélangées, l'agent devra souvent charger une grande partie de l'implémentation pour comprendre comment récupérer les commandes :
public class OrderRepository {
private final EntityManager entityManager;
private final RedisTemplate redisTemplate;
private final KafkaProducer kafkaProducer;
private final MetricsRegistry metricsRegistry;
private final RetryExecutor retryExecutor;
private final AuditService auditService;
public List<Order> findOrdersByUserId(String userId) {
metricsRegistry.increment("orders.search");
String cacheKey = "orders:user:" + userId;
List<Order> cachedOrders = redisTemplate.get(cacheKey);
if (cachedOrders != null) {
return cachedOrders;
}
return retryExecutor.execute(() -> {
List<OrderEntity> entities = entityManager
.createQuery("""
SELECT o
FROM orders o
LEFT JOIN order_items oi ON oi.order_id = o.id
LEFT JOIN payments p ON p.order_id = o.id
WHERE o.user_id = :userId
ORDER BY o.created_at DESC
""")
.setParameter("userId", userId)
.getResultList();
List<Order> orders = entities.stream()
.map(this::mapEntity)
.toList();
kafkaProducer.publish(
"orders.history.consulted",
new OrderHistoryConsultedEvent(userId)
);
auditService.track(
"ORDER_HISTORY_CONSULTED",
Map.of("userId", userId)
);
redisTemplate.set(cacheKey, orders);
return orders;
});
}
}Dans cet exemple, l'agent doit charger énormément d'informations qui ne sont pas forcément utiles pour comprendre le comportement métier :
- Cache redis
- Métriques
- retry
- Kafka
- Audit
- etc
A l'inverse, avec une architecture basée sur des interfaces, l'agent peut souvent raisonner uniquement à partir du contrat d'interface, sans avoir à charger l'implémentation complète, L'interface va donc agir comme une représentation compressée du système :
public interface OrderRepository {
List<Order> findOrdersByUserId(String userId);
}Dans cet exemple, l'agent peut comprendre le comportement métier simplement à partir de la signature de l'interface, sans avoir à charger les détails de l'implémentation.
Cela va donc permettre :
- De réduire fortement la taille du contexte
- Diminuer le bruit décisionnel
- Améliorer la lisibilité du système pour l'agent
- Limiter la consommation de token
La compaction du contexte est une technique qui consiste à réduire la taille du contexte en supprimant les informations inutiles ou redondantes, ou en les résumant de manière à ce qu'elles prennent moins de place.
Voici quelques techniques de compaction du contexte :
Cette technique est la plus simple, elle consiste à supprimer les informations qui ne sont pas nécessaires pour accomplir une tache. La plupart du temps cela se traduira par la suppression de la session courante de conversation. Cela sera fait généralement en faisant un /new dans opencode et en supprimant les anciennes sessions de conversation.
/new dans opencode ne supprime pas la mémoire de l'agent, il supprime uniquement la session courante de conversation. Les informations contenues dans la mémoire de l'agent (comme les fichiers de mémoire, les notes, etc) ne sont pas supprimées et peuvent toujours être utilisées par l'agent pour accomplir des tâches. C'est pour cela qu'il est important de faire le ménage régulièrement dans ses sessions et les supprimer lorsqu'elles ne sont plus nécessaires, pour éviter d'avoir un contexte trop chargé et de préserver les performances de l'agent.
Cette technique consiste à résumer les informations du contexte de manière à ce qu'elles prennent moins de place, tout en conservant l'essentiel de l'information. Contrairement à la suppression, l'agent utilise ici sa propre intelligence (ou utilise un autre modèle spécialisé dans le résumé) pour compresser les informations du contexte, de manière à ce qu'elles prennent moins de place, tout en conservant l'essentiel de l'information. La qualité de la compaction dépendra donc directement de la qualité du modèle utilisé.
C'est la méthode qui est utilisée quand un utilisateur fait compact session dans opencode, voici le code pour de l'implémentation pour les curieux.
Dans la pratique, il est préférable de l'utiliser quand le contexte n'est pas encore trop chargé (~60 % de la fenêtre de contexte maximum) car plus le contexte sera chargé, plus le résumé sera de mauvaise qualité et plus il y aura de pertes d'information. De plus, ne pas attendre la compaction automatique peut permettre de passer des instructions de compaction si l'outillage de compaction le permet.
Par exemple, dans claude code on peut faire :
/compact garde en priorité le schéma de la base de données
Cette technique consiste à remplacer une implémentation complète par une représentation plus compacte du système, à noter qu'on peut autant l'appliquer sur l'input que sur l'output de l'agent.
Quelques exemples concrets de compaction par sélection :
Toon est un format de sérialisation de données orienté token, conçu pour être utilisé dans les contextes d'agents IA. Il permet de représenter des objets complexes de manière compacte, en utilisant une syntaxe simple et facile à comprendre pour les modèles de langage.
Voici un exemple de données sérialisées en format Toon :
{
"context": {
"task": "Our favorite hikes together",
"location": "Boulder",
"season": "spring_2025"
},
"friends": ["ana", "luis", "sam"],
"hikes": [
{
"id": 1,
"name": "Blue Lake Trail",
"distanceKm": 7.5,
"elevationGain": 320,
"companion": "ana",
"wasSunny": true
},
{
"id": 2,
"name": "Ridge Overlook",
"distanceKm": 9.2,
"elevationGain": 540,
"companion": "luis",
"wasSunny": false
},
{
"id": 3,
"name": "Wildflower Loop",
"distanceKm": 5.1,
"elevationGain": 180,
"companion": "sam",
"wasSunny": true
}
]
}peut être sérialisé en format Toon de la manière suivante :
context:
task: Our favorite hikes together
location: Boulder
season: spring_2025
friends[3]: ana,luis,sam
hikes[3]{id,name,distanceKm,elevationGain,companion,wasSunny}:
1,Blue Lake Trail,7.5,320,ana,true
2,Ridge Overlook,9.2,540,luis,false
3,Wildflower Loop,5.1,180,sam,trueCaveman est un outil qui va faire effectuer la compression directement sur l'output. Pour ce faire il va demander aux modèles d'appliquer quelques contraintes visibles ici.
Exemple de compression en utilisant Caveman :
Réponse classique d'un agent sans Caveman :
(💰 69 Tokens)
The reason your React component is re-rendering is likely because you're creating a new object reference on each render cycle. When you pass an inline object as a prop, React's shallow comparison sees it as a different object every time, which triggers a re-render. I'd recommend using useMemo to memoize the object.
Réponse en utilisant les contraintes de Caveman :
(💰 19 Tokens)
New object ref each render. Inline object prop = new ref = re-render. Wrap in useMemo.
En moyenne selon les maintainer du projet il y'a une économie de ~75 % de token de sortie.
Les principaux avantages de ce genre d'outils sont :
- Un contexte de message réduit, ce qui implique directement une utilisation plus longue d'un même contexte.
- Une accélération du temps de réponse du fait de la réduction du token de sortie, la réponse est plus rapide
- Réduction de la consommation de token les tokens de sortie des LLM étant plus chers, la facture API sera donc réduite de manière non négligeable
Cependant, il est important de noter que dans certains cas d'usage l'outils n'est pas adapter, par exemple :
- La dégradation du raisonnement complexe : Pour des tâches de raisonnement complexes, les modèles semblent souvent bénéficier d’une génération intermédiaire de tokens permettant d’expliciter une partie du raisonnement. En le forçant à compresser ses pensées immédiatement, on vient affecter sa capacité de logique ce qui augmente le risque d'erreur et d'hallucinations.
Le RAG est une architecture de recherche et d'orchestration qui connecte un moteur de recherche (Votre base de connaissance) à un moteur de raisonnement (LLM, algorithme de classification, etc.). Cette structure permet à l'IA d'accéder à vos données.
En isolant la recherche, cette architecture offre deux avantages majeurs :
-
Economie de token : L'agent délègue la fouille de documents à un outil externe optimisé. Il ne reçoit que les extraits strictement nécessaires, évitant d'engorger son contexte avec des fichiers entiers.
-
Polyvalence de source : On peut utiliser le RAG pour stocker différents types de documents nous ne sommes pas limités à l'usage de texte. On peut l'utiliser pour faire de la recherche vidéo, recherche d'image etc...
sequenceDiagram
autonumber
actor User as Utilisateur
participant Agent as Agent IA<br/>(Moteur de Raisonnement)
box RGB(240, 245, 255) Moteur de Recherche (Externe)
participant DB as Base Vectorielle / Outil
end
User->>Agent: "Comment fonctionne l'authentification ?"
Note over Agent: Raisonnement :<br/>Je n'ai pas l'info dans mes poids.<br/>Je dois utiliser mon outil.
Agent->>DB: outil_recherche_code("auth")
Note over DB: Fouille & Filtrage :<br/>Extrait uniquement les 5 lignes utiles<br/>(Économie de tokens)
DB-->>Agent: Retourne l'extrait pertinent brut
Note over Agent: Injection du contexte<br/>+ Génération de la réponse
Agent-->>User: "Voici comment fonctionne l'auth : [Explication précise]"
La performance et l'efficacité d'un RAG dépend entièrement de la complexité de son architecture. Comme le montre l'article suivant obtenir des réponses de haute qualité implique un compromis entre la latence et le coût financier. De plus mettre en place un RAG performant demande un effort d'ingénierie (chunking intelligent, gestion des embeddings, ranking, etc.). De plus ce n'est pas une solution miracle, elle présente des limites :
-
Les questions avec un grand scope ou des demande de synthèse, par exemple il aura des performance très faible dans le cas où l'on va demander
Résume les interactions de ces 500 fichiers présents dans le repo de code. Dans ce contexte le RAG devient souvent moins efficace car il est conçu pour extraire des morceau de texte locaux, pas pour avoir une vue d'ensemble. -
Le manque de réflexion logique : un RAG est conçu pour indexer et restituer de la donnée, pas pour réfléchir. Il sera donc incapable de faire des déductions complexes à partir des données restituées. Donc si un problème demande de connecter des règles logiques éparpillées pour résoudre le problème. L'agent devra appeler n fois les RAG et celui-ci perdra énormément en performance.
Pour le curieux qui voudrais essayer de construire un RAG voici quelque cookbooks:
Un agent peut également être optimisé via la structure des tools qu'il utilise. Donner trop d'outils, des outils trop atomiques ou des outils inutiles peuvent nuire à votre agent car cela augmente sa complexité cognitive de la même manière qu'un humain et cela va ralentir ses décisions augmenter le risque d'erreur et d'hallucinations tout en augmentant sa consommation de token car celui-ci devra plus raisonner pour prendre ses décisions.
Pour optimiser cette partie il existe plusieurs stratégie dont :
C'est la manière la plus idiomatique pour redonner de la précision à un agent. Certaines études suggèrent qu’au-delà d’un certain nombre d’outils (~7 selon How Many Tools Should an LLM Agent See? A Chance-Corrected Answer), les performances de sélection se dégradent fortement.
Pour ce faire on a 2 stratégies à ma connaissance :
-
La suppression pure et simple des outils non essentiels ou utilisés par l'agent.
-
Le découpage par spécialisation, en utilisant une architecture "Superviseur / sous-agent" (que nous détaillerons plus tard dans une partie dédiée). Au lieu d'avoir un seul agent avec 20 outils, on crée un superviseur qui distribue les tâches au sous-agent qui aura 6-7 outils spécifiques.
Cette seconde stratégie consiste à éliminer l'enchaînement de tools répétitifs et prévisibles. Par exemple, si on remarque qu'un agent appelle systématiquement l'outil A, attend le résultat puis appelle l'outil B par exemple lire_log puis extraire_erreur. Laisser l'agent faire cela va augmenter sa consommation de token et la latence, car celui-ci va devoir raisonner entre chaque étape.
La solution est donc de fusionner ces 2 outils dans un script. Au lieu de donner 2 outils atomiques à l'agent, on lui fournit un outil packagé. Exemple diagnostique_erreur. L'agent déléguera donc l'ensemble de la chaîne d'exécution au script, économisant ainsi plusieurs cycles de réflexion coûteux.
graph TD
subgraph Approche Naïve : Chaining Inutile
A[Question] --> Agent1[Agent]
Agent1 -->|Appel LLM $| T1[Tool A: lire_logs]
T1 --> Agent1
Agent1 -->|Appel LLM $| T2[Tool B: extraire_erreur]
end
subgraph Approche Optimisée : Tools Batching
B[Question] --> Agent2[Agent]
Agent2 -->|Un seul appel $| MT[Macro-Tool: diagnostiquer_erreur]
end
Au vu de ce qui a été vu avant on peut tirer un constat. C'est que quand un système doit gérer un grand nombre de fonctionnalités, l'agent unique perd sa performance à cause de ses outils, de sa taille de contexte etc. Pour essayer de solutionner ce problème, on a mis à inventer le pattern supervisor / sub-agent.
Cette architecture part d'un constat simple, un agent unique peut posséder trop de responsabilités. De ce fait l'architecture multi agents peut être vue comme une stratégie de partitionnement du contexte
graph TD
User([Utilisateur]) -->|1. Requête globale| Supervisor[Agent Superviseur<br/>Outil: Assigner_Tâche]
subgraph Espace Cognitif Léger et Isolé
Supervisor -->|2. Route via Tool Handoff| AgentDev[Sous-Agent Dev<br/>3 tools de code]
Supervisor -->|2. Route via Tool Handoff| AgentSupport[Sous-Agent Support<br/>2 tools de ticketing]
end
AgentDev -->|3. Retourne le résultat brut| Supervisor
AgentSupport -->|3. Retourne le résultat brut| Supervisor
Supervisor -->|4. Synthèse finale| User
Les avantages de ce pattern :
- Isolation sémantique et cognitive : Chaque sous-agent évolue dans un contexte qui lui est propre et n'a accès qu'aux outils strictement nécessaires.
| Type | Avantage | Inconvénient |
|---|---|---|
| Agent générique | flexible | bruit cognitif |
| Agent spécialisé | efficace | moins adaptable |
- Spécialisation des modèles : on va pouvoir choisir le modèle utilisé par chaque agent de la chaîne, par exemple utiliser un modèle plus performant en logique pour le superviseur, tandis que nos sous-agents pourront tourner sur des modèles beaucoup plus spécialisés, plus rapides et moins coûteux. Chaque agent aura une fenêtre contextuelle qui lui est propre et donc pourra également être plus détaillée car on encadrera mieux le travail qu'il devra exécuter.
Les inconvénients :
-
Superviseur comme point de saturation : même si les sous-agents ont leur charge cognitive réduite, la complexité du système ne disparaît pas elle est simplement déplacée vers le supervisor.
-
propagation d'erreur / hallucination : si un modèle hallucine ou propage une erreur cela se répercute sur l'ensemble de la chaîne et peut même créer une boucle infinie d'appel entre les agents ce qui coûte cher (Les frameworks pour multi agent implémentent tout de même des protections (TTL, Tool call limit, etc.) contre cela, mais cela représente quand même un coût non négociable)
-
Perte de contexte pour les sous-agents : Pour garder les sous-agents léger, on évite de leur transférer l'intégralité de l'historique de la discussion et on leur donne un résumé.
-
Augmentation des coûts : pour répondre à une question l'utilisateur ne fait plus un seul appel LLM, mais une chaîne d'appels. par exemple dans l'architecture suivante :
graph LR
linkStyle default stroke:#eee,stroke-width:1.5px;
User([User Request])
Router[Router]
AgentA[Agent A]
AgentB[Agent B]
AgentC[Agent C]
Synth[Synthesizer]
Final([Final Response])
User --> Router
Router --> AgentA
Router --> AgentB
Router --> AgentC
AgentA --> Synth
AgentB --> Synth
AgentC --> Synth
Synth --> Final
style User fill:#fff,stroke:#0066cc,stroke-width:2px,color:#0066cc
style Final fill:#fff,stroke:#0066cc,stroke-width:2px,color:#0066cc
style Router fill:#fff,stroke:#800080,stroke-width:2px,color:#800080
style Synth fill:#fff,stroke:#800080,stroke-width:2px,color:#800080
style AgentA fill:#fff,stroke:#cc0000,stroke-width:2px,color:#cc0000
style AgentB fill:#fff,stroke:#cc0000,stroke-width:2px,color:#cc0000
style AgentC fill:#fff,stroke:#cc0000,stroke-width:2px,color:#cc0000
on se retrouveras avec :
Si nos agents doivent échanger des rapports ou faire des allers-retours, le nombre de requêtes API peut exploser.
Il sera tout de même important de prendre en compte qu'il existe plusieurs types d'architecture multi-agent comme le montre l'article suivant et que chaque architecture possède ses best of et ses trade off. C'est donc un pattern à adapter à chaque projet.
Au travers de ces différents exemples, on peut tirer une conclusion assez claire : le principal facteur limitant des agents IA n'est pas uniquement lié à la qualité du modèle utilisé, mais surtout à la manière dont on structure l'ensemble de son environnement de travail.
Le contexte devient une nouvelle ressource critique au même titre que la RAM, le processeur, etc. Plus un agent doit manipuler d'informations, de tools etc. plus ses performances se dégradent :
- augmentation du bruit décisionnel
- perte de précision
- hausse des hallucinations
- augmentation des coûts
- augmentation du temps de réponse
L'enjeu majeur des utilisateurs d'agents IA à grande échelle consiste donc à gérer cette pression contextuelle.
Cela passe par plusieurs stratégies :
- limiter les capacités globales
- concevoir des architectures logicielles lisibles et modulaires
- compacter intelligemment le contexte
- rationaliser les tools
- utiliser des architectures multi-agents lorsque la complexité devient importante
Cependant ces optimisations ne sont pas gratuites :
- pertes potentielles d'information
- augmentation de la complexité système
- hausse des coûts
- propagation d'erreurs entre agents
Après plusieurs expérimentations, j'ai l'impression que le développement assisté par agents IA est beaucoup moins "magique" que ce qu'on peut entendre. Dans la pratique, pour obtenir des résultats fiables à grande échelle, il ne suffit pas simplement d'ajouter un LLM dans un projet existant. Il faut souvent adapter toute l'architecture autour : structurer le code, organiser les tools, gérer le contexte, la mémoire, les workflow, etc.
Une partie importante du travail consiste finalement à rendre le projet compatible avec les contraintes des agents IA. J’ai l’impression qu’on ne parle plus uniquement de développement logiciel classique, mais aussi d'ingénierie du contexte et d'orchestration d'agents.
J'ai l'impression que pas mal de systèmes agentiques demandent un investissement de R&D important pour un ROI parfois difficilement justifiable. Les comportements étant probabilistes et non déterministes. Il devient compliqué de tester correctement le système, de garantir sa stabilité dans le temps ou même de prévoir précisément la faisabilité d'une fonctionnalité et de prévoir les coûts d'exécution. Deux exécutions identiques peuvent produire des outputs différents, des appels de tools différents, des temps d'exécution variables et même des coûts variables.
Cela introduit une nouvelle forme de complexité opérationnelle qui vient s’ajouter à la complexité applicative classique. D'autant que l'écosystème évolue extrêmement vite : un changement de modèle, de provider ou de fenêtre de contexte peut modifier complètement le comportement d'un pipeline pourtant fonctionnel avant. Une architecture qui semblait pertinente avec un modèle donné peut nécessiter des ajustements importants quelques mois plus tard.
J'ai l'intuition qu'on ne supprime pas réellement la complexité du développement logiciel, mais qu'on la déplace. Avant, la complexité était principalement dans le code métier. Maintenant, une partie importante de cette complexité se retrouve dans :
- la gestion du contexte
- le design des prompts
- l'orchestration des agents
- le choix et la rationalisation des tools
- les stratégies de compaction
- les systèmes de mémoire
- le monitoring des appels LLM
Au final, on se retrouve parfois à maintenir deux systèmes complexes en parallèle :
- l'application
- l'infrastructure agentique qui permet de travailler dessus
Pour autant, je ne considère toutefois pas ces outils comme inutiles. Dans mes cas d'usages personnel, les meilleurs résultats que j'obtiens viennent surtout de cas d'usage ciblés que j'estime à fort ROI. Par exemple :
- implémentation d'interface
- transposition de DTO entre différents langages
- écriture de test unitaire, E2E, etc.
- génération de documentation
- assistance type "rubber duck debugging"
- automatisation de tâches boilerplate
Dans ces scénarios, le gain de productivité est réel et les limites des modèles restent relativement contrôlables. A l'inverse, plus on cherche à rendre l'agent autonome et généraliste, plus les coûts, la complexité et l’imprévisibilité tendent à augmenter.
