Elton José logo
Elton José
Compressão de Contexto

Compressão de Contexto: Cortando a Conta de Tokens Sem Perder a Resposta

Compressão de Contexto: Cortando a Conta de Tokens Sem Perder a Resposta
0 visualizações
19 minutos de leitura
#Compressão de Contexto

Compressão de Contexto: Cortando a Conta de Tokens Sem Perder a Resposta

Abri a fatura da API na segunda de manhã esperando encontrar o suspeito de sempre: alguém tinha deixado um job rodando no fim de semana, ou o fan-out de subagentes tinha subido sem ninguém notar. Não era nenhum dos dois. O volume de chamadas estava dentro do normal, a distribuição entre modelos estava onde eu tinha deixado. O que tinha crescido era o tamanho médio do prompt de entrada.

Fui atrás do detalhe e a resposta era constrangedora de tão simples. Uma das etapas do nosso agente de manutenção lê a saída de um comando de teste e decide o que fazer. Alguém — provavelmente eu — resolveu, meses atrás, mandar a saída inteira em vez de filtrar. Em execução limpa isso é irrelevante, são duzentas linhas. Em falha em cascata, são dez mil linhas de stack trace repetido, e a etapa passa a custar quarenta vezes mais. O agente estava pagando tarifa de raciocínio para ler oitenta vezes a mesma exceção com endereço de memória diferente.

Já escrevi antes sobre gestão de tokens em produção sem explodir o custo, e o incômodo daquela vez foi o mesmo desta: a parte cara da conta quase nunca é a parte inteligente. É a parte braçal — ler, transportar, reenviar. O que mudou de lá para cá é que apareceu uma categoria de ferramenta que se propõe a atacar isso de forma sistemática, no caminho entre o seu código e o provider, sem exigir que você reescreva o agente.

A que mais chamou atenção este ano é o Headroom, uma camada de compressão de contexto open source com licença Apache 2.0, escrita por Tejas Chopra, engenheiro sênior da Netflix. Foi liberada em janeiro de 2026, passou de trinta e cinco mil estrelas, e os números que circulam nas manchetes são grandes: cerca de setecentos mil dólares economizados e duzentos bilhões de tokens recuperados por usuários. Este post é sobre o que a ferramenta realmente faz, sobre por que o número da manchete não é o número que você vai ver, e sobre por que a decisão de comprimir contexto é uma decisão de arquitetura com trade-off real — e não um botão de economia.


O Que É O Headroom, E Onde Ele Se Encaixa

O Headroom se posiciona como uma camada que fica na frente do provider. A ideia é interceptar o payload antes de ele virar cobrança: em vez de você reescrever a lógica do agente para mandar menos coisa, você coloca um estágio no caminho que reduz o que está sendo mandado. A arquitetura é local-first — a compressão acontece na sua máquina ou no seu serviço, não num intermediário remoto —, o que resolve de saída a objeção óbvia de quem trabalha com código proprietário.

Existem quatro formas de colocar isso no caminho, e a escolha entre elas muda bastante o esforço de adoção. A primeira é como biblioteca, em Python ou TypeScript, chamada explicitamente pelo seu código antes da requisição. A segunda é como proxy compatível com a API da OpenAI e da Anthropic, o que significa trocar a URL base do cliente e não tocar em mais nada. A terceira é como servidor MCP, expondo a compressão como ferramenta que o próprio agente pode chamar quando julgar necessário. A quarta é como wrapper de agente, um comando que envolve o processo existente.

Essa lista importa porque cada opção coloca a decisão de comprimir num lugar diferente da arquitetura. Como biblioteca, você decide o que comprimir com todo o contexto de negócio na mão. Como proxy, a decisão é cega ao domínio, mas cobre tudo sem exceção — inclusive os caminhos que você esqueceu que existiam. Como MCP, a decisão passa para o modelo, com o que isso tem de bom e de ruim. Como wrapper, você ganha adoção instantânea e perde granularidade.

Por baixo das quatro formas roda o mesmo pipeline de transformações: um CacheAligner na entrada, um ContentRouter que classifica o conteúdo, e um conjunto de compressores especializados — SmartCrusher, CodeCompressor, Kompress-base. O roteador é o coração da coisa: ele separa JSON, código-fonte, prosa, imagem e histórico de conversa, e manda cada tipo para o compressor que entende daquele formato. Comprimir um dump de JSON e comprimir um trecho de Python são problemas diferentes, e tratar os dois com a mesma heurística é a razão pela qual a maioria das tentativas caseiras de compressão dá resultado medíocre.

Vale registrar de onde isso nasceu, porque explica o desenho. A assinatura de quem vem de infraestrutura de armazenamento em escala está no projeto inteiro: classificar o dado antes de escolher o algoritmo, manter o original recuperável, tratar cache como parte do contrato e não como otimização opcional. É uma abordagem de sistemas aplicada a um problema que a maioria dos times ainda trata como questão de prompt.


O Número Da Manchete Não É O Número Do Seu Agente

Aqui está a parte mais valiosa deste post, e ela é chata de propósito. O número que circula na imprensa e nos posts de rede social é "60% a 95% menos tokens". O próprio repositório do Headroom distingue os ganhos por tipo de conteúdo, e a distinção é grande: cerca de 20% menos tokens para coding agents, e 60% a 95% menos para JSON.

Os dois números são verdadeiros e medem coisas diferentes. JSON tem redundância estrutural enorme: nomes de campo repetidos em cada elemento de um array, chaves longas, indentação, aspas, tipos previsíveis. Um compressor que entende a forma do JSON representa mil registros com o esquema declarado uma vez e os valores em sequência — a redução de 90% é a diferença entre transmitir o esquema mil vezes e transmitir uma. Se o seu payload dominante é resposta de API, catálogo ou telemetria estruturada, esse é o seu número.

O agente de código não tem essa redundância. Código-fonte já é denso: identificadores carregam significado, espaçamento tem semântica em algumas linguagens, e cortar comentário ou renomear variável para economizar caractere é exatamente o tipo de otimização que estraga a resposta. Vinte por cento sobre um prompt de código é um resultado honesto e defensável — só não é o resultado que a manchete promete. Se você for para a diretoria com "vamos cortar 80% da conta" e voltar com 20%, o problema não vai ser a ferramenta.

Faço questão de sublinhar isso porque é o padrão que mais vejo nesta categoria inteira: ferramenta de eficiência de token quase sempre reporta o melhor caso de um formato específico como se fosse o caso médio de uso. A pergunta que resolve isso é sempre a mesma — qual foi a distribuição de conteúdo do benchmark, e quanto ela se parece com a minha.

E há um detalhe que faz a diferença entre 20% e 60% ser menos brutal do que parece. Boa parte do que um coding agent manda para o modelo não é código — é saída de ferramenta: resultado de teste, retorno de linter, listagem de diretório, resposta de API de issue tracker, log de build. Muito disso é estruturado ou semiestruturado, e cai na faixa boa do compressor. O ganho real depende da proporção entre código de verdade e ruído de ferramenta no seu prompt médio, e essa é uma medida que você levanta hoje, sem instalar nada, só olhando alguns prompts reais.


CCR: Comprimir, Guardar Em Cache, Buscar De Volta

O mecanismo central do Headroom se chama CCR — Compress-Cache-Retrieve — e ele é mais interessante que a compressão em si. A ideia é que a versão comprimida vai para o modelo, o original fica guardado num cache local, e o agente ganha um caminho de retrieval para recuperar o que ficou de fora. Em vez de mandar o log de dez mil linhas inteiro no prompt, você manda uma representação compacta mais um conjunto de referências. Se o modelo precisar do stack trace omitido ou do registro exato, ele chama o retrieval.

O fluxo, desenhado, fica assim:

                     ┌──────────────┐
   payload original  │ CacheAligner │  preserva o prefixo estável
   ──────────────────▶              │  do prompt (cache do provider)
                     └──────┬───────┘

                     ┌──────▼────────┐
                     │ ContentRouter │  classifica: JSON, código,
                     └──────┬────────┘  prosa, imagem, histórico
              ┌────────────┼────────────┐
              │            │            │
     ┌────────▼─────┐ ┌────▼───────┐ ┌──▼──────────────┐
     │ SmartCrusher │ │ CodeCompr. │ │  Kompress-base  │
     └────────┬─────┘ └────┬───────┘ └──┬──────────────┘
              └────────────┼────────────┘

              ┌────────────┴─────────────┐
              │                          │
     ┌────────▼─────────┐      ┌─────────▼──────────┐
     │ versão comprimida│      │ original em cache  │
     │   → vai p/ LLM   │      │      local         │
     └──────────────────┘      └─────────┬──────────┘

                          retrieve(ref, query?) │ BM25

                              ┌──────────▼───────────┐
                              │ subconjunto relevante│
                              │   devolvido ao LLM   │
                              └──────────────────────┘

O retrieval aceita um parâmetro opcional de query. Quando ele é fornecido, o Headroom roda busca BM25 sobre os itens em cache e devolve só o subconjunto relevante em vez do bloco inteiro. É uma escolha sóbria: BM25 é lexical, barato, determinístico, roda local e não exige embedding nem serviço externo. Para o caso típico — "me devolve as linhas do log que mencionam ConnectionRefused" — busca lexical é a ferramenta certa, e gastar em busca semântica ali seria pagar por uma capacidade que o problema não pede.

Conceitualmente, isso é o mesmo movimento de context engineering que vem se firmando desde o ano passado: parar de tratar a janela de contexto como um balde onde você despeja tudo que pode ser útil, e começar a tratá-la como um recurso escasso que precisa ser curado a cada passo. A diferença é que o CCR faz a curadoria com rede de segurança. Filtro tradicional joga fora — e quando o modelo precisa do que foi jogado fora, você descobre pelo resultado errado. O CCR guarda e oferece um caminho de volta.

Isso reposiciona a natureza da decisão. Sem retrieval, comprimir é uma aposta irreversível: você aposta que o detalhe cortado não importava, e se errar, paga com resposta ruim. Com retrieval, comprimir vira uma aposta sobre frequência — você aposta que o detalhe raramente importa, e quando importa, paga uma ida a mais. A conta muda completamente. Um corte agressivo que precise de retrieval em 5% dos casos é um bom negócio; o mesmo corte sem caminho de volta seria imprudente.

O servidor MCP expõe três ferramentas — headroom_compress, headroom_retrieve e headroom_stats — e é a terceira que eu instalaria primeiro. Estatística de compressão é o que transforma isso de fé em medição. Sem saber qual foi a taxa por tipo de conteúdo e com que frequência o retrieval foi acionado, você não tem como responder se a camada está ajudando ou atrapalhando.


O CacheAligner É A Peça Que Separa Economia De Prejuízo

Se você ler o pipeline de cima e prestar atenção na ordem, vai notar que o CacheAligner vem antes de tudo. Não é detalhe de implementação. É a peça que decide se a compressão vai te economizar dinheiro ou te custar mais do que você economizou, e é o ponto em que dá para separar quem entendeu o problema de quem só quer cortar tokens.

O motivo é o prompt cache do provider. Os caches de prompt dos grandes providers funcionam por prefixo: eles guardam o estado computado dos primeiros N tokens da sua requisição e, se a próxima requisição começar exatamente com a mesma sequência de tokens, reaproveitam esse estado a uma fração do preço de entrada normal. A palavra que carrega o peso da frase é exatamente. Não é "parecido", não é "semanticamente equivalente". É byte a byte, token a token, a partir do começo.

Agora junte isso com compressão ingênua. Você pega o prompt, roda um compressor sobre ele, manda o resultado. Na chamada seguinte, o conteúdo mudou um pouco — uma mensagem nova no histórico, um resultado de ferramenta diferente — e o compressor, que é sensível ao conteúdo inteiro, produz uma saída diferente logo nos primeiros tokens. O system prompt comprimido saiu diferente. As instruções de ferramenta saíram diferentes. O prefixo quebrou. E como o prefixo quebrou, o cache do provider não bate, e você paga preço cheio de entrada por tudo.

A aritmética disso é cruel. Num agente de loop longo, com system prompt grande, definição de dezenas de ferramentas e histórico crescente, a fração cacheada do prompt costuma ser a maior parte do volume — e ela é cobrada a uma fração do preço. Se você comprime 30% dos tokens mas converte um prompt que estava 80% em cache barato num prompt 100% em preço cheio, você não economizou: você multiplicou a conta. É o cenário em que a ferramenta de economia vira a maior linha da fatura, e é perfeitamente possível rodar assim por semanas sem perceber, porque a métrica que você está olhando — contagem de tokens — melhorou.

O CacheAligner existe para impedir exatamente isso. Ele preserva a estabilidade do prefixo: o começo do prompt precisa sair idêntico entre chamadas, independentemente do que mudou no fim. Na prática, isso significa que a compressão só pode agir de forma agressiva na cauda do payload — a parte que muda a cada chamada de qualquer jeito e que, portanto, nunca ia ser cacheada — e precisa ser conservadora ou determinística na cabeça. É uma restrição que reduz o ganho bruto de compressão e aumenta o ganho líquido de custo, que é o que interessa.

Já tinha escrito sobre as três camadas de cache para LLMs e o argumento de lá se aplica inteiro aqui: cache de prompt, cache semântico e cache de KV resolvem problemas distintos e interagem entre si de formas que a documentação de nenhum dos três explica. Compressão é uma quarta peça entrando nesse arranjo, e ela não é ortogonal às outras. Qualquer avaliação de ferramenta de compressão que não meça o efeito sobre a taxa de acerto do cache está medindo a metade errada do problema. É o primeiro número que eu pediria numa prova de conceito, antes até da taxa de compressão.


Os Três Trade-offs Que O README Não Coloca Em Destaque

O primeiro trade-off é latência. Comprimir custa tempo de CPU, e esse tempo entra no caminho crítico de cada chamada. Para pipeline em lote, irrelevante. Para agente interativo, algumas centenas de milissegundos por chamada multiplicadas por vinte passos de loop viram segundos de espera adicional. A compensação é que prompt menor gera menos tempo de prefill do lado do provider, então o saldo pode até ser positivo — mas isso é hipótese a medir, não a assumir. E depende do quanto o CacheAligner preservou: se o cache está batendo, o prefill já era barato, e o tempo de compressão vira custo puro.

O segundo trade-off é o risco de perder o detalhe que importava. O retrieval reduz esse risco, não o elimina, e o motivo é sutil: para chamar o retrieval, o modelo precisa perceber que está faltando alguma coisa. Se a representação comprimida for boa demais no sentido errado — se ela parecer completa e coerente — o modelo responde com confiança sobre uma versão parcial da realidade e nunca pede o resto. Esse é o modo de falha silencioso da compressão, e é bem pior que o modo barulhento. Uma representação comprimida que declara explicitamente o que omitiu ("47 ocorrências similares omitidas, ref: log-8821") é muito mais segura que uma que apenas parece um log menor.

O terceiro trade-off é a interação com o cache, que é o assunto da seção anterior, e o coloco de novo aqui porque ele tem uma segunda face menos óbvia. Comprimir de forma dinâmica introduz não determinismo no seu prompt. Duas execuções do mesmo agente sobre a mesma entrada podem produzir prompts diferentes, se o compressor tiver qualquer sensibilidade a estado ou a limite de orçamento. Isso complica reprodução de bug, complica eval e complica a resposta a "por que ontem funcionou e hoje não". Vale exigir que a compressão seja determinística para a mesma entrada, e verificar isso, porque é o tipo de propriedade que ninguém garante até alguém perguntar.

Há ainda uma consideração que fica na sombra dos três. Compressão adiciona um componente no caminho crítico entre você e o provider, e esse componente pode falhar ou se comportar diferente numa versão nova. Na forma de proxy, ele vira ponto único de falha para todo o tráfego de IA do sistema. É uma escolha razoável, mas precisa do mesmo cuidado operacional de qualquer proxy: health check, fallback para passagem direta quando a compressão falhar, alarme quando a taxa de erro subir.

A conclusão que tiro dos três juntos não é que compressão seja má ideia. É que ela é uma decisão de arquitetura, com as mesmas obrigações de qualquer outra: hipótese explícita, medição antes e depois, plano de reversão. O que me incomoda no discurso em torno dessa categoria é a moldura de "instale e economize", que sugere um botão. Não é um botão. É um componente novo no seu sistema.


A Métrica Certa É Custo Por Tarefa Concluída Com Sucesso

Tokens por chamada é a métrica fácil, e é por isso que todo mundo usa. Ela tem duas virtudes: aparece direto no retorno da API e melhora imediatamente quando você liga a compressão. Tem também um defeito que anula as duas: ela não sabe se a tarefa foi feita.

O modo de falha é direto de descrever. Você comprime, o prompt encolhe 40%, o dashboard de tokens por chamada fica lindo. Só que o agente agora precisa de sete passos onde antes precisava de cinco, porque perdeu contexto e tentou duas abordagens erradas antes de acertar. Ou chama retrieval três vezes por passo, cada chamada com custo próprio. Ou termina com uma resposta pior, que gera uma correção humana, que gera uma nova execução. Você fez cada chamada mais barata e a tarefa inteira mais cara — e a métrica que você escolheu olhar não tem como te contar isso.

A métrica que resolve é custo por tarefa concluída com sucesso. O numerador soma tudo que a tarefa consumiu do início ao fim: todas as chamadas do loop, incluindo as de retrieval, incluindo as tentativas que não deram certo, incluindo a repetição quando o agente teve que recomeçar. O denominador conta só as tarefas que passaram no seu critério de sucesso — teste verde, revisão humana aprovada, o que for que você já usa como corte. É uma métrica mais trabalhosa de montar e é a única que responde à pergunta que você tem de verdade.

Para montar isso você precisa de duas coisas que provavelmente já deveria ter. A primeira é uma definição de tarefa com começo e fim identificáveis, o que significa propagar um identificador de execução por todas as chamadas do loop — assunto que já tratei em observabilidade de agentes em produção, e que continua sendo o pré-requisito de qualquer conversa séria sobre custo de agente. A segunda é um critério de sucesso automatizável. Sem os dois, qualquer avaliação de compressão vira anedota.

Com isso montado, o protocolo de avaliação fica quase óbvio. Congele um conjunto de tarefas representativas do tráfego real, com a mesma distribuição de conteúdo que você observa em produção. Rode sem compressão, registre custo por tarefa concluída, taxa de sucesso, latência de ponta a ponta e taxa de acerto do cache de prompt. Ligue a compressão, rode o mesmo conjunto, compare os quatro. Se o custo caiu e o sucesso ficou igual, é ganho. Se os dois caíram, você não economizou — apenas transferiu o custo para fora do dashboard, para o tempo de quem vai corrigir a saída.

Uma observação sobre o desenho do experimento: rode as duas condições sobre o mesmo conjunto, no mesmo dia, com o mesmo modelo e a mesma versão do agente. Comparar a fatura de julho com a de agosto não prova nada, porque entre uma e outra mudou tráfego, preço, prompt e modelo. Metade das economias que vejo reportadas em conversa de corredor tem esse defeito, e a outra metade não tem grupo de controle nenhum.


Conclusão

O que me deixa mais otimista sobre o Headroom não é a taxa de compressão nem o número de estrelas. É o CacheAligner. A existência dessa peça, e a decisão de colocá-la primeiro no pipeline, mostra que quem escreveu entendeu que o problema não é "quantos tokens eu consigo cortar" e sim "qual é o efeito líquido disso na fatura, considerando que o provider já me dá um desconto enorme por não mudar o começo do prompt". É o tipo de detalhe que só aparece quando alguém rodou aquilo em produção de verdade.

O que me deixa cético é o marketing em volta, e não é culpa do projeto: o repositório é honesto ao separar 20% para coding agent de 60-95% para JSON. Quem borra essa distinção é a camada de divulgação que se forma em torno de qualquer projeto que cresce rápido. Se você é a pessoa que vai levar essa proposta para dentro do seu time, leve os dois números, e leve com a distribuição de conteúdo do seu próprio tráfego ao lado. É uma conversa mais chata e é a única que não gera constrangimento três meses depois.

Continuo sem uma resposta fechada sobre em que ponto da maturidade de um sistema vale adotar uma camada dessas. Meu instinto hoje é que ela vem depois de duas coisas mais baratas: filtrar na origem o que nem deveria entrar no prompt — o meu log de dez mil linhas não precisava de compressor, precisava de um grep —, e garantir que o cache de prompt esteja realmente batendo, que é o desconto que já está na mesa e que muito time desperdiça sem saber. Compressão entra quando essas duas já foram feitas e a conta continua alta. Fora dessa ordem, é otimizar a parte errada com a ferramenta mais complicada disponível, o que é uma armadilha à qual eu, pelo visto, também sou vulnerável nas manhãs de segunda-feira.

Fontes:

Newsletter

Receba os artigos mais relevantes da semana, sem quebrar seu ritmo de leitura

Um resumo semanal com os melhores posts sobre IA, engenharia de software e tecnologia, enviado no melhor momento para continuar a conversa depois da leitura.

Foto de Elton José

Escrito por

eltonjose

Engenheiro de software e estrategista de produtos digitais, focado em IA pragmática e em transformar experiências de trabalho remoto em aprendizados aplicáveis. Compartilho frameworks e decisões reais que uso em consultorias e projetos.

  • Principais temasCompressão de Contexto, Headroom
  • Formato do conteúdoGuia prático + insights de carreira