Skip to content

muller-j/agent-ia-rex

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 

Repository files navigation

Sommaire

Développement applicatif à l'aide d'agents IA : retours d'expérience.

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.

Contexte

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.

Exemple de contexte

Par exemple voici les détails du contexte ci-dessus :

System prompt

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.

System tools

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).

MCP tools

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).

Memory files

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.

Skills

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.

Messages

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

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_code
  • grep_code
  • find_in_files
  • semantic_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 UserService dans 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

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

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.

⚠️ Attention il existe plusieurs techniques de compaction certaines possède des pertes d'information et d'autres non, il est donc important d'avoir une bonne compréhension de ces techniques avant de les utiliser.

Voici quelques techniques de compaction du contexte :

Compaction par suppression

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.

⚠️ Attention il est important de noter qu'un /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.

Compaction par résumé

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.

⚠️ Attention cette technique entraine forcement des pertes d'information car le résumé peut omettre des détails qui semblaient inutiles sur le moment. Cette action doit donc être utilisée avec précaution.

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

Compaction par sélection

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 (token oriented object notation)

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,true

Caveman

Caveman 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.

RAG (retrieval augmented generation)

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]"
Loading

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:

Rationalisation des tools

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 :

La réduction du nombre de tools

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.

Le Tools Batching

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
Loading

L'architecture Supervisor / sub-agent

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
Loading

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
Loading

on se retrouveras avec :

$$\text{Appel global} = \text{LLM Superviseur (Routage)} + \text{LLM Sous-Agent (Exécution)} + \text{LLM Superviseur (Synthèse)}$$

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.

Synthèse

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

Avis personnel

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.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors