API i SDKPrzewodnik
Klasyfikuj tysiące tekstów za pomocą AsyncOpenAI w Pythonie
Działający skrypt asynchroniczny: N żądań w locie, jedna etykieta JSON na tekst, ponowienia dla nieudanych elementów i suma tokenów z usage.

Na tej stronie
Aby sklasyfikować tysiące tekstów, użyj AsyncOpenAI, otocz każde wywołanie asyncio., aby tylko N żądań było w locie, poproś o etykietę JSON w jednej linii i ponownie uruchom tylko te elementy, które zawiodły. Poniższy skrypt robi dokładnie to względem https:/ i na końcu wypisuje sumę tokenów z pola usage.
31 października 2026 Andrej Karpathy (współzałożyciel OpenAI, były dyrektor ds. AI w Tesli) opisał HackerTube: zbudował go w około dwie godziny i użył Sonnet, aby zawęzić komentarze z Hacker News do około 40 tys. tych, które polecają film z YouTube. Jego wpis nie podaje promptu, rozmiaru partii ani współbieżności, więc to, co następuje, to standardowy wzorzec dla tego typu zadania, a nie jego przepis.
Skrypt#
Załóżmy, że masz 5000 zgłoszeń do wsparcia i chcesz każde oznaczyć jako bug, question lub praise. Oto całość. Zainstaluj SDK za pomocą pip install openai (Python 3.10+; python - pokazuje, co masz) i najpierw umieść klucz w zmiennej środowiskowej.
import asyncio
import json
import os
from openai import AsyncOpenAI
client = AsyncOpenAI(
base_url='https://api.cheaptokens.dev/v1',
api_key=os.environ['CHEAPTOKENS_API_KEY'],
max_retries=2,
timeout=60,
)
MODEL = 'deepseek-ai/DeepSeek-V4-Flash-0731'
LABELS = {'bug', 'question', 'praise'}
SYSTEM = (
'Classify the text as bug, question or praise. '
'Reply with JSON only: {"label": "<bug|question|praise>"}'
)
async def classify(text, sem, usage):
async with sem:
r = await client.chat.completions.create(
model=MODEL,
temperature=0,
max_tokens=100,
messages=[
{'role': 'system', 'content': SYSTEM},
{'role': 'user', 'content': text},
],
)
usage['in'] += r.usage.prompt_tokens
usage['out'] += r.usage.completion_tokens
raw = r.choices[0].message.content
label = json.loads(raw[raw.find('{'):raw.rfind('}') + 1])['label']
if label not in LABELS:
raise ValueError(f'unexpected label: {label!r}')
return label
async def run(texts, concurrency=5, rounds=3):
sem = asyncio.Semaphore(concurrency)
usage = {'in': 0, 'out': 0}
labels, pending = {}, list(range(len(texts)))
for _ in range(rounds):
if not pending:
break
results = await asyncio.gather(
*(classify(texts[i], sem, usage) for i in pending),
return_exceptions=True,
)
failed = []
for i, res in zip(pending, results):
if isinstance(res, Exception):
failed.append(i)
else:
labels[i] = res
pending = failed
return labels, pending, usage
texts = [
'The export button crashes the app on Android 15.',
'Does the Pro plan include API access?',
'Love the new dark theme, thank you!',
]
labels, failed, usage = asyncio.run(run(texts))
print(labels)
print('failed:', failed)
print('tokens in:', usage['in'], 'out:', usage['out'])Otrzymujesz z powrotem labels (indeks tekstu → etykieta), failed (indeksy, które nigdy nie wygenerowały poprawnej etykiety, pusta lista, gdy wszystko przeszło) oraz sumy tokenów. Identyfikator modelu to pełny deepseek-; identyfikatory dla innych modeli weź z dokumentacji CheapTokens, ponieważ krótkie nazwy różnią się między dostawcami.
Ułóż potok w kolejności, aby sprawdzić, czy masz w głowie właściwy kształt:
Ustaw w kolejności
Ułóż potok wsadowy w kolejności
Przeciągnij elementy lub użyj strzałek
- Zbuduj listę tekstów i ponumeruj je
- Utwórz semafor z limitem współbieżności
- Wyślij każdy tekst przez classify()
- Zbierz indeksy, które zgłosiły błąd
- Uruchom ponownie tylko te indeksy
- Wypisz sumę tokenów z usage
Dlaczego semafor, a nie 5000 żądań naraz#
asyncio.gather chętnie uruchamia wszystko, co mu dasz. Przekaż mu tysiące korutyn bez limitu, a wszystkie zaczną się naraz — wybuch żądań, który jest szybkim sposobem na błędy 429. Semafor to bramka: concurrency=5 oznacza, że w dowolnym momencie co najwyżej pięć wywołań jest w async with sem, a reszta czeka na swoją kolej. Python nadal tworzy wszystkie zadania, ale tylko pięć rozmawia z API.

Jaka liczba powinna być w concurrency? Zacznij nisko, jak w powyższej piątce, i zwiększaj, dopóki błędy nie znikną. Traktuj to jako ustawienie dostrajane dla każdego zadania, a nie stałą do skopiowania.
Ponowienia: dwie warstwy, dwa zadania#
Pierwsza warstwa jest wbudowana w SDK. Według README openai-python (sprawdzone 1 listopada 2026) błędy połączenia, 408, 409, 429 i odpowiedzi 5xx są domyślnie ponawiane dwukrotnie z krótkim wykładniczym backoffem. max_retries=2 w skrypcie tylko to uwidacznia; ustaw wyżej przez client. lub na 0, aby wyłączyć. Domyślny limit czasu to 10 minut, a przekroczenie limitu zgłasza APITimeoutError, dlatego skrypt ustawia timeout=60 dla krótkich etykiet. Szczegóły są w OpenAI Python SDK timeout and retries, a idea backoffu w 429 Too Many Requests from an AI API.
Druga warstwa należy do Ciebie i obejmuje to, czego SDK nie widzi: wywołanie się powiodło, ale model odpowiedział prozą bez żadnego JSON-a (I can't classify this) lub wymyślił etykietę o nazwie complaint. Tekst wokół {...} nie ma znaczenia, ponieważ kod wycina JSON z odpowiedzi. Wtedy json.loads lub sprawdzenie LABELS zgłasza wyjątek, gather(. zachowuje błąd jako wartość zamiast zabijać partię, a element trafia do następnej rundy. Po rounds próbach to, co pozostaje w failed, jest do ręcznego sprawdzenia. Jedna zła odpowiedź na 5000 kosztuje jedno dodatkowe żądanie, a nie całe zadanie.
Skrypt prosi o JSON w prompcie i waliduje go w kodzie. Jeśli chcesz mocniej wymusić format za pomocą response_format, zobacz Get valid JSON from an LLM API.
Ile kosztuje zadanie#
Tokeny to jedyny licznik, więc skrypt sumuje usage i koszt wynika z tego. Weź typowy przypadek: krótkie teksty, krótkie etykiety. Załóż 10 000 tekstów po około 300 tokenów wejściowych każdy (tekst plus instrukcja) i około 20 tokenów wyjściowych na etykietę JSON:
10 000 × 300 = 3 000 000 tokenów wejściowych; 10 000 × 20 = 200 000 tokenów wyjściowych.
W DeepSeek-V4-Flash to zadanie kosztuje $0.065, w GLM-5.3-Flash $0.068, w MiniMax-M2.7 $0.18. Tabela pokazuje to samo zadanie dla każdego modelu, obok OpenRouter; przy sumowaniu wejścia i wyjścia wychodzi taniej niż OpenRouter łącznie.
| Model | Wejście $/1M | Wyjście $/1M | Za żądanie | 10 000 żądań | OpenRouter, 10 000 żądań |
|---|---|---|---|---|---|
| GLM-5.3-Flash | $0.02 | $0.04 | $0.0000068 | $0.068 | $0.117 |
| DeepSeek-V4-Flash | $0.015 | $0.10 | $0.0000065 | $0.065 | $0.238 |
| MiniMax-M2.7 | $0.05 | $0.15 | $0.000018 | $0.18 | $2.16 |
300 tokenów wejściowych i 20 tokenów wyjściowych na żądanie. Ceny za 1 mln tokenów są aktualne i zmieniają się wraz z naszymi taryfami; kolumna OpenRouter pokazuje ceny referencyjne dla tego samego obciążenia.
Przesuń suwaki w kalkulatorze na własne liczby. Pola in i out dotyczą jednego żądania, a runs to liczba wysyłanych żądań.
Kalkulator kosztów
Klasyfikacja 10 000 krótkich tekstów
Tokeny wejściowe na żądanie: 300 · Tokeny wyjściowe na żądanie: 20 · Żądania dziennie: 10 000
| Model | Dziennie | Miesięcznie |
|---|---|---|
| GLM-5.3-Flash$0.02$0.04 | $0.068 | $2.04 |
| DeepSeek-V4-Flash$0.015$0.10 | $0.065 | $1.95 |
| MiniMax-M2.7$0.05$0.15 | $0.18 | $5.40 |
Strona wejściowa dominuje: tekst wchodzi, jedno słowo wychodzi. Dlatego model z tanim wejściem i oknem kontekstu 380K, jak DeepSeek-V4-Flash z jedną ceną o każdej porze, nadaje się do masowego filtrowania; dla krótkich etykiet GLM-5.3-Flash i MiniMax-M2.7 są alternatywami dla tego samego skryptu, zmieniasz tylko MODEL. Nie wiesz, jak rośnie taki rachunek? What an AI API bills you for ma obraz.
Przed dużym uruchomieniem#
Najpierw uruchom 20 tekstów i sprawdź failed oraz sumy tokenów. Jeśli 20 tekstów zużywa około 6000 tokenów wejściowych, 5000 zużyje około 1 500 000, i możesz pomnożyć przed zobowiązaniem.
Lista kontrolna
Zanim wyślesz cały zbiór danych
Jeśli nie działa#
KeyError: 'CHEAPTOKENS_: zmienna nie jest ustawiona w tym terminalu. Wyeksportuj ją i uruchom Pythona ponownie.API_ KEY' 401lub404: zły klucz, bazowy URL lub identyfikator modelu. API 401 or 404 error omawia każdy z nich.json.na wielu elementach: odpowiedź nie maJSONDecodeError {...}, często ponieważmax_tokens=100ją uciął. Wypiszr.;choices[0]. finish_ reason lengthoznacza, że limit został osiągnięty, więc zwiększmax_tokens.- Wiele
429i wolne uruchomienie: zmniejszconcurrency.

Ogólne szczegóły modelu i punktu końcowego znajdują się w dokumentacji CheapTokens i na stronie cen.



