Ir para o conteúdo
mcpd
sem daemon
Código

Apuração de custos

Um valor de custo só vale a pena se você consegue dizer de onde ele veio. Aqui está cada passo entre uma contagem de tokens e um número na tela.

Precificado uma vez, na ingestão

Quando um span chega trazendo um id de modelo e uma contagem de tokens, o mcpd procura esse modelo em uma tabela de preços, calcula o que a chamada custou e guarda o resultado no span. Nada é recalculado na leitura.

Essa é a decisão importante desta página. Preços mudam. Se o custo fosse derivado na hora da consulta, a partir da tabela que estivesse carregada naquele dia, um corte de tarifa no mês que vem reescreveria em silêncio o que o mês passado custou, e o número que você informou a alguém não estaria lá quando essa pessoa fosse conferir. Precificar na ingestão congela o valor contra as tarifas que valiam quando a chamada foi feita. Todo custo armazenado também registra o id da tabela que o produziu, então um valor pode ser rastreado até as tarifas que estão por trás dele.

Um span que já chega com um custo é deixado exatamente como chegou. Se o seu provedor entrega um valor real e você o registra, o mcpd mantém o seu e não precifica por cima. O campo is_estimated separa os dois: tudo o que o mcpd derivou de uma tabela é marcado como estimado, porque é.

As tarifas são por milhão de tokens

Toda tarifa em uma tabela de preços é em unidades da moeda por milhão de tokens, que é como todo provedor as publica. Uma tarifa copiada de uma página de preços entra na tabela sem mudança, sem nenhuma divisão para você errar às três da manhã. A aritmética de um medidor é tokens × tarifa ÷ 1.000.000.

O custo fica na etapa, não na execução

Cada chamada de modelo carrega o próprio valor. O total de uma execução é a soma dos spans dela, e o mcpd recalcula essa soma a partir dos spans toda vez que um deles chega, em vez de incrementar um contador. Spans chegam fora de ordem, são reenviados e são atualizados no lugar, e um total que só incrementa desanda nos três casos. Um número de destaque que não bate com as linhas abaixo dele destrói a confiança em todos os outros números da página.

Trace b3d9e71a Fault
0ms 2.1s 4.2s 6.3s 8.4s
  1. agent support_agent fail
  2. model chat
  3. tool returns_policy
  4. model chat
  5. tool returns_policy
  6. model chat
  7. tool returns_policy

The model called one tool three times and never answered. That is visible in four seconds here, and invisible in a log file.

As três chamadas de modelo acima estão precificadas em $0.00015, $0.00018 e $0.00021. O total da execução no cabeçalho é a soma delas e nada mais: as chamadas de ferramenta, a consulta à memória e o guardrail não consumiram tokens, então não carregam custo e não contribuem com nada.

Os medidores

Uma chamada de modelo não é um número só de tokens. Provedores cobram vários medidores a tarifas diferentes, e uma tabela de preços dá uma tarifa para cada um deles.

TarifaVale paraQuando a tabela a omite
input Tokens de entrada comuns. Obrigatória.
output Tokens de resposta comuns. Obrigatória.
cache_read Tokens servidos de um cache de prompt. Cobrados como input.
cache_write Tokens gravados em um cache de prompt. Cobrados como input.
cache_write_5m Gravações em um cache de cinco minutos, onde o provedor cobra os TTLs à parte. Recua para cache_write, depois para input.
cache_write_1h Gravações em um cache de uma hora. Recua para cache_write, depois para input.
audio_input Tokens de áudio de entrada. Cobrados como input.
audio_output Tokens de áudio de saída. Cobrados como output.
reasoning Tokens de raciocínio ocultos, onde um provedor os cobra à parte. Sem recuo. Eles são cobrados como output, dentro do total de saída, que é como os grandes provedores os cobram, a menos que o provedor também tenha informado uma contagem text separada. Veja abaixo.

Um token em cache é cobrado uma vez

O jeito clássico de inflar uma conta é cobrar um token em cache duas vezes: uma na tarifa de cache, e de novo como parte do total de entrada informado, dentro do qual ele já está. Então, quando um provedor informa um detalhamento por medidor, esse detalhamento vence. O mcpd precifica cada medidor na tarifa dele e cobra apenas o restante na tarifa de entrada comum.

Uma chamada a claude-sonnet-4 informando um milhão de tokens de entrada, dos quais novecentos mil foram lidos do cache, é precificada contra a tabela embutida assim:

  • 900.000 leituras de cache a $0.30 por milhão, o que dá $0.27.
  • 100.000 tokens de entrada restantes a $3.00 por milhão, o que dá $0.30.
  • $0.57 de custo de entrada, em vez dos $3.00 que o total informado, sozinho, teria produzido.

Onde um provedor dá uma contagem text explícita, essa contagem é usada diretamente como o restante. Onde ele não dá, o restante é o total informado menos todos os campos com medidor.

Isso deixa uma lacuna que vale nomear. Um medidor para o qual a tabela não tem tarifa só é cobrado como parte do restante, então, quando o provedor também mandou uma contagem text, ele não é cobrado de jeito nenhum. Tokens de raciocínio caem aí sempre que a sua tabela não lhes dá uma tarifa própria, e declarar uma resolve isso. Tokens de imagem caem aí sempre, porque uma tabela não tem tarifa de imagem para dar, então, em uma carga de trabalho bastante multimodal, leia o valor como baixo, e não como exato.

Um modelo desconhecido não tem custo, e não um custo de zero

Se a tabela não tem tarifas para o modelo, o mcpd guarda o span sem campo de custo nenhum. Ele não guarda um zero.

Zero é uma afirmação: esta chamada foi gratuita. Ausente é uma admissão: não sabemos o que esta chamada custou. As duas levam a decisões diferentes, e uma ferramenta que informa a segunda como se fosse a primeira está mentindo sobre uma conta. A mesma regra atravessa o caminho de leitura, onde uma execução cujos spans somam nada é devolvida sem valor de custo, em vez de com um custo de zero, então uma coluna de custo em branco quer dizer não precificado e nunca gratuito.

Vale internalizar a consequência prática: um modelo que falta na tabela falta em todo total. O gasto dele não aparece em /v1/api/analytics/cost, e as execuções dele são excluídas por qualquer filtro min_cost positivo. Um total semanal que parece baixo demais costuma ser um id de modelo que não está resolvendo, e não uma semana fraca.

Como um id de modelo é resolvido

O id em um span é o que o seu cliente enviou, e clientes enfeitam esse id. Um gateway prefixa o fornecedor dele, uma nuvem prefixa a região dela, um provedor fixa uma data de lançamento. Uma tabela é indexada por nomes de modelo puros, então a busca tenta quatro coisas em ordem e para na primeira que acerta.

PassoExemplo
O id como veio, sem diferenciar maiúsculas GPT-4o encontra gpt-4o
A parte depois da última barra openai/gpt-4o encontra gpt-4o
O final com pontos de um id de nuvem us.anthropic.claude-sonnet-4 encontra claude-sonnet-4
O id com a data do fim removida, com ou sem o prefixo openai/gpt-4o-2024-08-06 encontra gpt-4o

Os sufixos -YYYY-MM-DD e -YYYYMMDD são os dois reconhecidos. Falhar nos quatro passos é um desfecho normal, e não um erro: quer dizer nenhum custo, como acima.

A tabela que vem junto

O mcpd carrega uma pequena tabela embutida para que um docker run produza valores reais sem configuração nenhuma. Ela se chama builtin, precifica em USD, e todo custo que ela produz registra esse nome.

ModeloEntradaSaídaLeitura de cacheGravação de cache, 5 minGravação de cache, 1 hora
claude-opus-415.0075.001.5018.7530.00
claude-opus-4-115.0075.001.5018.7530.00
claude-opus-4-55.0025.000.506.2510.00
claude-sonnet-43.0015.000.303.756.00
claude-sonnet-4-53.0015.000.303.756.00
claude-haiku-4-51.005.000.101.252.00
claude-3-5-haiku0.804.000.081.001.60
gpt-4o2.5010.001.25como inputcomo input
gpt-4o-mini0.150.600.075como inputcomo input
gpt-4.12.008.000.50como inputcomo input
gpt-4.1-mini0.401.600.10como inputcomo input
o32.008.000.50como inputcomo input
o4-mini1.104.400.275como inputcomo input
gemini-2.5-pro1.2510.000.31como inputcomo input
gemini-2.5-flash0.302.500.075como inputcomo input
text-embedding-3-small0.020.00como inputcomo inputcomo input
text-embedding-3-large0.130.00como inputcomo inputcomo input

USD por milhão de tokens, como a tabela estava na última vez em que foi conferida contra um catálogo de modelos de terceiros, o que a tabela registra como a data as_of dela. As entradas da Anthropic trazem as duas tarifas de gravação de cache porque o daemon recua da tarifa de uma hora para a de cinco minutos, e uma tabela que omitisse a mais longa cobraria a menos em silêncio. A OpenAI não cobra nada para gravar no cache dela, então uma gravação de cache ali é cobrada na tarifa de entrada. O gemini-2.5-flash também traz uma tarifa de áudio de entrada de 1.00; toda outra entrada precifica áudio na tarifa de texto dela. Não existe claude-haiku-4: o id que a Anthropic serve é claude-haiku-4-5, e um id que não resolve para nada fica sem preço em vez de adivinhado.

Estes são preços de tabela publicados, não os seus preços. Se você negociou tarifas, ou roda um modelo que não está nesta tabela, os valores que o mcpd mostra são uma estimativa e devem ser lidos como tal. A tabela é curta de propósito: uma tabela longa envelheceria em mais lugares e passaria uma impressão de exatidão maior do que ela merece.

Fornecer a sua própria tabela ainda não tem uma opção de linha de comando. A tabela faz parte do estado do servidor, e o jeito de trocá-la hoje é embutir o mcpd como biblioteca e passar uma para AppState::with_price_book. Uma tabela é um id, uma moeda ISO 4217 e um mapa de id de modelo para tarifas, e todo valor que ela produz é carimbado com o id dela, para que duas tabelas possam ser distinguidas depois do fato. Os preços de um mesmo projeto deveriam compartilhar uma moeda; o mcpd soma os spans de uma execução aritmeticamente e não converte entre moedas.

Ler o dinheiro de volta

O GET /v1/api/analytics/cost responde para onde o dinheiro foi ao longo de uma janela, sem exportar nada para uma planilha.

curl -s 'http://localhost:4317/v1/api/analytics/cost?from=-7d&group_by=model'
ParâmetroSignificado
from, to A janela. RFC 3339 ou a forma relativa abreviada. O padrão são os últimos sete dias, terminando agora.
group_by model por padrão. Também provider, entity_name, environment, status, session_id, user_id, service_name ou name.
bucket 30s, 5m, 1h ou 1d. Omita para ter só os totais; informe para ter uma série temporal ao lado deles.
project Qual projeto ler. O padrão é o projeto padrão do daemon.

para onde foi

# Where the money went last week, by model.
curl -s --get http://localhost:4317/v1/api/analytics/cost \
  --data-urlencode 'from=-7d' \
  --data-urlencode 'group_by=model'

# The same week as a daily series, so you can see the day it changed.
curl -s --get http://localhost:4317/v1/api/analytics/cost \
  --data-urlencode 'from=-7d' \
  --data-urlencode 'group_by=model' \
  --data-urlencode 'bucket=1d'

Os grupos voltam do maior para o menor, limitados a cem linhas, e uma série por bucket é limitada a cinco mil pontos.

resposta

{
  "from": "2026-08-20T09:00:00Z",
  "to": "2026-08-27T09:00:00Z",
  "currency": "USD",
  "total_cost": 12.418264,
  "groups": [
    {
      "key": "claude-sonnet-4",
      "count": 1841,
      "error_count": 12,
      "total_cost": 9.612055,
      "input_tokens": 2903118,
      "output_tokens": 214477,
      "latency": { "mean": 1840.2, "max": 24193.0 }
    },
    {
      "key": "gpt-4o-mini",
      "count": 6204,
      "error_count": 3,
      "total_cost": 2.806209,
      "input_tokens": 9114882,
      "output_tokens": 388310,
      "latency": { "mean": 612.4, "max": 8801.0 }
    }
  ]
}

Uma sutileza no count. Agrupar por model, provider ou entity_name lê os spans, então count é um número de chamadas de modelo. Agrupar por qualquer outra coisa lê a projeção da execução, então count é um número de execuções. Os dois são úteis; eles não são o mesmo número, e comparar um com o outro é o erro mais fácil de cometer aqui.

A latência por grupo nesta resposta traz apenas uma média e um máximo. Os percentis vêm do GET /v1/api/analytics/summary, que os calcula sobre a janela.

Para onde ir depois

O custo é uma coluna em uma lista de execuções, e a lista de execuções é onde uma investigação de fato começa: Encontrar uma execução cobre o min_cost e a ordenação por gasto. Se os seus valores parecem errados porque payloads estão sendo descartados antes de serem precificados, veja Amostragem, que decide o que é registrado. Todo campo citado nesta página está listado no modelo de dados.