· 7 min · API

Como reduzir o uso de tokens com engenharia sem prejudicar a qualidade

Como reduzir o uso de tokens com engenharia sem prejudicar a qualidade

No mês passado, revisei um agente de suporte ao cliente que consumia 18,000–42,000 tokens de entrada por chamado antes de escrever uma única palavra. O problema real do usuário geralmente ocupava menos de 300 tokens. O restante era contexto incluído “por precaução”: todo o histórico da conversa, todas as páginas de políticas internas recuperadas por busca de palavras-chave, JSON resultados de ferramentas copiados na íntegra e um prompt de sistema de 900 tokens que havia crescido ao longo de seis meses.

A solução não foi “usar um modelo menor” nem “reduzir max_tokens e torcer”. Reduzimos o uso mediano de tokens em cerca de 68% e mantivemos a qualidade das respostas estável com trabalho de engenharia rotineiro: enxugar o contexto, recuperar evidências mais específicas, resumir o estado, impor esquemas de saída e medir os tokens com precisão.

Este artigo trata desse tipo de trabalho. Não de competições para encurtar prompts. Não de truques que pioram o modelo. Apenas de formas práticas de gastar menos tokens sem tornar seu sistema menos inteligente.

O uso de tokens é um problema de arquitetura

A maioria das equipes trata o custo dos tokens como um problema de preços dos modelos. Na prática, geralmente é um problema de roteamento de informações.

Uma chamada típica a um LLM em produção tem quatro categorias de tokens:

  • Tokens de instrução: prompt de sistema, prompt do desenvolvedor, regras de formatação, restrições de segurança.
  • Tokens de contexto: histórico do chat, documentos recuperados, saídas de ferramentas, registros do banco de dados.
  • Tokens de raciocínio/tarefa: a solicitação real do usuário e os detalhes intermediários da tarefa.
  • Tokens de saída: a resposta gerada, o objeto JSON, a consulta SQL, o resumo ou a chamada de ferramenta.

Quando o uso de tokens sai do controle, o contexto e a saída geralmente são os culpados. O modelo não é caro porque o usuário fez uma pergunta difícil. Ele é caro porque a aplicação colocou o mundo inteiro no prompt e depois deixou o modelo se alongar.

Um primeiro exercício útil é registrar o uso de tokens por seção, não apenas o tamanho total da solicitação:

{
  "request_id": "ticket_81291",
  "model": "sonnet-4.6",
  "tokens": {
    "system": 742,
    "history": 3890,
    "retrieval": 12640,
    "tool_results": 2110,
    "user": 184,
    "output": 931
  },
  "latency_ms": 4820
}

Ao ver essa distribuição, o caminho para a otimização fica óbvio. Você não precisa debater se Claude Opus 4.8, Sonnet 4.6, GPT-5.5, Gemini 3, Fable 5 ou um modelo aberto como Qwen ou DeepSeek é o “melhor” em termos abstratos. Primeiro, precisa parar de enviar 12,000 tokens irrelevantes de conteúdo recuperado.

Comece com um orçamento de tokens por rota

Gosto de atribuir orçamentos por rota do produto, não globalmente. Uma revisão de contrato jurídico, um agente de programação e um bot de perguntas frequentes não devem compartilhar as mesmas premissas de uso de tokens.

Exemplo de orçamento para um assistente de suporte:

OpçãoOrçamento de entradaOrçamento de saídaClasse de modeloObservações
Classificar chamado60050Modelo barato/rápidoRótulos, urgência, idioma
Recuperar evidências1,5000Embedding/buscaNão requer geração
Redigir resposta4,000600Modelo robusto de nível intermediário/altoUsa evidências selecionadas
Resumir conversa1,000250Modelo barato/rápidoAtualiza o estado da memória
Análise de escalonamento6,000900Modelo robustoApenas para casos complexos

A questão não é que esses números sejam universalmente corretos. Eles são limites iniciais intencionalmente modestos. O comportamento útil é que cada etapa precisa justificar os tokens que usa.

Uma armadilha comum: se você definir apenas max_tokens para a saída, sua entrada ainda poderá crescer silenciosamente. Limites de saída impedem respostas longas; eles não impedem o excesso de conteúdo recuperado.

Enxugue o contexto antes da recuperação, não depois

Muitos sistemas recuperam documentos usando a mensagem mais recente do usuário, mas ainda incluem todo o histórico da conversa no prompt final. Isso significa que digressões antigas, chamadas de ferramentas anteriores que falharam e premissas desatualizadas permanecem no contexto.

Em vez disso, mantenha um estado de trabalho compacto:

{
  "user_goal": "Get refund for duplicate annual subscription charge",
  "known_facts": [
    "User paid twice on 2025-01-08",
    "Invoice IDs: inv_1042 and inv_1077",
    "Account email: user@example.com"
  ],
  "constraints": [
    "Refund policy allows duplicate-charge refunds within 90 days",
    "User prefers email follow-up"
  ],
  "open_questions": [
    "Whether one charge has already been reversed"
  ],
  "last_updated_turn": 12
}

Assim, sua chamada final inclui:

  • O estado compacto.
  • A mensagem mais recente do usuário.
  • Apenas trechos relevantes das políticas recuperadas.
  • Talvez a última interação ou as duas últimas, se a continuidade do tom for importante.

Não inclua cegamente todas as mensagens anteriores. O histórico do chat não é memória; é uma transcrição. A memória é o estado essencial necessário para continuar a tarefa.

Na prática, uso três camadas:

  1. Interações recentes: últimas 1–4 interações, dependendo da sensibilidade da tarefa.
  2. Resumo contínuo: fatos e decisões estáveis.
  3. Recuperação externa: documentos, registros do banco de dados, saídas de ferramentas.

Isso geralmente funciona melhor do que incluir a transcrição bruta inteira, porque o modelo recebe menos ruído e menos instruções conflitantes.

A recuperação deve ser seletiva, não decorativa

A geração aumentada por recuperação muitas vezes começa como uma melhoria de qualidade e se transforma em desperdício de tokens. O padrão de falha é fácil de identificar: top-k é definido como 10 ou 20, cada trecho tem 1,000 tokens e o prompt final contém uma pilha de documentos vagamente relacionados.

Uma recuperação melhor geralmente é uma recuperação mais barata.

Um pipeline prático de recuperação:

def build_context(query, user_state):
    search_query = rewrite_for_search(query, user_state)

    candidates = vector_search(
        query=search_query,
        top_k=30,
        filters={"product": user_state["product"]}
    )

    reranked = rerank_cross_encoder(
        query=search_query,
        docs=candidates,
        top_k=6
    )

    selected = []
    token_budget = 1800

    for doc in reranked:
        snippet = extract_relevant_span(doc.text, query=search_query, max_tokens=300)
        if token_count(selected) + token_count(snippet) <= token_budget:
            selected.append({
                "doc_id": doc.id,
                "title": doc.title,
                "text": snippet
            })

    return selected

Os detalhes importantes:

  • Recupere mais candidatos do que pretende incluir.
  • Reordene os resultados por relevância antes da geração.
  • Extraia passagens relevantes em vez de enviar trechos inteiros.
  • Imponha um orçamento de tokens para a recuperação.
  • Mantenha os IDs dos documentos para que o modelo possa citar ou referenciar as evidências internamente.

O tamanho dos trechos importa. Se forem pequenos demais, o modelo perde contexto. Se forem grandes demais, cada correspondência traz texto irrelevante junto. Para documentação de produtos e páginas de políticas, geralmente começo com cerca de 300–600 tokens por trecho, preservando os títulos, e depois ajusto com base nos casos de falha.

O que realmente acontece quando você inclui contexto recuperado em excesso? O modelo frequentemente se torna menos preciso. Ele tenta conciliar políticas próximas, mas irrelevantes, incorpora ressalvas obsoletas ou responde à versão errada da pergunta. A redução de tokens pode melhorar a qualidade porque remove distrações.

Resumir é comprimir com responsabilidade

Resumir pode economizar enormes quantidades de contexto, mas resumos malfeitos são perigosos. Se um resumo omitir uma restrição, seu agente poderá fazer a coisa errada com confiança.

Prefiro resumos estruturados a texto corrido:

{
  "task": "Debug failing OAuth callback in staging",
  "environment": {
    "service": "auth-api",
    "branch": "release-2025-02-14",
    "runtime": "node 20"
  },
  "confirmed": [
    "Callback returns 302 to /login",
    "Provider sends code and state",
    "State validation passes"
  ],
  "suspected": [
    "Session cookie is not persisted after callback"
  ],
  "rejected": [
    "Provider credentials are invalid",
    "Redirect URI mismatch"
  ],
  "next_steps": [
    "Inspect Set-Cookie attributes in staging response",
    "Compare SameSite and Secure flags with production"
  ]
}

Isso é mais eficiente no uso de tokens e mais confiável do que:

O usuário está depurando o OAuth e já tentou várias coisas…

Uma boa etapa de sumarização deve preservar:

  • O objetivo do usuário.
  • Os fatos concretos.
  • As decisões já tomadas.
  • As restrições e preferências.
  • As tentativas que falharam.
  • As questões em aberto.
  • IDs, nomes de arquivos, registros de data e hora e outros identificadores exatos.

Não elimine valores exatos ao resumir, a menos que sejam realmente irrelevantes. “O usuário tem um problema com uma fatura” custa pouco, mas é inútil. “Cobrança duplicada: inv_1042 e inv_1077 em 2025-01-08” é o tipo de compactação que você quer.

Para agentes de longa duração, também armazeno versões dos resumos. Se o modelo produzir um resumo ruim, você precisa conseguir inspecionar quando o estado começou a divergir.

Evite saídas prolixas com esquemas

Uma quantidade surpreendente do desperdício de tokens ocorre na saída. O prompt diz “seja conciso”, mas o modelo escreve uma introdução, ressalvas, uma recapitulação e uma oferta de ajuda.

Para saídas consumidas por máquinas, use esquemas. Não peça simplesmente “uma resposta em JSON”; defina a estrutura e rejeite qualquer outra coisa.

Exemplo de chamada de classificação:

{
  "type": "object",
  "additionalProperties": false,
  "required": ["category", "priority", "needs_human", "reason"],
  "properties": {
    "category": {
      "type": "string",
      "enum": ["billing", "bug", "account", "how_to", "other"]
    },
    "priority": {
      "type": "string",
      "enum": ["low", "normal", "high", "urgent"]
    },
    "needs_human": {
      "type": "boolean"
    },
    "reason": {
      "type": "string",
      "maxLength": 160
    }
  }
}

Para respostas voltadas ao usuário, forneça um formato com um limite rígido:

Write the answer in this structure:

- First sentence: direct answer, max 25 words.
- Then 2-4 bullets with concrete steps.
- Do not include a greeting.
- Do not include "let me know if you need anything else."
- Stop after the final bullet.

Isso funciona melhor do que “seja breve” porque dá ao modelo uma estrutura.

Uma armadilha comum: um valor baixo de max_tokens pode truncar uma saída útil no meio de uma ideia. Um esquema ou formato restrito reduz o tamanho da saída sem comprometer sua completude. O truncamento é um último recurso, não uma estratégia de formatação.

Use sequências de parada com cuidado

Sequências de parada são úteis quando a conclusão tem um limite natural. Por exemplo, se o modelo estiver gerando uma seção de um documento baseado em um modelo, você pode interromper a geração no marcador da próxima seção.

{
  "model": "gpt-5.5",
  "messages": [
    {
      "role": "user",
      "content": "Generate only the Risk Summary section for this review..."
    }
  ],
  "max_tokens": 500,
  "stop": ["\n## Mitigations", "\n## Appendix"]
}

Sequências de parada são menos úteis em respostas abertas do assistente porque podem cortar conteúdo válido. Elas também se comportam de maneiras diferentes entre APIs e famílias de modelos, especialmente quando há chamadas de ferramentas ou saídas estruturadas. Trate-as como mecanismos de delimitação, não como uma técnica principal de compactação.

Bons usos:

  • Gerar uma seção de um modelo de documento conhecido.
  • Impedir que o modelo continue gerando exemplos.
  • Parar em um delimitador durante o processamento em lote.
  • Restringir prompts legados no estilo de completamento.

Usos arriscados:

  • Parar em sinais de pontuação comuns.
  • Parar em palavras que podem aparecer em respostas normais.
  • Usar sequências de parada em vez de um esquema de saída adequado.
  • Pressupor um comportamento idêntico entre Claude, GPT, Gemini e gateways de modelos abertos.

Distribua tarefas entre modelos mais baratos

Nem toda etapa precisa do seu modelo mais potente. Em sistemas de produção, costumo encaminhar subtarefas para modelos menores ou mais baratos:

SubtarefaRequisito típicoOpção adequada
Detecção de idiomaClassificação simplesModelo pequeno/barato
Categoria do chamadoSaída de rótulos estruturadaModelo pequeno ou médio
Reescrita de consultasTransformação curtaModelo pequeno ou médio
Reclassificação de documentosReclassificador especializado ou modelo pequenoNão generativo, quando possível
Resposta finalRaciocínio e tomModelo mais potente
Exceção à políticaNuances e riscoModelo mais potente disponível

É aqui que o acesso a vários modelos se torna útil operacionalmente. Você pode querer usar Sonnet 4.6 para a maior parte da escrita por agentes, GPT-5.5 para uma linha de raciocínio complexa, Gemini 3 para certos fluxos de trabalho multimodais ou de contexto longo, Fable 5 quando a janela de contexto de 1M for realmente necessária e modelos abertos como Llama, Qwen, DeepSeek, MiniMax ou Kimi para tarefas internas controladas. O ponto de engenharia é o roteamento, não a lealdade à marca.

A limitação: a sobrecarga de orquestração é real. Cada chamada adicional a um modelo acrescenta latência, modos de falha, complexidade nos logs e mais aspectos a avaliar. Se um classificador barato economiza 300 tokens, mas acrescenta 600 ms e outro fluxo de novas tentativas, talvez não valha a pena.

Um padrão de que gosto:

if route == "simple_faq":
    model = "haiku-4.5"
elif route == "standard_support":
    model = "sonnet-4.6"
elif route == "legal_or_financial_exception":
    model = "opus-4.8"
else:
    model = "sonnet-4.6"

Os nomes exatos vão mudar. A política deve permanecer: use o modelo mais barato que atenda de forma confiável ao nível de qualidade exigido pela tarefa.

Meça os tokens de entrada e saída com honestidade

A otimização de tokens falha quando as equipes observam apenas o custo médio por solicitação. Você precisa de distribuições, detalhadas por rota e etapa.

Acompanhe pelo menos:

  • Tokens de entrada por seção do prompt.
  • Tokens de saída.
  • Quantidade de documentos recuperados e tokens de recuperação.
  • Nome e versão do modelo.
  • Percentis de latência.
  • Taxa de acertos do cache, se estiver usando cache de prompts ou de contexto.
  • Quantidade de novas tentativas.
  • Indicadores de qualidade percebidos pelo usuário.
  • Taxas de encaminhamento para atendimento humano ou de correção humana.

Um painel útil mostra p50, p90 e p99. Problemas no uso de tokens costumam se concentrar na cauda da distribuição.

Exemplo de linha de log:

{
  "route": "support.draft_answer",
  "model": "sonnet-4.6",
  "input_tokens": 3820,
  "output_tokens": 514,
  "retrieved_docs": 4,
  "retrieval_tokens": 1320,
  "latency_ms": 3100,
  "cache_hit": false,
  "quality": {
    "user_thumb": "up",
    "human_edited": false
  }
}

Tenha cuidado com afirmações como “reduzimos os tokens em 40%”. As novas tentativas aumentaram? Os encaminhamentos para atendimento humano aumentaram? Os usuários fizeram mais perguntas de acompanhamento porque as respostas ficaram sucintas demais? O modelo mais barato produziu mais JSON inválido, exigindo chamadas de correção?

A métrica honesta não é tokens por chamada. É tokens por tarefa concluída com sucesso.

Contexto longo não é permissão para ser preguiçoso

Janelas de contexto grandes são realmente úteis. A classe de contexto de 1M do Fable 5, os pontos fortes do Gemini em contexto longo e os modos de contexto longo dos modelos de ponta viabilizam fluxos de trabalho que eram difíceis há alguns anos: análise de código em todo o repositório, revisão de documentos extensos, transcrições de várias horas e conjuntos densos de material de pesquisa.

Mas contexto longo é uma capacidade, não uma arquitetura padrão.

Chamadas com contexto longo podem ser apropriadas quando:

  • A resposta depende de muitas partes distantes da entrada.
  • A recuperação deixaria passar relações entre documentos.
  • A tarefa é exploratória e você ainda não sabe o que importa.
  • O custo de perder um detalhe é maior do que o custo de ler mais.

Elas geralmente são um desperdício quando:

  • O usuário faz uma pergunta factual específica.
  • Alguns trechos das fontes são suficientes.
  • O contexto contém informações duplicadas ou obsoletas.
  • O modelo está sendo usado como filtro de banco de dados.

Um padrão comum é usar contexto longo para análise offline e produzir artefatos compactos para uso online. Por exemplo, analise um corpus completo de políticas uma vez e depois gere um mapa estruturado das políticas, embeddings, resumos e casos de teste. O assistente em execução não deve reler todo o corpus a cada chamado.

Um fluxo de trabalho prático de otimização

Quando assumo um sistema de LLM que consome muitos tokens, geralmente faço o seguinte, nesta ordem:

  1. Instrumente primeiro
    Adicione a contabilização de tokens por seção. Não otimize às cegas.

  2. Limite o contexto recuperado
    Defina um limite rígido de tokens para recuperação. Reclassifique e extraia trechos.

  3. Substitua o acúmulo de transcrições por um estado
    Mantenha as interações recentes junto com uma memória estruturada.

  4. Restrinja as saídas
    Use esquemas para saídas destinadas a máquinas e formatos explícitos para saídas voltadas ao usuário.

  5. Encaminhe subtarefas
    Transfira tarefas de classificação, reescrita e resumo para modelos mais baratos quando forem confiáveis.

  6. Avalie a qualidade no nível da tarefa
    Compare resoluções bem-sucedidas, não apenas o custo por chamada.

  7. Monitore a cauda da distribuição
    Investigue picos de tokens em p95 e p99. Eles costumam revelar falhas na recuperação de informações ou um histórico que cresce sem controle.

Aqui está uma proteção simples que tenho usado em serviços:

TOKEN_LIMITS = {
    "support.classify": {"input": 800, "output": 80},
    "support.retrieve": {"input": 500, "output": 0},
    "support.draft": {"input": 4500, "output": 700},
    "support.summarize": {"input": 1800, "output": 300},
}

def assert_token_budget(route, prompt_sections, max_output_tokens):
    input_tokens = {
        name: count_tokens(text)
        for name, text in prompt_sections.items()
    }

    total_input = sum(input_tokens.values())
    limits = TOKEN_LIMITS[route]

    if total_input > limits["input"]:
        raise TokenBudgetError(route, total_input, input_tokens)

    if max_output_tokens > limits["output"]:
        raise TokenBudgetError(route, max_output_tokens, {"output": max_output_tokens})

Falhar rapidamente é melhor do que descobrir uma semana depois que uma consulta de recuperação mal formulada gastou seu orçamento com PDFs irrelevantes.

Recomendações práticas

  • A redução de tokens deve começar pela visibilidade: registre os tokens de entrada e saída por seção do prompt, rota e modelo.
  • A recuperação de informações supera o acúmulo de conteúdo quando é seletiva: reordene por relevância, extraia trechos e imponha um orçamento de contexto.
  • Os resumos devem representar um estado estruturado, não ser textos vagos. Preserve fatos exatos, IDs, restrições e tentativas malsucedidas.
  • Esquemas de saída reduzem a prolixidade com mais segurança do que simplesmente diminuir max_tokens.
  • Sequências de parada são úteis para delimitar a saída, mas não substituem saídas estruturadas.
  • Encaminhe subtarefas simples para modelos mais baratos apenas quando a latência adicional e os modos de falha compensarem.
  • Avalie a economia pelos tokens por tarefa bem-sucedida, não pelos tokens por chamada individual.
  • Modelos de contexto longo são poderosos, mas não eliminam a necessidade de uma arquitetura da informação.
A

A AI Prime Tech publica este blog. Os artigos são redigidos com assistência de IA; confira detalhes específicos de cada versão, como IDs de modelos, preços e limites, na documentação oficial antes de se basear neles.

Obtenha acesso mais barato à API do Claude

Uma única chave de API para Claude Opus 5.5, Sonnet 5, Haiku 4.5 e Fable 5.1, além de GPT-6 modelos. Pague conforme o uso, sem assinatura.

Obtenha Sua Chave de API →
A AI Prime Tech é um gateway de API independente de terceiros. Claude™ e Anthropic® são marcas comerciais da Anthropic, PBC. Não há afiliação nem endosso implícitos.