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.
- agent support_agent fail
- model chat
- tool returns_policy
- model chat
- tool returns_policy
- model chat
- 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.
| Tariffa | Si applica a | Quando 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.
| Passo | Esempio |
|---|---|
| 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.
| Modello | Input | Output | Lettura da cache | Scrittura in cache, 5 min | Scrittura in cache, 1 ora |
|---|---|---|---|---|---|
claude-opus-4 | 15.00 | 75.00 | 1.50 | 18.75 | 30.00 |
claude-opus-4-1 | 15.00 | 75.00 | 1.50 | 18.75 | 30.00 |
claude-opus-4-5 | 5.00 | 25.00 | 0.50 | 6.25 | 10.00 |
claude-sonnet-4 | 3.00 | 15.00 | 0.30 | 3.75 | 6.00 |
claude-sonnet-4-5 | 3.00 | 15.00 | 0.30 | 3.75 | 6.00 |
claude-haiku-4-5 | 1.00 | 5.00 | 0.10 | 1.25 | 2.00 |
claude-3-5-haiku | 0.80 | 4.00 | 0.08 | 1.00 | 1.60 |
gpt-4o | 2.50 | 10.00 | 1.25 | a input | a input |
gpt-4o-mini | 0.15 | 0.60 | 0.075 | a input | a input |
gpt-4.1 | 2.00 | 8.00 | 0.50 | a input | a input |
gpt-4.1-mini | 0.40 | 1.60 | 0.10 | a input | a input |
o3 | 2.00 | 8.00 | 0.50 | a input | a input |
o4-mini | 1.10 | 4.40 | 0.275 | a input | a input |
gemini-2.5-pro | 1.25 | 10.00 | 0.31 | a input | a input |
gemini-2.5-flash | 0.30 | 2.50 | 0.075 | a input | a input |
text-embedding-3-small | 0.02 | 0.00 | a input | a input | a input |
text-embedding-3-large | 0.13 | 0.00 | a input | a input | a 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' | Parametro | Significato |
|---|---|
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' import { Mcpd } from '@mcpd/sdk'
const mcpd = new Mcpd()
const spend = await mcpd.analyticsCost({
from: '-7d',
to: '-0s',
groupBy: 'model',
bucket: '1d',
})
console.log(`${spend.currency} ${spend.totalCost.toFixed(4)} over the window`)
for (const group of spend.groups) {
console.log(group.key, group.totalCost, group.inputTokens, group.outputTokens)
} from mcpd import Mcpd
mcpd = Mcpd()
spend = mcpd.analytics_cost("-7d", "-0s", group_by="model", bucket="1d")
print(f"{spend.currency} {spend.total_cost:.4f} over the window")
for group in spend.groups:
print(group.key, group.total_cost, group.input_tokens, group.output_tokens) 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.