Как работает потоковая передача токенов
Разберитесь, как потоковый API отправляет частично сгенерированные ответы по мере их создания, как работает параметр OpenAI stream=True и когда потоковая передача улучшает пользовательский опыт.
«Как работает потоковая передача токенов» — бесплатный урок AI Engineering Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Engineering Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Engineering Academy содержит 4 уроков всего.
Почему потоковая передача важна для удобства пользователей
Без потоковой передачи приложение должно дождаться, пока LLM полностью сгенерирует ответ, прежде чем что-либо отображать — для длинных ответов это часто занимает 5–30 секунд. При потоковой передаче первый токен появляется через 200–500 мс после отправки запроса, а последующие токены поступают по мере генерации. Это меняет воспринимаемый опыт: вместо ожидания пользователь видит увлекательный эффект генерации в реальном времени, что значительно повышает ощущение быстрого отклика, хотя общее время генерации остаётся тем же.
Как LLM генерируют токены
LLM работают авторегрессионно: они генерируют текст по одному токену, при этом каждый новый токен зависит от всех предыдущих. Когда API получает запрос, GPU начинает выбирать первый токен сразу после обработки запроса. Каждый следующий токен занимает примерно столько же времени. Потоковая передача отправляет каждый токен клиенту сразу после его выбора, вместо того чтобы буферизовать все токены и отправлять полную строку в конце.
# 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_tokenTTFT и TPOT: две метрики задержки
Потоковая передача вводит два отдельных понятия задержки. TTFT (время до первого токена) — это задержка от отправки запроса до получения первого токена; в основном она определяется временем обработки запроса. TPOT (время на один выходной токен) — это интервал между последовательными токенами, зависящий от размера модели и оборудования. TTFT влияет на скорость отклика интерфейса, а TPOT — на плавность передачи текста. Обе метрики следует отслеживать отдельно в системе наблюдаемости.
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')Параметр stream=True
Чтобы включить потоковую передачу в OpenAI SDK, нужно задать stream=True при вызове chat.completions.create. Тип ответа изменяется с объекта ChatCompletion на итератор Stream[ChatCompletionChunk]. Каждый фрагмент содержит delta с фрагментом строки content или значением None, если токен является вызовом инструмента или поток завершается.
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Накопление полного ответа
Во многих сценариях приложения нужно одновременно передавать токены в интерфейс для быстрого отклика и накапливать полный текст ответа для последующей обработки, например ведения журналов, кэширования или дальнейших этапов системы. Шаблон прост: перебирайте поток, выводите или передавайте каждый фрагмент клиенту и одновременно объединяйте содержимое в одну полную строку.
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Потоковая передача со статистикой использования
По умолчанию потоковый ответ не содержит статистику использования токенов (токены запроса и завершения). Чтобы включить её, передайте stream_options={'include_usage': True}. Данные об использовании поступают в последнем фрагменте после завершения потока содержимого. Это важно для отслеживания расходов и контроля ограничений частоты запросов в рабочих приложениях.
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}')Когда не следует использовать потоковую передачу
Потоковая передача подходит не всегда. Не используйте её, если: (1) Вам нужен полный ответ до выполнения каких-либо действий с ним, например для разбора JSON или обнаружения вызова инструмента; (2) ответ очень короткий (менее 30 токенов), и накладные расходы потоковой передачи создают большую задержку, чем удаётся сэкономить; или (3) Вы выполняете пакетную обработку множества запросов, где пропускная способность важнее задержки отдельного ответа. В таких случаях обычные вызовы без потоковой передачи проще и столь же быстры.
Потоковая передача через API Anthropic и Gemini
Потоковая передача доступна во всех основных API поставщиков LLM, а не только в OpenAI. Принцип похож, но интерфейсы SDK немного различаются. Python SDK Anthropic использует client.messages.stream() как менеджер контекста, а Gemini — generate_content(stream=True). При создании приложений, не зависящих от конкретного поставщика, скройте интерфейс потоковой передачи за общей функцией-генератором.
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}')Интерфейс потоковой передачи на основе генератора
Чистый архитектурный подход заключается в обёртывании потоковой передачи в функцию-генератор Python, возвращающую строки токенов. Это отделяет логику потоковой передачи от логики использования: вызывающий код может перебирать генератор, записывать данные в файл, передавать их через WebSocket или накапливать в строке, а код потоковой передачи не знает, как используется его вывод. Это основа большинства рабочих API с потоковой передачей.
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!'}]))Потоковая передача в терминальных приложениях и приложениях CLI
В терминальных приложениях потоковый вывод выглядит так же, как набор текста: каждый символ появляется сразу после генерации. Главное требование — использовать flush=True в каждом вызове print. Без принудительной очистки Python буферизует вывод до символа новой строки, что сводит смысл потоковой передачи на нет. Для большего контроля над форматированием вывода можно также использовать sys.stdout.write(token), а затем 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)')Потоковая передача и восстановление после ошибок
Потоковая передача усложняет обработку ошибок, поскольку сбой может произойти в середине потока после того, как клиенту уже были отправлены некоторые токены. Рекомендуемый подход — обернуть перебор потока в блок try/except, а при ошибке либо отправить клиенту специальный маркер ошибки, либо корректно закрыть поток. Всегда устанавливайте ограничение времени ожидания для всего потока, чтобы обрабатывать случаи, когда сервер начинает потоковую передачу, а затем останавливается в середине генерации.
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Быстрая проверка
Проверьте, насколько Вы поняли потоковую передачу токенов LLM из этого урока.
Итоги урока
В этом уроке Вы узнали: потоковая передача отправляет каждый сгенерированный токен клиенту сразу после его получения, значительно повышая воспринимаемую отзывчивость; TTFT и TPOT — две ключевые метрики задержки, которые следует отслеживать раздельно; а stream=True преобразует ответ OpenAI SDK в итератор фрагментов, который обрабатывается с помощью цикла for. Оборачивайте потоки в функции-генераторы, чтобы получить удобный повторно используемый интерфейс. Далее мы реализуем асинхронную потоковую передачу с помощью Python SDK.
Часто задаваемые вопросы
Урок «Как работает потоковая передача токенов» бесплатный?
Да — полный текст урока «Как работает потоковая передача токенов» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Engineering Academy, подпишись на CoddyKit PRO. Курс AI Engineering Academy содержит 4 уроков всего.
Чему я научусь в уроке «Как работает потоковая передача токенов»?
Разберитесь, как потоковый API отправляет частично сгенерированные ответы по мере их создания, как работает параметр OpenAI stream=True и когда потоковая передача улучшает пользовательский опыт. Ты практикуешь AI Engineering Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать AI Engineering Academy?
Предыдущий опыт не требуется. AI Engineering Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.
Сколько времени занимает урок «Как работает потоковая передача токенов»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке AI Engineering Academy?
Да. Каждый урок AI Engineering Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Как работает потоковая передача токенов
- Работа с потоками через Python SDK
- Потоковая передача в FastAPI с событиями, отправляемыми сервером
- Обработка вызовов инструментов в потоковых ответах