Aller au contenu principal

Appels Parallèles de Fonctions

Quand le modèle appelle plusieurs fonctions d’un coup

Dans certaines situations, le modèle détermine qu’il a besoin de plusieurs informations simultanément pour répondre à la question de l’utilisateur. Plutôt que de faire des appels séquentiels (appeler la première fonction, attendre le résultat, appeler la deuxième), il peut générer plusieurs tool_calls dans une seule réponse.

Par exemple, si l’utilisateur demande « Quel est le statut et la date du paiement T1001 ? », le modèle peut appeler retrieve_payment_status et retrieve_payment_date en parallèle.

Comment ça fonctionne

Quand le modèle décide de faire des appels parallèles, message.tool_calls contient plusieurs éléments :

response = client.chat.complete(
    model="mistral-large-latest",
    messages=messages,
    tools=tools
)

message = response.choices[0].message

# Plusieurs tool_calls dans la même réponse
for tc in message.tool_calls:
    print(f"Fonction : {tc.function.name}")
    print(f"Arguments : {tc.function.arguments}")
    print(f"ID : {tc.id}")
    print("---")

# Sortie possible :
# Fonction : retrieve_payment_status
# Arguments : {"transaction_id": "T1001"}
# ID : call_abc123
# ---
# Fonction : retrieve_payment_date
# Arguments : {"transaction_id": "T1001"}
# ID : call_def456

Traiter les appels parallèles

Vous devez exécuter chaque fonction et renvoyer chaque résultat avec le bon tool_call_id :

message = response.choices[0].message
messages.append(message)

# Exécuter toutes les fonctions demandées
for tool_call in message.tool_calls:
    function_name = tool_call.function.name
    function_params = json.loads(tool_call.function.arguments)

    # Exécuter la fonction
    result = available_functions[function_name](**function_params)

    # Ajouter le résultat avec le bon tool_call_id
    messages.append({
        "role": "tool",
        "name": function_name,
        "content": result,
        "tool_call_id": tool_call.id
    })

# Renvoyer tous les résultats au modèle
final_response = client.chat.complete(
    model="mistral-large-latest",
    messages=messages,
    tools=tools
)

print(final_response.choices[0].message.content)
# → "Le paiement T1001 a été effectué le 15 mars 2026 et son statut est : payé."

Chaque message tool est associé à un tool_call_id unique. Le modèle utilise ces identifiants pour faire correspondre chaque résultat au bon appel.

Exécution véritablement parallèle en Python

Dans le code ci-dessus, les fonctions sont exécutées séquentiellement dans la boucle for. Si vos fonctions font des appels réseau (API externe, base de données), vous pouvez gagner du temps avec une exécution véritablement parallèle :

import asyncio
import json

async def execute_tools_parallel(tool_calls, available_functions):
    """Exécute les tool_calls en parallèle et retourne les résultats."""

    async def execute_one(tool_call):
        function_name = tool_call.function.name
        function_params = json.loads(tool_call.function.arguments)
        # Si vos fonctions sont async :
        result = await available_functions[function_name](**function_params)
        return {
            "role": "tool",
            "name": function_name,
            "content": result,
            "tool_call_id": tool_call.id
        }

    # Exécuter toutes les fonctions en parallèle
    tasks = [execute_one(tc) for tc in tool_calls]
    return await asyncio.gather(*tasks)

Pour des fonctions synchrones, vous pouvez utiliser concurrent.futures :

from concurrent.futures import ThreadPoolExecutor

def execute_tools_threaded(tool_calls, available_functions):
    """Exécute les tool_calls avec un pool de threads."""
    def execute_one(tool_call):
        function_name = tool_call.function.name
        function_params = json.loads(tool_call.function.arguments)
        result = available_functions[function_name](**function_params)
        return {
            "role": "tool",
            "name": function_name,
            "content": result,
            "tool_call_id": tool_call.id
        }

    with ThreadPoolExecutor(max_workers=5) as executor:
        results = list(executor.map(execute_one, tool_calls))

    return results

Contrôler les appels parallèles

Vous pouvez contrôler si le modèle a le droit de faire des appels parallèles via le paramètre parallel_tool_calls :

# Permettre les appels parallèles (par défaut)
response = client.chat.complete(
    model="mistral-large-latest",
    messages=messages,
    tools=tools,
    parallel_tool_calls=True  # Le modèle décide
)

# Forcer les appels séquentiels
response = client.chat.complete(
    model="mistral-large-latest",
    messages=messages,
    tools=tools,
    parallel_tool_calls=False  # Un seul tool_call par réponse
)

Utilisez parallel_tool_calls=False quand :

  • L’ordre d’exécution des fonctions est important
  • Le résultat d’une fonction est nécessaire pour appeler la suivante
  • Vous voulez un contrôle plus fin sur le flux

Le pattern de flux parallèle

Comparaison des patterns :

# Séquentiel (2 appels API au modèle)
user → assistant(fc.1) → tool(r.1) → assistant(fc.2) → tool(r.2) → assistant(réponse)

# Parallèle (1 seul appel API supplémentaire)
user → assistant(fc.1, fc.2) → tool(r.1), tool(r.2) → assistant(réponse)

Le pattern parallèle est plus efficace : une seule requête au modèle suffit pour obtenir tous les tool_calls, et une seule requête supplémentaire pour la réponse finale.

Points clés à retenir

  • Le modèle peut générer plusieurs tool_calls dans une seule réponse
  • Chaque résultat doit être renvoyé avec le bon tool_call_id
  • Pour des fonctions réseau, utilisez asyncio ou ThreadPoolExecutor pour une exécution parallèle réelle
  • Le paramètre parallel_tool_calls contrôle ce comportement (True par défaut)
  • Le pattern parallèle réduit le nombre de requêtes API et améliore la latence