AI Engineering Academy · Leçon

Appels de fonctions en parallèle

Gérez les réponses dans lesquelles le modèle appelle plusieurs fonctions simultanément, exécutez-les en parallèle avec asyncio et regroupez les résultats dans un seul appel ultérieur à l’API.

Leçon 3 sur 413 étapes

Appels de fonctions en parallèle est une leçon AI Engineering Academy gratuite sur CoddyKit. Ceci est la leçon 3 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage AI Engineering Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours AI Engineering Academy comprend 4 leçons au total.

Qu’est-ce que l’appel de fonctions en parallèle ?

Les modèles d’OpenAI peuvent appeler plusieurs fonctions simultanément dans une seule réponse lorsque celle-ci nécessite des informations provenant de plusieurs sources indépendantes. Au lieu d’enchaîner les appels d’outils séquentiellement — chacun attendant le précédent — le modèle émet plusieurs appels d’outils à la fois. Votre application les exécute en parallèle et renvoie tous les résultats ensemble, ce qui réduit considérablement la latence.

Reconnaître les appels d’outils en parallèle

Lorsque le modèle émet des appels d’outils en parallèle, le message de réponse contient une liste tool_calls avec plusieurs entrées. Chaque entrée possède un id unique, un nom de fonction et des arguments. Vous devez tous les traiter avant d’effectuer l’appel suivant à l’API : le modèle attend un résultat pour chaque appel d’outil émis.

from openai import OpenAI
import json

client = OpenAI()

# A question that naturally requires two independent lookups
response = client.chat.completions.create(
    model='gpt-4o',
    messages=[{'role': 'user', 'content': 'Compare the weather in London and Tokyo right now.'}],
    tools=tools
)

message = response.choices[0].message
print('Number of tool calls:', len(message.tool_calls))
# Might print: Number of tool calls: 2

for tc in message.tool_calls:
    print(f'  {tc.function.name}({tc.function.arguments})')
# get_current_weather({"location": "London"})
# get_current_weather({"location": "Tokyo"})

Exécuter des appels d’outils avec asyncio

Exécutez plusieurs appels d’outils simultanément avec asyncio.gather(). Chaque fonction d’outil s’exécute dans une coroutine distincte et tous les résultats sont collectés lorsque toutes les coroutines sont terminées. Cette approche est bien plus rapide qu’une exécution séquentielle lorsque chaque appel d’outil effectue une requête réseau.

import asyncio
import json

async def execute_tool_call_async(tool_call) -> tuple:
    '''Execute a single tool call and return (tool_call_id, result).'''
    name = tool_call.function.name
    args = json.loads(tool_call.function.arguments)

    # Async version of your tool (uses httpx, aiohttp, etc.)
    if name == 'get_current_weather':
        result = await async_get_weather(**args)
    elif name == 'get_stock_price':
        result = await async_get_stock(**args)
    else:
        result = f'Unknown tool: {name}'

    return tool_call.id, str(result)

async def execute_all_parallel(tool_calls) -> list:
    '''Execute all tool calls concurrently.'''
    tasks = [execute_tool_call_async(tc) for tc in tool_calls]
    results = await asyncio.gather(*tasks, return_exceptions=True)
    return results

Renvoyer tous les résultats ensemble

Après avoir exécuté tous les appels d’outils en parallèle, ajoutez chaque résultat sous forme d’un message distinct avec role='tool' dans la conversation. Chaque message doit inclure le tool_call_id correspondant. Envoyez-les tous dans un seul appel suivant à l’API afin que le modèle puisse synthétiser l’ensemble des résultats en une réponse cohérente.

async def run_parallel_tool_calls(user_message: str) -> str:
    messages = [{'role': 'user', 'content': user_message}]

    response = client.chat.completions.create(
        model='gpt-4o', messages=messages, tools=tools
    )
    assistant_message = response.choices[0].message
    messages.append(assistant_message)  # Add assistant's tool_calls

    if response.choices[0].finish_reason == 'tool_calls':
        # Execute all tool calls in parallel
        results = await execute_all_parallel(assistant_message.tool_calls)

        # Add all results to conversation
        for tool_call_id, result in results:
            messages.append({
                'role': 'tool',
                'tool_call_id': tool_call_id,
                'content': result
            })

        # One more API call to synthesize results
        final = client.chat.completions.create(model='gpt-4o', messages=messages)
        return final.choices[0].message.content

    return assistant_message.content

Utiliser ThreadPoolExecutor pour les outils synchrones

Si vos fonctions d’outils sont synchrones, c’est-à-dire qu’elles utilisent requests plutôt que httpx de manière asynchrone, vous pouvez tout de même les exécuter en parallèle avec concurrent.futures.ThreadPoolExecutor. Cette solution est plus simple à mettre en place, mais légèrement moins efficace qu’un code entièrement asynchrone.

from concurrent.futures import ThreadPoolExecutor, as_completed
import json

def execute_all_with_threads(tool_calls) -> list:
    results = []
    with ThreadPoolExecutor(max_workers=len(tool_calls)) as executor:
        future_to_id = {
            executor.submit(execute_tool_call, tc): tc.id
            for tc in tool_calls
        }
        for future in as_completed(future_to_id):
            tool_call_id = future_to_id[future]
            try:
                result = future.result(timeout=15)
            except Exception as e:
                result = f'Tool failed: {str(e)}'
            results.append((tool_call_id, str(result)))
    return results

Gérer les échecs individuels des outils

Lors de l’exécution parallèle d’appels d’outils, l’un d’eux peut échouer tandis que les autres réussissent. Ne laissez jamais un échec bloquer les autres. Utilisez return_exceptions=True dans asyncio.gather() afin de collecter tous les résultats, même si certains appels lèvent des exceptions. Convertissez les exceptions en chaînes d’erreur pour que le modèle reçoive tous les résultats et puisse raisonner malgré les échecs partiels.

async def safe_execute_all(tool_calls) -> list:
    tasks = [execute_tool_call_async(tc) for tc in tool_calls]
    raw_results = await asyncio.gather(*tasks, return_exceptions=True)

    results = []
    for tc, result in zip(tool_calls, raw_results):
        if isinstance(result, Exception):
            results.append((tc.id, f'Tool error: {str(result)}'))
        else:
            tool_call_id, output = result
            results.append((tool_call_id, output))
    return results

Quand les modèles utilisent des appels en parallèle

Le modèle émet des appels d’outils en parallèle lorsqu’il détermine que les informations nécessaires peuvent être obtenues indépendamment — aucun résultat ne dépend d’un autre. Exemples : récupérer les cours de plusieurs actions, obtenir la météo de plusieurs villes ou interroger plusieurs tables de base de données. Les appels séquentiels sont utilisés lorsque les résultats dépendent les uns des autres : rechercher d’abord l’identifiant d’un utilisateur, puis récupérer les commandes associées à cet identifiant.

Limiter le débit des appels en parallèle

Exécuter de nombreux appels d’outils en parallèle peut submerger les API externes de requêtes simultanées. Utilisez un sémaphore pour plafonner le nombre maximal d’appels d’outils simultanés. Vous respecterez ainsi les limites de débit de l’API tout en exécutant les appels plus efficacement qu’avec un traitement entièrement séquentiel.

import asyncio

async def rate_limited_execute_all(tool_calls, max_concurrent: int = 5) -> list:
    semaphore = asyncio.Semaphore(max_concurrent)

    async def limited_call(tc):
        async with semaphore:
            return await execute_tool_call_async(tc)

    tasks = [limited_call(tc) for tc in tool_calls]
    results = await asyncio.gather(*tasks, return_exceptions=True)

    return [
        (tc.id, str(r) if not isinstance(r, Exception) else f'Error: {r}')
        for tc, r in zip(tool_calls, results)
    ]

Chaîner des appels séquentiels et parallèles

Les flux de travail réels d’agents combinent souvent des appels séquentiels et parallèles. Le modèle peut d’abord appeler lookup_user(email), puis — en utilisant le user_id renvoyé — appeler get_orders(user_id) et get_preferences(user_id) en parallèle. Implémentez la boucle externe de manière à détecter les appels qui peuvent être parallélisés, car ils sont indépendants, et ceux qui doivent rester séquentiels, car ils sont dépendants.

async def multi_round_agent(user_message: str) -> str:
    messages = [{'role': 'user', 'content': user_message}]
    MAX_ROUNDS = 5

    for _ in range(MAX_ROUNDS):
        response = client.chat.completions.create(
            model='gpt-4o', messages=messages, tools=tools
        )
        choice = response.choices[0]
        messages.append(choice.message)

        if choice.finish_reason == 'stop':
            return choice.message.content  # Done

        if choice.finish_reason == 'tool_calls':
            # Execute all tool calls in parallel (may be 1 or many)
            results = await safe_execute_all(choice.message.tool_calls)
            for tc_id, result in results:
                messages.append({'role': 'tool', 'tool_call_id': tc_id, 'content': result})
            # Loop continues for potentially sequential next call

    return 'Max rounds reached.'

Mesurer l’amélioration de la latence

Les appels d’outils en parallèle peuvent réduire considérablement la latence. Si chacun de trois appels d’outils prend 500 ms lorsqu’il est exécuté séquentiellement, le total est de 1 500 ms. Les exécuter en parallèle ramène la durée à environ 500 ms, soit une accélération d’un facteur 3. Mesurez et comparez toujours l’exécution séquentielle et parallèle dans votre contexte particulier, en tenant compte du coût de la concurrence et des limites de débit.

import time
import asyncio

async def benchmark_parallel_vs_sequential():
    tool_calls = [...]  # 5 independent tool calls

    # Sequential
    start = time.time()
    for tc in tool_calls:
        await execute_tool_call_async(tc)
    sequential_time = time.time() - start

    # Parallel
    start = time.time()
    await asyncio.gather(*[execute_tool_call_async(tc) for tc in tool_calls])
    parallel_time = time.time() - start

    print(f'Sequential: {sequential_time:.2f}s')
    print(f'Parallel:   {parallel_time:.2f}s')
    print(f'Speedup:    {sequential_time/parallel_time:.1f}x')

Déboguer les appels d’outils en parallèle

Lors du débogage de problèmes liés aux appels d’outils en parallèle, journalisez le cycle complet requête-réponse : le message du modèle contenant tous les tool_calls, chaque résultat ajouté à la conversation et la réponse finale du modèle. Si un résultat d’outil manque ou arrive dans le désordre, le modèle peut produire des réponses incohérentes. Une journalisation structurée utilisant tool_call_id comme clé de corrélation facilite grandement le suivi des problèmes.

Vérification rapide

Testez votre compréhension des appels de fonctions en parallèle avec OpenAI.

Récapitulatif de la leçon

Dans cette leçon, vous avez appris que les appels d’outils parallèles apparaissent comme plusieurs entrées dans la liste tool_calls, que asyncio.gather les exécute simultanément pour atteindre une vitesse maximale et que chaque résultat doit inclure l’identifiant tool_call_id correspondant lorsqu’il est renvoyé au modèle. Nous allons maintenant créer une interface de base de données en langage naturel utilisant les appels de fonctions pour traduire des requêtes en anglais courant en SQL.

Gratuit pour commencer

Apprends Python avec un tuteur IA — gratuit

Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.

Cours
30
Leçons
120

Questions Fréquemment Posées

La leçon « Appels de fonctions en parallèle » est-elle gratuite ?

Oui — le texte complet de « Appels de fonctions en parallèle » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours AI Engineering Academy, passe à CoddyKit PRO. Le cours AI Engineering Academy comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Appels de fonctions en parallèle » ?

Gérez les réponses dans lesquelles le modèle appelle plusieurs fonctions simultanément, exécutez-les en parallèle avec asyncio et regroupez les résultats dans un seul appel ultérieur à l’API. Tu pratiques AI Engineering Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer AI Engineering Academy ?

Aucune expérience préalable n'est requise. AI Engineering Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 4.

Combien de temps prend la leçon « Appels de fonctions en parallèle » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon AI Engineering Academy ?

Oui. Chaque leçon AI Engineering Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Définir les schémas de fonctions pour l’API
  2. Traiter les appels d’outils dans votre application
  3. Appels de fonctions en parallèle
  4. Créer une interface de base de données en langage naturel
← Retour à AI Engineering Academy