Entendendo o streaming de tokens
Entenda como a API de streaming envia conclusões parciais à medida que são geradas, como funciona o parâmetro stream=True da OpenAI e quando o streaming melhora a experiência do usuário.
Entendendo o streaming de tokens é uma aula grátis de AI Engineering Academy no CoddyKit. Esta é a aula 1 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de AI Engineering Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de AI Engineering Academy inclui 4 aulas no total.
Por que a transmissão é importante para a experiência do usuário
Sem transmissão, sua aplicação precisa esperar que o LLM gere a resposta completa antes de exibir qualquer coisa — geralmente de 5 a 30 segundos para respostas longas. Com a transmissão, o primeiro token aparece entre 200 e 500 ms após o envio da solicitação, e os tokens seguintes são transmitidos à medida que são gerados. Isso transforma a experiência percebida pelo usuário: em vez de esperar, ele acompanha uma geração ao vivo envolvente, melhorando significativamente a sensação de responsividade, embora o tempo total de geração seja idêntico.
Como os LLMs geram tokens
Os LLMs são autoregressivos: geram o texto um token por vez, e cada novo token é condicionado por todos os tokens anteriores. Quando a API recebe uma solicitação, a GPU começa a amostrar o primeiro token assim que o processamento do prompt termina. Cada token seguinte leva aproximadamente o mesmo tempo. A transmissão envia cada token ao cliente assim que ele é amostrado, em vez de armazenar todos os tokens e enviar a sequência completa ao final.
# 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 e TPOT: duas métricas de latência
A transmissão introduz dois conceitos distintos de latência. TTFT (tempo até o primeiro token) é o intervalo entre o envio da solicitação e o recebimento do primeiro token — dominado pelo tempo de processamento do prompt. TPOT (tempo por token de saída) é o intervalo entre tokens consecutivos — determinado pelo tamanho do modelo e pelo hardware. O TTFT afeta a rapidez com que a interface responde; o TPOT afeta a fluidez da transmissão do texto. Ambos devem ser acompanhados separadamente na sua camada de observabilidade.
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')O parâmetro stream=True
Ativar a transmissão no SDK da OpenAI exige definir stream=True na chamada chat.completions.create. O tipo da resposta muda de um objeto ChatCompletion para um iterador Stream[ChatCompletionChunk]. Cada fragmento contém um delta com um fragmento de texto content ou None quando o token é uma chamada de ferramenta ou quando a transmissão está terminando.
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 endAcumulando a resposta completa
Em muitos fluxos de aplicação, você precisa tanto transmitir os tokens para a interface para obter responsividade quanto acumular o texto completo da resposta para processamento posterior, como logging, armazenamento em cache ou outras etapas do fluxo de processamento. O padrão é simples: percorra a transmissão, imprima ou forneça cada fragmento ao cliente e, simultaneamente, concatene o conteúdo em uma string completa.
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_textTransmissão com estatísticas de uso
Por padrão, a resposta transmitida não inclui estatísticas de uso de tokens (tokens do prompt e da conclusão). Para incluí-las, passe stream_options={'include_usage': True}. Os dados de uso chegam em um fragmento final depois que a transmissão do conteúdo termina. Isso é importante para acompanhar custos e monitorar limites de requisições em aplicações de produção.
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}')Quando não usar transmissão
A transmissão nem sempre é a escolha certa. Evite usá-la quando: (1) você precisa da resposta completa antes de fazer qualquer coisa com ela, como analisar JSON ou detectar chamadas de ferramentas; (2) a resposta é muito curta (menos de 30 tokens), de modo que a sobrecarga da transmissão adiciona mais atraso do que economiza; ou (3) você está processando muitas solicitações em lote, situação em que a vazão é mais importante do que a latência de cada resposta. Nesses casos, as chamadas padrão sem transmissão são mais simples e igualmente rápidas.
Transmissão com as APIs da Anthropic e do Gemini
A transmissão está disponível nas APIs de todos os principais provedores de LLM, não apenas na OpenAI. O padrão é semelhante, mas as interfaces dos SDKs diferem um pouco. O SDK da Anthropic para Python usa client.messages.stream() como um gerenciador de contexto, enquanto o Gemini usa generate_content(stream=True). Ao criar aplicações independentes de provedor, abstraia a interface de transmissão por trás de uma função geradora comum.
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}')Interface de transmissão baseada em gerador
Um padrão de arquitetura limpo encapsula a transmissão em uma função geradora do Python que fornece strings de tokens. Isso desacopla a lógica de transmissão da lógica de consumo — os chamadores podem percorrer o gerador, gravar em um arquivo, encaminhar para um WebSocket ou acumular em uma string sem que o código de transmissão saiba como sua saída será usada. Essa é a base da maioria das APIs de transmissão de produção.
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!'}]))Transmissão em aplicações de terminal e CLI
Em aplicações de terminal, a saída transmitida se parece exatamente com uma digitação — cada caractere aparece imediatamente assim que é gerado. O requisito principal é usar flush=True em cada chamada de print. Sem liberar o conteúdo, o Python armazena a saída em um buffer até encontrar uma nova linha, o que elimina o propósito da transmissão. Você também pode usar sys.stdout.write(token) seguido de sys.stdout.flush() para ter mais controle sobre a formatação da saída.
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)')Transmissão e recuperação de erros
A transmissão complica o tratamento de erros porque uma falha pode ocorrer no meio da transmissão, depois que você já enviou alguns tokens ao cliente. O padrão recomendado é envolver a iteração da transmissão em um bloco try/except e, em caso de erro, enviar um marcador de erro ao cliente ou fechar a transmissão corretamente. Sempre implemente um tempo limite para a transmissão inteira, a fim de lidar com situações em que o servidor começa a transmitir, mas para no meio da geração.
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 timeoutVerificação rápida
Teste sua compreensão sobre a transmissão de tokens de LLM nesta lição.
Recapitulação da lição
Nesta lição, você aprendeu: o streaming envia cada token gerado ao cliente assim que ele é amostrado, melhorando significativamente a responsividade percebida; TTFT e TPOT são as duas principais métricas de latência e devem ser acompanhadas separadamente; e stream=True transforma a resposta do SDK da OpenAI em um iterador de partes que você consome com um laço for. Envolva os fluxos em funções geradoras para obter uma interface limpa e reutilizável. A seguir, implementaremos o streaming assíncrono com o SDK do Python.
Perguntas Frequentes
A aula “Entendendo o streaming de tokens” é grátis?
Sim — o texto completo de “Entendendo o streaming de tokens” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de AI Engineering Academy, atualize para CoddyKit PRO. O curso de AI Engineering Academy inclui 4 aulas no total.
O que vou aprender em “Entendendo o streaming de tokens”?
Entenda como a API de streaming envia conclusões parciais à medida que são geradas, como funciona o parâmetro stream=True da OpenAI e quando o streaming melhora a experiência do usuário. Você pratica AI Engineering Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar AI Engineering Academy?
Nenhuma experiência prévia é necessária. AI Engineering Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 1 de 4.
Quanto tempo leva a aula “Entendendo o streaming de tokens”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de AI Engineering Academy?
Sim. Cada aula de AI Engineering Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Entendendo o streaming de tokens
- Consumindo streams com o SDK para Python
- Streaming no FastAPI com eventos enviados pelo servidor
- Lidando com chamadas de ferramentas em respostas em streaming