AI Engineering Academy · Oppitunti

Funktioiden rinnakkainen kutsuminen

Käsittele vastauksia, joissa malli kutsuu useita funktioita samanaikaisesti, suorita ne rinnakkain asyncion avulla ja kokoa tulokset yhteen myöhempään API-kutsuun.

Oppitunti 3/413 vaihetta

Funktioiden rinnakkainen kutsuminen on ilmainen AI Engineering Academy-oppitunti CoddyKitissä. Tämä on oppitunti 3/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu AI Engineering Academy-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. AI Engineering Academy-kurssilla on yhteensä 4 oppituntia.

Mitä rinnakkaiset funktiokutsut ovat?

OpenAI:n mallit voivat kutsua useita funktioita samanaikaisesti yhdessä vastauksessa, kun vastaus edellyttää tietoja useista toisistaan riippumattomista lähteistä. Sen sijaan että työkalukutsut ketjutettaisiin peräkkäin niin, että kukin odottaa edellistä, malli tuottaa useita työkalukutsuja kerralla. Sovelluksenne suorittaa ne rinnakkain ja lähettää kaikki tulokset yhdessä, mikä vähentää viivettä huomattavasti.

Rinnakkaisten työkalukutsujen tunnistaminen

Kun malli tekee rinnakkaisia työkalukutsuja, vastausviesti sisältää tool_calls-luettelon, jossa on useampi kuin yksi alkio. Jokaisella alkiolla on yksilöllinen id, funktion nimi ja argumentit. Käsitelkää ne kaikki ennen seuraavaa API-kutsua — malli odottaa tuloksia jokaisesta tekemästään työkalukutsusta.

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"})

Työkalukutsujen suorittaminen asyncio-kirjastolla

Suorittakaa useita työkalukutsuja samanaikaisesti käyttämällä asyncio.gather()-funktiota. Kukin työkalufunktio suoritetaan erillisessä coroutine-oliossa, ja kaikki tulokset kerätään, kun kaikki coroutinet ovat valmistuneet. Tämä on paljon nopeampaa kuin peräkkäinen suoritus, kun kukin työkalukutsu tekee verkkopyynnön.

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

Kaikkien tulosten lähettäminen yhdessä

Kun kaikki rinnakkaiset työkalukutsut on suoritettu, lisätkää jokainen tulos keskusteluun erillisenä role='tool'-viestinä. Jokaisen viestin tulee sisältää sitä vastaava tool_call_id. Lähettäkää kaikki viestit yhdessä seuraavassa API-kutsussa, jotta malli voi yhdistää kaikki tulokset yhdeksi johdonmukaiseksi vastaukseksi.

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

ThreadPoolExecutorin käyttäminen synkronisille työkaluille

Jos työkalufunktionne ovat synkronisia ja käytätte asynkronisen httpx-kirjaston sijaan requests-kirjastoa, voitte silti suorittaa ne rinnakkain käyttämällä concurrent.futures.ThreadPoolExecutor-luokkaa. Tämä on helpompi ottaa käyttöön, mutta hieman tehottomampi kuin täysin asynkroninen koodi.

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

Yksittäisten työkaluhäiriöiden käsittely

Kun työkalukutsuja suoritetaan rinnakkain, yksi kutsu voi epäonnistua muiden onnistuessa. Älkää koskaan antako yhden epäonnistumisen estää muita. Käyttäkää asyncio.gather()-funktiossa asetusta return_exceptions=True, jotta kaikki tulokset kerätään, vaikka osa kutsuista aiheuttaisi poikkeuksia. Muuntakaa poikkeukset virhemerkkijonoiksi, jotta malli saa kaikki tulokset ja voi päätellä osittaisista epäonnistumisista.

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

Milloin mallit käyttävät rinnakkaisia kutsuja

Malli tekee rinnakkaisia työkalukutsuja, kun se päättelee, että tarvittavat tiedot voidaan kerätä riippumattomasti — yksi tulos ei riipu toisesta. Esimerkkejä ovat osakekurssien hakeminen useille tunnuksille, säätietojen hakeminen useista kaupungeista tai useiden tietokantataulujen kysely. Peräkkäisiä kutsuja käytetään, kun tulokset riippuvat toisistaan — ensin haetaan käyttäjätunnus ja sitten kyseisen tunnuksen tilaukset.

Rinnakkaisten kutsujen nopeusrajoitus

Useiden työkalukutsujen suorittaminen rinnakkain voi kuormittaa ulkoisia API-rajapintoja samanaikaisilla pyynnöillä. Rajoittakaa samanaikaisten työkalukutsujen enimmäismäärää semaforilla. Näin noudatatte API-rajapinnan nopeusrajoituksia ja suoritatte kutsut silti tehokkaammin kuin täysin peräkkäisessä käsittelyssä.

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)
    ]

Peräkkäisten ja rinnakkaisten kutsujen ketjuttaminen

Reaaliaikaisissa agenttityönkuluissa yhdistetään usein peräkkäisiä ja rinnakkaisia kutsuja. Malli saattaa ensin kutsua lookup_user(email)-funktiota ja kutsua palautetun user_id-arvon avulla rinnakkain funktioita get_orders(user_id) ja get_preferences(user_id). Toteuttakaa pääsilmukka tunnistamaan, mitkä kutsut voidaan suorittaa rinnakkain (riippumattomat) ja mitkä on suoritettava peräkkäin (riippuvaiset).

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.'

Viiveen paranemisen mittaaminen

Rinnakkaiset työkalukutsut voivat vähentää viivettä huomattavasti. Jos jokainen kolmesta työkalukutsusta kestää peräkkäin suoritettuna 500 ms, kokonaisaika on 1500 ms. Rinnakkain suoritettuna aika lyhenee noin 500 millisekuntiin — nopeus kolminkertaistuu. Mitatkaa ja verratkaa aina peräkkäistä ja rinnakkaista suoritusta omassa käyttötapauksessanne huomioiden samanaikaisuuden aiheuttama kuormitus ja nopeusrajoitukset.

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')

Rinnakkaisten työkalukutsujen virheenkorjaus

Kun korjaatte rinnakkaisten työkalukutsujen ongelmia, kirjatkaa lokiin koko pyyntö-vastausketju: mallin viesti kaikkine tool_calls-kutsuineen, jokainen keskusteluun lisätty tulos ja mallin lopullinen vastaus. Jos työkalun tulos puuttuu tai on väärässä järjestyksessä, malli saattaa tuottaa epäjohdonmukaisia vastauksia. Rakenteinen lokitus, jossa tool_call_id toimii korrelaatioavaimena, helpottaa ongelmien jäljittämistä huomattavasti.

Pikatarkistus

Testatkaa ymmärrystänne rinnakkaisista funktiokutsuista OpenAI:n kanssa.

Oppitunnin kertaus

Tässä oppitunnissa opitte, että rinnakkaiset työkalukutsut näkyvät tool_calls-luettelossa useina merkintöinä, asyncio.gather suorittaa ne samanaikaisesti mahdollisimman nopeasti ja jokaiselle tulokselle on liitettävä sitä vastaava tool_call_id, kun se lähetetään takaisin mallille. Seuraavaksi rakennamme luonnollisen kielen tietokantakäyttöliittymän, joka käyttää funktiokutsuja tavallisten englanninkielisten kyselyiden kääntämiseen SQL:ksi.

Aloita maksutta

Opi Python tekoälytuutorin avulla — ilmaiseksi

Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.

Kurssit
30
Oppitunnit
120

Usein kysytyt kysymykset

Onko oppitunti ”Funktioiden rinnakkainen kutsuminen” ilmainen?

Kyllä – oppitunnin ”Funktioiden rinnakkainen kutsuminen” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko AI Engineering Academy-kurssin, päivitä CoddyKit PROhon. AI Engineering Academy-kurssilla on yhteensä 4 oppituntia.

Mitä opin oppitunnilla ”Funktioiden rinnakkainen kutsuminen”?

Käsittele vastauksia, joissa malli kutsuu useita funktioita samanaikaisesti, suorita ne rinnakkain asyncion avulla ja kokoa tulokset yhteen myöhempään API-kutsuun. Harjoittelet AI Engineering Academy-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.

Tarvitsenko kokemusta aloittaakseni AI Engineering Academy-opiskelun?

Aiempi kokemus ei ole tarpeen. CoddyKitin AI Engineering Academy-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 3/4.

Kuinka kauan ”Funktioiden rinnakkainen kutsuminen”-oppitunnin suorittaminen kestää?

Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.

Voinko kirjoittaa ja suorittaa koodia tällä AI Engineering Academy-oppitunnilla?

Kyllä. Jokainen AI Engineering Academy-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.

Kaikki tämän kurssin oppitunnit

  1. Funktion skeemojen määrittäminen API:a varten
  2. Työkalukutsujen käsittely sovelluksessa
  3. Funktioiden rinnakkainen kutsuminen
  4. Luonnollisen kielen tietokantakäyttöliittymän rakentaminen
← Takaisin: AI Engineering Academy