03-01-02 — Context window management
TL;DR
O context window é a memória de trabalho do LLM — tudo que cabe nele existe, o resto não existe. Quando você excede esse limite, o modelo trunca silenciosamente ou retorna erro. Gerenciar tokens ativamente é a diferença entre uma aplicação LLM robusta e uma que falha de forma imprevisível em produção.
O que é um token (e por que você deveria se importar)
Tokens não são palavras. São chunks de texto que o modelo processa internamente. Em inglês, 1 token ≈ 4 caracteres. Em português, a proporção é pior — palavras mais longas e acentuação fazem 1 token ≈ 3 caracteres. Na prática:
- 1.000 palavras em inglês ≈ 1.300 tokens
- 1.000 palavras em português ≈ 1.500–1.700 tokens
- 1 página A4 de texto ≈ 500–700 tokens
- Um contrato de 10 páginas ≈ 5.000–7.000 tokens
Limites dos modelos principais
| Modelo | Context window | Custo input (por 1M tokens) |
|---|---|---|
| GPT-4o | 128K tokens | $2,50 |
| GPT-4o mini | 128K tokens | $0,15 |
| Claude Opus 4 | 200K tokens | $15,00 |
| Claude Sonnet 4 | 200K tokens | $3,00 |
| Gemini 1.5 Pro | 1M tokens | $1,25 |
O que acontece quando você excede o limite
Depende do provider e da configuração:
- Erro 400: a API recusa a chamada (comportamento mais seguro)
- Truncagem silenciosa: o início da conversa é descartado — o modelo perde contexto sem avisar você
- Degradação de qualidade: modelos performam pior quando a janela está quase cheia ("lost in the middle" effect)
Contando tokens antes de enviar: tiktoken
tiktoken é a biblioteca da OpenAI para tokenização. Use-a para saber exatamente quantos tokens sua mensagem vai consumir:
import tiktoken
def count_tokens(messages: list[dict], model: str = "gpt-4o") -> int:
"""Conta tokens para um array de mensagens (formato OpenAI)."""
encoding = tiktoken.encoding_for_model(model)
tokens_per_message = 3 # overhead por mensagem
tokens_per_name = 1 # overhead se houver campo 'name'
total = 0
for message in messages:
total += tokens_per_message
for key, value in message.items():
total += len(encoding.encode(value))
if key == "name":
total += tokens_per_name
total += 3 # overhead do array
return total
# Exemplo de uso
messages = [
{"role": "system", "content": "Você é um assistente de suporte técnico."},
{"role": "user", "content": "Como faço para configurar um worker service no .NET 8?"},
]
token_count = count_tokens(messages)
print(f"Tokens: {token_count}") # ~35 tokens
print(f"Custo estimado (GPT-4o): ${token_count / 1_000_000 * 2.50:.6f}")
Estratégias de gerenciamento
1. Truncagem por janela deslizante (sliding window)
Mantém as N mensagens mais recentes. Simples, mas perde contexto antigo:
def sliding_window(messages: list[dict], max_tokens: int = 4000, model: str = "gpt-4o") -> list[dict]:
"""Mantém o system prompt + mensagens recentes dentro do limite."""
system_messages = [m for m in messages if m["role"] == "system"]
conversation = [m for m in messages if m["role"] != "system"]
# Começa do final e vai adicionando até o limite
kept = []
running_tokens = count_tokens(system_messages)
for message in reversed(conversation):
msg_tokens = count_tokens([message])
if running_tokens + msg_tokens > max_tokens:
break
kept.insert(0, message)
running_tokens += msg_tokens
return system_messages + kept
2. Sumarização automática
Quando o histórico fica longo, chama o próprio LLM para criar um resumo e substitui as mensagens antigas pelo resumo:
def summarize_history(messages: list[dict], client, model: str = "gpt-4o-mini") -> list[dict]:
"""Substitui histórico longo por um resumo conciso."""
system = [m for m in messages if m["role"] == "system"]
history = [m for m in messages if m["role"] != "system"]
if count_tokens(messages) < 3000:
return messages # não precisa sumarizar ainda
# Pede ao modelo para resumir a conversa
summary_response = client.chat.completions.create(
model=model, # usa modelo mais barato para sumarização
messages=[
{"role": "system", "content": "Resuma a conversa abaixo em bullet points concisos. Preserve fatos importantes e decisões tomadas."},
{"role": "user", "content": str(history)}
]
)
summary = summary_response.choices[0].message.content
# Retorna: system + mensagem de resumo + últimas 2 mensagens reais
return system + [
{"role": "assistant", "content": f"[Resumo da conversa anterior]: {summary}"}
] + history[-2:]
3. Retrieval-Augmented Generation (prévia do Módulo 04)
Em vez de injetar toda a documentação no contexto, você recupera apenas os trechos relevantes via busca semântica. É a estratégia mais escalável para bases de conhecimento grandes — detalhada no Módulo 04 (RAG).
Prompt tokens vs completion tokens
A cobrança é diferente para entrada e saída. Você controla o tamanho do prompt; você estima o completion via max_tokens:
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
max_tokens=500 # limita o tamanho da resposta — controla custo e latência
)
# Inspeciona o uso real
usage = response.usage
print(f"Prompt tokens: {usage.prompt_tokens}")
print(f"Completion tokens: {usage.completion_tokens}")
print(f"Total: {usage.total_tokens}")
# Custo real (GPT-4o pricing)
cost = (usage.prompt_tokens / 1_000_000 * 2.50) + (usage.completion_tokens / 1_000_000 * 10.00)
print(f"Custo: ${cost:.6f}")
Implementação em C# (.NET)
using Azure.AI.OpenAI;
using System.ClientModel;
var client = new AzureOpenAIClient(
new Uri(Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")),
new ApiKeyCredential(Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"))
);
var chatClient = client.GetChatClient("gpt-4o");
var messages = new List<ChatMessage>
{
new SystemChatMessage("Você é um assistente técnico especializado em .NET."),
new UserChatMessage("Como configurar health checks no ASP.NET Core?")
};
var response = await chatClient.CompleteChatAsync(messages, new ChatCompletionOptions
{
MaxOutputTokenCount = 500 // controla custo
});
Console.WriteLine($"Prompt tokens: {response.Value.Usage.InputTokenCount}");
Console.WriteLine($"Completion tokens: {response.Value.Usage.OutputTokenCount}");
Console.WriteLine(response.Value.Content[0].Text);
Como isso se conecta
- → 03-01-01 Roles: o histórico multi-turn que cresce a cada turno é o principal consumidor de tokens
- → Módulo 04 RAG: RAG existe em grande parte para resolver o problema de context window — injetar só o que é relevante
- → 03-03-02 Avaliação de prompts: custo por token é uma das métricas que você deve monitorar continuamente