FrameworksAgents.com Logo

Créer un tool CrewAI personnalisé : guide pas à pas

Tutorielcalendar_todayPublié le 14 avril 2026schedule11 min de lecturetools crewaicrewai agent tools

Créez un tool CrewAI personnalisé en Python : décorateur @tool, validation, gestion des timeouts et rate limits, debugging et bonnes pratiques.

Introduction

Un agent sans outil, c'est un navigateur sans connexion — du potentiel, mais aucune action possible. Les tool sont le pont entre vos agents et le monde réel : recherche web, appels API, lecture de fichiers, requêtes base de données.

Ce tutoriel couvre la création d'un tool personnalisé, son association à un agent, et la gestion des erreurs en production.


Résumé rapide

CritèreDétail
TypeOutil (@tool decorator ou classe Tool)
Dépendancecrewai[tools]
Associationvia le paramètre tools= de l'agent
Erreurs fréquentestimeout, rate limit, schéma de retour invalide
Debugverbose=True, dry_run, examen du résultat

Qu'est-ce qu'un tool dans CrewAI ?

Un tool est une fonction Python annotée qu'un agent peut invoquer pendant son raisonnement. Il expose :

  • Un nom — utilisé par l'agent pour décider quand appeler le tool.
  • Une description — c'est elle que le modèle lit pour comprendre quand déclencher l'appel (tool calling).
  • Un schéma d'entrée — paramètres attendus, typés et documentés.
  • Une fonction sous-jacente — le code qui s'exécute réellement.

Deux interfaces de définition, toutes deux importées depuis crewai.tools :

# Méthode 1 : décorateur @tool (recommandée pour les cas simples)
from crewai.tools import tool

@tool("recherche_web")
def recherche_web(query: str) -> str:
    """Recherche des informations sur le web."""
    # ... logique
    return "..."

# Méthode 2 : sous-classe BaseTool (pour plus de contrôle et un schéma typé)
from crewai.tools import BaseTool

class RechercheWebTool(BaseTool):
    name: str = "recherche_web"
    description: str = "Recherche des informations sur le web."

    def _run(self, query: str) -> str:
        # ... logique
        return "..."

recherche_tool = RechercheWebTool()

Attention aux imports. Le décorateur s'appelle tool (minuscule) et la classe BaseTool ; les deux vivent dans crewai.tools, pas dans crewai. Avec le décorateur, le nom du tool est passé en argument positionnel et la docstring sert de description si vous n'en fournissez pas d'autre.

Point critique : la description. Floue = agent qui n'appelle jamais son tool, ou qui l'appelle mal.


Créer un tool simple

import json
from crewai.tools import tool

@tool("recherche_web")
def recherche_web(query: str) -> str:
    """Recherche des informations récentes sur le web.
    Accepte une requête en français ou en anglais.
    Retourne un résumé des premiers résultats avec titre et URL.
    """
    # Tool de recherche web simulé.
    # En production : remplacez par SerpAPI, Tavily ou DuckDuckGo.
    try:
        if not query or len(query.strip()) < 3:
            return json.dumps({"erreur": "Requête trop courte (min 3 caractères)."})

        query = query.strip()
        resultats = [
            {"titre": f"Résultat pour '{query}' — article 1", "url": "https://exemple.com/1"},
            {"titre": f"Résultat pour '{query}' — article 2", "url": "https://exemple.com/2"},
        ]
        return json.dumps({"requete": query, "resultats": resultats}, ensure_ascii=False)

    except Exception as e:
        return json.dumps({"erreur": f"Échec de la recherche : {str(e)}"})
ÉlémentRôle
@toolEnregistre la fonction comme tool CrewAI
Argument du décorateurNom unique du tool pour le tool calling
DocstringDescription lue par le modèle — être précis
Validation entréeÉvite les appels vides ou malformed
try/exceptRetourne un message structuré même en cas d'erreur

Associer un tool à un agent

Un tool n'a de valeur que relié à un agent. CrewAI utilise le paramètre tools= :

from crewai import Agent, Task, Crew

# 1. Définir l'agent avec son tool
chercheur = Agent(
    role="Chercheur web",
    goal="Trouver les informations les plus pertinentes sur le sujet demandé.",
    backstory="""Tu es un chercheur rigoureux. Tu utilises toujours tes outils de recherche
    pour obtenir des informations à jour avant de formuler tes conclusions.""",
    tools=[recherche_web],
    verbose=True,
)

# 2. Définir la task
tache_recherche = Task(
    description="Recherche les dernières avancées sur les frameworks d'agents IA en 2026.",
    agent=chercheur,
    expected_output="Un résumé de 5 résultats avec titre, source et date.",
)

# 3. Orchestrer dans un crew
crew = Crew(
    agents=[chercheur],
    tasks=[tache_recherche],
    process="sequential",
    verbose=True,
)

resultat = crew.kickoff()
print(resultat)

L'agent décide seul quand invoquer recherche_web — en fonction de sa description et de son goal.


Exemple concret : agent de veille IA

Deux tools complémentaires : recherche web + stockage fichier.

from crewai import Agent, Task, Crew
from crewai.tools import tool
import json

@tool("recherche_web")
def recherche_web(query: str) -> str:
    """Recherche des informations sur le web."""
    return json.dumps({"resultats": [{"titre": "IA Act adopté", "url": "https://example.com"}]})

@tool("sauvegarder_fichier")
def sauvegarder_fichier(nom_fichier: str, contenu: str) -> str:
    """Écrit du contenu dans un fichier texte local."""
    try:
        with open(nom_fichier, "w", encoding="utf-8") as f:
            f.write(contenu)
        return f"Fichier {nom_fichier} créé avec succès."
    except Exception as e:
        return f"Erreur d'écriture : {e}"

veilleur = Agent(
    role="Veilleur IA",
    goal="Surveiller les évolutions du secteur de l'IA et produire un rapport synthétique.",
    backstory="Tu es un veilleur technologique. Tu effectues des recherches ciblées, compiles les résultats et les stockes.",
    tools=[recherche_web, sauvegarder_fichier],
    verbose=True,
)

tache = Task(
    description="Fais une veille sur l'IA agents en 2026 et sauvegarde un rapport dans veille_ia.txt.",
    agent=veilleur,
    expected_output="Un fichier veille_ia.txt contenant 3 actualités majeures.",
)

crew = Crew(agents=[veilleur], tasks=[tache], process="sequential")
resultat = crew.kickoff()

L'agent invoque recherche_web pour rassembler les informations, puis sauvegarder_fichier pour les persister.


Valider les arguments avec un schéma Pydantic

Avec le décorateur @tool, CrewAI infère le schéma des arguments à partir des annotations de type. Mais pour des entrées complexes (bornes, formats, valeurs autorisées), il est plus sûr de déclarer explicitement un schéma Pydantic via BaseTool et son attribut args_schema. Le modèle reçoit alors la description de chaque champ, et toute valeur hors contrat est rejetée avant d'atteindre votre code.

from typing import Type
from pydantic import BaseModel, Field
from crewai.tools import BaseTool


class RechercheInput(BaseModel):
    """Schéma d'entrée du tool de recherche."""
    query: str = Field(..., min_length=3, description="Termes de recherche (3 caractères min).")
    max_resultats: int = Field(5, ge=1, le=20, description="Nombre de résultats (1 à 20).")


class RechercheWebTool(BaseTool):
    name: str = "recherche_web"
    description: str = (
        "Recherche des informations récentes sur le web. "
        "Retourne une liste de résultats avec titre, URL et extrait."
    )
    args_schema: Type[BaseModel] = RechercheInput

    def _run(self, query: str, max_resultats: int = 5) -> str:
        # Les arguments sont déjà validés par Pydantic : query >= 3 caractères,
        # max_resultats borné entre 1 et 20. Inutile de re-vérifier ici.
        resultats = [
            {"titre": f"Résultat {i+1} pour '{query}'", "url": f"https://exemple.com/{i+1}"}
            for i in range(max_resultats)
        ]
        return json.dumps({"requete": query, "resultats": resultats}, ensure_ascii=False)


recherche_tool = RechercheWebTool()

Avantages concrets :

  • Erreurs explicites : une requête trop courte lève une ValidationError claire, que l'agent peut interpréter et corriger à l'itération suivante.
  • Meilleur tool calling : les description des champs Field sont transmises au LLM, qui sait exactement quoi remplir.
  • Code plus propre : la validation vit dans le schéma, pas éparpillée dans _run.

Tester un tool isolément

Avant de brancher un tool sur un agent — et de payer des appels LLM pour découvrir qu'il plante —, testez-le seul. Un tool reste une fonction Python ; appelez-la directement avec .run() (méthode exposée par CrewAI sur les tools) ou via la fonction sous-jacente.

# Test rapide d'un tool décoré avec @tool
resultat = recherche_web.run(query="frameworks agents IA 2026")
print(resultat)

# Test d'un tool BaseTool, y compris la validation du schéma
from pydantic import ValidationError

tool = RechercheWebTool()
print(tool.run(query="LangGraph", max_resultats=3))  # OK

try:
    tool.run(query="ab")  # query < 3 caractères
except ValidationError as e:
    print("Validation refusée comme prévu :", e)

Industrialisez avec pytest pour couvrir les cas limites : entrée vide, caractères spéciaux, dépassement de borne, panne réseau (mockée). Un tool testé en isolation économise des heures de debug au niveau du crew, où les logs sont noyés dans le raisonnement de l'agent.

# test_recherche_web.py
import json

def test_recherche_retourne_json_valide():
    sortie = recherche_web.run(query="test recherche")
    data = json.loads(sortie)            # ne doit pas lever
    assert "resultats" in data
    assert isinstance(data["resultats"], list)

Gérer erreurs, timeouts et rate limits

Trois causes principales d'échec : timeout, rate limit, schéma de retour invalide.

Timeout

import requests
from crewai.tools import tool

@tool("recherche_web")
def recherche_web(query: str) -> str:
    """Recherche des informations sur le web via une API externe."""
    try:
        response = requests.get(
            "https://api.exemple.com/search",
            params={"q": query},
            timeout=10,  # délai max en secondes
        )
        response.raise_for_status()
        return response.text
    except requests.Timeout:
        return json.dumps({"erreur": "Délai dépassé (10s). Réessayez."})
    except requests.RequestException as e:
        return json.dumps({"erreur": f"Échec requête : {str(e)}"})

Toujours renvoyer un message d'erreur plutôt que de lever une exception non gérée : un tool qui raise interrompt brutalement le crew, alors qu'un retour structuré laisse l'agent décider de réessayer ou de changer de stratégie.

Rate limit

import time
from crewai.tools import tool

_appels_recent: list[float] = []

@tool("recherche_web_rate_limited")
def recherche_web_rate_limited(query: str) -> str:
    """Recherche web avec un throttling de 10 requêtes par minute."""
    global _appels_recent
    maintenant = time.time()
    # On ne garde que les appels de la dernière minute
    _appels_recent = [t for t in _appels_recent if maintenant - t < 60]

    if len(_appels_recent) >= 10:
        return json.dumps({"erreur": "Rate limit atteint (10 req/min). Patientez."})

    _appels_recent.append(maintenant)
    # ... logique de recherche
    return json.dumps({"resultats": []})

Pour les API tierces qui renvoient un code 429, ajoutez un retry avec backoff exponentiel (par exemple via la librairie tenacity) : on attend 1s, puis 2s, puis 4s avant d'abandonner. Cela absorbe les pics transitoires sans surcharger le service distant.

Schema invalide

Retournez toujours un format cohérent. Si l'agent attend du JSON structuré et que votre tool renvoie du texte brut, le raisonnement déraille. Documentez le format de retour dans la description du tool.


Bonnes pratiques

1. Une responsabilité par tool. Un tool qui fait recherche + traitement + stockage fait mal une seule chose.

2. Descriptions précises et actionnables. Pas "Outil de recherche" — mais "Recherche des articles récents sur [sujet]. Retourne une liste de 5 résultats avec titre, URL et date."

3. Validation des entrées en premier. Vérifiez les paramètres avant toute logique coûteuse. Un tool qui échoue élégamment vaut mieux qu'un tool qui crash.

4. Naming : verbes pour les actions (recherche_web, lit_fichier) — noms pour les données (scrape_page, appelle_api).

5. Limitez le nombre de tools par agent. 2 à 5 tools maximum. Au-delà, l'agent disperse son attention.

6. Documentez dans le code ET dans la description. Le premier est lu par le modèle, le second par les développeurs.


Pièges courants

PiègeSymptômeCorrectif
Mauvais importImportError: cannot import name 'Tool'Importez tool ou BaseTool depuis crewai.tools, jamais depuis crewai.
Décorateur sans docstringL'agent n'appelle jamais le toolAjoutez une docstring claire — elle sert de description au LLM.
Retour non sérialisableErreur de parsing dans le raisonnementRenvoyez toujours une str (JSON via json.dumps), pas un objet brut.
Annotations de type manquantesLe LLM ne sait pas quoi passerTypez chaque argument (query: str) ou utilisez args_schema.
Exception non géréeLe crew s'arrête netCapturez les erreurs et renvoyez un message structuré.
Trop de tools sur un agentL'agent hésite, boucle ou se trompe d'outilLimitez à 2-5 tools, ou répartissez sur plusieurs agents.

Un dernier réflexe : si un tool semble ignoré, lisez les logs verbose=True. CrewAI affiche le nom du tool envisagé et les arguments générés — c'est souvent là qu'on repère une description ambiguë ou un argument mal nommé.


Questions fréquentes

Comment créer un tool personnalisé ? Importez tool depuis crewai.tools et décorez votre fonction avec @tool("mon_nom"). Donnez-lui une docstring précise (elle sert de description au LLM) et des arguments typés. Associez ensuite via tools=[mon_tool] sur l'agent. Pour un schéma d'arguments validé, sous-classez plutôt BaseTool.

Peut-on utiliser plusieurs tools avec un seul agent ? Oui. Passez une liste au paramètre tools=. L'agent choisit lequel invoquer selon la tâche. Gardez 2-5 tools par agent.

Un tool n'est jamais appelé — comment debugger ? Activez verbose=True, vérifiez la description (le modèle doit comprendre quand l'appeler), et testez le tool isolément avec des entrées variées.

Les tools supportent-ils les appels asynchrones ? Oui. Définissez votre fonction en async def — CrewAI gère l'appel non-bloquant. Idéal pour les HTTP parallelisés.

Comment gérer les rate limits ? Implémentez un throttling simple (compteur par minute) avec message d'erreur structuré. Prévoyez un retry avec backoff exponentiel.


Aller plus loin

Les tools sont un pilier de l'architecture agentique. Consultez le guide complet de CrewAI pour comprendre comment ils s'intègrent dans une architecture multi-agents. Pour installer votre premier environnement, le guide d'installation vous donne un setup prêt en 10 minutes. Et pour une vision large de l'outillage agentique, notre article sur les agent tools fait le tour des approches et bonnes pratiques.

Restez informé sur les agents IA

Nouveaux tutoriels, comparatifs et guides pratiques directement dans votre boîte mail.

homeAccueilcodeFrameworkssmart_toyAgentsmenu_bookTutorielsTwitter