Modele i platformy AI

Wywołania Asynchroniczne API LLM w Pythonie: Kompleksowy Przewodnik

mm
Dodaj Unite.AI do preferowanych ÅšrÃģdeł w Google

Jako deweloperzy i naukowcy danych, często musimy wchodzić w interakcje z tymi potęŞnymi modelami za pomocą API. Jednak gdy nasze aplikacje rosną w złoÅžoności i skali, potrzeba wydajnych i efektywnych interakcji API staje się kluczowa. To właśnie tutaj programowanie asynchroniczne błyszczy, pozwalając nam maksymalizować przepływność i minimalizować opÃģÅšnienia podczas pracy z API LLM.

W tym kompleksowym przewodniku, będziemy eksplorować świat asynchronicznych wywołań API LLM w Pythonie. Pokryjemy wszystko, od podstaw programowania asynchronicznego po zaawansowane techniki obsługi złoÅžonych przepływÃģw pracy. Po przeczytaniu tego artykułu, będziesz miał solidne zrozumienie, jak wykorzystać programowanie asynchroniczne, aby zwiększyć wydajność swoich aplikacji zasilanych przez LLM.

Przed tym, jak zagłębimy się w szczegÃģły asynchronicznych wywołań API LLM, ustanÃģwmy solidną podstawę w pojęciach programowania asynchronicznego.

Programowanie asynchroniczne pozwala na wykonywanie wielu operacji jednocześnie bez blokowania głÃģwnego wątku wykonywania. W Pythonie, jest to głÃģwnie osiągane za pomocą modułu asyncio, ktÃģry zapewnia ramy dla pisania kodu wspÃģłbieÅžnego za pomocą koreutin, pętli zdarzeń i przyszłości.

Kluczowe pojęcia:

  • Koreutyny: Funkcje zdefiniowane z async def, ktÃģre mogą być wstrzymane i wznowione.
  • Pętla zdarzeń: Centralny mechanizm wykonywania, ktÃģry zarządza i uruchamia zadania asynchroniczne.
  • Obiekty oczekujące: Obiekty, ktÃģre mogą być uÅžywane z kluczem await (koreutyny, zadania, przyszłości).

Oto prosty przykład, ktÃģry ilustruje te pojęcia:

import asyncio

<p>async def greet(name):</p>
<p> await asyncio.sleep(1) # Symulacja operacji wejścia/wyjścia</p>
<p> print(f"Cześć, {name}!")</p>

<p>async def main():</p>
<p> await asyncio.gather(</p>
<p> greet("Alice"),</p>
<p> greet("Bob"),</p>
<p> greet("Charlie")</p>
<p> )</p>

asyncio.run(main())

W tym przykładzie, definiujemy funkcję asynchroniczną greet, ktÃģra symuluje operację wejścia/wyjścia za pomocą asyncio.sleep(). Funkcja main uÅžywa asyncio.gather(), aby uruchomić wiele powitań jednocześnie. Pomimo opÃģÅšnienia spowodowanego przez asyncio.sleep(), wszystkie trzy powitania zostaną wydrukowane po około 1 sekundzie, demonstrując moc wykonywania asynchronicznego.

Potrzeba Asynchroniczności w Wywołaniach API LLM

Podczas pracy z API LLM, często spotykamy się z sytuacjami, w ktÃģrych musimy wykonać wiele wywołań API, albo sekwencyjnie, albo rÃģwnolegle. Tradycyjny kod synchroniczny moÅže prowadzić do znacznych wąskich gardeł wydajności, szczegÃģlnie podczas radzenia sobie z operacjami o wysokiej latencji, takimi jak Şądania sieciowe do usług LLM.

RozwaÅžmy sytuację, w ktÃģrej musimy wygenerować podsumowania dla 100 rÃģÅžnych artykułÃģw za pomocą API LLM. Z podejściem synchronicznym, kaÅžde wywołanie API zablokuje się, aÅž do otrzymania odpowiedzi, potencjalnie zajmując kilka minut, aby ukończyć wszystkie Şądania. Podejście asynchroniczne pozwala nam natomiast zainicjować wiele wywołań API jednocześnie, znacznie redukując całkowity czas wykonywania.

Konfigurowanie Środowiska

Aby rozpocząć pracę z asynchronicznymi wywołaniami API LLM, musisz skonfigurować swoje środowisko Pythona z niezbędnymi bibliotekami. Oto, co będziesz potrzebować:

  • Python 3.7 lub nowszy (dla natywnego wsparcia asyncio)
  • aiohttp: Asynchroniczna biblioteka klienta HTTP
  • openai: Oficjalna biblioteka klienta OpenAI dla Pythona (jeśli uÅžywasz modeli GPT OpenAI)
  • langchain: Ramy dla budowania aplikacji z LLM (opcjonalnie, ale zalecane dla złoÅžonych przepływÃģw pracy)

MoÅžesz zainstalować te zaleÅžności za pomocą pip:


<p>pip install aiohttp openai langchain
&lt;div class=&quot;relative flex flex-col rounded-lg&quot;&gt;

Podstawowe Asynchroniczne Wywołania API LLM z asyncio i aiohttp

Zacznijmy od wykonania prostego asynchronicznego wywołania do API LLM za pomocą aiohttp. Będziemy uÅžywać API GPT-3.5 OpenAI jako przykładu, ale pojęcia te mają zastosowanie do innych API LLM.

import asyncio
import aiohttp
from openai import AsyncOpenAI

<p>async def generate_text(prompt, client):</p>
<p> response = await client.chat.completions.create(</p>
<p> model="gpt-3.5-turbo",</p>
<p> messages=[{"role": "user", "content": prompt}]</p>
<p> )</p>
<p> return response.choices[0].message.content</p>

<p>async def main():</p>
<p> prompts = [</p>
<p> "Wyjaśnij kwantową komputację w prostych słowach.",</p>
<p> "Napisz haiku o sztucznej inteligencji.",</p>
<p> "Opisz proces fotosyntezy."</p>
<p> ]</p>

<p>async with AsyncOpenAI() as client:</p>
<p> tasks = [generate_text(prompt, client) for prompt in prompts]</p>
<p> results = await asyncio.gather(*tasks)</p>

<p>for prompt, result in zip(prompts, results):</p>
<p> print(f"Prompt: {prompt}\nOdpowiedÅš: {result}\n")</p>

asyncio.run(main())

W tym przykładzie, definiujemy funkcję asynchroniczną generate_text, ktÃģra wykonuje wywołanie do API OpenAI za pomocą klienta AsyncOpenAI. Funkcja main tworzy wiele zadań dla rÃģÅžnych prompty i uÅžywa asyncio.gather(), aby uruchomić je jednocześnie.

To podejście pozwala nam wysyłać wiele Şądań do API LLM jednocześnie, znacznie redukując całkowity czas przetwarzania.

Zaawansowane Techniki: Partionowanie i Kontrola WspÃģłbieÅžności

Podczas gdy poprzedni przykład demonstruje podstawy asynchronicznych wywołań API LLM, aplikacje rzeczywiste często wymagają bardziej zaawansowanych podejść. RozwaÅžmy dwie waÅžne techniki: partionowanie Şądań i kontrola wspÃģłbieÅžności.

Partionowanie Åŧądań: Podczas radzenia sobie z duŞą liczbą prompty, często jest bardziej efektywne partionowanie ich w grupy, zamiast wysyłania poszczegÃģlnych Şądań dla kaÅždego promptu. To redukuje nakład pracy związany z wieloma wywołaniami API i moÅže prowadzić do lepszej wydajności.

import asyncio
from openai import AsyncOpenAI

<p>async def process_batch(batch, client):</p>
<p> responses = await asyncio.gather(*[</p>
<p> client.chat.completions.create(</p>
<p> model="gpt-3.5-turbo",</p>
<p> messages=[{"role": "user", "content": prompt}]</p>
<p> ) for prompt in batch</p>
<p> ])</p>
<p> return [response.choices[0].message.content for response in responses]</p>

<p>async def main():</p>
<p> prompts = [f"Powiedz mi fakt o liczbie {i}" for i in range(100)]</p>
<p> batch_size = 10</p>

<p>async with AsyncOpenAI() as client:</p>
<p> results = []</p>
<p> for i in range(0, len(prompts), batch_size):</p>
<p> batch = prompts[i:i+batch_size]</p>
<p> batch_results = await process_batch(batch, client)</p>
<p> results.extend(batch_results)</p>

<p>for prompt, result in zip(prompts, results):</p>
<p> print(f"Prompt: {prompt}\nOdpowiedÅš: {result}\n")</p>

asyncio.run(main())

Kontrola WspÃģłbieÅžności: Podczas gdy programowanie asynchroniczne pozwala na wykonywanie wspÃģłbieÅžne, waÅžne jest kontrolowanie poziomu wspÃģłbieÅžności, aby uniknąć przeciÄ…Åženia serwera API lub przekroczenia limitÃģw stawek. MoÅžemy uÅžyć asyncio.Semaphore do tego celu.

import asyncio
from openai import AsyncOpenAI

<p>async def generate_text(prompt, client, semaphore):</p>
<p> async with semaphore:</p>
<p> response = await client.chat.completions.create(</p>
<p> model="gpt-3.5-turbo",</p>
<p> messages=[{"role": "user", "content": prompt}]</p>
<p> )</p>
<p> return response.choices[0].message.content</p>

<p>async def main():</p>
<p> prompts = [f"Powiedz mi fakt o liczbie {i}" for i in range(100)]</p>
<p> max_concurrent_requests = 5</p>
<p> semaphore = asyncio.Semaphore(max_concurrent_requests)</p>

<p>async with AsyncOpenAI() as client:</p>
<p> tasks = [generate_text(prompt, client, semaphore) for prompt in prompts]</p>
<p> results = await asyncio.gather(*tasks)</p>

<p>for prompt, result in zip(prompts, results):</p>
<p> print(f"Prompt: {prompt}\nOdpowiedÅš: {result}\n")</p>

asyncio.run(main())

W tym przykładzie, uÅžywamy semafora, aby ograniczyć liczbę wspÃģłbieÅžnych Şądań do 5, zapewniając, Åže nie przeciÄ…Åžymy serwera API.

Obsługa BłędÃģw i Ponowienia w Asynchronicznych Wywołaniach LLM

Podczas pracy z zewnętrznymi API, waÅžne jest wdroÅženie solidnej obsługi błędÃģw i mechanizmÃģw ponowienia. RozwaÅžmy, jak moÅžemy udoskonalić nasz kod, aby obsłuÅžyć typowe błędy i wdroÅžyć wykładnicze ponowienie.

import asyncio
import random
from openai import AsyncOpenAI
from tenacity import retry, stop_after_attempt, wait_exponential

class APIError(Exception):
pass

<p>@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))</p>
<p>async def generate_text_with_retry(prompt, client):</p>
<p> try:</p>
<p> response = await client.chat.completions.create(</p>
<p> model="gpt-3.5-turbo",</p>
<p> messages=[{"role": "user", "content": prompt}]</p>
<p> )</p>
<p> return response.choices[0].message.content</p>
<p> except Exception as e:</p>
<p> print(f"Wystąpił błąd: {e}")</p>
<p> raise APIError("Nie udało się wygenerować tekstu")</p>

<p>async def process_prompt(prompt, client, semaphore):</p>
<p> async with semaphore:</p>
<p> try:</p>
<p> result = await generate_text_with_retry(prompt, client)</p>
<p> return prompt, result</p>
<p> except APIError:</p>
<p> return prompt, "Nie udało się wygenerować odpowiedzi po wielu prÃģbach."</p>

<p>async def main():</p>
<p> prompts = [f"Powiedz mi fakt o liczbie {i}" for i in range(20)]</p>
<p> max_concurrent_requests = 5</p>
<p> semaphore = asyncio.Semaphore(max_concurrent_requests)</p>

<p>async with AsyncOpenAI() as client:</p>
<p> tasks = [process_prompt(prompt, client, semaphore) for prompt in prompts]</p>
<p> results = await asyncio.gather(*tasks)</p>

<p>for prompt, result in results:</p>
<p> print(f"Prompt: {prompt}\nOdpowiedÅš: {result}\n")</p>

asyncio.run(main())

Ta ulepszona wersja zawiera:

  • Niestandardowy wyjątek APIError dla błędÃģw związanych z API.
  • Funkcję generate_text_with_retry ozdobioną dekoratorem @retry z biblioteki tenacity, wdraÅžając wykładnicze ponowienie.
  • Obsługę błędÃģw w funkcji process_prompt, aby przechwytywać i zgłaszać niepowodzenia.

Optymalizacja Wydajności: Transmisja Strumieniowa

Dla generacji długich treści, transmisja strumieniowa moÅže znacznie poprawić postrzeganą wydajność Twojej aplikacji. Zamiast czekać na całą odpowiedÅš, moÅžesz przetwarzać i wyświetlać fragmenty tekstu, gdy stają się one dostępne.

import asyncio
from openai import AsyncOpenAI

<p>async def stream_text(prompt, client):</p>
<p> stream = await client.chat.completions.create(</p>
<p> model="gpt-3.5-turbo",</p>
<p> messages=[{"role": "user", "content": prompt}],</p>
<p> stream=True</p>
<p> )</p>

<p>full_response = ""</p>
<p> async for chunk in stream:</p>
<p> if chunk.choices[0].delta.content is not None:</p>
<p> content = chunk.choices[0].delta.content</p>
<p> full_response += content</p>
<p> print(content, end="", flush=True)</p>

<p>print("\n")</p>
<p> return full_response</p>

<p>async def main():</p>
<p> prompt = "Napisz krÃģtką historię o podrÃģÅžującym w czasie naukowcu."</p>

<p>async with AsyncOpenAI() as client:</p>
<p> result = await stream_text(prompt, client)</p>

<p>print(f"Pełna odpowiedÅš:\n{result}")</p>

asyncio.run(main())

Ten przykład demonstruje, jak transmitować strumieniowo odpowiedÅš z API, drukując kaÅždy fragment, gdy tylko jest dostępny. To podejście jest szczegÃģlnie przydatne w aplikacjach czatu lub w kaÅždej sytuacji, w ktÃģrej chcesz zapewnić uÅžytkownikowi natychmiastową informację zwrotną.

Budowanie Asynchronicznych PrzepływÃģw Pracy z LangChain

Dla bardziej złoÅžonych aplikacji zasilanych przez LLM, ramy LangChain zapewniają wysoki poziom abstrakcji, ktÃģry upraszcza proces łączenia wielu wywołań LLM i integracji z innymi narzędziami. RozwaÅžmy przykład uÅžycia LangChain z moÅžliwościami asynchronicznymi:

Ten przykład pokazuje, jak LangChain moÅže być uÅžyty do tworzenia bardziej złoÅžonych przepływÃģw pracy z transmisją strumieniową i wykonywaniem asynchronicznym. AsyncCallbackManager i StreamingStdOutCallbackHandler umoÅžliwiają transmisję strumieniową wygenerowanego tekstu w czasie rzeczywistym.

import asyncio
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain.callbacks.manager import AsyncCallbackManager
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler

<p>async def generate_story(topic):</p>
<p> llm = OpenAI(temperature=0.7, streaming=True, callback_manager=AsyncCallbackManager([StreamingStdOutCallbackHandler()]))</p>
<p> prompt = PromptTemplate(</p>
<p> input_variables=["topic"],</p>
<p> template="Napisz krÃģtką historię o {topic}."</p>
<p> )</p>
<p> chain = LLMChain(llm=llm, prompt=prompt)</p>
<p> return await chain.arun(topic=topic)</p>

<p>async def main():</p>
<p> topics = ["magiczny las", "miasto przyszłości", "podwodna cywilizacja"]</p>
<p> tasks = [generate_story(topic) for topic in topics]</p>
<p> stories = await asyncio.gather(*tasks)</p>

<p>for topic, story in zip(topics, stories):</p>
<p> print(f"\nTemat: {topic}\nHistoria: {story}\n{'='*50}\n")</p>

asyncio.run(main())

Uruchamianie Asynchronicznych Aplikacji LLM z FastAPI

Aby udostępnić swoją asynchroniczną aplikację LLM jako usługę internetową, FastAPI jest doskonałym wyborem ze względu na jego natywne wsparcie dla operacji asynchronicznych. Oto przykład, jak utworzyć prosty punkt końcowy API dla generacji tekstu:

from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel
from openai import AsyncOpenAI

app = FastAPI()
client = AsyncOpenAI()

<p>class GenerationRequest(BaseModel):</p>
<p> prompt: str</p>

<p>class GenerationResponse(BaseModel):</p>
<p> generated_text: str</p>

<p>@app.post("/generate", response_model=GenerationResponse)</p>
<p>async def generate_text(request: GenerationRequest, background_tasks: BackgroundTasks):</p>
<p> response = await client.chat.completions.create(</p>
<p> model="gpt-3.5-turbo",</p>
<p> messages=[{"role": "user", "content": request.prompt}]</p>
<p> )</p>
<p> generated_text = response.choices[0].message.content</p>

<p># Symulacja pewnych post-procesÃģw w tle</p>
<p> background_tasks.add_task(log_generation, request.prompt, generated_text)</p>

<p> return GenerationResponse(generated_text=generated_text)</p>

<p>async def log_generation(prompt: str, generated_text: str):</p>
<p> # Symulacja logowania lub dodatkowej obrÃģbki</p>
<p> await asyncio.sleep(2)</p>
<p> print(f"Zalogowano: Prompt '{prompt}' wygenerował tekst o długości {len(generated_text)}")</p>

<p>if __name__ == "__main__":</p>
<p> import uvicorn</p>
<p> uvicorn.run(app, host="0.0.0.0", port=8000)</p>

Ta aplikacja FastAPI tworzy punkt końcowy /generate, ktÃģry akceptuje prompt i zwraca wygenerowany tekst. Pokazuje rÃģwnieÅž, jak uÅžywać zadań w tle do dodatkowej obrÃģbki bez blokowania odpowiedzi.

Najlepsze Praktyki i Powszechne Pułapki

Podczas pracy z asynchronicznymi wywołaniami API LLM, pamiętaj o tych najlepszych praktykach:

  1. UÅžywaj puli połączeń: Podczas wykonywania wielu Şądań, ponownie uÅžywaj połączeń, aby zmniejszyć nakład pracy.
  2. WdraÅžaj odpowiednią obsługę błędÃģw: Zawsze uwzględniaj problemy z siecią, błędy API i nieoczekiwane odpowiedzi.
  3. Szanhuj limity stawek: UÅžywaj semaforÃģw lub innych mechanizmÃģw kontroli wspÃģłbieÅžności, aby uniknąć przeciÄ…Åženia serwera API.
  4. Monitoruj i rejestruj: WdraÅžaj kompleksywne logowanie, aby śledzić wydajność i identyfikować problemy.
  5. UÅžywaj transmisji strumieniowej dla długich treści: Poprawia to doświadczenie uÅžytkownika i pozwala na wczesną obrÃģbkę częściowych wynikÃģw.

Przez ostatnie pięć lat zanurzałem się w fascynującym świecie Machine Learning i Deep Learning. Moja pasja i ekspertyza doprowadziły mnie do udziału w ponad 50 rÃģÅžnorodnych projektach inÅžynierii oprogramowania, ze szczegÃģlnym uwzględnieniem AI/ML. Moja nieustanna ciekawość rÃģwnieÅž skierowała mnie w stronę Natural Language Processing, dziedziny, ktÃģrą chcę dalej eksplorować.