Vai al contenuto
mcpd
nessun daemon
Codice

Contabilità dei costi

Una cifra di costo vale la pena averla solo se sai dire da dove viene. Ecco ogni passo fra un conteggio di token e un numero sullo schermo.

Valorizzata una volta sola, all’ingest

Quando arriva uno span che porta l’id di un modello e un conteggio di token, mcpd cerca quel modello in un listino, calcola quanto è costata la chiamata e archivia il risultato sullo span. In lettura non viene ricalcolato niente.

È la decisione che conta, in questa pagina. I prezzi cambiano. Se il costo fosse ricavato al momento della query dal listino che quel giorno si trova caricato, un taglio di tariffa il mese prossimo riscriverebbe in silenzio quanto è costato il mese scorso, e il numero che hai riferito a qualcuno non ci sarebbe più quando quel qualcuno va a controllarlo. Valorizzare all’ingest congela la cifra contro le tariffe che valevano quando la chiamata è stata fatta. Ogni costo archiviato registra anche l’id del listino che lo ha prodotto, così una cifra si può ricondurre alle tariffe che ci stanno dietro.

Uno span che porta già un costo viene lasciato esattamente come è arrivato. Se il tuo provider ti dà una cifra vera e tu la registri, mcpd tiene la tua e non ci scrive sopra la sua. Il flag is_estimated separa i due casi: tutto quello che mcpd ha ricavato da un listino è segnato come stimato, perché lo è.

Le tariffe sono per milione di token

Ogni tariffa di un listino è in unità di valuta per milione di token, che è come le pubblicano tutti i provider. Una tariffa copiata da una pagina dei prezzi entra nel listino così com’è, senza nessuna divisione da sbagliare alle tre di notte. Il conto per un contatore è token × tariffa ÷ 1.000.000.

Il costo sta sul passo, non sull’esecuzione

Ogni chiamata al modello porta la sua cifra. Il totale di un’esecuzione è la somma dei suoi span, e mcpd ricalcola quella somma dagli span ogni volta che ne arriva uno, invece di incrementare un contatore. Gli span arrivano fuori ordine, vengono ritentati e vengono aggiornati sul posto, e un totale che si incrementa va alla deriva sotto tutte e tre le cose. Un numero di testata che non torna con le righe che ci stanno sotto distrugge la fiducia in ogni altro numero della pagina.

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.

Le tre chiamate al modello qui sopra sono valorizzate a $0.00015, $0.00018 e $0.00021. Il totale dell’esecuzione nell’intestazione è la loro somma e nient’altro: le chiamate agli strumenti, la ricerca in memoria e il guardrail non hanno consumato token, quindi non portano nessun costo e non ne aggiungono.

I contatori

Una chiamata al modello non è un solo numero di token. I provider fatturano più contatori a tariffe diverse, e un listino dà una tariffa per ognuno di essi.

TariffaSi applica aQuando il listino non ce l’ha
input Token di input ordinari. Obbligatoria.
output Token di completion ordinari. Obbligatoria.
cache_read Token serviti da una cache di prompt. Addebitati a input.
cache_write Token scritti dentro una cache di prompt. Addebitati a input.
cache_write_5m Scritture su una cache da cinque minuti, dove il provider valorizza i TTL a parte. Ripiega su cache_write, poi su input.
cache_write_1h Scritture su una cache da un’ora. Ripiega su cache_write, poi su input.
audio_input Token di input audio. Addebitati a input.
audio_output Token di output audio. Addebitati a output.
reasoning Token di ragionamento nascosti, dove un provider li fattura a parte. Nessun ripiego. Vengono addebitati a output come parte del totale di output, che è come li fatturano i provider principali, a meno che il provider non abbia riportato anche un conteggio text separato. Vedi qui sotto.

Un token in cache si addebita una volta sola

Il modo classico di gonfiare una fattura è addebitare due volte un token in cache: una alla tariffa di cache, e una come parte del totale di input riportato, dentro cui sta già. Quindi quando un provider riporta una ripartizione, quella ripartizione vince. mcpd valorizza ogni contatore alla sua tariffa e addebita alla tariffa di input semplice solo quello che resta.

Una chiamata a claude-sonnet-4 che riporta un milione di token di input, di cui novecentomila letti dalla cache, contro il listino integrato si valorizza così:

  • 900.000 letture da cache a $0.30 per milione, che fanno $0.27.
  • 100.000 token di input rimanenti a $3.00 per milione, che fanno $0.30.
  • $0.57 di costo di input, invece dei $3.00 che il totale riportato avrebbe prodotto da solo.

Dove un provider dà un conteggio text esplicito, quel conteggio è usato direttamente come quello che resta. Dove non lo dà, quello che resta è il totale riportato meno ogni campo contato.

Rimane un buco che vale la pena nominare. Un contatore per cui il listino non tiene nessuna tariffa viene addebitato solo come parte di quello che resta, quindi, quando il provider ha mandato anche un conteggio text, non viene addebitato affatto. I token di ragionamento stanno lì ogni volta che il tuo listino non dà loro una tariffa propria, e dichiararne una lo sistema. I token delle immagini ci stanno sempre, perché un listino non ha nessuna tariffa per le immagini da dare, quindi su un carico fortemente multimodale leggi la cifra come bassa, non come esatta.

Un modello sconosciuto non ha costo, non ha un costo di zero

Se il listino non tiene nessuna tariffa per il modello, mcpd archivia lo span senza nessun campo di costo. Non archivia uno zero.

Zero è un’affermazione: questa chiamata era gratis. Assente è un’ammissione: non sappiamo quanto è costata questa chiamata. Portano a decisioni diverse, e uno strumento che riporta la seconda come la prima sta mentendo su una fattura. La stessa regola attraversa il percorso di lettura, dove un’esecuzione i cui span non sommano niente viene restituita senza cifra di costo, invece che con un costo di zero, così una colonna di costo vuota vuol dire senza prezzo e mai gratis.

La conseguenza pratica va tenuta a mente: un modello che manca dal listino manca da ogni totale. La sua spesa non compare in /v1/api/analytics/cost, e le sue esecuzioni restano fuori da qualunque filtro min_cost positivo. Un totale settimanale che sembra troppo basso di solito è l’id di un modello che non si risolve, non una settimana tranquilla.

Risolvere l’id di un modello

L’id su uno span è quello che ha mandato il tuo client, e i client lo decorano. Un gateway ci mette davanti il fornitore, un cloud ci mette davanti la regione, un provider ci appunta una data di rilascio. Un listino è indicizzato su nomi di modello nudi, quindi la ricerca prova quattro cose in ordine e si ferma alla prima che va a segno.

PassoEsempio
L’id così com’è, senza distinzione fra maiuscole e minuscole GPT-4o trova gpt-4o
La parte dopo l’ultima barra openai/gpt-4o trova gpt-4o
La coda puntata di un id di cloud us.anthropic.claude-sonnet-4 trova claude-sonnet-4
L’id con la data finale tolta, con o senza il suo prefisso openai/gpt-4o-2024-08-06 trova gpt-4o

Vengono riconosciuti sia i suffissi -YYYY-MM-DD sia i suffissi -YYYYMMDD. Fallire tutti e quattro i passi è un esito normale e non un errore: vuol dire nessun costo, come sopra.

Il listino che arriva insieme

mcpd porta un piccolo listino integrato, così docker run produce cifre vere senza nessuna configurazione. Si chiama builtin, valorizza in USD, e ogni costo che produce registra quel nome.

ModelloInputOutputLettura da cacheScrittura in cache, 5 minScrittura in cache, 1 ora
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.25a inputa input
gpt-4o-mini0.150.600.075a inputa input
gpt-4.12.008.000.50a inputa input
gpt-4.1-mini0.401.600.10a inputa input
o32.008.000.50a inputa input
o4-mini1.104.400.275a inputa input
gemini-2.5-pro1.2510.000.31a inputa input
gemini-2.5-flash0.302.500.075a inputa input
text-embedding-3-small0.020.00a inputa inputa input
text-embedding-3-large0.130.00a inputa inputa input

USD per milione di token, come stava il listino l’ultima volta che è stato confrontato con un catalogo di modelli di terze parti, data che il listino registra come il suo as_of. Le voci di Anthropic portano tutte e due le tariffe di scrittura in cache perché il daemon ripiega dalla tariffa da un’ora a quella da cinque minuti, e un listino che tralasciasse la più lunga fatturerebbe in difetto senza dirlo. OpenAI non fa pagare niente per scrivere nella sua cache, quindi lì una scrittura in cache è fatturata alla tariffa di input. gemini-2.5-flash porta anche una tariffa di input audio di 1.00; ogni altra voce valorizza l’audio alla sua tariffa di testo. Non c’è nessun claude-haiku-4: l’id che Anthropic serve è claude-haiku-4-5, e un id che non si risolve a niente resta senza prezzo invece di essere indovinato.

Questi sono prezzi di listino pubblicati, non i tuoi prezzi. Se hai tariffe negoziate, o se fai girare un modello che in questa tabella non c’è, le cifre che mcpd ti mostra sono una stima e vanno lette come tali. Il listino è corto di proposito: uno lungo invecchierebbe in più punti e darebbe un’impressione di precisione più forte di quella che si merita.

Fornire il tuo listino non ha ancora un’opzione da riga di comando. Il listino fa parte dello stato del server, e oggi il modo per sostituirlo è incorporare mcpd come libreria e passarne uno a AppState::with_price_book. Un listino è un id, una valuta ISO 4217 e una mappa da id di modello a tariffe, e ogni cifra che produce porta impresso il suo id, così due listini si possono distinguere a cose fatte. I prezzi dentro un progetto dovrebbero condividere una valuta sola; mcpd somma gli span di un’esecuzione per aritmetica e non converte fra valute.

Rileggere i soldi

GET /v1/api/analytics/cost risponde a dove sono andati i soldi lungo una finestra, senza esportare niente su un foglio di calcolo.

curl -s 'http://localhost:4317/v1/api/analytics/cost?from=-7d&group_by=model'
ParametroSignificato
from, to La finestra. RFC 3339 oppure la forma breve relativa. Se non dici altro, gli ultimi sette giorni, che finiscono adesso.
group_by model se non dici altro. Anche provider, entity_name, environment, status, session_id, user_id, service_name o name.
bucket 30s, 5m, 1h o 1d. Lascialo fuori per avere solo i totali; passalo per avere accanto una serie temporale.
project Quale progetto leggere. Se non dici altro, il progetto predefinito del daemon.

dove sono andati

# Dove sono andati i soldi la settimana scorsa, per modello.
curl -s --get http://localhost:4317/v1/api/analytics/cost \
  --data-urlencode 'from=-7d' \
  --data-urlencode 'group_by=model'

# La stessa settimana come serie giornaliera, per vedere il giorno in cui è cambiata.
curl -s --get http://localhost:4317/v1/api/analytics/cost \
  --data-urlencode 'from=-7d' \
  --data-urlencode 'group_by=model' \
  --data-urlencode 'bucket=1d'

I gruppi tornano indietro dal più grande, con un tetto di cento righe, e una serie a intervalli ha un tetto di cinquemila punti.

risposta

{
  "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 }
    }
  ]
}

Una sottigliezza dentro count. Raggruppare per model, provider o entity_name legge gli span, quindi count è un numero di chiamate al modello. Raggruppare per qualunque altra cosa legge la proiezione delle esecuzioni, quindi count è un numero di esecuzioni. Servono tutti e due; non sono lo stesso numero, e confrontare l’uno con l’altro è l’errore più facile da fare qui dentro.

La latenza per gruppo in questa risposta porta solo una media e un massimo. I percentili vengono da GET /v1/api/analytics/summary, che li calcola sulla finestra.

Dove andare adesso

Il costo è una colonna in un elenco di esecuzioni, ed è dall’elenco delle esecuzioni che parte davvero un’indagine: Trovare un’esecuzione copre min_cost e l’ordinamento per spesa. Se le tue cifre sembrano sbagliate perché i payload vengono scartati prima di essere valorizzati, controlla il Campionamento, che decide che cosa viene registrato. Ogni campo nominato in questa pagina è elencato nel modello dei dati.