Blog

Quota de contexto por cliente: quando a conversa longa vira prejuízo

O custo de uma conversa não cresce de forma linear com o número de mensagens: ele cresce mais rápido, porque a cada turno o histórico inteiro volta para dentro do prompt. Uma conversa de quarenta turnos não custa quarenta vezes a de um turno, custa muito mais, e o cliente que mais escreve costuma ser exatamente o que menos paga. O sintoma clássico aparece no fechamento do mês: a fatura subiu trinta por cento, o número de conversas ficou igual, e ninguém sabe explicar a diferença. A explicação quase sempre é uma minoria de conversas que ficaram longas demais e nunca encontraram um limite. Este artigo mostra por que o custo por conversa é quadrático e não linear, como definir uma quota de contexto por cliente que não pune o uso legítimo, como escolher o que sai da janela quando a quota estoura, e por que a decisão final não é técnica: é sobre qual conversa vale a pena continuar carregando.

2026-08-17 / IA Aplicada / 15 min

01

O custo da conversa longa é quadrático, não linear

A intuição enganosa é tratar uma conversa como uma lista de mensagens e assumir que o custo total é a soma do custo de cada mensagem. Não é. Num agente com histórico completo, o turno N envia como entrada tudo que foi dito nos turnos anteriores mais a nova mensagem. Se cada turno acrescenta em média K tokens ao histórico, o turno N envia aproximadamente N vezes K tokens de entrada, e o total acumulado depois de N turnos é proporcional a N ao quadrado. É a mesma matemática de uma soma aritmética, e é a razão pela qual dobrar o comprimento da conversa quadruplica o custo dela.

Isso muda completamente onde procurar quando a fatura sobe. Um aumento de dez por cento no número de conversas produz um aumento de dez por cento no custo. Um aumento de dez por cento no comprimento médio das conversas produz um aumento de aproximadamente vinte e um por cento. E como o comprimento médio esconde a distribuição, o cenário mais comum é pior ainda: a média mal se move enquanto uma cauda de conversas muito longas absorve metade do orçamento sozinha.

Turnos na conversaTokens de entrada no turnoEntrada acumuladaCusto relativo ao turno 1
11.2001.2001x
54.40014.00011,7x
108.40048.00040x
2016.400176.000146x
4032.400672.000560x
8064.4002.624.0002.186x

A tabela assume um prompt de sistema de mil tokens, duzentos tokens por mensagem do cliente e da resposta somados, e nenhuma compressão. Os números absolutos mudam com o seu sistema, o formato da curva não muda. O que essa tabela revela é que uma conversa de oitenta turnos custa mais que duzentas conversas de cinco turnos, e que qualquer política de custo que trate as duas como "uma conversa" está medindo a coisa errada. A unidade de cobrança do provedor é o token, então a unidade de controle do seu sistema também precisa ser o token.

Vale registrar a exceção importante: cache de prefixo muda a inclinação, não a forma. Se o histórico é estável e vem no começo do prompt, os tokens já vistos custam uma fração do preço cheio, e o fator de crescimento cai bastante. Mas o cache tem tempo de vida curto, e conversa longa é exatamente o caso em que há pausas de horas entre turnos, que é quando o cache expira. Contar com cache para resolver conversa longa é contar justamente com a condição que a conversa longa quebra.

02

Quota é diferente de teto de gasto e de limite de janela

Três controles se confundem com frequência e resolvem problemas distintos. O limite de janela do modelo é uma restrição técnica: acima dele a chamada falha. O teto de gasto por cliente é uma proteção de margem: acima dele o cliente para de ser atendido ou é degradado. A quota de contexto fica entre os dois e é a que quase ninguém implementa: ela limita quantos tokens de histórico uma conversa específica tem direito de carregar por turno, independentemente de caber na janela e independentemente de o cliente ainda ter orçamento.

ControleUnidadeO que protegeO que acontece ao estourar
Limite de janela do modeloTokens por chamadaNada, é restrição físicaA chamada falha com erro do provedor
Rate limitRequisições por minutoCapacidade do sistemaA requisição espera na fila ou é rejeitada
Teto de gasto por clienteDinheiro por períodoMargem do contratoO cliente é degradado ou bloqueado
Quota de contexto por conversaTokens de histórico por turnoCusto marginal de continuarO histórico é comprimido, nunca a conversa

A última linha carrega a diferença que importa: quando a quota de contexto estoura, a conversa continua. Nenhum cliente é bloqueado, nenhuma mensagem é recusada. O que muda é a quantidade de histórico que o agente carrega para responder, e essa é uma decisão que pode ser tomada sem que o cliente perceba, desde que a escolha do que descartar seja boa. É por isso que a quota de contexto é o controle mais barato politicamente: ela protege margem sem produzir uma conversa difícil com o cliente.

A quota também precisa ser diferenciada por plano, e aqui o erro comum é derivá-la do preço. O critério certo é o valor esperado da conversa, não a mensalidade. Uma conversa de suporte pós-venda de um cliente de plano básico pode valer mais em retenção do que uma consulta de catálogo de um cliente enterprise. Quota por plano é um bom padrão inicial, e quota por tipo de conversa é o refinamento que quase sempre paga.

03

Medir antes de limitar: onde o orçamento realmente vai

Antes de escolher qualquer número, é preciso saber a distribuição real. A métrica que quase todo time tem é o custo médio por conversa, e ela é praticamente inútil aqui porque a média é dominada pela cauda em distribuições assim. As três métricas que decidem a política são outras: a distribuição de comprimento em percentis, a fração do custo total consumida pelo percentil noventa e cinco, e o custo marginal do turno atual em relação ao primeiro turno da mesma conversa.

// telemetry/context-cost.js
// Instrumenta o custo marginal de cada turno e a concentracao do gasto.
// A pergunta que isso responde: qual fatia do orcamento vive na cauda?

export function createContextMeter({ metrics, priceUsdPerMillion }) {
  // Custo em dolares de uma chamada, separando entrada cacheada de nova.
  // Cache de prefixo custa uma fracao do preco cheio, entao somar tudo
  // como "entrada" superestima a cauda em sistemas com cache ativo.
  function costOf({ freshInputTokens, cachedInputTokens, outputTokens }) {
    const p = priceUsdPerMillion;
    return (
      (freshInputTokens * p.input +
        cachedInputTokens * p.cachedInput +
        outputTokens * p.output) /
      1_000_000
    );
  }

  function recordTurn({ conversationId, tenantId, plan, turnIndex, usage }) {
    const turnCost = costOf(usage);
    const historyTokens = usage.freshInputTokens + usage.cachedInputTokens;

    // Rotular por faixa de turno, nao por turno exato: cardinalidade de
    // rotulo cresce sem limite se o turno virar dimensao da metrica.
    const bucket =
      turnIndex <= 5 ? '1-5' : turnIndex <= 15 ? '6-15' : turnIndex <= 40 ? '16-40' : '41+';

    metrics.histogram('conversation.turn.cost_usd', turnCost, { plan, turn_bucket: bucket });
    metrics.histogram('conversation.turn.history_tokens', historyTokens, { plan, turn_bucket: bucket });

    // Contador por tenant permite responder "quem consumiu" sem varrer
    // o log inteiro no fechamento do mes.
    metrics.counter('conversation.cost_usd_total', turnCost, { tenant: tenantId, plan });

    return { turnCost, historyTokens, bucket };
  }

  // Concentracao: fracao do custo total que vive nas conversas mais longas.
  // Acima de 0.5 significa que metade da fatura esta numa minoria de
  // conversas, e a quota passa a ser a intervencao de maior retorno.
  function concentration(conversations) {
    const sorted = [...conversations].sort((a, b) => b.totalCostUsd - a.totalCostUsd);
    const total = sorted.reduce((acc, c) => acc + c.totalCostUsd, 0);
    if (total === 0) return { p95Share: 0, p99Share: 0 };

    const shareOfTop = (fraction) => {
      const count = Math.max(1, Math.ceil(sorted.length * fraction));
      const slice = sorted.slice(0, count).reduce((acc, c) => acc + c.totalCostUsd, 0);
      return slice / total;
    };

    return { p95Share: shareOfTop(0.05), p99Share: shareOfTop(0.01) };
  }

  return { recordTurn, concentration, costOf };
}

A função de concentração é a que justifica o trabalho inteiro. Se os cinco por cento de conversas mais caras consomem quinze por cento do custo, a distribuição é saudável e quota de contexto é otimização prematura. Se consomem cinquenta por cento ou mais, você tem uma minoria de conversas absorvendo metade do orçamento, e qualquer política que atue sobre elas rende mais que qualquer otimização de prompt aplicada às outras noventa e cinco por cento.

A separação entre entrada nova e entrada cacheada no cálculo de custo não é detalhe contábil. Num sistema com cache de prefixo bem configurado, uma conversa longa em rajada pode custar bem menos do que a curva quadrática sugere, enquanto a mesma conversa espalhada ao longo do dia paga preço cheio em cada turno. Se você mede tudo como entrada única, a cauda parece maior do que é em rajada e menor do que é quando espaçada, e a política nasce calibrada errado nas duas pontas.

04

O que descartar quando a quota estoura

Estourar a quota não deveria significar truncar o histórico pelo começo, que é o comportamento padrão da maioria das implementações e o pior de todos. O começo da conversa costuma conter exatamente o que não pode ser perdido: quem é o cliente, qual é o pedido em questão, qual problema originou o atendimento. Cortar pelo começo produz o efeito mais irritante que um agente pode ter, que é esquecer no turno trinta o que o cliente explicou no turno dois.

A alternativa é tratar o histórico como três camadas com prioridades diferentes: fatos duráveis extraídos da conversa, resumo dos episódios antigos e transcrição literal dos turnos recentes. Quando a quota aperta, a transcrição literal é a primeira a encolher, o resumo é comprimido em seguida, e os fatos duráveis nunca saem. É a mesma ideia de um orçamento com fatias reservadas: cada camada tem um mínimo garantido e um máximo, e a folga é distribuída entre elas.

Alocacao da janela sob quota de 8.000 tokens de historico

  CONVERSA CURTA (turno 4)          CONVERSA LONGA (turno 47)
  +----------------------+          +----------------------+
  | fatos duraveis   400 |          | fatos duraveis   900 |  <- nunca cortado
  +----------------------+          +----------------------+
  | resumo             0 |          | resumo         2.600 |  <- comprimido
  +----------------------+          +----------------------+
  | transcricao    1.100 |          | transcricao    4.500 |  <- janela movel
  +----------------------+          +----------------------+
  | folga          6.500 |          | folga              0 |
  +----------------------+          +----------------------+
    total: 1.500                      total: 8.000 (no teto)

  Turno 48 chega e nao cabe:
    1. transcricao cede os turnos mais antigos ate o piso de 2.000
    2. o que saiu da transcricao entra no resumo (custa 1 chamada barata)
    3. o resumo e recomprimido se passar do seu teto de 3.000
    4. fatos duraveis seguem intactos: sao 900 tokens que valem mais
       que os 4.500 da transcricao inteira

  O que o cliente percebe: nada, desde que o passo 2 rode antes do
  descarte e nao depois. Resumir o que ja foi jogado fora e o bug
  mais comum dessa implementacao.
// context/quota.js
// Aloca a janela de historico por camadas com piso e teto por camada.
// Regra central: o que sai da transcricao passa pelo resumo antes de
// ser descartado, nunca depois.

const LAYERS = {
  facts: { floor: 200, ceiling: 1200 },
  summary: { floor: 0, ceiling: 3000 },
  transcript: { floor: 2000, ceiling: Infinity },
};

export function createQuotaAllocator({ countTokens, summarize, metrics }) {
  async function allocate({ conversationId, quotaTokens, facts, summary, transcript }) {
    const factsTokens = Math.min(countTokens(facts), LAYERS.facts.ceiling);
    let summaryText = summary;
    let summaryTokens = countTokens(summaryText);
    let kept = [...transcript];

    const fits = () =>
      factsTokens + summaryTokens + countTokens(kept) <= quotaTokens;

    // Enquanto nao cabe, os turnos mais antigos saem da transcricao e
    // sao absorvidos pelo resumo. O piso da transcricao garante que a
    // conversa nao perca o contexto imediato do turno atual.
    const evicted = [];
    while (!fits() && countTokens(kept) > LAYERS.transcript.floor) {
      evicted.push(kept.shift());
    }

    if (evicted.length > 0) {
      // Uma chamada barata, com modelo pequeno: resumir e a operacao
      // que preserva o valor do que seria simplesmente descartado.
      summaryText = await summarize({ previous: summaryText, turns: evicted });
      summaryTokens = countTokens(summaryText);
      metrics.counter('context.turns_evicted_total', evicted.length, { reason: 'quota' });
    }

    // Se mesmo assim nao cabe, o resumo e quem cede: ele e reconstruivel
    // a partir da conversa persistida, a transcricao recente nao e.
    if (!fits() && summaryTokens > LAYERS.summary.floor) {
      summaryText = await summarize({ previous: summaryText, turns: [], targetTokens: Math.max(
        LAYERS.summary.floor,
        quotaTokens - factsTokens - countTokens(kept),
      ) });
      summaryTokens = countTokens(summaryText);
      metrics.counter('context.summary_recompressed_total', 1, {});
    }

    const usedTokens = factsTokens + summaryTokens + countTokens(kept);
    metrics.histogram('context.window_used_tokens', usedTokens, {
      at_ceiling: usedTokens >= quotaTokens * 0.95 ? 'yes' : 'no',
    });

    return { facts, summary: summaryText, transcript: kept, usedTokens, evictedCount: evicted.length };
  }

  return { allocate, LAYERS };
}

O detalhe que faz essa implementação funcionar é a ordem: resumir antes de descartar. A versão ingênua corta os turnos antigos, envia o prompt e resume depois, o que na prática significa resumir a partir de um histórico que já perdeu o que interessava. Também vale notar que o resumo cede antes da transcrição recente, e não o contrário: o resumo pode ser reconstruído a partir da conversa persistida no banco, enquanto perder os turnos imediatamente anteriores quebra a coerência da resposta atual.

05

Quando a conversa longa não é abuso e a quota está errada

Nem toda conversa longa é desperdício, e tratar comprimento como sinal de abuso é o erro que transforma uma política de custo numa política de piorar o atendimento. Existe uma categoria inteira de conversa que é longa porque o problema é difícil, e ela costuma ser justamente a de maior valor: negociação de contrato, diagnóstico técnico, disputa de cobrança, onboarding de cliente grande. Cortar contexto exatamente aí é economizar centavos e arriscar o contrato.

  • Conversa longa com progresso: cada turno resolve uma parte e o cliente confirma. Aqui a quota deve ser generosa, porque o custo está comprando um desfecho.
  • Conversa longa em laço: o cliente repete a mesma pergunta reformulada e o agente repete a mesma resposta reformulada. Aqui o problema não é custo, é que o agente não sabe responder e deveria escalar.
  • Conversa longa por exploração: o cliente está navegando o catálogo sem intenção clara. Quota apertada é apropriada, porque cada turno carrega pouco valor.
  • Conversa longa artificial: automação do outro lado mantendo a sessão aberta. Isso é abuso e o controle certo é teto de gasto, não quota de contexto.
  • Conversa longa por retomada: o cliente volta depois de dias sobre o mesmo assunto. Aqui o que importa é fato durável, não transcrição, e a quota nem chega perto do teto.

A segunda categoria merece destaque porque é a mais cara e a mais fácil de detectar. Uma conversa em laço tem assinatura clara: a similaridade entre a mensagem atual e alguma mensagem anterior do cliente é alta, e a similaridade entre a resposta atual e alguma resposta anterior também. Quando as duas coisas acontecem por dois ou três turnos seguidos, gastar mais tokens não vai resolver, e o comportamento certo é oferecer transbordo para humano em vez de continuar carregando um histórico que só cresce.

Isso muda a natureza da política. Uma quota de contexto que apenas comprime é incompleta: ela precisa vir acompanhada de um gatilho de saída. Se a conversa ultrapassa o teto de turnos definido para aquele tipo e não houve progresso mensurável, o certo é escalar, não continuar comprimindo. Custo alto sem desfecho é o pior dos dois mundos, e é exatamente o que uma política de compressão isolada produz.

06

A política em produção: números, gatilhos e o que observar

A implementação em produção precisa de três decisões numéricas e duas de comportamento. As numéricas são a quota base por classe de conversa, o piso da transcrição e o teto de turnos antes do gatilho de saída. As de comportamento são o que fazer quando a quota estoura e o que fazer quando o teto de turnos estoura, que são situações diferentes e frequentemente tratadas como a mesma.

Classe de conversaQuota de históricoPiso da transcriçãoGatilho de saída
Consulta de catálogo2.000 tokens600 tokens8 turnos sem intenção clara
Suporte de primeiro nível6.000 tokens2.000 tokens12 turnos sem progresso
Diagnóstico técnico12.000 tokens4.000 tokens25 turnos sem progresso
Disputa ou cobrança10.000 tokens3.000 tokens6 turnos, escala por política
Onboarding assistido16.000 tokens5.000 tokensSem gatilho, é acompanhamento

A linha de disputa é a que mais destoa e a mais importante de acertar. A quota é alta porque o histórico completo importa quando há divergência sobre o que foi dito, mas o gatilho de saída é o mais curto da tabela, porque uma disputa que passa de seis turnos com um agente automático raramente termina bem. Quota generosa com escalada rápida não é contradição: é reconhecer que o contexto precisa estar completo justamente para que a transferência ao humano seja útil.

  1. Instrumente o custo marginal por turno e a concentração antes de definir qualquer número, para saber se a cauda justifica a política.
  2. Comece com quota por classe de conversa, não por plano, porque o valor está no tipo de problema e não no contrato.
  3. Implemente a alocação em camadas com piso por camada, garantindo que fatos duráveis nunca sejam descartados.
  4. Resuma o que sai da transcrição antes do descarte, e nunca depois, com um modelo pequeno para que a operação seja barata.
  5. Adicione o gatilho de saída por ausência de progresso, separado da quota, porque comprimir não resolve conversa em laço.
  6. Monitore a proporção de conversas que atingem o teto da quota: se passa de dez por cento numa classe, a quota daquela classe está errada.
  7. Compare a taxa de resolução das conversas com e sem compressão no conjunto de avaliação antes de considerar a política aprovada.

O penúltimo item é o alarme mais útil e o mais esquecido. A quota deveria ser um limite raramente tocado, não um regime operacional permanente. Se uma classe inteira de conversa vive comprimindo histórico, o número não está protegendo margem, está degradando o atendimento de forma sistemática e invisível, e o certo é elevar a quota daquela classe ou repensar o que entra no prompt de sistema, que costuma ser onde estão os tokens desperdiçados de verdade.

Vale terminar reconhecendo o que essa política não resolve. Quota de contexto controla o custo marginal de continuar uma conversa, e faz isso bem. Ela não conserta um prompt de sistema inchado, não substitui roteamento para modelo menor nas rotas simples e não impede que uma automação abusiva mantenha mil sessões abertas. Cada um desses tem o seu controle próprio, e o valor da quota é justamente ser específica: ela responde a uma única pergunta, que é quanto histórico esta conversa merece carregar agora, e responde de um jeito que o cliente não sente.

FAQ

Perguntas frequentes

Por que a minha fatura subiu se o número de conversas não mudou?

Quase sempre porque o comprimento médio das conversas subiu, e o custo cresce com o quadrado do comprimento e não de forma linear. Num agente que reenvia o histórico completo a cada turno, o turno N carrega tudo que veio antes, então o acumulado depois de N turnos é proporcional a N ao quadrado. Na prática isso significa que um aumento de dez por cento no comprimento médio produz cerca de vinte e um por cento a mais de custo, com o mesmo volume de conversas. E como a média esconde a distribuição, o caso mais comum é ainda mais desigual: a média mal se move enquanto uma minoria de conversas muito longas absorve metade do orçamento. A forma de confirmar isso é medir a concentração, ou seja, qual fração do custo total vive nos cinco por cento de conversas mais caras. Acima de cinquenta por cento, o problema está na cauda e nenhuma otimização de prompt aplicada ao caso médio vai resolver.

Truncar o histórico pelo começo não é a solução mais simples?

É a mais simples de implementar e a pior em resultado, porque o começo da conversa costuma conter exatamente o que não pode ser perdido: quem é o cliente, qual pedido está em questão, qual problema originou o atendimento. Truncar pelo começo produz o comportamento mais irritante que um agente pode ter, que é esquecer no turno trinta o que o cliente explicou no turno dois, e o cliente reage repetindo a informação, o que gasta mais turnos e mais tokens. A alternativa é tratar o histórico em três camadas com prioridades distintas: fatos duráveis extraídos da conversa, resumo dos episódios antigos e transcrição literal dos turnos recentes. Sob pressão de quota, a transcrição cede primeiro até um piso, o que sai dela é absorvido pelo resumo antes de ser descartado, e os fatos duráveis nunca saem. O detalhe que mais quebra na prática é a ordem: resumir precisa acontecer antes do descarte, nunca depois.

Como diferenciar uma conversa longa legítima de uma que só está queimando dinheiro?

Pelo progresso, não pelo comprimento. Uma conversa longa com progresso resolve uma parte do problema a cada turno e o cliente confirma o avanço, e essa costuma ser a conversa de maior valor do sistema: negociação, diagnóstico técnico, disputa de cobrança, onboarding. Cortar contexto exatamente aí economiza centavos e arrisca o contrato. Uma conversa em laço tem assinatura oposta e detectável: a mensagem atual é muito parecida com alguma anterior do próprio cliente e a resposta atual é muito parecida com alguma resposta anterior, por dois ou três turnos seguidos. Nesse caso gastar mais tokens não vai resolver, porque o agente não sabe responder, e o comportamento certo é oferecer transbordo para humano. Por isso a quota precisa vir acompanhada de um gatilho de saída por ausência de progresso, separado do limite de tokens: comprimir uma conversa em laço só produz custo alto sem desfecho, que é o pior dos dois mundos.

A conversa longa é uma decisão de produto disfarçada de detalhe técnico

O custo de uma conversa cresce com o quadrado do número de turnos, então uma minoria de conversas longas costuma absorver metade do orçamento sem aparecer em nenhuma métrica média. A quota de contexto é o controle que atua exatamente nesse ponto, e é o mais barato politicamente porque não bloqueia ninguém: ela decide quanto histórico o agente carrega por turno, com alocação em camadas onde a transcrição cede primeiro, o resumo absorve o que sai antes do descarte e os fatos duráveis nunca são cortados. O que a quota sozinha não resolve é a conversa em laço, que precisa de um gatilho de saída por ausência de progresso em vez de mais compressão. E o número certo nunca vem do plano do cliente, vem do tipo de problema que a conversa está tentando resolver. Posso medir a concentração de custo do seu agente, dimensionar a quota por classe de conversa e implementar a alocação em camadas sem que o cliente perceba a diferença.