Nous developpons votre site vitrine SPA À PARTIR DE 300€. Oui, c'est dingue. Web SPA À PARTIR DE 300€ — Oui, c'est dingue. Parlons-en →

Pourquoi un agent IA est bien plus qu'un appel à un modèle

Pourquoi un agent IA est bien plus qu'un appel à un modèle

Presque tous ceux qui intègrent pour la première fois un modèle de langage dans une application arrivent à la même conclusion erronée : c'est déjà fait. L'appel fonctionne, le modèle répond, et il ne semble rester qu'à peaufiner le prompt. Ce qui sépare cette démo d'un agent que vous pouvez mettre devant un client, ce n'est pas le modèle, c'est tout ce qu'il faut construire autour pour qu'il ne casse pas, ne fasse pas exploser la facture et ne reste pas muet au milieu d'une conversation. Les exemples sont en Laravel, l'une de nos spécialités chez AndorraDev, mais la liste des pièces à construire est la même en Python, Node ou ce que vous utilisez.

L'appel au modèle tient en quatre lignes

Voici la requête HTTP entière vers l'API du modèle, telle qu'elle est dans le code que nous utilisons en production :

$response = Http::withHeaders([
    'Authorization' => 'Bearer ' . $this->apiKey,
    'Content-Type' => 'application/json',
])->timeout($this->timeout)->post($this->baseUrl . '/chat/completions', $body);

Quatre lignes. Le paquet qui les entoure en compte près de sept mille. Cette proportion est tout l'article : faire répondre un modèle est trivial, et ce n'est pas le travail.

Le modèle ne répond pas, il réclame des outils

Dès que vous lui donnez des capacités, le modèle cesse de répondre en un seul tour. Il répond en demandant que vous exécutiez quelque chose, vous l'exécutez, vous lui renvoyez le résultat, et il reprend la parole. Parfois il demande autre chose. Parfois cinq fois de suite.

C'est une boucle, et là apparaît le premier problème que personne n'anticipe en démo : il faut lui rendre son propre tour même s'il arrive vide. Quand le modèle ne demande que des outils, son message ne porte pas de texte. Si vous l'omettez de l'historique parce qu'il semble inutile, les résultats envoyés ensuite pointent vers des appels qui n'existent plus et le fournisseur rejette la requête entière.

Et les outils échouent. La bonne réponse à un outil qui casse n'est pas de propager l'exception, c'est de renvoyer l'erreur au modèle comme un résultat de plus. D'une trace de pile il ne peut pas se relever ; d'un {"error": "..."} il peut choisir une autre voie.

Cette boucle a besoin d'un plafond, et pas par élégance

Un modèle qui insiste sur une recherche qui ne donne rien la reformulera indéfiniment. Sans limite, la boucle tourne jusqu'à ce que quelque chose expire, après avoir payé chaque tour.

Le plafond n'est pas une erreur pour autant : une fois atteint, la dernière réponse revient marquée, un avertissement est enregistré et l'utilisateur reçoit quelque chose. Un agent muet parce qu'il a atteint sa limite est pire qu'un agent qui dit ce qu'il a.

Le détail qui sépare un agent utilisable d'un agent ruineux

Un modèle n'a aucune mémoire de ses propres tentatives à l'intérieur d'un même tour. Si une recherche ne renvoie rien, il n'en conclut pas que la donnée n'existe pas : il en conclut qu'il a mal formulé, et il recommence avec d'autres mots. Cette insistance est raisonnable chez une personne et ruineuse quand chaque tentative est facturée.

Quand le modèle ne trouve rien, sa réaction naturelle est de reformuler et de rechercher. C'est raisonnable une fois. À la quatrième, il brûle de l'argent pour arriver à la même conclusion : ce n'est pas là.

Le problème pour le détecter, c'est que le système ignore ce que renvoient vos outils. Chaque projet a les siens. La solution a été de regarder la forme plutôt que le contenu : si le résultat porte l'une des clés typiques d'une recherche, items, results, matches, c'est une recherche ; et si elles sont toutes vides, il n'a rien trouvé.

Au deuxième vide consécutif, le résultat que reçoit le modèle porte ceci en plus :

Cela fait deux recherches d'affilée sans résultat. Si vous êtes à court de reformulations raisonnables, dites à l'utilisateur que vous n'avez rien trouvé plutôt que de chercher encore.

Cela marche parce que vous ne lui interdisez rien, vous lui donnez la donnée qui lui manque. Le modèle ne sait pas combien de fois il a cherché ; personne ne le lui avait dit.

Un détail fin : un résultat qui ne ressemble pas à une recherche ne casse pas la série. Si entre deux recherches mortes il y a une requête sur autre chose, le compte reste vivant.

La conversation grossit jusqu'à ne plus tenir

Toute longue conversation finit par dépasser la fenêtre du modèle. Il faut résumer, et presque tout le monde commet la même erreur : tout résumer.

Deux règles qu'il nous a fallu apprendre :

Les derniers messages ne sont jamais compressés. C'est là que vit le fil de ce qui se dit, et « un résumé de ce que nous venons de dire » est exactement là où ces systèmes deviennent vagues et répondent à côté.

Les résultats d'outils n'entrent jamais dans le résumé. Ils appartiennent au tour qui les a produits. Les traîner d'un tour à l'autre, c'est ainsi qu'une boucle finit par relire sa propre plomberie au lieu de la conversation.

Le résumé est en plus incrémental : on enregistre jusqu'à quel message on a résumé, et la compression suivante repart du résumé précédent au lieu de le refaire. Et si la compression échoue, on garde l'ancien résumé. Le perdre jetterait tout ce que la conversation avait établi ; en garder un légèrement daté coûte un peu de contexte et rien d'autre.

Andie recommande

Pour décider s'il faut compresser, pas besoin d'un vrai tokeniseur : diviser les caractères par quatre suffit. Se tromper de dix pour cent signifie seulement compresser un tour trop tôt ou trop tard, et embarquer une dépendance entière pour affiner cela ne vaut pas le coup. Gardez la précision pour ce qui facture, c'est à dire la réponse du fournisseur.

Demander s'il reste du solde avant de dépenser, pas après

Si vous vendez cela, chaque tour de boucle coûte de l'argent. Et comme la boucle appelle le modèle une fois par itération, la vérification du solde a lieu plusieurs fois dans un même tour, donc elle doit être peu coûteuse ou mémoïsée.

Il y a une distinction à poser dès le départ, parce qu'elle décide si vous réessayez :

Un 429 du fournisseur est son problème, il survient après le départ de la requête, il est temporaire et on réessaie.

Un solde épuisé est votre problème, il survient avant le départ de la requête, il n'est pas temporaire et on ne réessaie jamais. Là, aucune attente n'y fera : soit le plan change, soit la période suivante commence.

Les traiter pareil revient à réessayer contre votre propre manque de crédit, ce qui est une façon élégante de ne rien faire pendant un moment.

Deux fournisseurs, un seul dialecte

Dès que vous voulez pouvoir changer de fournisseur, arrive le travail ingrat de la traduction. Et ce n'est pas un mappage de champs, ce sont de vraies incompatibilités.

Anthropic refuse des résultats d'outil dans des tours séparés : il faut les fusionner en un seul. Et il refuse un tour d'assistant sans contenu, donc il faut le remplir avec quelque chose.

Pire encore : les capacités ne dépendent pas du fournisseur mais du modèle, et on ne les découvre qu'en essayant. Mesuré contre l'API réelle :

claude-opus-4-8   + temperature  ->  400 "`temperature` is deprecated for this model"
claude-sonnet-4-6 + temperature  ->  200 OK

Même fournisseur, même famille, comportement différent. La seule façon de le savoir, c'est que quelqu'un se prenne le 400.

Et voici une décision de conception que je recommande de copier : face à un modèle inconnu, supposer qu'il permet tout. Si vous vous trompez de ce côté, un 400 visible apparaît le premier jour. Si vous vous trompez dans l'autre sens, vous envoyez des requêtes bridées sans le savoir, et cela peut durer des mois.

Trois choix de conception à connaître

Les trois sont délibérés et déterminent ce à quoi la brique convient.

La boucle est synchrone de bout en bout. La réponse est livrée entière, pas mot à mot. Si vous voulez l'effet de frappe progressive, cela se règle dans la couche de livraison : chez nous le tour se calcule dans une file et l'interface interroge, ce qui est le bon patron pour un assistant de travail.

Sur un 429 on change de fournisseur, on n'attend pas. Et c'est désactivé par défaut. Le choix est délibéré : une clé mal renseignée ou une requête malformée n'ira pas mieux ailleurs, et les réessayer partout transforme un échec en autant d'échecs que de fournisseurs, chacun facturé. L'attente croissante, si vous la voulez, est votre politique et se place en dehors.

Et une erreur très facile à commettre. Il est tentant de placer la décision d'anonymiser les données personnelles dans la boucle d'outils, puisque c'est là qu'on appelle le modèle. Le problème, c'est que la boucle n'est pas la seule à appeler : le compresseur d'historique envoie lui aussi des transcriptions entières à l'extérieur. Si l'anonymiseur est privé à la boucle, cette seconde voie n'a nulle part où le chercher et ne demande tout simplement rien. La décision d'anonymiser doit vivre en dehors et être partagée par tout ce qui parle au fournisseur.

Ce qui existe sur le marché et en quoi cela diffère

En PHP, il y a trois couches distinctes qu'il vaut mieux ne pas confondre, car elles ne résolvent pas la même chose.

openai-php/laravel et les clients équivalents de chaque fournisseur sont la couche du bas : ils enveloppent l'API dans un objet commode. Ils font très bien ce qu'ils font et ne prétendent pas à autre chose. La boucle, le plafond, la compression de l'historique et le contrôle du solde restent à votre charge.

Prism monte d'un cran : il normalise plusieurs fournisseurs derrière une même interface et apporte outils et sortie structurée. Si votre besoin est de changer de modèle sans réécrire la moitié du projet, c'est un choix très solide.

Neuron AI se présente directement comme un framework d'agents en PHP, avec mémoire, RAG et observabilité. Hors PHP, les références sont LangChain et LlamaIndex, avec bien plus de surface que ce dont on a généralement besoin.

Laragents, celui que nous maintenons, est né du besoin d'avoir la boucle complète avec plafond dur, contrôle du solde par utilisateur et compression de l'historique à l'intérieur d'une application Laravel déjà existante. Si votre cas se limite à un appel ponctuel à un modèle, n'importe lequel des précédents suffit largement. Si ce que vous mettez en production dépense de l'argent à chaque conversation, la liste des pièces est celle de cet article, quel que soit l'outil retenu.

Et si vous ne travaillez pas avec Laravel

La liste de ce qu'il faut construire est la même sur n'importe quelle stack, et c'est ce que vous devriez réellement budgéter :

La boucle d'outils avec un plafond dur. La réinjection du tour de l'assistant même vide. Les erreurs d'outil en résultat et non en exception. Une compression d'historique qui respecte les derniers tours. La vérification du solde avant chaque appel, en distinguant votre limite de la sienne. La normalisation entre fournisseurs. Et un mécanisme qui coupe la recherche infinie.

L'appel au modèle, je le répète, tient en quatre lignes. Si votre budget « intégrer l'IA » ne prévoit que cela, tout le reste manquera.

Où est le code

Le paquet est publié sur Packagist, le code sur GitHub et la documentation du paquet couvre toutes les options. Il est en production sur Abodara, où l'assistant cherche des biens et croise les demandes avec les propriétés.

Si vous envisagez d'ajouter un assistant à votre produit et voulez savoir ce qu'il y a sous le titre, c'est ce sur quoi nous travaillons le plus en ce moment en intelligence artificielle et applications web sur mesure. Racontez-nous le cas et nous vous dirons quelle part est réelle et quelle part est une démo.

Écrit par
Edu Lazaro
Edu Lazaro
Founder & Lead Developer en AndorraDev

Développeur full-stack avec plus de 15 ans d'expérience en Laravel, React, Node.js et architectures cloud. J'aide les entreprises en Andorre à construire leur présence digitale.

Partner de diseño · ionospace.
Besoin d'aide? ×
Andie by AndorraDev
Assistant IA + équipe humaine
Assistant IA d'AndorraDev
Andie
Bonjour! Je suis Andie, l'assistant IA d'AndorraDev. Comment puis-je vous aider? Si vous souhaitez parler avec Edu, il suffit de le demander.
17:28