Aller au contenu principal

Appels parallèles

Plusieurs outils en une seule réponse

L’une des fonctionnalités les plus efficaces du function calling Grok est la capacité à retourner plusieurs appels d’outils dans une seule réponse. Plutôt que de faire un aller-retour pour chaque fonction, le modèle identifie toutes les informations dont il a besoin et génère tous les appels en parallèle.

Le paramètre parallel_tool_calls

Les appels parallèles sont activés par défaut. Le paramètre parallel_tool_calls contrôle ce comportement :

# Activé par défaut (pas besoin de le spécifier)
response = client.chat.completions.create(
    model="grok-3",
    messages=messages,
    tools=tools,
    parallel_tool_calls=True  # C'est le défaut
)

Quand un utilisateur demande « Quel temps fait-il à Paris et à Lyon ? », le modèle retourne deux tool_calls dans la même réponse :

{
  "tool_calls": [
    {
      "id": "call_001",
      "function": {
        "name": "get_weather",
        "arguments": "{\"city\": \"Paris\"}"
      }
    },
    {
      "id": "call_002",
      "function": {
        "name": "get_weather",
        "arguments": "{\"city\": \"Lyon\"}"
      }
    }
  ]
}

Traiter les appels parallèles

Votre code doit gérer un tableau de tool_calls, pas un seul :

import json
import asyncio

message = response.choices[0].message

if message.tool_calls:
    # Exécuter toutes les fonctions
    tool_results = []
    for tool_call in message.tool_calls:
        args = json.loads(tool_call.function.arguments)
        result = execute_function(tool_call.function.name, args)
        tool_results.append({
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": json.dumps(result)
        })

    # Renvoyer TOUS les résultats au modèle
    messages = [
        {"role": "user", "content": "Quel temps à Paris et Lyon ?"},
        message,
        *tool_results  # Tous les résultats
    ]

    final = client.chat.completions.create(
        model="grok-3",
        messages=messages,
        tools=tools
    )

Point critique : vous devez renvoyer tous les résultats d’outils, pas seulement le premier. Chaque résultat est lié à son tool_call_id correspondant.

Exécution réellement parallèle côté client

Le modèle retourne les appels en parallèle, mais c’est votre code qui doit les exécuter en parallèle. En Python, utilisez asyncio ou concurrent.futures :

import concurrent.futures

def execute_parallel(tool_calls):
    results = []
    with concurrent.futures.ThreadPoolExecutor() as executor:
        futures = {}
        for tc in tool_calls:
            args = json.loads(tc.function.arguments)
            future = executor.submit(execute_function, tc.function.name, args)
            futures[future] = tc.id

        for future in concurrent.futures.as_completed(futures):
            call_id = futures[future]
            result = future.result()
            results.append({
                "role": "tool",
                "tool_call_id": call_id,
                "content": json.dumps(result)
            })
    return results

L’exécution parallèle réduit considérablement la latence totale. Si chaque appel API prend 200ms, deux appels séquentiels = 400ms, mais deux appels parallèles = ~200ms.

Désactiver les appels parallèles

Dans certains cas, vous préférez des appels séquentiels :

response = client.chat.completions.create(
    model="grok-3",
    messages=messages,
    tools=tools,
    parallel_tool_calls=False
)

Quand désactiver le parallélisme :

  • Dépendances entre appels : le résultat d’un outil est nécessaire pour appeler le suivant
  • Rate limiting : votre API externe a des limites de requêtes simultanées
  • Ordre important : les opérations doivent s’exécuter dans un ordre précis (ex: créer un utilisateur avant de lui assigner un rôle)
  • Debugging : pour simplifier le suivi des appels pendant le développement

Appels parallèles avec des outils différents

Le modèle peut aussi appeler des outils différents en parallèle :

# Requête : "Réserve une table et dis-moi le temps qu'il fera ce soir"
# Le modèle retourne :
# - tool_call_1 : book_table(restaurant="Le Petit Bistrot", time="20:00")
# - tool_call_2 : get_weather(city="Paris", time="tonight")

C’est particulièrement utile pour les assistants polyvalents qui doivent rassembler des informations de sources différentes.

Gestion des erreurs en parallèle

Quand un appel échoue mais pas les autres, renvoyez quand même tous les résultats. Le modèle gère les erreurs individuelles :

results = []
for tc in message.tool_calls:
    try:
        result = execute_function(tc.function.name, args)
        results.append({
            "role": "tool",
            "tool_call_id": tc.id,
            "content": json.dumps(result)
        })
    except Exception as e:
        results.append({
            "role": "tool",
            "tool_call_id": tc.id,
            "content": json.dumps({"error": str(e)})
        })

Points clés à retenir

  • Les appels parallèles sont activés par défaut (parallel_tool_calls: true)
  • Le modèle retourne plusieurs tool_calls dans une seule réponse
  • Vous devez renvoyer tous les résultats, chacun lié à son tool_call_id
  • Implémentez l’exécution parallèle côté client pour réduire la latence
  • Désactivez le parallélisme quand il y a des dépendances entre appels
  • Gérez les erreurs individuellement — ne bloquez pas tout pour un échec