Trop d’outils : effets nocifs
Tool Factory permet de créer automatiquement de très nombreux outils de fonction. Cependant, passer des centaines d’outils à un agent tel que ReAct peut avoir des effets négatifs significatifs — en particulier sur les performances, la qualité des décisions, et la robustesse du raisonnement.
Voici une analyse détaillée :
1. Latence accrue
• Plus il y a d’outils, plus le modèle doit parcourir et évaluer de descriptions à chaque étape.
• Cela ralentit la boucle de réflexion (ReAct = Reasoning + Acting), surtout si les descriptions sont longues.
2. Bruit sémantique
• Trop d’outils diluent la pertinence : le modèle peut hésiter entre plusieurs outils proches.
• Cela augmente le risque de mauvais choix d’outil, ou de non-sélection (il “passe son tour”).
3. Conflits ou redondances
• Si plusieurs outils ont des noms ou descriptions similaires, le modèle peut :
• choisir au hasard,
• ou générer une erreur de sélection (ex. : mauvais paramètre, mauvaise fonction).
4. Fenêtre de contexte saturée
• Chaque outil injecté consomme du contexte tokenisé (nom, description, schéma).
• Au-delà de 100–200 outils, tu risques de dépasser la fenêtre utile, surtout si tu ajoutes des documents ou des messages longs.
On peut estimer que :
– de 10 à 50 outils : pas ou peu de problèmes,
– 50 à 150 outils : latence et bruit,
– plus de 150 outils : latence et bruit excessifs, saturation probable
Le ToolRouter
Un ToolRouter est une couche intermédiaire entre l’intention de l’utilisateur (user query) ou la pensée actuelle de l’agent ( tought) et la liste complète des outils disponibles.
L’objectif est de sélectionner, étape par étape, un sous-ensemble pertinent d’outils à injecter dans le prompt de l’agent, en se basant sur :
• des mots-clés,
• des tags sémantiques,
• des descriptions vectorisées (embedding),
• ou une classification supervisée.
Sélection vectorielle
La sélection des outils à l’aide de descriptions vectorisées (vector matching) est un procédé qui fera intervenir un modèle d’embeddings pour la description et la sélection : on situe dans un espace vectoriel la requête et les descriptions d’outils, puis on mesure leur similarité outil par outil.
Ce procédé est autonome et s’avère très précis. Nous nous y intéressons en premier.
Étape 1 : Indexation vectorielle des outils
Chaque outil est représenté par une description textuelle (issue de ses métadonnées), projetée dans un espace vectoriel via un modèle d’embedding. Lorsqu’une requête est soumise, elle est également vectorisée, puis comparée à chaque outil par similarité cosinus.
Cette classe permet ainsi de filtrer et classer les outils les plus adaptés à une intention donnée, tout en restant agnostique au backend LLM ou à la logique agentique.
- from llama_index.tools import FunctionTool
- from llama_index.embeddings.base import BaseEmbedding
- from llama_index.schema import TextNode
- import numpy as np
- class ToolRouter:
- """
- Paramètres
- --------------
- tools : list[FunctionTool]
- Liste des outils disponibles, chacun avec une description dans ses métadonnées.
- embed_model : BaseEmbedding
- Modèle d'embedding utilisé pour vectoriser les descriptions et les requêtes.
- """
- def __init__(self, tools: list[FunctionTool], embed_model: BaseEmbedding):
- self.tools = tools
- self.embed_model = embed_model
- self.tool_nodes = [
- TextNode(
- text=tool.metadata.description or tool.name,
- metadata={"name": tool.metadata.name}
- )
- for tool in tools
- ]
- self.embeddings = self.embed_model.get_text_embedding_batch([n.text for n in self.tool_nodes])
- def route(self, query: str, top_k: int = 3, min_score: float = 0.5):
- """
- Sélectionne dynamiquement les outils les plus pertinents en fonction d'une requête utilisateur,
- en utilisant la similarité cosinus entre l'embedding de la requête et ceux des outils disponibles.
- Parameters
- ----------
- query : str
- La requête utilisateur ou le "thought" à router vers les outils les plus adaptés.
- top_k : int, optional
- Nombre maximum d'outils à retourner, triés par score décroissant (par défaut : 3).
- min_score : float, optional
- Seuil minimal de similarité cosinus pour qu'un outil soit retenu (par défaut : 0.5).
- Returns
- -------
- List[Tuple[FunctionTool, float]]
- Une liste triée des outils pertinents, chacun accompagné de son score de similarité.
- Usage
- -----
- selected_tools = router.route("extraire les entités d'un texte")
- """
- query_vec = self.embed_model.get_text_embedding(query)
- scored = [
- (tool, float(np.dot(query_vec, vec) / (np.linalg.norm(query_vec) * np.linalg.norm(vec))))
- for tool, vec in zip(self.tools, self.embeddings)
- ]
- filtered = [(tool, score) for tool, score in scored if score >= min_score]
- return sorted(filtered, key=lambda x: x[1], reverse=True)[:top_k]
Usage :
- router = ToolRouter(tools, embed_model)
- thought = "Je dois extraire des infos d’un PDF"
- selected_tools = router.route(thought)
- for tool, score in selected_tools:
- print(f"{tool.name} ({score:.2f})")
Étape 2 : Injection dans un agent React
# ReaAct Engine, basé sur AgentWorkflow :
engine= ReActAgent(
tools=[tool for tool, _ in selected_tools],
llm=Settings.llm,
system_prompt=react_system_prompt,
initial_state={
"langue": self.language,
"profil": "expert",
},
verbose=True,
)
return engine Et dans le react_system_prompt :
...
A chaque étape du raisonnement, ne considère que les outils ayant un score suffisant.
...