Classe Chat Engine ReAct avec outils

, par Bertrand Degoy

Cette nouvelle version du ChatEngine met en oeuvre un moteur d’inférences React et des outils. Le Query Engine RAG des versions précédentes est intégré comme l’un de ces outils. Les autres outils comprennent des outils généraux (par exemple calculs mathématiques et statistiques) et des outils particularisés selon le thème. Une attention particulière est apportée à l’extraction de données en temps réel, notamment avec des outils obtenus par Model Context Protocol (MCP) .

Note : cet article sera bientôt remplacé par une série centrée sur les notions de routage et d’orchestration, dans laquelle le moteur ReAct sera mis en concurrence avec d’autres approches, dans un but d’économie d’énergie ! Commencez ici

L’évolution consiste à utiliser un moteur d’inférences ’React’ (Reasoning and Acting).
Cela permet à l’agent LLM de raisonner et agir en utilisant des outils, dans une boucle logique inspirée du document : Synergizing Reasoning and Acting in Language Models.

Dans cette architecture, les outils comprennent :
 un outil QueryEngineTool pour mettre en oeuvre le RAG sur les index créés par RAG Manager ou IngestCmd ;
 des outils généraux tels que calcul statistique etc.
 des outils personnalisés (FunctionTools) selon le thème. On peut par exemple intégrer de façon dynamique des données "temps réel".

L’outil Query Engine RAG

Un QueryEngine semblable à celui des versions précédentes est intégré au moteur d’inférences React en tant que QueryEngineTool, directement dans la classe ChatEngine. Il fonctionne avec le même paramétrage que les versions précédentes et ne requiert donc aucune modification de ChattyBot.

Contrairement aux versions précédentes dans lesquelles le Query Engine n’était appelé qu’une fois par cycle question-réponse (une seule inférence), le moteur d’inférences peut l’appeler plusieurs fois pour une recherche par étapes dans le cadre d’un raisonnement. Ceci améliore grandement la qualité des réponses, notamment leur concision au regard de la question posée.

Les outils généraux

La classe GeneralToolKit (modules/tools/_general_.py) expose des outils généraux. Ses outils sont systématiquement mis à disposition du moteur d’inférences React.

Les outils personnalisés

Chaque thème peut, de façon optionnelle, mettre des outils spécifiques à disposition du moteur d’inférences React.
Une classe ThemeToolKit (modules/tools/theme.py) expose des outils spécifiques du thème.

Saisie de données "temps réel"

Un ToolKit particulier expose des outils capables de fournir des données "temps réel" issues d’applications ou de service tiers (qu’ils soient privés ou sur le cloud).

Principe général
Par données "temps réel", nous entendons des valeurs obtenues au moyen d’un outil qui interroge un service selon un protocole donné. Le service répond en fournissant les dernières valeurs ou informations connues avec datation. Nous nous limitons à une interrogation "passive", ainsi, la sécurité du système de données du côté du serveur est préservée, aucune action n’étant déclenchée du côté de l’application serveur.

Les données "temps réel" peuvent être saisies par appel à un simple Web Service attaché à une source de données.
Il est également possible d’utiliser Model Context Protocol (MCP) pour accéder à des sources sous ce standard.

Choix entre MCP ou Web Service simple

Critère MCP WS
Externalisation de traitement LLM standard non
définition des outils côté serveur côté client
protocole standard propriétaire
sécurité publique OAuth2 et OpenID Connect à développer

Au vu de ce simple tableau, on voit que MCP est adapté à des traitements d’IA répartis sur réseau ouvert, au prix d’une certaine complexité côté serveur.

Dans le cas de MCP, il faut noter que la description d’un outil est effectuée côté serveur, ce qui peut poser des problème de compréhension de leur fonctionnalité par le moteur d’inférence, d’instructions mal adaptées ou manquantes ou encore des conflits avec d’autres outils.

Dans tous les cas, il est utile (nécessaire ?) d’orchestrer le choix des outils par la boucle ReAct au moyen d’instructions particulières. Ceci se fait du côté client au moyen du routage, en définissant un plan pour chaque outil ainsi que des formats de question et de réponse.

En conclusion :
 Si on veut utiliser des outils existants (par exemple WolframAlpha) ainsi que des ressources LLM, le serveur MCP, lorsqu’il est offert, sera une alternative à la création d’outils sur l’API.
 Si on n’a besoin que d’une simple extraction de données en communiquant dans un espace de confiance, un Web Service est plus simple et mieux maîtrisable. Ceci est d’autant plus vrai que le serveur MCP peut être construit sur une API REST ou équivalent.

Un peu de stratégie...

Le principe d’un outil, vu de l’IA, est d’exécuter une tâche au cours de la boucle d’exécution ReAct. Un des outils est notre bon vieux RAG qui travaille sur des données informelles. Les autres sont des fonctions déterministes auxquelles la boucle ReAct passe des paramètres pour obtenir un retour formel. Elle choisit ses outils d’après leur description fonctionnelle et celle des E/S.

Le propre de l’IA générative est de travailler sur de l’informel. Lui faire faire des sélections de façon déterministe, c’est le marteau-pilon qui écrase la mouche, les pingouins qui se désespèrent etc.

Voici donc le principe qui doit nous guider :
Aux outils les sélections déterministes pour sélectionner de l’informel. A l’IA le travail d’analyse et de synthèse sur de l’informel.

Lister les outils disponibles

Pour obtenir la liste des outils et leur description, posons la question à ChattyBot :

Q : what function tools and query tool do you have available ? [1]

R : Je suis équipé de plusieurs outils pour vous aider avec différentes tâches. Voici une liste de mes outils et leurs fonctions :

rag_search : Cet outil de recherche RAG permet de sélectionner des informations dans des documents indexés localement, propres à un domaine particulier (thème).

calcul_tool : Cet outil effectue des calculs arithmétiques simples. Vous pouvez l’utiliser pour additionner, soustraire, multiplier ou diviser des nombres, et plus encore.

tva_tool : Cet outil calcule la TVA (Taxe sur la Valeur Ajoutée) sur un montant donné. Il est utile pour les calculs financiers et fiscaux.

stats_tool : Cet outil réalise des analyses statistiques telles que le calcul de la moyenne, de la médiane, de l’écart type, de la variance et de la tendance linéaire. Il est utile pour l’analyse de données.

alerte_stats_tool : Cet outil déclenche une alerte si la dernière valeur s’écarte trop de la tendance linéaire. Il est utile pour la surveillance des données et la détection d’anomalies.

stats_alert_tool : Cet outil retourne les valeurs qui s’écartent trop de la tendance linéaire selon l’écart type. Il est utile pour identifier les valeurs aberrantes dans un ensemble de données.

La boucle ReAct "native"

Le moteur ReAct de LlamaIndex fonctionne comme ceci :
 Le modèle (LLM) reçoit le prompt.
 Il réfléchit (Reasoning), choisit un outil (Action), l’exécute (Observation).
à cette étape, le modèle peut :
- Revenir au prompt pour vérifier s’il manque des éléments ou s’il faut ajuster le raisonnement.
- Comparer l’observation au prompt pour voir si elle répond à la demande ou si elle nécessite une nouvelle action.
- Utiliser l’observation pour nourrir une nouvelle phase de raisonnement (parfois en intégrant des sous-questions implicites du prompt).
 Il répète le cycle jusqu’à produire une réponse.

Dans la boucle ReAct "native" de Llamaindex, l’outil sélectionné dépend entièrement :
 du contenu du prompt, y compris un system prompt par défaut,
 de la description des outils (ToolMetadata.description)
 et de la logique d’analyse du LLM.

On notera que :
 Le routage (décrit par la suite) permet la prise en compte, au bon moment du raisonnement, d’outils particuliers.
 Le modèle utilise le prompt initial comme point de départ pour raisonner. Mais lorsqu’il décide d’appeler un outil, il formule un nouveau prompt, adapté à cet outil. Ce nouveau prompt est souvent :
 Plus ciblé : reformulé pour coller à ce que l’outil attend ou peut gérer.
 Contextualisé : enrichi avec des éléments issus du raisonnement ou d’une observation précédente.
 Optimisé : pour maximiser la qualité ou la pertinence de la réponse de l’outil.

Ainsi décrite, la boucle ReAct est un processus réflexif par lequel le moteur d’inférences peut revenir au prompt tout au long du cycle pour réévaluer ou réorienter sa stratégie dans le but de fournir une réponse pertinente [2].

Prompt(s)

Il faut distinguer :
 le prompt de l’utilisateur (que nous préférons appeler "question" ou "query" ou encore "user query"),
 le system prompt du Query Engine RAG,
 celui de l’agent ReAct.

System prompt du Query Engine RAG
Nous avons deux possibilités :
 transformer en outil un des Query Engines RAG construits précédemment sans modification, donc avec son system prompt,
 recréer sur ces modèles un Query Engine simplifié, ayant notamment un system prompt minimal, ou pas de system prompt, et utiliser le routage pour lui affecter un system prompt et des formats particuliers.

System prompt de l’agent ReAct, par défaut
le ReActAgent dans LlamaIndex utilise un system prompt par défaut appelé react_header, qui définit son comportement général, son format de réponse, et la manière dont il interagit avec les outils.

You are designed to help with a variety of tasks, from answering questions to providing summares to other types of analyses.

## Tools
You have access to a wide variety of tools. You are responsible for using the tools in any sequence you deem appropriate to complete the task at hand.
This may require breaking the task into subtasks and using different tools to complete each subtask.

You have access to the following tools: {tool_desc}

## Output Format
Please answer in the same language as the question and use the following format:

Thought: The current language of the user is: (user's language). I need to use a tool to help me answer the question.
Action: tool name (one of {tool_names})
Action Input: JSON format of the tool input

Observation: tool response

Repeat until you can answer without tools.

Then respond with:
Thought: I can answer without using any more tools.
Answer: [your answer here]

On notera l’absence de format de réponse dans ce system prompt. Ce sera réglé dans le cadre du routage.

Le system prompt par défaut est injecté automatiquement lors de la création du ReActAgent via ReActAgent.from_tools(...). On peut le récupérer ou le modifier avec :

  1. agent.get_prompts()["react_header"]

On peut le modifier comme ceci :

  1. from llama_index.core import PromptTemplate
  2. custom_prompt = PromptTemplate("...ton prompt ici...")
  3. agent.update_prompts({"react_header": custom_prompt})

Télécharger

Routage

Le routeur
Un routeur, comme la fonction detect_tool_and_plan(query), intervient avant ReAct. Il sert à :
 répondre à une requête simple par appel direct à un l’outil approprié (Direct Agent),
 rediriger certaines requêtes vers des agents ou des plans dédiés (wikipedia_plan, rag_plan, etc.) (Tool Agent),
 décider s’il faut exécuter un plan complexe, une requête enrichie, ou laisser ReAct agir (React Agent).

C’est un niveau d’orchestration au-dessus de ReAct.
Il permet d’avoir une logique métier plus explicite :
“Si on parle de documents → je préfère un plan RAG avec rag_search plutôt que laisser ReAct improviser.”

Un plan est un script pour l’enchaînement logique qui précise :
 quel outil appeler (ex : rag_search, arxiv_api, wikipedia, etc.),
 comment poser la question à l’outil (formulation, prompt),
 éventuellement comment reformuler ou enrichir la réponse.

La fonction detect_tool_and_plan() agit comme un routeur sémantique, en amont de la logique ReAct :
 Elle choisit un “plan” selon les mots-clés → chaque plan génère un prompt_enrichi.
 Ce prompt est envoyé à ton SmartReActAgent, qui choisit l’outil en se basant sur son raisonnement LLM et les descriptions de tools.
 Finalement, l’agent exécute l’action (ex : recherche, calcul, extraction) et renvoie une réponse.

Qu’est-ce qu’un “routeur intelligent”  ?
C’est une composante logique qui analyse une requête (ou un signal) en s’appuyant sur un LLM et décide dynamiquement de quel chemin suivre ou quelle action déclencher. L’intelligence vient de sa capacité à :
 Comprendre le contexte ou les mots-clés dans la requête
 Choisir parmi plusieurs options selon des critères (domaines, intentions, fiabilité, etc.)
 Rediriger vers une méthode adaptée – ici, un plan spécifique qui enrichit le prompt pour ReAct.

Sauter ReAct ?
Comme nous sommes en amont de ReAct, il sera possible de répondre à une requête simple par un appel direct à un outil, sans poursuivre avec la boucle ReAct.
Deux cas s’imposent :
 la question est une requête de données élémentaire (comme l’interrogation d’une table),
 la question ne nécessite que l’outil RAG, et on répondra directement avec cet outil.

Pourquoi “intelligent”  ?
Ce n’est pas juste un if...else statique. Le routeur peut :
 Gérer plusieurs entrées lexicales ou sémantiques
 S’adapter à de nouveaux mots-clés,
 Faire des choix de plan ou même court-circuiter ReAct si besoin.
Il est même possible de l’enrichir avec un système de scoring ou une couche classificatrice ML légère, pour qu’il anticipe encore mieux le plan optimal.

Outils "simples"
Ces outils sont stateless, locaux et déterministes, donc il n’y a pas besoin de routage complexe comme pour les outils d’info ou de recherche.

Les outils comme calcul_tool, tva_tool, stats_tool, etc. :
 Ne requièrent pas d’accès à des sources externes
 Ont une structure d’appel directe : input → output
 Peuvent être utilisés dès que leur nécessité est détectée dans la requête
De ce fait, il n’y a pas besoin de sélectionner dynamiquement entre eux — on peut déclencher leur usage avec un simple pattern matching ou un classificateur sémantique.
La méthode call_tool() permet d’interroger un outil sans passer par un agent.

Routage ou non ?
On peut très bien combiner les deux approches  :
 Le routeur pré-analyse la requête
 Si elle concerne un domaine spécifique (scientifique, corpus…) → il lance un plan
 Sinon → il laisse le moteur ReAct la traiter avec ses propres outils.
Ça donne une architecture hybride où la sémantique métier guide l’agent ReAct, mais sans l’enfermer.

Pourquoi les plans ne sont pas des méthodes de la classe d’outil correspondante ? [3]

Bien que chaque plan utilise un outil, il est conçu comme une stratégie indépendante, pour permettre :

1. Découplage clair entre logique métier et exécution technique
 Le plan décrit quoi faire et pourquoi (thought), l’outil exécute comment le faire.
 Ce découplage permet de composer plusieurs outils dans un plan — donc un outil ne peut pas forcément posséder le plan qui l’utilise.

2. Flexibilité multi-outils
 Un plan peut appeler plusieurs outils en séquence (exemple : stats_tool → format_tool → alert_tool).
 Du coup, aucun outil seul ne peut représenter cette orchestration complète.

3. Réutilisation des outils dans différents plans
 Un outil comme stats_tool pourrait être utilisé dans :
 stats_plan
 alerte_plan
 fallback_plan [4]
 Si le plan vivait dans l’outil, cette flexibilité serait réduite.

4. Contrôle sur les prompts enrichis
 Le plan peut adapter le action_input ou injecter des thought spécifiques selon le contexte.
 C’est une logique située, pas générale — donc mieux gérée en dehors de l’outil.

Fixer les formats dans le system prompt ou dans le plan du routeur ?

Le format (ou schéma) permet de valider et documenter les données.

Fixer le format de question/réponse dans le system prompt ou dans le plan du routeur (comme dans un MultiModalRouter ou QueryPlan) peut sembler similaire, mais ces deux approches ont des implications différentes en termes de contrôle, flexibilité et modularité.

1. System Prompt
Le format est défini dans le prompt initial de l’agent (ex. : react_header pour ReActAgent).
Cependant il est difficile d’adapter le format à des outils requérant un format de requête et/ou de réponse particulier.
Si plusieurs outils ou branches du raisonnement sont impliqués, le format unique peut ne pas convenir à tous.

2. Plan du routeur (Router Plan / QueryPlan)
Le format est défini dans chaque branche ou étape du plan, souvent via des prompts ou des instructions spécifiques à chaque outil ou sous-agent. Cette solution présente l’avantage d’adapter le format à chaque sous-tâche ou outil (ex. : tableau pour l’analyse, texte libre pour la synthèse).

Notre approche consistera à conserver le format général défini dans le system prompt de ReAct, complété par les formats d’objets particuliers (par exemple stats_descriptives) définis dans les plans du routeur.

Accès réservé : connectez vous pour en savoir plus.

Notes

[1Il faut poser la question en anglais, langage dans lequel sont définies sans risque d’hallucination les termes "function tools" et "query tools".

[2On notera que ceci conduit à des réponses très ciblées, voire succinctes, car tout contenu ne répondant pas directement à la question est éliminé. Ceci contraste avec un simple moteur de recherche en une passe qui donnera des informations connexes. On en déduit que :
 ReAct est approprié à l’élaboration de réponses précises à des questions détaillées, voire complexes,
 Un simple Query Engine, surtout si la température n’est pas nulle, donne des réponses détaillées à des questions simples.
La deuxième approche convient à une démarche de découverte par un néophyte (comme un chatbot de FAQ), tandis que la deuxième serait plus professionnelle (comme un outil pour data analyst).

[3On comprend que je me suis égaré un long moment à créer une méthode d’outil pour fournir le plan : fausse piste ! Parfois la programmation OO n’est qu’une tentation esthétique...

[4Mauvais exemple !