0Pricing
AI Engineering Academy · Lekcja

Zrozumienie strumieniowania tokenów

Poznaj sposób, w jaki streaming API wysyła częściowe uzupełnienia w miarę ich generowania, działanie parametru OpenAI stream=True oraz sytuacje, w których streaming poprawia doświadczenie użytkownika.

Zrozumienie strumieniowania tokenów to bezpłatna lekcja AI Engineering Academy na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Engineering Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Dlaczego strumieniowanie ma znaczenie dla wygody użytkownika

Bez strumieniowania aplikacja musi czekać na wygenerowanie całej odpowiedzi przez LLM, zanim cokolwiek wyświetli — w przypadku długich odpowiedzi często trwa to 5–30 sekund. Dzięki strumieniowaniu pierwszy token pojawia się w ciągu 200–500 ms od wysłania żądania, a kolejne tokeny są przesyłane w miarę ich generowania. Zmienia to postrzeganie działania aplikacji z oczekiwania na angażujący efekt generowania na żywo, znacznie poprawiając odczuwaną responsywność, nawet jeśli całkowity czas generowania pozostaje taki sam.

Jak LLM generują tokeny

LLM są autoregresyjne: generują tekst po jednym tokenie, przy czym każdy nowy token zależy od wszystkich poprzednich tokenów. Gdy API otrzymuje żądanie, GPU rozpoczyna próbkowanie pierwszego tokena natychmiast po przetworzeniu promptu. Generowanie każdego kolejnego tokena zajmuje mniej więcej tyle samo czasu. Strumieniowanie wysyła każdy token do klienta od razu po jego wylosowaniu, zamiast buforować wszystkie tokeny i wysyłać kompletny ciąg na końcu.

# Conceptual model of autoregressive generation
prompt = 'The capital of France is'

# Step 1: process full prompt, predict next token
# token_1 = sample(logits) → ' Paris'

# Step 2: append token_1 to context, predict next
# token_2 = sample(logits) → '.'

# Step 3: append token_2 to context, predict next
# token_3 = sample(logits) → '<|end|>'

# Total time: time_to_process_prompt + n_tokens * time_per_token
# With streaming: first token arrives after time_to_process_prompt (TTFT)
# Without streaming: everything arrives after TTFT + n_tokens * time_per_token

TTFT i TPOT: dwie metryki opóźnienia

Strumieniowanie wprowadza dwa odrębne pojęcia związane z opóźnieniem. TTFT (Time to First Token) to czas od wysłania żądania do otrzymania pierwszego tokena — zdominowany przez czas przetwarzania promptu. TPOT (Time Per Output Token) to czas między kolejnymi tokenami — zależny od rozmiaru modelu i sprzętu. TTFT wpływa na szybkość reakcji interfejsu, a TPOT na płynność strumieniowania tekstu. Obie wartości należy śledzić osobno w systemie obserwowalności.

import time
from openai import OpenAI

client = OpenAI()

def measure_streaming_latency(prompt: str):
    t_start = time.perf_counter()
    t_first_token = None
    token_times = []

    stream = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[{'role': 'user', 'content': prompt}],
        stream=True,
    )
    for chunk in stream:
        if chunk.choices[0].delta.content:
            t_now = time.perf_counter()
            if t_first_token is None:
                t_first_token = t_now
                print(f'TTFT: {(t_first_token - t_start) * 1000:.0f}ms')
            else:
                token_times.append(t_now - token_times[-1] if token_times else t_now - t_first_token)
            token_times.append(t_now)
    print(f'TPOT avg: {1000 * (token_times[-1] - t_first_token) / max(len(token_times)-1, 1):.1f}ms')

Parametr stream=True

Włączenie strumieniowania w OpenAI SDK wymaga ustawienia stream=True w wywołaniu chat.completions.create. Typ odpowiedzi zmienia się z obiektu ChatCompletion na iterator Stream[ChatCompletionChunk]. Każdy fragment zawiera element delta z fragmentem tekstu w postaci ciągu content albo wartością None, gdy token jest wywołaniem narzędzia lub strumień się kończy.

from openai import OpenAI

client = OpenAI()

# Non-streaming: wait for complete response
response = client.chat.completions.create(
    model='gpt-4o-mini',
    messages=[{'role': 'user', 'content': 'Explain RAG in one paragraph.'}],
)
full_text = response.choices[0].message.content

# Streaming: receive tokens incrementally
stream = client.chat.completions.create(
    model='gpt-4o-mini',
    messages=[{'role': 'user', 'content': 'Explain RAG in one paragraph.'}],
    stream=True,
)
for chunk in stream:
    delta = chunk.choices[0].delta.content
    if delta:  # delta can be None for non-content chunks
        print(delta, end='', flush=True)
print()  # newline at end

Gromadzenie pełnej odpowiedzi

W wielu przepływach aplikacji potrzebują Państwo zarówno przesyłać tokeny strumieniowo do interfejsu, aby zapewnić jego responsywność, jak i gromadzić pełny tekst odpowiedzi na potrzeby dalszego przetwarzania, takiego jak rejestrowanie, buforowanie lub kolejne kroki potoku. Schemat jest prosty: należy iterować po strumieniu, wyświetlać lub przekazywać każdy fragment klientowi, a jednocześnie łączyć treść w jeden pełny ciąg.

def stream_and_accumulate(prompt: str) -> str:
    stream = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[{'role': 'user', 'content': prompt}],
        stream=True,
    )

    full_text = ''
    finish_reason = None

    for chunk in stream:
        choice = chunk.choices[0]
        delta = choice.delta.content
        if delta:
            print(delta, end='', flush=True)  # real-time display
            full_text += delta               # accumulate
        if choice.finish_reason:
            finish_reason = choice.finish_reason

    print()  # newline
    print(f'Finished: {finish_reason}, total chars: {len(full_text)}')
    return full_text

Strumieniowanie ze statystykami użycia

Domyślnie odpowiedź strumieniowana nie zawiera statystyk użycia tokenów (tokenów promptu i ukończenia). Aby je uwzględnić, należy przekazać stream_options={'include_usage': True}. Dane dotyczące użycia docierają w ostatnim fragmencie po zakończeniu strumienia treści. Jest to ważne dla śledzenia kosztów i monitorowania limitów zapytań w aplikacjach produkcyjnych.

stream = client.chat.completions.create(
    model='gpt-4o-mini',
    messages=[{'role': 'user', 'content': 'What is a vector database?'}],
    stream=True,
    stream_options={'include_usage': True},  # include token counts
)

full_text = ''
usage = None

for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        full_text += chunk.choices[0].delta.content
    if chunk.usage:  # arrives in the final chunk
        usage = chunk.usage

if usage:
    print(f'Prompt tokens: {usage.prompt_tokens}')
    print(f'Completion tokens: {usage.completion_tokens}')
    print(f'Total tokens: {usage.total_tokens}')

Kiedy nie używać strumieniowania

Strumieniowanie nie zawsze jest właściwym wyborem. Należy go unikać, gdy: (1) potrzebują Państwo pełnej odpowiedzi przed podjęciem jakichkolwiek działań, na przykład w celu analizy JSON lub wykrycia wywołania narzędzia; (2) odpowiedź jest bardzo krótka (poniżej 30 tokenów), przez co narzut strumieniowania powoduje większe opóźnienie, niż wynosi uzyskana oszczędność; lub (3) wykonują Państwo przetwarzanie wsadowe wielu żądań, w którym przepustowość ma większe znaczenie niż opóźnienie pojedynczej odpowiedzi. W takich przypadkach standardowe wywołania bez strumieniowania są prostsze i równie szybkie.

Strumieniowanie z interfejsami API Anthropic i Gemini

Strumieniowanie jest dostępne we wszystkich głównych interfejsach API dostawców LLM, nie tylko w OpenAI. Schemat jest podobny, ale interfejsy SDK nieznacznie się różnią. Python SDK firmy Anthropic używa client.messages.stream() jako menedżera kontekstu, natomiast Gemini używa generate_content(stream=True). Podczas tworzenia aplikacji niezależnych od dostawcy należy ukryć interfejs strumieniowania za wspólną funkcją generatora.

import anthropic

ant_client = anthropic.Anthropic(api_key='YOUR_KEY')

# Anthropic streaming
with ant_client.messages.stream(
    model='claude-sonnet-4-5',
    max_tokens=1024,
    messages=[{'role': 'user', 'content': 'Explain hybrid search briefly.'}],
) as stream:
    for text in stream.text_stream:
        print(text, end='', flush=True)

# Final message with usage stats
final_msg = stream.get_final_message()
print(f'\nInput tokens: {final_msg.usage.input_tokens}')
print(f'Output tokens: {final_msg.usage.output_tokens}')

Interfejs strumieniowania oparty na generatorze

Przejrzysty wzorzec architektoniczny polega na opakowaniu strumieniowania w funkcję generatora Python, która zwraca ciągi tokenów. Oddziela to logikę strumieniowania od logiki wykorzystania danych — wywołujący może iterować po generatorze, zapisywać dane do pliku, przekazywać je przez WebSocket lub gromadzić w jednym ciągu, a kod strumieniowania nie musi wiedzieć, jak jego wynik zostanie wykorzystany. Stanowi to podstawę większości produkcyjnych interfejsów API obsługujących strumieniowanie.

from typing import Generator

def stream_completion(
    messages: list[dict],
    model: str = 'gpt-4o-mini',
    **kwargs,
) -> Generator[str, None, None]:
    stream = client.chat.completions.create(
        model=model,
        messages=messages,
        stream=True,
        **kwargs,
    )
    for chunk in stream:
        delta = chunk.choices[0].delta.content
        if delta:
            yield delta

# Usage: pipe to stdout
for token in stream_completion([{'role': 'user', 'content': 'Hello!'}]):
    print(token, end='', flush=True)

# Usage: accumulate
full = ''.join(stream_completion([{'role': 'user', 'content': 'Hello!'}]))

Strumieniowanie w aplikacjach terminalowych i CLI

W aplikacjach terminalowych strumieniowane dane wyjściowe wyglądają tak, jakby były wpisywane — każdy znak pojawia się natychmiast po wygenerowaniu. Kluczowym wymaganiem jest użycie flush=True w każdym wywołaniu print. Bez opróżniania bufora Python przechowuje dane wyjściowe do momentu napotkania znaku nowej linii, co niweczy cel strumieniowania. Aby uzyskać większą kontrolę nad formatowaniem danych wyjściowych, można także użyć sys.stdout.write(token), a następnie sys.stdout.flush().

import sys

def stream_to_terminal(messages: list[dict]):
    stream = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=messages,
        stream=True,
    )
    token_count = 0
    for chunk in stream:
        delta = chunk.choices[0].delta.content
        if delta:
            sys.stdout.write(delta)  # no newline added
            sys.stdout.flush()       # MUST flush or output buffers
            token_count += 1
    print()  # final newline
    print(f'({token_count} tokens generated)')

Strumieniowanie i odzyskiwanie po błędach

Strumieniowanie komplikuje obsługę błędów, ponieważ awaria może wystąpić w środku strumienia, po wysłaniu części tokenów do klienta. Zalecany schemat polega na opakowaniu iteracji po strumieniu w blok try/except, a w razie błędu — wysłaniu do klienta specjalnego znacznika błędu lub prawidłowym zamknięciu strumienia. Należy zawsze ustawić limit czasu dla całego strumienia, aby obsługiwać sytuacje, w których serwer rozpoczyna strumieniowanie, a następnie zatrzymuje się w trakcie generowania.

import signal

def stream_with_timeout(messages, timeout_seconds=30):
    def timeout_handler(signum, frame):
        raise TimeoutError('LLM stream timed out')

    signal.signal(signal.SIGALRM, timeout_handler)
    signal.alarm(timeout_seconds)

    try:
        stream = client.chat.completions.create(
            model='gpt-4o-mini',
            messages=messages,
            stream=True,
        )
        for chunk in stream:
            delta = chunk.choices[0].delta.content
            if delta:
                yield delta
    except TimeoutError:
        yield '\n[Response timed out]'
    except Exception as e:
        yield f'\n[Error: {str(e)}]'
    finally:
        signal.alarm(0)  # cancel timeout

Szybkie sprawdzenie

Sprawdź swoją wiedzę na temat strumieniowania tokenów LLM z tej lekcji.

Podsumowanie lekcji

W tej lekcji nauczyli się Państwo, że: streaming wysyła każdy wygenerowany token do klienta natychmiast po jego wylosowaniu, znacznie poprawiając postrzeganą responsywność; TTFT i TPOT to dwa kluczowe wskaźniki opóźnienia, które należy śledzić osobno; a stream=True zmienia odpowiedź OpenAI SDK w iterator fragmentów, który przetwarza się za pomocą pętli for. Strumienie warto opakowywać w funkcje generatora, aby uzyskać przejrzysty i wielokrotnego użytku interfejs. Następnie zaimplementujemy asynchroniczne strumieniowanie za pomocą Python SDK.

Często zadawane pytania

Czy lekcja „Zrozumienie strumieniowania tokenów” jest bezpłatna?

Tak — pełny tekst „Zrozumienie strumieniowania tokenów” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Engineering Academy, przejdź na CoddyKit PRO. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Zrozumienie strumieniowania tokenów”?

Poznaj sposób, w jaki streaming API wysyła częściowe uzupełnienia w miarę ich generowania, działanie parametru OpenAI stream=True oraz sytuacje, w których streaming poprawia doświadczenie użytkownika. Ćwiczysz AI Engineering Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć AI Engineering Academy?

Nie wymagamy żadnego doświadczenia. AI Engineering Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.

Ile czasu zajmuje lekcja „Zrozumienie strumieniowania tokenów”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji AI Engineering Academy?

Tak. Każda lekcja AI Engineering Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Zrozumienie strumieniowania tokenów
  2. Obsługa strumieni za pomocą Python SDK
  3. Streaming w FastAPI z Server-Sent Events
  4. Obsługa wywołań narzędzi w strumieniowanych odpowiedziach
← Powrót do AI Engineering Academy