Prompt Engineering

Bridging the Gap: A Technical Guide to Function Calling in Large Language Models

Pendant des années, la principale limitation des grands modèles de langage (LLM) était leur incapacité à effectuer des actions dans le monde réel. Ils étaient essentiellement des perroquets sophistiqués, capables de générer du texte mais incapables d'interroger une base de données, de calculer une somme complexe ou d'appeler une API externe. L'introduction de l'appel de fonctions (ou utilisation d'outils) a fondamentalement changé ce paradigme, transformant les LLM de générateurs de texte passifs en agents actifs capables d'interagir avec des systèmes externes.

Pour les développeurs intermédiaires et avancés, comprendre la mécanique de l'appel de fonctions n'est plus une option ; c'est une condition essentielle pour construire des applications d'IA robustes et prêtes pour la production. Cet article explore l'architecture technique, les modèles d'implémentation et les meilleures pratiques pour intégrer l'appel de fonctions dans vos workflows.

Comprendre l'architecture

L'appel de fonctions n'est pas de la magie ; c'est un protocole de communication structuré entre le LLM et votre infrastructure backend. Le processus suit généralement un cycle strict :

  1. Définition : Le développeur fournit au modèle un ensemble de définitions de fonctions (schémas), généralement au format JSON. Ces schémas décrivent le nom de la fonction, sa description et les paramètres attendus.
  2. Invocation : L'utilisateur envoie une requête. Le LLM analyse la demande et, si nécessaire, détermine qu'un appel de fonction est requis pour répondre à la requête. Au lieu de générer du texte, le modèle renvoie un payload JSON structuré contenant le nom de la fonction et ses arguments.
  3. Exécution : Le backend de l'application intercepte ce payload JSON, exécute la fonction réelle dans la base de code hôte (par exemple, Python, JavaScript) et récupère le résultat.
  4. Réponse : Le résultat est renvoyé au LLM dans un message ultérieur, permettant au modèle de synthétiser une réponse finale et précise pour l'utilisateur.

Exemple d'implémentation

Regardons un exemple pratique en utilisant l'API OpenAI. Imaginons que nous construisions un assistant météo. Nous devons définir une fonction qui récupère les données météorologiques.

import openai

# Définir le schéma de la fonction
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Obtenir la météo actuelle dans un lieu donné",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "La ville et l'état, par ex. San Francisco, CA"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"]
                    }
                },
                "required": ["location"]
            }
        }
    }
]

# Requête utilisateur
messages = [{"role": "user", "content": "Quel temps fait-il à Tokyo ?"}]

# Appeler l'API
response = openai.chat.completions.create(
    model="gpt-4-turbo",
    messages=messages,
    tools=tools,
    tool_choice="auto"
)

# Vérifier si le modèle souhaite appeler une fonction
response_message = response.choices[0].message
tool_calls = response_message.tool_calls

if tool_calls:
    # Le modèle a demandé un appel de fonction
    available_functions = {
        "get_weather": get_weather_from_api
    }
    
    # Exécuter la fonction
    function_name = tool_calls[0].function.name
    function_args = json.loads(tool_calls[0].function.arguments)
    function_response = available_functions[function_name](**function_args)
    
    # Envoyer la réponse au modèle
    messages.append(response_message) # Ajouter le message de l'assistant
    messages.append({
        "tool_call_id": tool_calls[0].id,
        "role": "tool",
        "name": function_name,
        "content": function_response
    })
    
    # Obtenir la réponse finale
    second_response = openai.chat.completions.create(
        model="gpt-4-turbo",
        messages=messages
    )
    print(second_response.choices[0].message.content)

Meilleures pratiques pour la robustesse

Bien que le concept soit simple, l'implémentation en production nécessite une attention particulière aux détails :

  • Conception sémantique des schémas : Vos schémas JSON doivent être précis. Utilisez enum pour les valeurs contraintes et fournissez des champs description détaillés. Le LLM repose entièrement sur ces descriptions pour déterminer quand appeler une fonction.
  • Gestion des erreurs : Les requêtes réseau vers des API externes peuvent échouer. Assurez-vous que votre backend intercepte ces erreurs et les renvoie au LLM sous forme de message role: "tool" avec un statut d'erreur. Cela permet au modèle de tenter une nouvelle tentative ou d'informer l'utilisateur de manière élégante.
  • Sanitisation de la sécurité : N'exécutez jamais aveuglément les arguments de fonction reçus d'un LLM. Validez toutes les entrées par rapport à vos types et contraintes internes avant l'exécution pour prévenir les attaques par injection.

Conclusion

L'appel de fonctions représente un bond significatif en avant dans l'utilisabilité de l'IA. Il permet aux modèles de rester ancrés dans la réalité en accédant à des données en temps réel et en effectuant des calculs en dehors de leurs ensembles d'entraînement statiques. En maîtrisant l'implémentation de l'utilisation d'outils, les développeurs peuvent créer des applications qui sont non seulement conversationnelles, mais aussi capables d'agir, ouvrant la voie à la prochaine génération d'agents intelligents.

Share: