Aller au contenu principal

Appels parallèles et séquentiels

Appels paralleles et sequentiels

Le modele peut emettre plusieurs appels de fonction en une seule reponse. Comprendre la difference entre appels paralleles et sequentiels est essentiel pour concevoir des workflows performants.

Appels paralleles

Quand le modele identifie plusieurs besoins independants, il emet tous les appels en meme temps :

from openai import OpenAI
import json
import asyncio

client = OpenAI()

tools = [
    {
        "type": "function",
        "name": "get_meteo",
        "description": "Obtenir la meteo actuelle d'une ville",
        "parameters": {
            "type": "object",
            "properties": {
                "ville": {"type": "string"}
            },
            "required": ["ville"],
            "additionalProperties": false
        },
        "strict": true
    },
    {
        "type": "function",
        "name": "get_taux_change",
        "description": "Obtenir le taux de change entre deux devises",
        "parameters": {
            "type": "object",
            "properties": {
                "source": {"type": "string"},
                "cible": {"type": "string"}
            },
            "required": ["source", "cible"],
            "additionalProperties": false
        },
        "strict": true
    }
]

response = client.responses.create(
    model="gpt-5.3",
    input="Quel temps fait-il a Paris et Berlin, "
          "et quel est le taux EUR/USD ?",
    tools=tools
)

# Le modele emet 3 appels en parallele
appels = [item for item in response.output if item.type == "function_call"]
print(f"Nombre d'appels : {len(appels)}")
# => Nombre d'appels : 3

Executer les appels en parallele cote client

Puisque les appels sont independants, executez-les en parallele avec asyncio :

async def executer_fonction(name: str, arguments: str) -> dict:
    args = json.loads(arguments)
    if name == "get_meteo":
        return await api_meteo(args["ville"])
    elif name == "get_taux_change":
        return await api_change(args["source"], args["cible"])

async def traiter_appels_paralleles(appels):
    taches = [
        executer_fonction(appel.name, appel.arguments)
        for appel in appels
    ]
    resultats = await asyncio.gather(*taches)

    return [
        {
            "type": "function_call_output",
            "call_id": appel.call_id,
            "output": json.dumps(resultat)
        }
        for appel, resultat in zip(appels, resultats)
    ]

# Executer
resultats = asyncio.run(traiter_appels_paralleles(appels))

# Renvoyer au modele
response_finale = client.responses.create(
    model="gpt-5.3",
    input=response.output + resultats,
    tools=tools
)
print(response_finale.output_text)

Desactiver les appels paralleles

Certains cas necessitent un seul appel a la fois. Utilisez parallel_tool_calls :

response = client.responses.create(
    model="gpt-5.3",
    input="Traite ces trois commandes",
    tools=tools,
    parallel_tool_calls=False  # Un seul appel par reponse
)

Cas d’usage pour desactiver le parallele :

  • Operations qui doivent s’executer dans un ordre precis
  • Fonctions avec des effets de bord qui interferent entre elles
  • Quand le resultat d’un appel conditionne le suivant

Appels sequentiels avec boucle

Pour les workflows ou chaque etape depend de la precedente, implementez une boucle :

def boucle_function_calling(prompt: str, tools: list, max_tours: int = 10):
    """Boucle complete de function calling sequentiel."""
    messages_input = prompt
    tour = 0

    while tour < max_tours:
        response = client.responses.create(
            model="gpt-5.3",
            input=messages_input,
            tools=tools,
            parallel_tool_calls=False
        )

        # Verifier s'il y a des appels de fonction
        appels = [i for i in response.output if i.type == "function_call"]

        if not appels:
            # Le modele a fini, il repond en texte
            return response.output_text

        # Executer chaque appel
        resultats = []
        for appel in appels:
            resultat = executer_fonction_sync(appel.name, appel.arguments)
            resultats.append({
                "type": "function_call_output",
                "call_id": appel.call_id,
                "output": json.dumps(resultat)
            })

        # Preparer le prochain tour
        messages_input = response.output + resultats
        tour += 1

    return "Limite de tours atteinte"

Exemple concret : pipeline de traitement

Un cas typique ou le sequentiel est necessaire :

tools_pipeline = [
    {
        "type": "function",
        "name": "valider_commande",
        "description": "Verifier la validite d'une commande (stock, prix, client)",
        "parameters": {
            "type": "object",
            "properties": {
                "commande_id": {"type": "string"}
            },
            "required": ["commande_id"],
            "additionalProperties": false
        },
        "strict": true
    },
    {
        "type": "function",
        "name": "calculer_livraison",
        "description": "Calculer les frais et delais de livraison. "
                       "Necessite une commande validee.",
        "parameters": {
            "type": "object",
            "properties": {
                "commande_id": {"type": "string"},
                "mode": {"type": "string", "enum": ["standard", "express"]}
            },
            "required": ["commande_id", "mode"],
            "additionalProperties": false
        },
        "strict": true
    },
    {
        "type": "function",
        "name": "confirmer_commande",
        "description": "Confirmer la commande et declencher le paiement. "
                       "Necessite validation et calcul livraison prealables.",
        "parameters": {
            "type": "object",
            "properties": {
                "commande_id": {"type": "string"},
                "livraison_id": {"type": "string"}
            },
            "required": ["commande_id", "livraison_id"],
            "additionalProperties": false
        },
        "strict": true
    }
]

# Le modele appellera naturellement dans l'ordre :
# 1. valider_commande
# 2. calculer_livraison (avec le resultat de l'etape 1)
# 3. confirmer_commande (avec les resultats des etapes 1 et 2)
resultat = boucle_function_calling(
    "Traite la commande CMD-42567 en livraison express",
    tools_pipeline
)

Pattern hybride : parallele puis sequentiel

Dans certains workflows, vous combinez les deux approches :

# Tour 1 : collecte parallele de donnees
# Le modele appelle get_client ET get_stock en parallele

# Tour 2 : decision sequentielle basee sur les resultats
# Le modele appelle valider_commande avec les infos collectees

# Tour 3 : action finale
# Le modele appelle confirmer_commande

Ce pattern est naturel : le modele parallelise automatiquement quand les appels sont independants, puis enchaine sequentiellement quand une dependance apparait.

Points cles a retenir

  • Le modele parallelise automatiquement les appels independants
  • Utilisez asyncio.gather pour executer les appels paralleles cote client
  • Desactivez avec parallel_tool_calls=False quand l’ordre compte
  • Implementez une boucle pour les workflows multi-etapes
  • Les descriptions de fonctions guident le modele sur les dependances entre etapes