Engenharia de contexto começa com uma realização operacional: o modelo NÃO recebe uma 'conversa'; ele recebe uma sequência fixa de tokens — a janela de contexto. O system prompt, o histórico, o resultado de uma busca RAG, o turn atual e até as saídas anteriores, tudo é serializado nessa mesma sequência. Quem decide a ordem, a estrutura e o que entra é você.
💡 Janela de contexto: a unidade fundamental de engenharia
Pense no modelo como uma função pura: recebe uma string (a janela serializada) e devolve uma string (a saída). Tudo o que muda no comportamento mora nessa string de entrada — system prompt, exemplos, contexto recuperado, ordem. Não há 'memória' fora dela.
- •Você decide o que entra e em que ordem.
- •Cada token de entrada custa, mesmo se o modelo 'ignora' por atenção baixa.
- •Mudanças no início da janela invalidam cache; mudanças no fim, não.
📊 Liu et al. (2023) — Lost in the Middle (ArXiv:2307.03172)
- 30% — queda média de acurácia quando info crítica está no meio.
- U-shape — curva replicada em modelos de janela 4k até 32k+.
- Mitigação validada: reranking + recuperar menos resolvem na prática.
🎯 Heurística rápida
Antes de aumentar a janela, pergunte: o modelo precisa de TODO esse contexto, ou só de 3-5 trechos relevantes? Reranking + top-5 quase sempre bate 'jogar 50 docs'.
🪟 A janela é o ambiente físico do modelo
Tokens, não conversas
A janela é uma string serializada que vive na memória do servidor do provedor durante a chamada. Quando você faz client.messages.create(...), o SDK monta essa string em um formato específico do modelo (ChatML, Anthropic XML, etc.) e envia. O modelo processa tudo de uma vez no prefill, depois gera tokens um a um. Essa visão muda decisões: ordenar para maximizar cache, recuperar menos para reduzir input, ou comprimir histórico antigo deixam de ser 'truques' e passam a ser engenharia.
<system>
Você é um assistente conciso. Use formato JSON.
</system>
<few_shot>
<example><in>...</in><out>...</out></example>
</few_shot>
<context>
<doc id=42>...trecho recuperado por RAG...</doc>
</context>
<user>Qual a janela do modelo?</user>
📑 Resumo navegável
🎯 Atenção causal e KV cache
O mecanismo que pesa tokens
Atenção causal significa que cada token, ao ser processado, só pode 'olhar' para tokens anteriores — não para o futuro. Isso permite paralelização do prefill (todos os tokens de input são processados juntos) e geração autoregressiva no decode. O KV cache guarda os estados key e value de cada camada para cada token já processado; se o prefixo não muda entre chamadas, o provedor reutiliza esses estados. Daí o ganho de prompt caching: ~10% do preço para tokens cacheados.
o que invalida o KV cache
- ▸Mudar 1 caractere no system prompt → invalida TUDO daquele ponto em diante.
- ▸Reordenar few-shots → invalida do primeiro deslocamento em diante.
- ▸Adicionar uma mensagem do usuário no fim → NÃO invalida cache (apenas adiciona).
- ▸Trocar o modelo (mesmo da mesma família) → invalida tudo.
📑 Resumo navegável
📍 Posição: rotary embeddings e o efeito recência
Por que tokens recentes têm vantagem
Posição não é só 'tokens 1, 2, 3...'. Modelos modernos usam RoPE (Rotary Position Embedding, Su et al. 2021), que codifica posição via rotação de pares de dimensões em cada cabeça de atenção. Resultado prático: o modelo dá peso estrutural diferente para tokens em posições diferentes — o início (prefix bias) e o fim (recency bias) recebem mais 'sinal' que o meio. Esse é o mecanismo concreto por trás do 'lost in the middle'.
| Região | Posição | Peso relativo de atenção | Comportamento |
|---|---|---|---|
| Início | 0–1k | alto (~1.0) | prefix bias — fixa contexto |
| Meio inferior | 1k–4k | moderado (~0.7) | atenção decai gradualmente |
| Meio (zona crítica) | 4k–7k | baixo (~0.5) | lost in the middle |
| Meio superior | 7k–9k | moderado (~0.7) | começa a recuperar |
| Fim | 9k–10k | alto (~1.0) | recency bias — atenção máxima |
📑 Resumo navegável
📜 Lost in the middle (Liu et al. 2023)
A curva U de atenção
Liu et al. (2023) construíram experimento controlado: pegaram um documento com a resposta correta e o inseriram em posições variáveis dentro de janelas de 10-32k tokens preenchidas com documentos distratores. Mediram a acurácia do modelo em encontrar a resposta. O resultado é uma curva em U consistente — acurácia ~75% quando a info está no topo, cai para ~50% no meio, sobe de novo para ~70% no fim. O efeito persiste em GPT-3.5, GPT-4, Claude 2, Llama-2. Não é bug de modelo único; é arquitetural.
mitigações validadas (com evidência)
- ▸Reranking: top-50 retrieval → cross-encoder rerank → top-5 (Anthropic 2024 reporta -49% failure rate).
- ▸Recuperar menos: 5 docs bem rankeados > 50 docs concatenados.
- ▸Ancoragem: repetir a pergunta antes E depois do bloco de contexto.
- ▸Auditoria de eval: ' needle in haystack' test antes de aceitar a janela atual.
📑 Resumo navegável
🧭 Ordem das seções: estável → variável → instrução
O padrão que casa atenção e cache
A ordem certa não é estética — é alinhada a três forças: prompt cache (estável fica no início), atenção (instrução fica no fim), e ancoragem (pergunta repetida antes/depois do contexto recuperado). O padrão mais robusto é: system → few-shot → âncora-pergunta → contexto recuperado → âncora-pergunta → user-turn. Isso maximiza cache hit rate (~70-95% típico em chats), reduz lost-in-middle e dá ao modelo um sinal claro de 'a pergunta de fato é esta'.
msgs = [
Message(role=SYSTEM, content=system_prompt), # estável (cacheável)
Message(role=USER, content=few_shot_canonico), # estável (cacheável)
Message(role=USER, content=(
f'Pergunta: {pergunta}\n\n' # âncora 1
f'<contexto>\n{contexto_recuperado}\n</contexto>\n\n'
f'Pergunta (repete): {pergunta}' # âncora 2
)),
]
📑 Resumo navegável
📏 Janela nominal vs. efetiva
200k de marketing ≠ 200k de qualidade
Janela nominal é o que o marketing fala (200k tokens, 1M tokens). Janela efetiva é onde a qualidade não degrada. O benchmark RULER (Hsieh et al. 2024) mede isso: muitos modelos com 128k nominal degradam significativamente acima de 32-64k em tarefas que exigem múltipla recuperação. Antes de assumir que cabe E funciona, faça um needle-in-a-haystack próprio com seus dados.
| Modelo | Nominal | Efetiva (RULER) | Gap |
|---|---|---|---|
| GPT-4 Turbo | 128k | ~64k | 50% |
| Claude 3 Opus | 200k | ~128k | 64% |
| Llama-3-8B-Instruct | 8k (nativo) | ~4k | 50% |
| Qwen2.5-7B-Instruct | 32k | ~16k | 50% |
📑 Resumo navegável
📑 Resumo navegável dos tópicos
1 🪟 A janela é o ambiente físico do modelo — Tokens, não conversas
2 🎯 Atenção causal e KV cache — O mecanismo que pesa tokens
3 📍 Posição: rotary embeddings e o efeito recência — Por que tokens recentes têm vantagem
4 📜 Lost in the middle (Liu et al. 2023) — A curva U de atenção
5 🧭 Ordem das seções: estável → variável → instrução — O padrão que casa atenção e cache
6 📏 Janela nominal vs. efetiva — 200k de marketing ≠ 200k de qualidade
✓ O que FAZER
- ✓Manter system prompt e few-shot estáveis no início (cacheáveis)
- ✓Rerankear documentos antes de incluir no contexto
- ✓Repetir a pergunta antes E depois do contexto (ancoragem)
- ✓Medir janela efetiva no harness (eval)
✗ O que NÃO fazer
- ✗Mexer no system prompt a cada turn (invalida cache)
- ✗Concatenar 50 docs sem ordem e esperar que o modelo encontre
- ✗Colocar a pergunta só no início, longe do fim atendido
- ✗Confiar que '200k = 200k' funciona igual
🚫 Quando NÃO usar
- •Tarefas pontuais e estruturadas (classificação binária, extração de campo único): system prompt curto + user turn é suficiente.
- •Quando RAG resolve: 3-5 documentos relevantes batem 50 documentos despejados.
- •Quando latência importa: 100k tokens adicionam centenas de ms de prefill mesmo com cache.
- •Modelos OSS pequenos: janela efetiva raramente passa 8-16k mesmo se anunciada como 32k+.
💻 Exemplo de código
from fec_sdk import Message, MessageRole, check_compat
from fec_sdk.adapters import get_adapter
check_compat("modulo-1-1", expected_sdk_version=">=1.0,<2.0")
client = get_adapter("mock", scripted=[("Liu", "Posição 12 (meio)")])
doc_relevante = "FATO: a janela do modelo X é 200000 tokens."
ruido = "\n".join(f"Doc {i}: lorem ipsum..." for i in range(20))
# Doc relevante NO MEIO — prejudicial
contexto = ruido + "\n" + doc_relevante + "\n" + ruido
resp = client.chat([
Message(role=MessageRole.SYSTEM, content="Responda apenas com fatos do contexto."),
Message(role=MessageRole.USER, content=f"{contexto}\n\nQual a janela do modelo X?"),
])
print(resp.content)
🏋️ Exercício hands-on
Em exercicios/modulo-1-1/ você recebe 5 cenários onde o doc relevante está no meio. Sua função reordenar(documentos) deve aplicar uma das mitigações ensinadas e fazer ≥4/5 casos passarem. Rode pytest exercicios/modulo-1-1/test.py.
📚 Bibliografia
- Liu et al. (2023) — Lost in the Middle: How Language Models Use Long Contexts
- Su et al. (2021) — RoFormer: Enhanced Transformer with Rotary Position Embedding
- Hsieh et al. (2024) — RULER: What's the Real Context Size of your Long-Context LMs?
- Anthropic (2024) — Prompt caching with Claude
🎯 Resumo do Módulo
- ✓Janela de contexto é a unidade operacional — sequência fixa de tokens.
- ✓Atenção é não-uniforme — início e fim atendem mais que o meio.
- ✓Lost in the middle documentado: até 30% de queda quando info está no meio.
- ✓Ordem importa: estável→variável→instrução maximiza cache e atenção.
- ✓Janela nominal ≠ efetiva — meça no harness antes de assumir.
Próximo Módulo:
Tokens, custo e limites práticos