tokentoll
Escaneia bases de código em busca de chamadas de API de LLM e estima custos mensais. Compara custos entre referências do git para detectar regressões de custo durante a revisão de código.
Documentação
tokentoll
Evite regressões de custo de LLM antes da produção.
tokentoll é um gate de CI para custo de LLM. Ele analisa estaticamente Python, JavaScript e TypeScript para chamadas de API de LLM, pontua cada pull request contra uma política que você controla e publica um veredito PASS/WARN/FAIL diretamente no PR. Opcionalmente, ele falha o workflow quando a política é violada, para que regressões de custo não possam ser mescladas.
Demonstração ao vivo
Jwrede/tokentoll-demo é um pequeno aplicativo LLM poliglota (Python + TypeScript) conectado ao gate de custo do tokentoll. Dois PRs já estão abertos contra ele:
- PR #1: Adicionar auxiliar de tradução Anthropic Haiku. Novo ponto de chamada, bem dentro do orçamento. Veredito: PASS, workflow verde.
- PR #2: trocar supportbot para gpt-4o. Uma troca de modelo que aciona duas regras de política. Veredito: FAIL, workflow vermelho.
Abra a aba de conversa de cada PR para ver o comentário de veredito que o tokentoll realmente publica.
O comentário de veredito
Quando um PR viola sua política, o tokentoll comenta com um veredito e uma lista de descobertas bloqueantes, e então sai com código não zero para que a verificação falhe. Exemplo:
## tokentoll verdict: FAIL
**Blocking findings (2):**
- `src/agent.py:42` - per-call cost grew 15.0x (threshold 5x)
- total monthly delta +$812.00 exceeds budget $250.00
> Required action: revert the regression, raise the threshold in `.tokentoll.yml`, or add an exemption.
Quando o PR está limpo, o veredito é PASS e o comentário mostra apenas a tabela de delta de custo. Quando nenhuma política está configurada, o tokentoll publica um comentário informativo de delta sem veredito.
Início rápido (60 segundos)
Adicione .github/workflows/tokentoll.yml:
name: tokentoll
on:
pull_request:
paths:
- "**.py"
- "**.ts"
- "**.tsx"
- "**.js"
- "**.jsx"
permissions:
contents: read
pull-requests: write
jobs:
cost-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: Jwrede/tokentoll@v0.7.0
with:
fail-on-policy-violation: true
Em seguida, adicione .tokentoll.yml à raiz do seu repositório:
budgets:
max_monthly_delta_usd: 250
max_callsite_monthly_usd: 100
max_relative_increase: 5.0
policies:
block_unknown_models: true
fail_on_policy_violation: true
PRs futuros recebem um comentário de veredito. PRs que excedem os limites falham o workflow.
Para instalações com SHA fixado e configurações de permissões mínimas, veja docs/github-action.md. Para o esquema completo de política, veja docs/policy.md. Para a postura de segurança, veja docs/security.md.
O que ele detecta
Python
| SDK | Padrões |
|---|---|
| OpenAI | chat.completions.create, responses.create |
| Anthropic | messages.create, messages.stream |
| Google GenAI | models.generate_content |
| LiteLLM | completion, acompletion |
| LangChain | ChatOpenAI, ChatAnthropic, init_chat_model |
| Zhipu AI | ZhipuAiClient, ZhipuAI (modelos GLM) |
JavaScript / TypeScript (analisado via tree-sitter, lida com .js, .jsx, .ts, .tsx)
| SDK | Padrões |
|---|---|
| OpenAI Node SDK | client.chat.completions.create, client.responses.create, client.embeddings.create |
| Anthropic SDK | client.messages.create, client.messages.stream |
| Vercel AI SDK | generateText, streamText, generateObject, streamObject, embed, embedMany |
| LangChain.js | new ChatOpenAI, new ChatAnthropic, new ChatGoogleGenerativeAI, ... |
| OpenAI-compatible | mesma forma que o OpenAI Node SDK, detectado automaticamente |
Regras de política
O bloco de política em .tokentoll.yml controla quando um PR falha:
| Regra | Gatilho |
|---|---|
budgets.max_monthly_delta_usd | delta mensal estimado total excede o limite |
budgets.max_callsite_monthly_usd | qualquer ponto de chamada novo ou alterado excede o limite |
budgets.max_relative_increase | custo por chamada para qualquer ponto de chamada modificado cresce mais do que este multiplicador |
policies.block_unknown_models | qualquer ponto de chamada novo ou modificado usa um modelo sem preço ou não resolvido |
policies.fail_on_policy_violation | tokentoll diff sai com 1 em FAIL (comportamento de gate de CI) |
Cada regra é independente. Deixe um campo não definido para desabilitar essa regra. Referência completa em docs/policy.md.
CLI
pip install tokentoll
# Scan current directory for LLM API calls and their costs
tokentoll scan .
# Show cost impact of your last commit
tokentoll diff HEAD~1
# Compare two refs and fail on policy violation
tokentoll diff main..HEAD --fail-on-policy-violation
Subcomandos:
tokentoll scan [PATH...] [--format table|json|markdown] [--calls-per-month N] [--config PATH]
tokentoll diff [REF] [--base REF] [--head REF] [--format table|json|markdown|github-comment]
[--config PATH] [--fail-on-policy-violation]
tokentoll update # refresh bundled pricing data from LiteLLM
Configuração
.tokentoll.yml fica na raiz do repositório e é descoberto automaticamente. Além do bloco de política:
# Per-SDK defaults for dynamic (runtime-resolved) model names
default_models:
openai: gpt-4o-mini
anthropic: claude-haiku-3-20240307
# Assumed monthly call volume per call site (used for dollar estimates)
calls_per_month: 5000
# Skip cost estimation for dynamic models entirely.
# Default false: dynamic calls are priced against the per-SDK default.
skip_dynamic_models: false
# Default excludes (tests/, examples/, docs/, cookbook/, benchmarks/, evals/,
# scripts/, notebooks/) are applied automatically. Opt out with:
use_default_excludes: false
# Additional excludes (prefix or glob)
exclude:
- "*_test.py"
- vendor/
# Per-path overrides (longest prefix match)
overrides:
- path: src/agents/
default_model: gpt-4o
calls_per_month: 10000
- path: src/azure/
skip_dynamic_models: true
Ordem de resolução para padrões de modelo dinâmicos: default_models (por SDK) > default_model (genérico) > padrões integrados do SDK.
Segurança
tokentoll não requer chaves de API, não envia telemetria e roda inteiramente dentro do seu ambiente de CI. Os dados de preços acompanham o pacote e são atualizados do LiteLLM sob demanda. Para o conjunto de permissões recomendado, fixação de SHA e risco de PR de fork, veja docs/security.md.
Servidor MCP
tokentoll inclui um servidor MCP (Model Context Protocol) para que o Claude Code e outros hosts MCP possam verificar o impacto de custo de mudanças de código LLM de dentro de uma conversa de agente:
pip install tokentoll[mcp]
claude mcp add --transport stdio tokentoll -- tokentoll-mcp
Duas ferramentas são expostas: scan (estimar custos em um caminho) e diff (comparar dois refs). Ambas retornam JSON.
Como funciona
Source code (.py, .ts, .tsx, .js, .jsx)
|
v
+----------------+ +------------------+
| AST scanners |-->| SDK detectors |
| ast (Python) + | | OpenAI, Anthropic|
| tree-sitter | | Google, LiteLLM, |
| (JS/TS) | | LangChain, Zhipu,|
+----------------+ | Vercel AI SDK |
+------------------+
|
v
+------------------+
| Pricing engine |
| 2200+ models |
+------------------+
|
v
+------------------+
| Diff engine |
| (old vs new) |
+------------------+
|
v
+------------------+
| Policy evaluator |
| PASS/WARN/FAIL |
+------------------+
|
v
+------------------+
| PR comment / CLI |
| output |
+------------------+
Um mecanismo de propagação de constantes em múltiplas passagens resolve nomes de modelos através de atribuições de variáveis, fallbacks de os.getenv() / process.env.X, padrões de função, atributos de classe, argumentos de construtor, literais de dict e objeto, desempacotamento de **kwargs e wrappers de provedor do Vercel AI SDK (openai("gpt-4o")), para que código do mundo real com indireção ainda produza estimativas úteis.
Dados de preços
Os preços são incluídos e funcionam offline. Para atualizar do LiteLLM:
tokentoll update
Cobertura: mais de 300 modelos em OpenAI, Anthropic, Google, AWS Bedrock, Azure e outros, além de mais de 2200 entradas do catálogo combinado do LiteLLM.
Limitações
- Apenas análise estática. Modelos carregados de bancos de dados ou configuração remota não podem ser resolvidos; tokentoll recorre ao padrão por SDK configurado e marca o ponto de chamada como
(default). - As estimativas de tokens usam uma heurística de caracteres/4, a menos que tiktoken esteja instalado (
pip install tokentoll[tiktoken]). - As estimativas mensais assumem volume de chamadas uniforme por ponto de chamada. Substitua por projeto com
calls_per_monthou por caminho comoverrides. - A resolução JS/TS é apenas no mesmo arquivo. Importar um nome de modelo de outro módulo produz um ponto de chamada dinâmico em vez de um valor resolvido.
Roadmap
- v0.9: Repositório de demonstração público com um PR com falha conhecida, estudo de caso gpt-researcher, seção de adoção expandida
- Futuro: Inferência de frequência de chamadas sensível ao contexto (rotas FastAPI versus scripts versus loops); resolução de importação entre arquivos para JS/TS
Licença
MIT