Accueil / Articles / Un boucle d’outil LangChain générée manuellement pour comparer les métriques en temps réel des actions

Un boucle d’outil LangChain générée manuellement pour comparer les métriques en temps réel des actions

Créez pas à pas un petit assistant de comparaison de actions LangChain et apprenez le cycle de création, de liaison, d’appel et d’exécution qui permet à un LLM de demander des données en temps réel au lieu de deviner.

1667 mots

Pourquoi un modèle sans outils ne fait que deviner

Imaginez un vendeur qui connaît par cœur tous les produits et toutes les règles, mais qui n’a pas accès au système de stock. Si vous lui demandez s’il reste des vestes en taille moyenne, il vous donnera une réponse basée sur les informations de la semaine dernière. C’est un LLM fonctionnant seul : capable de s’exprimer, mais aveugle au présent.

Donnez à l’assistant un lecteur de codes-barres afin qu’il puisse vérifier les informations au lieu de deviner. Dans LangChain, le lecteur de codes-barres est un outil : une fonction ordinaire que le modèle peut demander d’exécuter, comme une recherche de prix, une requête de base de données ou une appel API. L’appel d’outil est le mécanisme qui permet au modèle de décider quand utiliser le lecteur, quel élément scanner et comment traiter les résultats obtenus.

Le cycle en quatre étapes

L’appel d’outil (souvent appelé appel de fonction) suit toujours le même cycle :

  1. Créer l’outil. Écrivez une fonction Python avec un nom descriptif, des paramètres typés et une documentation expliquant son fonctionnement.
  2. L’associer au modèle. Appelz .bind_tools() sur le modèle de chat afin qu’il sache quels outils existent et quels arguments ils acceptent.
  • Appeler le modèle. Envoyez la question de l’utilisateur. Si des données externes sont nécessaires, le modèle renvoie une demande structurée telle que "appeler get_stock_price avec ticker='MSFT'" au lieu d’une réponse finale.
  • Exécuter et retourner les résultats. Votre code exécute la fonction demandée et transmet la sortie au modèle afin qu’il puisse composer la réponse.
  • La caractéristique importante est que c’est le modèle, et non votre code, qui décide quand un outil est nécessaire. Une question comme « Quel est le résultat de 2+2 ? » reçoit une réponse directe, tandis que « Comparer MSFT et AAPL » peut générer deux appels à des outils dans une seule réponse. Le modèle ne fait que demander ces appels ; il reste à vous de les exécuter. Pour savoir comment les frameworks automatisent ce cycle, consultez ce que LangChain automatise réellement une fois qu’un cycle d’agent a été créé.

    Étape 1 : Installer les dépendances lors de la première exécution

    Le script commence par vérifier la présence de ses paquets et installer ceux qui manquent, afin que quiconque puisse l’exécuter sans avoir à effectuer une étape séparée de pip install. Il convertit chaque nom de paquet en son nom d’importation en remplaçant les traits par des underscores (langchain-groq devient langchain_groq) et recourt sinon à l’appel de pip via l’interpréteur en cours d’exécution :

    import sys
    import subprocess
    
    required_packages = ["langchain-groq", "langchain-core", "yfinance"]
    
    for package in required_packages:
        try:
            __import__(package.replace("-", "_"))
        except ImportError:
            print(f"📦 Package '{package}' not found. Installing now...")
            subprocess.check_call([sys.executable, "-m", "pip", "install", package])
    

    Cela convient aux démos, mais les projets réels devraient spécifier leurs dépendances dans requirements.txt ou pyproject.toml ; les installations en temps de exécution rendent les builds non reproductibles.

    Étape 2 : Fournir la clé API

    Le modèle s’exécute sur Groq, donc le client Groq a besoin d’une clé API dans l’environnement. La démo la définit directement à titre indicatif :

    import os
    os.environ["GROQ_API_KEY"] = "Your-API"
    

    Les clés réelles doivent se trouver dans un fichier .env exclu du contrôle de version ou dans un gestionnaire de secrets, jamais dans du code commité.

    Étape 3 : Définir l’outil de données boursières

    Cet outil utilise yfinance pour rechercher un symbole boursier, obtenir le dernier prix de clôture d’une journée dans l’historique, ainsi que la capitalisation boursière et le ratio P/E sur la base des informations relatives à ce symbole :

    from langchain_core.tools import tool
    import yfinance as yf
    
    @tool
    def get_stock_price(ticker: str) -> str:
        """Fetches the current stock price and key statistics for a given ticker symbol."""
        try:
            stock = yf.Ticker(ticker)
            todays_data = stock.history(period='1d')
            if todays_data.empty:
                return f"Could not find data for ticker {ticker}."
    
            price = todays_data['Close'].iloc[-1]
            info = stock.info
            market_cap = info.get('marketCap', 'N/A')
            pe_ratio = info.get('trailingPE', 'N/A')
    
            return f"{ticker} Current Price: ${price:.2f}, Market Cap: {market_cap}, P/E Ratio: {pe_ratio}"
        except Exception as e:
            return f"Error fetching data for {ticker}: {str(e)}"
    

    Trois éléments font de cette fonction un bon outil :

    • Le décorateur @tool transforme la fonction en outil LangChain et déduit un schéma d’entrée à partir de ses indications de type.
    • La documentation n’est pas une simple décoration. Le modèle la lit, ainsi que le nom de l’outil, pour déterminer quand celui-ci doit être utilisé ; elle doit donc indiquer clairement ce que l’outil renvoie.
    • Les erreurs sont renvoyées sous forme de chaînes de caractères descriptives plutôt que d’exceptions. Un symbole boursier inconnu ou une demande échouée génèrent un message que le modèle peut interpréter (“aucunes données trouvées”), au lieu d’une exception qui met fin à toute l’exécution.

    Notez que yfinance est un wrapper non officiel de Yahoo Finance : les données peuvent être en retard et certains champs manquer, d’où l’utilisation des valeurs par défaut 'N/A'.

    Étape 4 : Créer le modèle et lier les outils

    Ensuite, le modèle de chat est créé et la liste des outils y est associée :

    from langchain_groq import ChatGroq
    
    llm = ChatGroq(
        model="openai/gpt-oss-120b",
        temperature=0
    )
    
    tools = [get_stock_price]
    llm_with_tools = llm.bind_tools(tools)
    

    bind_tools() envoie les noms, descriptions et schémas d’arguments des outils au modèle à chaque demande, afin que celui-ci sache ce qu’il peut demander. Notez que le llm non lié est également conservé ; il sera réutilisé plus tard pour le résumé final. Le nom du modèle reflète ce que Groq offrait au moment de la rédaction ; vérifiez donc la liste actuelle des modèles du fournisseur avant de l’exécuter.

    La mise à temperature=0 permet d’obtenir des résultats plus ciblés et cohérents, bien qu’elle réduise le hasard plutôt que de garantir des réponses identiques.

    Étape 5 : Construire une chaîne de prompts

    Un modèle de prompt fournit une instruction système et insère la question de l’utilisateur dans le message destiné à l’être humain. L’opérateur pipe relie ensuite ce prompt au modèle conscient des outils pour former une commande exécutable, à l’instar d’un pipeline Unix :

    from langchain_core.prompts import ChatPromptTemplate
    from IPython.display import display, Markdown
    
    prompt = ChatPromptTemplate.from_messages([
        ("system", "You are an expert financial analyst. Use the tools provided to pull real-time data before comparing or concluding."),
        ("human", "{input}")
    ])
    
    chain = prompt | llm_with_tools
    

    Le message système effectue ici le travail réel : il indique au modèle de récupérer des données à l’aide des outils avant d’effectuer toute comparaison. Sans lui, le modèle pourrait répondre en se basant sur des données d’entraînement obsolètes.

    Étape 6 : Exécuter la boucle et gérer plusieurs appels d’outils

    Le bloc principal relie tout ensemble. Il lance la chaîne d’opérations, vérifie si la réponse contient des tool_calls, exécute chaque recherche demandée, collecte les résultats, puis demande enfin au modèle classique d’écrire une comparaison à partir des données rassemblées. S’aucun outil n’a été demandé, il affiche directement la réponse du modèle :

    if __name__ == "__main__":
        query = "Compare the current stock price and P/E ratio of Microsoft (MSFT) AND Apple (AAPL). Which one looks cheaper based on P/E?"
        print(f"🚀 Invoking Financial Pipeline with query: '{query}'\n")
    
        # 1. Ask the model what tools it wants to use
        ai_message = chain.invoke({"input": query})
    
        # 2. Check if the model requested tool use
        if ai_message.tool_calls:
            print(f"🛠️ Model requesting {len(ai_message.tool_calls)} real-time tool lookups...\n")
            tool_outputs = []
    
            # 3. Execute ALL generated tool calls
            for tool_call in ai_message.tool_calls:
                if tool_call["name"] == "get_stock_price":
                    ticker_symbol = tool_call["args"]["ticker"]
                    print(f"   -> Executing tool lookup for: {ticker_symbol}")
    
                    result = get_stock_price.invoke(tool_call["args"])
                    print(f"      [Tool Output] {result}")
                    tool_outputs.append(result)
    
            # 4. Supply the full collective data back to the LLM
            summary_prompt = f"""
            User Query: {query}
            Real-time Data Harvested: {'; '.join(tool_outputs)}
    
            Synthesize a final response evaluating which asset looks cheaper.
            """
            final_answer = llm.invoke(summary_prompt)
    
            print("\n--- Final Analysis Output ---")
            display(Markdown(final_answer.content))
        else:
            print("\n--- Final Analysis Output ---")
            print(ai_message.content)
    

    Pour une question concernant à la fois Microsoft et Apple, le modèle renvoie généralement deux appels d’outils dans une seule réponse, un par symbole boursier. La boucle les exécute tous avant de continuer, de sorte que l’étape finale affiche simultanément les deux ensembles de chiffres. Le modèle demande ces appels en parallèle, mais ce code les exécute un après l’autre ; pour des API lentes, on pourrait les exécuter en même temps.

    Lorsque cette méthode fonctionne, deux améliorations méritent d’être connues. Premièrement, la vérification des noms à l’intérieur du boucle permet de choisir parmi plusieurs outils ; un dictionnaire qui associe les noms des outils aux objets correspondants s’avère plus efficace qu’une série d’instructions if. Deuxièmement, cette version renvoie les résultats en créant un nouveau prompt de texte. L’approche plus courante de LangChain consiste à ajouter chaque résultat sous forme de ToolMessage contenant l’tool_call_id correspondant à la conversation, puis à invoquer à nouveau le modèle lié à l’outil, ce qui conserve l’échange complet et permet au modèle de demander d’autres appels si les premiers résultats ne sont pas suffisants.

    L’extension de l’assistant se fait progressivement : un outil comme get_financial_news ou calculate_valuation peut être intégré via la même fonction bind_tools(), et le modèle choisit l’outil en se basant sur ses descriptions.

    Points clés

    • Un outil est une fonction dotée d’un nom clair, de suggestions de type et d’une documentation ; le décorateur @tool s’occupe du reste.
    • .bind_tools() relie vos fonctions au modèle en les décrivant dans chaque requête.
    • Le modèle ne demande que des appels d’outils. Votre code les exécute et renvoie les résultats, et ce contrôle est utile, pas une limitation.
    • Renvoyez les erreurs provenant des outils sous forme de chaînes lisible afin qu’une seule recherche infructueuse ne mette pas fin à l’exécution.
    • Commencez par un outil et une boucle, puis ajoutez des outils, un transfert de résultats basé sur des messages et la concurrence selon les besoins.

    Le même schéma s’applique partout où un modèle a besoin d’informations en temps réel, que ce soit pour la météo et les stocks, les données CRM ou votre propre base de données, afin que les assistants puissent raisonner sur des systèmes en activité plutôt qu’à partir d’une image figée.