Aller au contenu
mcpd
aucun daemon
Source

Comptabilité des coûts

Un chiffre de coût ne vaut que si vous pouvez dire d’où il vient. Voici chaque étape entre un compte de tokens et un nombre à l’écran.

Tarifé une fois, à l’ingestion

Quand un span arrive avec un identifiant de modèle et un compte de tokens, mcpd cherche ce modèle dans une grille tarifaire, calcule ce que l’appel a coûté et stocke le résultat sur le span. Rien n’est recalculé à la lecture.

C’est la décision importante de cette page. Les prix changent. Si le coût était dérivé au moment de la requête depuis la grille chargée ce jour-là, une baisse de tarif le mois prochain réécrirait en silence ce que le mois dernier a coûté, et le nombre que vous avez rapporté à quelqu’un ne serait plus là quand cette personne irait le vérifier. Tarifer à l’ingestion fige le chiffre sur les tarifs qui étaient vrais au moment de l’appel. Chaque coût stocké enregistre aussi l’identifiant de la grille qui l’a produit : un chiffre peut donc être remonté jusqu’aux tarifs qui sont derrière.

Un span qui porte déjà un coût est laissé exactement tel qu’il est arrivé. Si votre fournisseur vous donne un chiffre réel et que vous l’enregistrez, mcpd garde le vôtre et ne tarife pas par-dessus. L’indicateur is_estimated sépare les deux : tout ce que mcpd a dérivé d’une grille est marqué comme estimé, parce qu’il l’est.

Les tarifs sont au million de tokens

Chaque tarif d’une grille est en unités monétaires par million de tokens, ce qui est la façon dont tous les fournisseurs les publient. Un tarif copié depuis une page de prix entre dans une grille sans changement, sans division à rater à trois heures du matin. Le calcul pour un compteur est tokens × rate ÷ 1,000,000.

Le coût vit sur l’étape, pas sur l’exécution

Chaque appel de modèle porte son propre chiffre. Le total d’une exécution est la somme de ses spans, et mcpd recalcule cette somme depuis les spans chaque fois que l’un d’eux arrive, plutôt que d’incrémenter un compteur. Les spans arrivent dans le désordre, sont réessayés et sont mis à jour sur place, et un total incrémenté dérive sous ces trois effets. Un nombre en tête qui ne correspond pas aux lignes en dessous détruit la confiance dans tous les autres nombres de la page.

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.

Les trois appels de modèle ci-dessus sont tarifés à $0.00015, $0.00018 et $0.00021. Le total de l’exécution dans l’en-tête est leur somme et rien d’autre : les appels d’outils, la consultation de mémoire et le garde-fou n’ont consommé aucun token, donc ils ne portent aucun coût et n’en apportent aucun.

Les compteurs

Un appel de modèle n’est pas un seul nombre de tokens. Les fournisseurs facturent plusieurs compteurs à des tarifs différents, et une grille donne un tarif pour chacun.

TarifS’applique àQuand la grille l’omet
input Les tokens d’entrée ordinaires. Obligatoire.
output Les tokens de complétion ordinaires. Obligatoire.
cache_read Les tokens servis depuis un cache de prompt. Facturés au tarif input.
cache_write Les tokens écrits dans un cache de prompt. Facturés au tarif input.
cache_write_5m Les écritures dans un cache de cinq minutes, quand le fournisseur tarife les TTL à part. Se replie sur cache_write, puis sur input.
cache_write_1h Les écritures dans un cache d’une heure. Se replie sur cache_write, puis sur input.
audio_input Les tokens audio en entrée. Facturés au tarif input.
audio_output Les tokens audio en sortie. Facturés au tarif output.
reasoning Les tokens de raisonnement cachés, quand un fournisseur les facture à part. Pas de repli. Ils sont facturés au tarif output dans le total de sortie, ce qui est la façon dont les grands fournisseurs les facturent, sauf si le fournisseur a aussi rapporté un compte text séparé. Voir plus bas.

Un token en cache est facturé une fois

La façon classique de gonfler une facture est de facturer un token en cache deux fois : une fois au tarif du cache, et encore une fois dans le total d’entrée rapporté où il se trouve déjà. Alors, quand un fournisseur rapporte une ventilation, c’est cette ventilation qui l’emporte. mcpd tarife chaque compteur à son propre tarif et ne facture que le reste au tarif d’entrée simple.

Un appel claude-sonnet-4 qui rapporte un million de tokens d’entrée, dont neuf cent mille lus depuis le cache, se tarife ainsi contre la grille intégrée :

  • 900 000 lectures de cache à $0.30 le million, soit $0.27.
  • 100 000 tokens d’entrée restants à $3.00 le million, soit $0.30.
  • $0.57 de coût d’entrée, au lieu des $3.00 qu’aurait produits le total rapporté à lui seul.

Là où un fournisseur donne un compte text explicite, ce compte sert directement de reste. Là où il n’en donne pas, le reste est le total rapporté moins chaque champ mesuré.

Reste un trou qui mérite d’être nommé. Un compteur pour lequel la grille n’a aucun tarif n’est facturé que dans le reste : quand le fournisseur a aussi envoyé un compte text, il n’est donc pas facturé du tout. Les tokens de raisonnement sont dans ce cas dès que votre grille ne leur donne pas leur propre tarif, et en déclarer un le corrige. Les tokens d’image y sont toujours, parce qu’une grille n’a pas de tarif d’image à donner : sur une charge fortement multimodale, lisez le chiffre comme bas plutôt que comme exact.

Un modèle inconnu n’a pas de coût, pas un coût de zéro

Si la grille ne détient aucun tarif pour le modèle, mcpd stocke le span sans aucun champ de coût. Il ne stocke pas un zéro.

Zéro est une affirmation : cet appel était gratuit. Absent est un aveu : nous ne savons pas ce que cet appel a coûté. Les deux mènent à des décisions différentes, et un outil qui rapporte le second comme le premier ment sur une facture. La même règle court dans le chemin de lecture, où une exécution dont les spans ne totalisent rien est renvoyée sans chiffre de coût plutôt qu’avec un coût de zéro : une colonne de coût vide veut donc dire non tarifé et jamais gratuit.

La conséquence pratique mérite d’être intégrée : un modèle absent de la grille est absent de tous les totaux. Sa dépense n’apparaît pas dans /v1/api/analytics/cost, et ses exécutions sont exclues par tout filtre min_cost positif. Un total hebdomadaire qui semble trop bas est en général un identifiant de modèle qui ne se résout pas, pas une semaine calme.

Résoudre un identifiant de modèle

L’identifiant sur un span est celui que votre client a envoyé, et les clients le décorent. Une passerelle préfixe son éditeur, un cloud préfixe sa région, un fournisseur y épingle une date de sortie. Une grille est indexée sur des noms de modèles nus, donc la recherche essaie quatre choses dans l’ordre et s’arrête à la première qui aboutit.

ÉtapeExemple
L’identifiant tel quel, en minuscules GPT-4o trouve gpt-4o
La partie après la dernière barre oblique openai/gpt-4o trouve gpt-4o
La fin pointée d’un identifiant de cloud us.anthropic.claude-sonnet-4 trouve claude-sonnet-4
L’identifiant sans la date finale, avec ou sans son préfixe openai/gpt-4o-2024-08-06 trouve gpt-4o

Les suffixes -YYYY-MM-DD et -YYYYMMDD sont tous deux reconnus. Échouer sur les quatre étapes est une issue normale et non une erreur : cela veut dire aucun coût, comme ci-dessus.

La grille livrée

mcpd embarque une petite grille intégrée pour qu’un docker run produise de vrais chiffres sans aucune configuration. Elle s’appelle builtin, elle tarife en USD, et chaque coût qu’elle produit enregistre ce nom.

ModèleEntréeSortieLecture de cacheÉcriture de cache, 5 minÉcriture de cache, 1 heure
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.25au tarif d’entréeau tarif d’entrée
gpt-4o-mini0.150.600.075au tarif d’entréeau tarif d’entrée
gpt-4.12.008.000.50au tarif d’entréeau tarif d’entrée
gpt-4.1-mini0.401.600.10au tarif d’entréeau tarif d’entrée
o32.008.000.50au tarif d’entréeau tarif d’entrée
o4-mini1.104.400.275au tarif d’entréeau tarif d’entrée
gemini-2.5-pro1.2510.000.31au tarif d’entréeau tarif d’entrée
gemini-2.5-flash0.302.500.075au tarif d’entréeau tarif d’entrée
text-embedding-3-small0.020.00au tarif d’entréeau tarif d’entréeau tarif d’entrée
text-embedding-3-large0.130.00au tarif d’entréeau tarif d’entréeau tarif d’entrée

En USD par million de tokens, tels que la grille se présentait à sa dernière vérification contre un catalogue de modèles tiers, date que la grille enregistre sous as_of. Les entrées d’Anthropic portent les deux tarifs d’écriture de cache parce que le daemon se replie du tarif d’une heure sur celui de cinq minutes, et une grille qui aurait omis le plus long sous-facturerait en silence. OpenAI ne facture rien pour écrire dans son cache, donc une écriture de cache y est facturée au tarif d’entrée. gemini-2.5-flash porte aussi un tarif d’entrée audio de 1.00 ; toutes les autres entrées tarifent l’audio à leur tarif texte. Il n’y a pas de claude-haiku-4 : l’identifiant qu’Anthropic sert est claude-haiku-4-5, et un identifiant qui ne correspond à rien est laissé sans prix plutôt que deviné.

Ce sont des prix publics, pas les vôtres. Si vous avez des tarifs négociés, ou si vous exploitez un modèle absent de ce tableau, les chiffres que mcpd vous montre sont une estimation et doivent se lire comme telle. La grille est délibérément courte : une longue vieillirait à plus d’endroits et donnerait une impression d’exactitude plus forte qu’elle ne le mérite.

Fournir votre propre grille n’a pas encore d’option en ligne de commande. La grille fait partie de l’état du serveur, et la façon de la remplacer aujourd’hui est d’embarquer mcpd comme bibliothèque et d’en passer une à AppState::with_price_book. Une grille est un identifiant, une monnaie ISO 4217 et une table qui associe un identifiant de modèle à des tarifs, et chaque chiffre qu’elle produit est estampillé de son identifiant, pour que deux grilles puissent être distinguées après coup. Les prix d’un même projet devraient partager une seule monnaie : mcpd additionne les spans d’une exécution arithmétiquement et ne convertit pas entre monnaies.

Relire la dépense

GET /v1/api/analytics/cost répond où est parti l’argent sur une fenêtre, sans rien exporter vers un tableur.

curl -s 'http://localhost:4317/v1/api/analytics/cost?from=-7d&group_by=model'
ParamètreSens
from, to La fenêtre. RFC 3339 ou la notation relative abrégée. Vaut par défaut les sept derniers jours, jusqu’à maintenant.
group_by model par défaut. Aussi provider, entity_name, environment, status, session_id, user_id, service_name ou name.
bucket 30s, 5m, 1h ou 1d. Omettez-le pour n’avoir que les totaux ; donnez-le pour obtenir une série temporelle à côté.
project Quel projet lire. Vaut par défaut le projet par défaut du daemon.

où c’est parti

# Où est parti l’argent la semaine dernière, par modèle.
curl -s --get http://localhost:4317/v1/api/analytics/cost \
  --data-urlencode 'from=-7d' \
  --data-urlencode 'group_by=model'

# La même semaine en série quotidienne, pour voir le jour où cela a changé.
curl -s --get http://localhost:4317/v1/api/analytics/cost \
  --data-urlencode 'from=-7d' \
  --data-urlencode 'group_by=model' \
  --data-urlencode 'bucket=1d'

Les groupes reviennent du plus grand au plus petit, plafonnés à cent lignes, et une série découpée en intervalles est plafonnée à cinq mille points.

réponse

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

Une subtilité dans count. Grouper par model, provider ou entity_name lit les spans, donc count est un nombre d’appels de modèle. Grouper par autre chose lit la projection des exécutions, donc count est un nombre d’exécutions. Les deux sont utiles ; ce n’est pas le même nombre, et comparer l’un à l’autre est l’erreur la plus facile à commettre ici.

La latence par groupe dans cette réponse ne porte qu’une moyenne et un maximum. Les centiles viennent de GET /v1/api/analytics/summary, qui les calcule sur la fenêtre.

Où aller ensuite

Le coût est une colonne d’une liste d’exécutions, et cette liste est le point de départ réel d’une enquête : Retrouver une exécution couvre min_cost et le tri par dépense. Si vos chiffres semblent faux parce que des payloads sont jetés avant d’être tarifés, regardez Échantillonnage, qui décide de ce qui est enregistré. Chaque champ nommé sur cette page est listé dans le modèle de données.