Comparaison entre `class ReActAgent(Workflow)` et `ReActAgent` préconstruit...

, par Bertrand Degoy

Cet article compare deux approches pour concevoir un agent ReAct dans LlamaIndex :
 l’utilisation directe de l’agent ReAct fourni par la bibliothèque.
 l’implémentation personnalisée par héritage de `Workflow`

L’approche par héritage de `Workflow` est recommandée pour construire un système évolutif. Elle permet une maîtrise fine du raisonnement, notamment en permettant une meilleure utilisation des outils.

1. Utiliser `ReActAgent` depuis `llama_index.core.agent.workflow`

Définition

Utilisation de l’agent ReAct préconfiguré :

from llama_index.core.agent.workflow import ReActAgent

agent = ReActAgent.from_tools(tools=[...], llm=..., memory=...)
response = agent.chat("Quel est le prix du bitcoin ?")

Caractéristiques

 Agent ReAct prêt à l’emploi avec moteur de raisonnement, formatter et parser intégrés.
 Ne nécessite pas de définition de graphe ou d’étapes personnalisées.
 Moins flexible pour les cas avancés.

Cas d’usage

Approche adaptée pour :

 Prototypage rapide ou démonstration.
 Cas simples de question-réponse avec outils.
 Utilisation sans personnalisation profonde du raisonnement.

2. Hériter de `Workflow` pour construire un agent personnalisé

Définition

Création d’un agent en héritant explicitement de la classe `Workflow` :

from llama_index.core.workflow import Workflow

class ReActAgent(Workflow):
   ...

Caractéristiques

 Permet de définir un graphe explicite d’étapes connectées via `add_step(...)` et `connect(...)`.
 Offre un contrôle total sur la logique de raisonnement, l’injection de mémoire, la gestion du contexte, l’utilisation des outils, etc.
 Autorise l’ajout d’étapes personnalisées (prétraitement, validation, résumés, appels API…).
 Requiert une bonne compréhension des événements (`StartEvent`, `PrepEvent`, etc.) et du moteur `Workflow`.

Voici, par exemple, une écriture de l’étape handle_tool_call qui permet une validation via introspection Pydantic de l’appel aux outils, avant l’étape d’observation (lignes 15 à 49). En effet, ReAct a la mauvaise habitude d’essayer d’utiliser les outils avant de prendre en compte la définition des schéma d’entrée. On gagne du temps en bloquant les appels faits avec des formats erronés.

  1. @step
  2.     async def handle_tool_calls(
  3.         self, ctx: Context, ev: ToolCallEvent
  4.     ) -> PrepEvent:
  5.         """ Appeller les outils en toute sécurité en gérant les erreurs et en ajoutant leurs résultats au raisonnement en cours. Ensuite, en émettant un PrepEvent, effectuer une nouvelle itération d'invite et d'analyse ReAct.
  6.        """
  7.         tool_calls = ev.tool_calls
  8.         tools_by_name = {tool.metadata.get_name(): tool for tool in self.tools}
  9.         current_reasoning = await ctx.store.get(
  10.             "current_reasoning", default=[]
  11.         )
  12.         sources = await ctx.store.get("sources", default=[])
  13.  
  14.         '''
  15.        validation anticipée via introspection Pydantic
  16.        On intercepte chaque appel, et pour chaque outil :
  17.        1. Identifier sa classe d’entrée Pydantic.
  18.        2. Instancier cette classe avec les tool_kwargs.
  19.        3. Si la validation échoue → bloquer l’appel et injecter une observation.
  20.        4. Sinon exécuter normalement.
  21.        '''
  22.         for tool_call in tool_calls:
  23.             tool_name = tool_call.tool_name
  24.             tool = tools_by_name.get(tool_name)
  25.  
  26.             if not tool:
  27.                 current_reasoning.append(
  28.                     ObservationReasoningStep(observation=f"Tool {tool_name} does not exist")
  29.                 )
  30.                 continue
  31.  
  32.             # Étape 1 : récupérer la classe Pydantic d'entrée
  33.             input_model = getattr(tool.metadata, "input_model", None)
  34.  
  35.             if input_model:
  36.                 try:
  37.                     # Étape 2 : valider les kwargs via Pydantic
  38.                     input_model(**tool_call.tool_kwargs)
  39.                 except ValidationError as ve:
  40.                     # Étape 3 : bloquer l’appel et injecter l’erreur
  41.                     current_reasoning.append(
  42.                         ObservationReasoningStep(
  43.                             observation=(
  44.                                 f"Appel bloqué : les paramètres fournis pour l’outil '{tool_name}' "
  45.                                 f"sont invalides selon sa définition. Détail : {ve.errors()}"
  46.                             )
  47.                         )
  48.                     )
  49.                     continue
  50.  
  51.             # Étape 4 : poursuivre si tout est valide
  52.             try:
  53.                 tool_output = tool(**tool_call.tool_kwargs)
  54.                 sources.append(tool_output)
  55.                 current_reasoning.append(
  56.                     ObservationReasoningStep(observation=tool_output.content)
  57.                 )
  58.             except Exception as e:
  59.                 current_reasoning.append(
  60.                     ObservationReasoningStep(
  61.                         observation=f"Error calling tool {tool.metadata.get_name()}: {e}"
  62.                     )
  63.                 )

Télécharger

Cas d’usage

Approche recommandée pour :

 Architectures modulaires et extensibles.
 Tracabilité et audit du raisonnement étape par étape.
 Utilisation du debugger Python dans le moteur ReAct.
 Intégration de politiques techniques explicites.
 Meilleur contrôle de l’utilisation des outils.
 Développement d’agents complexes ou hybrides.

Tableau comparatif

Critère `class ReActAgent(Workflow)` `ReActAgent` préconstruit
Architecture Personnalisée, modulaire Préconfigurée
Graphe d’étapes Défini manuellement (`add_step`) Interne, non modifiable
Injection mémoire/contexte Libre et dynamique Partielle via `.chat()`
Contrôle sur le raisonnement Total Limité
Extensibilité Élevée Faible
Complexité de mise en œuvre Moyenne à élevée Faible
Cas d’usage Agents complexes, auditables, debuggables Agents simples, rapides

Conclusion

L’approche par héritage de `Workflow` est recommandée pour les systèmes robustes, auditables et évolutifs. Elle permet une maîtrise fine du raisonnement, de l’utilisation des outils et une architecture extensible.
L’agent ReAct préconstruit est utile pour des cas simples ou des prototypes rapides, mais offre peu de flexibilité.

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