Gerenciar custos e limites em sistemas com modelos de linguagem depende de uma unidade pouco intuitiva: o token, um pedaço de texto que pode ser menor que uma palavra ou até representar espaços e sinais. Em produção, pequenas variações de entrada, idioma, formatação e histórico de conversa podem multiplicar o número de tokens sem que mudanças de código sejam evidentes.
O Tiktoken é uma biblioteca open source que implementa o mesmo processo de tokenização usado por modelos da OpenAI, permitindo contagens determinísticas antes de qualquer chamada de API. Isso viabiliza previsões de custo, controle do tamanho do contexto e prevenção de falhas por extrapolar a janela de contexto, tudo de forma reproduzível e auditável.
Conceito de tokens e por que a contagem precisa importa
Um token é uma unidade de texto usada internamente pelo modelo para processar entradas e gerar saídas. Em vez de trabalhar com palavras inteiras, o modelo opera com fragmentos frequentes e combinações comuns, o que reduz ambiguidades e melhora a generalização. Como a cobrança e os limites técnicos são definidos por tokens, erros de contagem viram erros financeiros e operacionais. Em produção, uma diferença de 10% pode se tornar enorme quando o volume de requisições é alto.
A contagem precisa é relevante em três frentes principais: custo, limite de contexto e previsibilidade. O limite de contexto é a quantidade máxima de tokens que cabem somando instruções, histórico e nova mensagem. Quando esse limite é ultrapassado, ocorre truncamento, erro de validação ou perda de partes importantes do prompt. A previsibilidade exige que a mesma entrada gere a mesma contagem ao longo do tempo, o que depende de versões fixas e encoders consistentes.
O que é Tiktoken e o que ele entrega em sistemas reais
O Tiktoken é uma biblioteca de tokenização que reproduz o encoder (codificador) usado por determinados modelos, gerando a mesma sequência de tokens para um texto. Um encoder é o componente que converte texto em uma lista de inteiros, onde cada inteiro representa um token. Como a conversão é determinística, a contagem local pode ser usada para estimar consumo antes de enviar qualquer requisição. Isso viabiliza políticas de orçamento por usuário, por sessão e por serviço, além de controles de segurança que bloqueiam entradas excessivas.
Outra entrega importante é a reversibilidade: a sequência de tokens pode ser decodificada de volta ao texto original. Essa propriedade permite validações de integridade, evitando surpresas com caracteres especiais e idiomas mistos. Em sistemas com auditoria, torna-se possível registrar métricas de consumo sem armazenar o texto completo, caso políticas internas exijam minimização de dados. O Tiktoken também facilita testes de migração, comparando contagens ao trocar de modelo e encoder.
Como funciona a Byte Pair Encoding (BPE) de forma simples
A Byte Pair Encoding (BPE) é um método de segmentação que transforma texto em tokens por meio de fusões de sequências frequentes de bytes. Em vez de partir por palavras ou letras, o algoritmo aprende quais fragmentos aparecem com mais frequência e os trata como unidades. Fragmentos comuns como espaços, sufixos e partes de palavras viram tokens únicos, enquanto palavras raras podem se dividir em várias partes. Essa estratégia equilibra vocabulário e eficiência, reduzindo o tamanho médio da representação sem perder capacidade de representar qualquer texto.
Na prática, a mesma palavra pode virar múltiplos tokens, e isso varia conforme idioma, acentuação, emojis e pontuação. Caracteres fora do ASCII (como “ç”, “á”, “こんにちは” e “🚀”) continuam representáveis, mas frequentemente aumentam a contagem. A contagem também pode ser impactada por quebras de linha e espaços repetidos, pois esses elementos podem ser tokens próprios. Por isso, estimativas por “caracteres ÷ 4” tendem a falhar em textos mistos e em dados reais de produção.
Instalação e isolamento de dependências (ambientes reproduzíveis)
Ambientes reproduzíveis evitam que mudanças de versão alterem a tokenização e quebrem previsões de custo. O termo pinagem significa fixar a versão de um pacote para garantir resultados consistentes. Em produção, também é importante isolar dependências para evitar conflitos entre bibliotecas. A seguir estão exemplos completos de criação de ambiente e instalação com versões fixas.
Os comandos abaixo mostram formas comuns de preparar um ambiente Python com isolamento. A escolha entre venv e Conda depende do padrão do time e das restrições do deploy, mas o objetivo é o mesmo: consistência.
# Criação de ambiente virtual com venv (Linux/macOS)
python -m venv .venv
source .venv/bin/activate
# Atualização do pip e instalação com versão fixada (pinada)
pip install --upgrade pip
pip install "tiktoken==0.5.2"
# Alternativa com Conda
conda create -n token_env python=3.10 -y
conda activate token_env
pip install "tiktoken==0.5.2"
Validação de integridade: encode, decode e “round-trip”
Uma validação essencial é confirmar que o texto codificado e depois decodificado permanece igual, o que é conhecido como round-trip. Isso reduz risco de erros silenciosos em strings com emojis, idiomas mistos e caracteres invisíveis. Em sistemas críticos, a validação pode rodar no boot do serviço e falhar rapidamente em caso de ambiente quebrado. A seguir está um exemplo completo em Python para validar versão, plataforma e reversibilidade.
O exemplo também mostra como obter um encoder apropriado e como produzir uma contagem de tokens confiável. Caso o encoder específico do modelo não esteja disponível, um fallback controlado pode ser aplicado com registro em log.
import platform
import tiktoken
def obter_encoder(nome_modelo: str):
"""
Tenta obter o encoder do modelo. Se não for possível, aplica fallback.
O fallback deve ser explícito para evitar contagens inesperadas.
"""
try:
return tiktoken.encoding_for_model(nome_modelo)
except KeyError:
# Fallback conhecido e amplamente usado em modelos recentes
return tiktoken.get_encoding("cl100k_base")
def validar_tokenizacao(texto: str, nome_modelo: str) -> None:
encoder = obter_encoder(nome_modelo)
ids = encoder.encode(texto)
texto_reconstruido = encoder.decode(ids)
assert texto_reconstruido == texto, "Falha no round-trip de tokenização"
print("Versão do tiktoken:", tiktoken.__version__)
print("Plataforma:", platform.platform())
print("Modelo (referência):", nome_modelo)
print("Quantidade de tokens:", len(ids))
if __name__ == "__main__":
validar_tokenizacao("🌍 Olá, mundo! こんにちは 🚀", "gpt-4")
Contagem de tokens para prompts simples e textos multilíngues
A contagem para um prompt simples consiste em escolher o encoder e medir o tamanho da lista de tokens. A lista contém inteiros que representam o vocabulário do encoder, e o tamanho dessa lista é o total de tokens cobrados para aquele trecho de texto. Textos multilíngues e com emojis devem ser contados com o mesmo método, pois aproximações falham com facilidade. Em pipelines, a contagem é frequentemente registrada junto com metadados do job para auditoria e rastreabilidade.
O exemplo a seguir mostra a contagem para entradas distintas e ilustra como a variação de caracteres impacta a tokenização. A saída numérica exata pode variar entre encoders e versões, mas o processo permanece consistente quando a versão está fixada.
import tiktoken
def contar_tokens(texto: str, nome_modelo: str = "gpt-4") -> int:
encoder = tiktoken.encoding_for_model(nome_modelo)
return len(encoder.encode(texto))
if __name__ == "__main__":
exemplos = [
"Texto curto em português.",
"Server-side streaming 🚀",
"Mistura de idiomas: Olá / Hello / こんにちは",
"Espaços repetidos e quebras\nde\nlinha"
]
for texto in exemplos:
total = contar_tokens(texto)
print(f"Entrada: {texto!r}")
print(f"Tokens: {total}")
print("-" * 40)
Por que heurísticas (caracteres ÷ 4) falham e geram custos inesperados
Heurísticas tentam aproximar tokens com base em tamanho de string ou número de palavras, mas tokens não correspondem a caracteres nem a palavras. O mesmo número de caracteres pode gerar contagens bem diferentes por causa de emojis, acentos, símbolos, trechos de código e idiomas não latinos. Em produção, o erro se soma e pode distorcer painéis financeiros e alarmes. Além disso, heurísticas não ajudam a gerenciar a janela de contexto com segurança, pois não existe garantia de limite superior.
O exemplo abaixo compara uma aproximação ingênua com a contagem real via Tiktoken. A diferença percentual pode ser alta mesmo em textos curtos, e tende a piorar em dados reais com variação de formatos e fontes.
import tiktoken
def aproximacao_por_caracteres(texto: str) -> float:
return len(texto) / 4.0
def contagem_real(texto: str, nome_modelo: str = "gpt-4") -> int:
encoder = tiktoken.encoding_for_model(nome_modelo)
return len(encoder.encode(texto))
if __name__ == "__main__":
texto = "Server-side streaming 🚀"
aprox = aproximacao_por_caracteres(texto)
real = contagem_real(texto)
erro_percentual = abs(real - aprox) / real * 100.0
print("Texto:", texto)
print("Aproximação (len/4):", aprox)
print("Contagem real:", real)
print("Erro percentual aproximado:", round(erro_percentual, 2), "%")
Contagem para mensagens de chat: overhead e estrutura
Em aplicações de conversa, o total de tokens não é apenas a soma dos textos “visíveis”. Existem metadados e marcadores internos associados a papéis como sistema, usuário e assistente, além de delimitadores estruturais. Esse overhead é o custo extra de tokens para representar a estrutura do chat. Como esse custo depende do modelo e do formato exato da API, a prática segura é medir de forma consistente e manter margem de segurança para evitar extrapolar limites.
Uma abordagem robusta é contar tokens por partes e impor orçamentos: instruções do sistema, histórico recente e nova entrada. Isso permite controlar crescimento gradual do histórico e reduzir risco de falhas. A seguir está um exemplo que contabiliza tokens por campo textual e produz um total estimado, útil para políticas internas de budget. O método não tenta “adivinhar” o overhead interno exato do provedor, mantendo foco no controle conservador.
import tiktoken
from typing import List, Dict
def contar_tokens_texto(texto: str, encoder) -> int:
return len(encoder.encode(texto or ""))
def contar_tokens_mensagens(mensagens: List[Dict[str, str]], nome_modelo: str = "gpt-4") -> dict:
"""
Conta tokens apenas do conteúdo textual dos campos.
O overhead estrutural varia por implementação e deve ser tratado como margem.
"""
encoder = tiktoken.encoding_for_model(nome_modelo)
total_conteudo = 0
detalhes = []
for msg in mensagens:
role = msg.get("role", "")
content = msg.get("content", "")
name = msg.get("name", "")
tokens_role = contar_tokens_texto(role, encoder)
tokens_content = contar_tokens_texto(content, encoder)
tokens_name = contar_tokens_texto(name, encoder)
subtotal = tokens_role + tokens_content + tokens_name
total_conteudo += subtotal
detalhes.append({
"role": role,
"tokens_role": tokens_role,
"tokens_content": tokens_content,
"tokens_name": tokens_name,
"subtotal": subtotal
})
return {
"total_conteudo": total_conteudo,
"mensagens": detalhes
}
if __name__ == "__main__":
mensagens = [
{"role": "system", "content": "Respostas curtas e objetivas."},
{"role": "user", "content": "Explicar tokenização com um exemplo simples."},
{"role": "assistant", "content": "Tokenização divide texto em unidades menores chamadas tokens."}
]
resultado = contar_tokens_mensagens(mensagens)
print("Total (conteúdo textual):", resultado["total_conteudo"])
for item in resultado["mensagens"]:
print(item)
Gestão de janela de contexto: orçamento, margem e truncamento inteligente
A janela de contexto é o limite de tokens que pode ser enviado e processado em uma única interação, somando entrada e instruções. O risco operacional é acumular histórico até ultrapassar o limite, causando erro ou perda silenciosa de partes do prompt. Uma estratégia comum é definir um orçamento fixo por componente e preservar o que é mais importante para manter coerência. O objetivo é manter o sistema dentro de limites com previsibilidade de custo e latência.
O truncamento inteligente preserva instruções do sistema e mensagens recentes, descartando itens antigos quando necessário. Em vez de cortar caracteres, a abordagem correta corta por tokens, evitando quebrar sequências de forma inconsistente. A seguir está um exemplo de “janela deslizante”, que mantém o sistema e as últimas mensagens até caberem no orçamento. O procedimento também reserva uma margem para a resposta esperada, reduzindo risco de extrapolar no retorno.
import tiktoken
from typing import List, Dict, Tuple
def tokens_do_texto(encoder, texto: str) -> int:
return len(encoder.encode(texto or ""))
def tokens_mensagem(encoder, msg: Dict[str, str]) -> int:
# Contagem conservadora apenas do texto; overhead estrutural vira margem.
return tokens_do_texto(encoder, msg.get("role", "")) + tokens_do_texto(encoder, msg.get("content", ""))
def aplicar_janela_deslizante(
mensagens: List[Dict[str, str]],
nome_modelo: str,
limite_total: int,
margem_resposta: int,
margem_overhead: int
) -> Tuple[List[Dict[str, str]], int]:
"""
Retorna uma lista reduzida de mensagens que cabe no orçamento.
- limite_total: limite total de tokens desejado
- margem_resposta: tokens reservados para a saída do modelo
- margem_overhead: reserva para estrutura interna de chat
"""
encoder = tiktoken.encoding_for_model(nome_modelo)
orcamento_entrada = limite_total - margem_resposta - margem_overhead
if orcamento_entrada < 0:
raise ValueError("Orçamento negativo; ajustar margens e limite_total.")
if not mensagens:
return [], 0
# Preserva a primeira mensagem do tipo system, quando existir
system_msgs = [m for m in mensagens if m.get("role") == "system"]
system = system_msgs[:1]
resto = [m for m in mensagens if m.get("role") != "system"]
selecionadas = []
total = 0
# Primeiro adiciona system
for m in system:
t = tokens_mensagem(encoder, m)
if total + t <= orcamento_entrada:
selecionadas.append(m)
total += t
# Depois adiciona mensagens do fim para o começo (mais recentes)
for m in reversed(resto):
t = tokens_mensagem(encoder, m)
if total + t <= orcamento_entrada:
selecionadas.append(m)
total += t
else:
break
selecionadas = list(reversed(selecionadas))
return selecionadas, total
if __name__ == "__main__":
mensagens = [
{"role": "system", "content": "Política: respostas curtas; evitar dados sensíveis."},
{"role": "user", "content": "Mensagem antiga 1 com bastante contexto..."},
{"role": "assistant", "content": "Resposta antiga 1..."},
{"role": "user", "content": "Mensagem recente com pergunta objetiva."}
]
reduzidas, tokens_entrada = aplicar_janela_deslizante(
mensagens=mensagens,
nome_modelo="gpt-4",
limite_total=8000,
margem_resposta=800,
margem_overhead=200
)
print("Tokens estimados de entrada:", tokens_entrada)
print("Mensagens mantidas:", len(reduzidas))
Processamento em lote com desempenho: reuso de encoder e paralelismo
Em cargas altas, inicializar o encoder repetidamente aumenta latência e custo de CPU. O reuso de uma única instância de encoder por processo reduz overhead e estabiliza o throughput. Paralelismo pode ajudar quando há muitas strings para tokenizar, mas deve ser aplicado com cautela para não aumentar consumo de memória. Em Python, a tokenização do Tiktoken é implementada em baixo nível e pode liberar gargalos, mas o padrão ideal depende do ambiente.
O exemplo a seguir mostra um pipeline de lote que cria o encoder uma vez e processa uma lista de textos. Para volumes grandes, o processamento por blocos (chunks) evita carregar tudo em memória. O resultado retorna contagens por item e um total agregado, que pode alimentar monitoramento interno e limites por job.
import tiktoken
from typing import Iterable, List, Dict
def contar_lote(textos: Iterable[str], nome_modelo: str = "gpt-4") -> List[Dict[str, int]]:
encoder = tiktoken.encoding_for_model(nome_modelo)
saida = []
for texto in textos:
ids = encoder.encode(texto or "")
saida.append({"tokens": len(ids)})
return saida
def processar_em_blocos(textos: List[str], tamanho_bloco: int = 200) -> int:
total = 0
for i in range(0, len(textos), tamanho_bloco):
bloco = textos[i:i + tamanho_bloco]
resultados = contar_lote(bloco)
total += sum(item["tokens"] for item in resultados)
return total
if __name__ == "__main__":
textos = [f"Item {i}: texto de exemplo com variação {i}." for i in range(1000)]
total_tokens = processar_em_blocos(textos, tamanho_bloco=250)
print("Total de tokens no lote:", total_tokens)
Migração e troca de modelo: impacto na tokenização e previsibilidade
Trocar de modelo pode alterar o encoder subjacente e mudar a contagem de tokens para o mesmo texto. Isso afeta previsões de custo, limites de contexto e até decisões de truncamento, pois o orçamento pode deixar de caber. Em termos de engenharia, a migração precisa de amostragem representativa do tráfego real e comparação de contagens. O resultado prático é um fator de ajuste por tipo de conteúdo, como mensagens longas, trechos de código e idiomas específicos.
Uma forma segura de tratar essa mudança é medir a mesma amostra em encoders distintos e registrar estatísticas como média, percentis e máximos. O exemplo abaixo mede contagens para dois “alvos” e produz um resumo simples. Mesmo quando nomes de modelo variam por disponibilidade, a prática central é comparar encoders de forma reprodutível e manter logs da versão do Tiktoken utilizada.
import tiktoken
from statistics import mean
def contar_com_encoder(encoder, textos):
return [len(encoder.encode(t)) for t in textos]
def comparar_encoders(textos, encoder_a_nome: str, encoder_b_nome: str):
enc_a = tiktoken.get_encoding(encoder_a_nome)
enc_b = tiktoken.get_encoding(encoder_b_nome)
a = contar_com_encoder(enc_a, textos)
b = contar_com_encoder(enc_b, textos)
relacoes = [bb / aa if aa else 0 for aa, bb in zip(a, b)]
return {
"encoder_a": encoder_a_nome,
"encoder_b": encoder_b_nome,
"media_a": mean(a),
"media_b": mean(b),
"media_relacao_b_por_a": mean(relacoes)
}
if __name__ == "__main__":
amostra = [
"Texto com acentuação: ação, órgão, informação.",
"Código e símbolos: if (a != b) { return a+b; }",
"Emojis e misc: 🚀✅—“aspas”",
"Japonês: こんにちは世界"
]
resumo = comparar_encoders(amostra, "cl100k_base", "p50k_base")
print(resumo)
Registro e observabilidade open source: métricas de tokens e alertas
Observabilidade é a capacidade de entender o comportamento do sistema por meio de métricas, logs e rastreamento. Para tokens, as métricas mais úteis incluem tokens de entrada, tokens de saída, total por requisição, total por sessão e máximos por janela de tempo. Em vez de depender de plataformas pagas, o registro pode ser feito via logs estruturados e métricas expostas para coletores open source. O ponto central é ter séries temporais que revelem crescimento gradual de histórico e picos anormais.
Uma prática comum é emitir logs em JSON para facilitar ingestão por stacks internas. A seguir está um exemplo simples que registra contagem de tokens por evento, incluindo identificadores e timestamps. Esse tipo de log suporta auditoria e agregação posterior sem acoplar o sistema a um fornecedor específico.
import json
import time
import tiktoken
from typing import Dict
encoder = tiktoken.encoding_for_model("gpt-4")
def log_tokens(evento: str, dados: Dict):
registro = {
"timestamp": int(time.time()),
"evento": evento,
**dados
}
print(json.dumps(registro, ensure_ascii=False))
def medir_e_registrar(texto: str, request_id: str, usuario_id: str):
ids = encoder.encode(texto or "")
log_tokens("contagem_tokens", {
"request_id": request_id,
"usuario_id": usuario_id,
"tokens_entrada": len(ids)
})
if __name__ == "__main__":
medir_e_registrar("Pergunta curta com emoji 🚀", "req-123", "user-9")
Falhas comuns em produção e padrões de mitigação
Custos podem explodir quando o histórico de conversa cresce sem controle, quando prompts incluem trechos redundantes ou quando múltiplos componentes repetem contexto. Outro problema frequente é a inconsistência de ambiente: versões diferentes do pacote e do encoder geram contagens divergentes e quebram previsões. Também existe o risco de entradas inesperadas, como texto extremamente longo ou conteúdo com muitos caracteres especiais, elevando tokens e latência. Essas falhas normalmente aparecem apenas com diversidade real de dados e volume.
Mitigações eficazes combinam limites rígidos, margens de segurança e políticas de retenção de memória. Um padrão é definir um teto por requisição e recusar ou resumir conteúdo acima do orçamento, mantendo rastreabilidade em logs. Outro padrão é segmentar o orçamento por componente do chat e aplicar janela deslizante. Por fim, a pinagem de versões e testes de round-trip reduzem risco de drift operacional e garantem reprodutibilidade.
Conclusão
O Tiktoken fornece uma base técnica sólida para medir tokens com precisão, reduzindo incerteza de custo e evitando falhas por limite de contexto. A combinação de ambientes reproduzíveis, validação de integridade e contagem consistente permite que sistemas em produção mantenham previsibilidade mesmo com entradas variadas. Estratégias como janela deslizante e reuso de encoder controlam crescimento de histórico e melhoram desempenho em lote. Com métricas registradas de forma aberta e auditável, o consumo de tokens deixa de ser um risco invisível e passa a ser um aspecto controlável da operação.