1. Pourquoi « ça fonctionne » ≠ « c’est rentable »
Les applications LLM ont une particularité importante : en plus des coûts fixes d’hébergement, elles ont souvent des coûts variables pour l’exécution de certaines requêtes, liés aux appels de modèles.
Il est important de distinguer deux mondes :
- lorsque le modèle fonctionne du côté de ChatGPT (l’utilisateur interagit avec votre App dans ChatGPT, qui appelle mcp-tools) — les jetons sont payés par l’utilisateur via son abonnement ChatGPT ;
- lorsque votre backend/serveur MCP appelle lui-même l’API OpenAI ou d’autres services LLM — ces jetons sont à votre charge.
C’est dans le second cas que vous avez des coûts LLM variables classiques, dépendant du nombre et de la « lourdeur » (tokens_in/tokens_out) des requêtes.
Scénario classique :
- Vous déployez GiftGenius en prod, tout va vite, les utilisateurs sont ravis.
- Un mois plus tard arrive la facture OpenAI + cloud + commissions Stripe, et on découvre soudain que « croissance réussie » signifie en réalité « nous payons plus pour chaque cadeau que ce que nous gagnons sur la vente ».
L’approche FinOps (FinOps) dit : le coût est une métrique comme la latence ou le taux d’erreur. Il faut la consigner, l’agréger et s’en servir pour décider, pas « deviner dans Excel ».
L’objectif de cette leçon est de vous permettre de répondre à des questions du type :
- « Combien a coûté cette sélection de cadeaux pour l’utilisateur user42 ? »
- « Combien d’argent l’outil suggest_gifts a-t-il brûlé cette semaine et combien de commandes a-t-il générées ? »
Et pour que les réponses viennent des journaux et métriques, pas de l’intuition.
2. Structure des coûts d’une application ChatGPT
Commençons par la carte des dépenses. Sans elle, tout le reste n’est qu’une collecte chaotique de chiffres.
Coûts LLM (variables)
C’est tout ce qui est lié aux appels de modèles depuis votre backend :
- Appels de modèles OpenAI depuis un serveur MCP ou des agents : GPT-5.1 / GPT-5-mini / embeddings / rerank / vision / TTS/STT, etc.
- Modèles additionnels : reranking pour la recherche, embeddings pour les recommandations, génération d’images.
À noter : lorsque vous construisez l’interface via l’Apps SDK et n’utilisez que le modèle intégré de ChatGPT, vous ne payez pas les jetons — c’est l’utilisateur qui paie (via son abonnement ChatGPT). Mais dès que votre serveur MCP appelle lui-même l’API OpenAI (Agents, Responses API, embeddings, etc.), les jetons sont facturés sur votre compte.
Idée de base : le coût de ces appels est proportionnel à tokens_in et tokens_out, multipliés par le prix par jeton.
Un appel d’outil MCP, en soi, est gratuit pour le développeur du point de vue des jetons ; les dépenses n’apparaissent que là où, dans son handler, vous décidez d’appeler l’API OpenAI ou un autre LLM.
Infrastructure
Tout le matériel et les services autour :
- Serveurs MCP : Vercel / AWS / GCP / bare metal.
- Agents (s’ils tournent comme services séparés).
- Bases de données : Postgres/MySQL, bases vectorielles, S3/stockage objet.
- Caches : Redis/KeyDB.
- Files et workers : par exemple, pour la génération en tâche de fond, le recalcul de feeds, etc.
Ces coûts sont souvent fixes au mois (ou fixes par paliers), et on les calcule donc généralement à partir des dépenses agrégées des services cloud, pas par requête.
Paiements et services externes
GiftGenius utilise ACP/Stripe, d’où l’apparition de :
- Commissions pour chaque paiement réussi (Stripe à quelques pourcents + une partie fixe).
- Pertes dues à la fraude et aux chargebacks.
- Coût d’API externes : e‑mail / SMS / notifications push, analytics additionnelle, etc.
Au début, c’est insignifiant, mais à l’échelle cela se ressent ; il est utile de les isoler au minimum dans les journaux et rapports.
Petit tableau mémo
| Catégorie | Exemples | Calcul approximatif |
|---|---|---|
| LLM | GPT‑5.1, GPT‑5‑mini, embeddings, rerank | |
| Infrastructure | MCP, agents, BD, Redis, files/queues, CDN | Répartir la facture fournisseur par trafic/période |
| Paiements et services | Stripe, e‑mail API, SMS, analytics | Nb d’événements × tarif/commission |
Notre objectif : lier ces catégories à des événements concrets du système (appels d’outils, workflow, checkout), et pas seulement regarder les montants mensuels finaux.
3. Où capter les données d’usage : trois couches
Pour calculer le coût non pas « une fois par mois », mais en temps réel, il faut intégrer de l’instrumentation dans le code. Il y a trois endroits.
Serveur MCP : chaque appel d’outil
Le serveur MCP est un point naturel par lequel ChatGPT appelle vos tools. Ici, nous pouvons :
- Repérer le début/la fin de l’appel.
- Mesurer duration_ms (ou latency_ms).
- Récupérer les jetons de la réponse OpenAI (si notre modèle est appelé via le MCP) ou au moins les estimer.
- Renseigner user_id, tenant_id, request_id/trace_id pour relier les logs.
Schématiquement, l’événement de log tool_invocation pour GiftGenius ressemble à :
{
"timestamp": "2025-11-20T12:34:56Z",
"level": "info",
"event": "tool_invocation",
"request_id": "abc123",
"user_id": "user42",
"service": "mcp-giftgenius",
"tool_name": "suggest_gifts",
"tokens_in": 120,
"tokens_out": 350,
"cost_estimate_usd": 0.045,
"latency_ms": 320
}
La même chose sous forme de type TypeScript et d’un extrait de code.
// types/telemetry.ts
export interface ToolInvocationLog {
event: 'tool_invocation';
requestId: string;
userId?: string;
toolName: string;
tokensIn?: number;
tokensOut?: number;
costEstimateUsd?: number;
latencyMs: number;
}
// mcp/logger.ts
export function logToolInvocation(payload: ToolInvocationLog) {
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
level: 'info',
...payload,
}));
}
Et maintenant, un wrapper autour du handler d’un outil MCP (disons suggest_gifts).
// mcp/tools/suggestGifts.ts
export async function handleSuggestGifts(ctx: Context, input: Input) {
const started = Date.now();
const llmResult = await callGiftModel(input); // appel à OpenAI ici
const duration = Date.now() - started;
const { prompt_tokens, completion_tokens } = llmResult.usage ?? {};
const costEstimate = estimateCost(prompt_tokens, completion_tokens);
logToolInvocation({
event: 'tool_invocation',
requestId: ctx.requestId,
userId: ctx.userId,
toolName: 'suggest_gifts',
tokensIn: prompt_tokens,
tokensOut: completion_tokens,
costEstimateUsd: costEstimate,
latencyMs: duration,
});
return llmResult.output;
}
Même si vous estimez les jetons « à la louche » via la longueur du texte, c’est déjà mieux que rien.
Niveau agent (Agents SDK) : étapes du workflow
Si vous utilisez l’Agents SDK, l’agent peut appeler plusieurs outils à la suite. Il est important de journaliser le contexte de l’étape : quelle tâche l’agent essaie-t-il de résoudre.
Par exemple, à chaque appel d’outil du runner de l’agent, on peut ajouter les champs workflow_name et step_name : « recherche d’idées », « filtrage par budget », « préparation du checkout ».
Cela permettra ensuite de faire des rapports non seulement par outil, mais aussi par étape du scénario : peut-être que 80 % du coût partent dans une « étape d’affinage supplémentaire » inutile.
Exemple d’un petit « hook » autour de l’agent :
// agents/logStep.ts
export function logAgentStep(data: {
requestId: string;
workflow: string;
step: string;
toolName: string;
}) {
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
level: 'info',
event: 'agent_step',
...data,
}));
}
Et l’utiliser depuis le runner :
// agents/giftAgent.ts
logAgentStep({
requestId: run.requestId,
workflow: 'gift_selection',
step: 'rank_candidates',
toolName: 'rerank_gifts',
});
Commerce : checkout et argent
Au niveau commerce, les événements qui nous intéressent :
- checkout_started — début de l’achat.
- checkout_success — paiement réussi.
- checkout_failed — erreur avec code/type.
Et il faut y attacher :
- amount, currency.
- request_id de la même session que tool_invocation.
Nous pourrons alors répondre : « Cet achat nous a coûté N cents en coûts LLM et a rapporté M dollars de chiffre d’affaires. »
Exemple d’un simple handler d’événements de checkout :
// api/commerce/logCheckout.ts
export function logCheckoutEvent(e: {
type: 'checkout_started' | 'checkout_success' | 'checkout_failed';
requestId: string;
userId?: string;
amountCents?: number;
currency?: string;
errorCode?: string;
}) {
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
level: 'info',
service: 'commerce',
...e,
}));
}
4. Journaux structurés pour le coût (liaison avec M17)
Point clé : pas de journaux « libres » en texte du genre console.log("Tool suggest_gifts used 123 tokens"). Tout en JSON.
Dans le module 17, nous avons déjà convenu de journaliser les requêtes en JSON avec des champs de base tels que request_id, user_id, tool_name, etc. Désormais nous ajoutons les champs liés au coût par-dessus.
Champs qui doivent impérativement figurer dans les logs liés aux coûts :
- timestamp, level.
- event (tool_invocation, agent_step, checkout_success, etc.).
- request_id, trace_id — pour relier la chaîne d’événements d’un même workflow.
- user_id, tenant_id — pour agréger par utilisateurs/équipes.
- tool_name / service.
- tokens_in, tokens_out, cost_estimate_usd.
- latency_ms, success/error_code.
Dans les exemples, nous appellerons le champ de coût cost_estimate_usd (coût en dollars US) et nous garderons ce nom dans le code et les dashboards.
Cette structure permet de :
- Construire des agrégats : moyenne de cost_estimate_usd par tool_name, par user_id, par workflow.
- Corréler les requêtes « chères » avec une latence accrue ou des erreurs, et décider quoi optimiser en premier.
Si vous avez déjà, dans M17, implémenté un logger.info({...}) de base, ajouter des champs liés au coût n’est pas un nouveau framework, mais juste quelques propriétés supplémentaires.
5. Comment estimer approximativement le coût LLM dans le code
Les formules ne font pas peur. Nous avons seulement besoin de l’ordre de grandeur, pas d’une correspondance parfaite avec la facturation au centime près.
Récupérer usage à partir de la réponse OpenAI
Quand votre serveur MCP appelle l’OpenAI Response API, il reçoit généralement un objet usage :
{
"usage": {
"prompt_tokens": 120,
"completion_tokens": 350,
"total_tokens": 470
}
}
C’est pratique pour calculer le coût. Différents modèles ont des prix différents par 1 M de jetons d’entrée/sortie.
Fonction d’estimation simple en TypeScript :
// mcp/cost.ts
type Usage = { prompt_tokens?: number; completion_tokens?: number };
const PRICING = {
inputPerMillion: 2.5, // dollars pour 1M de jetons d’entrée, exemple
outputPerMillion: 10.0, // pour la sortie
};
export function estimateCost(
promptTokens?: number,
completionTokens?: number,
): number {
const inTokens = promptTokens ?? 0;
const outTokens = completionTokens ?? 0;
const inputCost = (inTokens / 1_000_000) * PRICING.inputPerMillion;
const outputCost = (outTokens / 1_000_000) * PRICING.outputPerMillion;
return Number((inputCost + outputCost).toFixed(6)); // on arrondit légèrement
}
Les prix ici sont indicatifs ; vous prendrez les réels depuis le pricing actuel d’OpenAI et les mettrez en configuration. L’important est que cette fonction soit appelée à chaque appel d’outil, et que le résultat soit inscrit dans le champ cost_estimate_usd du log.
Si usage n’est pas disponible
Parfois vous utilisez un LLM tiers qui n’envoie pas usage, ou vous avez besoin d’un contrôle préventif avant l’appel réel. Dans ce cas :
- Estimez les jetons avec une bibliothèque type tiktoken ou un équivalent pour le modèle concerné.
- Prenez des valeurs moyennes depuis l’historique des logs (median_tokens_in/median_tokens_out pour l’outil) et multipliez par le prix.
Code « stub » pour estimer la longueur :
// mcp/costEstimateFallback.ts
export function roughTokenEstimate(text: string): number {
// Estimation grossière : 1 jeton ≈ 4 caractères latins
return Math.ceil(text.length / 4);
}
Ce n’est pas sorcier, mais cela permet, par exemple, d’empêcher d’envoyer sur une offre bon marché un prompt de 200000 jetons.
6. Métriques de coût clés
Les journaux collectés sont la matière première. Voyons maintenant quels agrégats sont vitaux.
cost_per_tool_call
Qu’est-ce que c’est : le coût moyen d’un appel d’un outil donné.
Pourquoi :
- Voir quels outils sont particulièrement coûteux.
- Détecter les « chers et inutiles » : avg_cost_per_call élevé et faible conversion au succès du scénario.
Comment calculer depuis les logs :
- Prendre les logs avec event = "tool_invocation" sur la période.
- Grouper par tool_name.
- Pour chacun, calculer avg(cost_estimate_usd) et éventuellement p95 (95e percentile de coût).
cost_per_successful_task (ou cost_per_workflow)
Task/workflow — un scénario utilisateur terminé :
- Pour GiftGenius, cela peut être « sélection de cadeaux + affichage des fiches + l’utilisateur a enregistré N idées » ou « sélection → checkout → achat réussi ».
Ce que l’on fait :
- À la fin du workflow, on écrit un événement workflow_completed avec request_id, workflow_name et un indicateur de succès.
- Via request_id, on « rattache » tous les tool_invocation de ce workflow et on somme leurs cost_estimate_usd.
On obtient ainsi « combien a coûté une tâche réussie » — clé pour comprendre la structure de coût du scénario.
cost_per_user / cost_per_tenant
Pour les scénarios B2B, la question fréquente : « Combien nous coûte un utilisateur/une équipe par mois ? »
Calcul :
- Grouper les tool_invocation et autres événements liés au coût par user_id ou tenant_id.
- Sommer cost_estimate_usd sur la période (jour, mois).
Ensuite on compare au prix de l’abonnement. Si cost_per_user s’approche fortement du prix de l’offre, il est temps soit d’augmenter le prix, soit d’optimiser l’usage (on en parlera dans la prochaine leçon sur la tarification et les expériences « coût ↔ qualité »).
7. Exemple : format tool_invocation et tableau de bord pour GiftGenius
Passons à ce qui était prévu dans l’exercice : concevoir l’événement de log et un tableau de bord minimal par outil.
Format de l’événement tool_invocation pour GiftGenius
Plus tôt, nous avons vu un log minimal pour un outil MCP. Concevons maintenant un événement plus détaillé tool_invocation, exploitable en production et dans des dashboards : la même idée, avec des champs supplémentaires pour les services, erreurs et liaison aux modèles.
D’abord — le type TypeScript :
// telemetry/events.ts
export interface ToolInvocationEvent {
timestamp: string;
level: 'info' | 'error';
event: 'tool_invocation';
service: 'mcp-giftgenius';
requestId: string;
traceId?: string;
userId?: string;
tenantId?: string;
toolName: string;
modelId?: string;
tokensIn?: number;
tokensOut?: number;
costEstimateUsd?: number;
latencyMs: number;
success: boolean;
errorCode?: string;
}
Et un helper pratique :
// telemetry/emitToolInvocation.ts
export function emitToolInvocation(e: ToolInvocationEvent) {
console.log(JSON.stringify(e));
// En production : envoyer vers Logtail/Datadog/ELK, etc.
}
Pour chaque outil (par exemple, suggest_gifts, rerank_gifts, fetch_catalog), on ajoute un appel à emitToolInvocation à la fin du handler (ou dans le bloc finally, pour journaliser même en cas d’erreur).
Tableau de bord minimal par outils
Table minimale pour un dashboard (par exemple, Metabase / Grafana / n’importe quel BI) :
| Colonne | Description |
|---|---|
|
Nom de l’outil (suggest_gifts, checkout_create_session, …) |
|
Part de tous les tool_invocation attribuée à cet outil |
|
Coût moyen d’un appel (d’après cost_estimate_usd) |
|
Pourcentage d’événements avec success = false |
|
Latence moyenne |
|
Revenu moyen associé à cet outil (le cas échéant) |
Visuellement, cela ressemble généralement à : un tableau en haut, et deux graphiques en bas :
- Bar chart : tool_name sur l’axe X, avg_cost_per_call sur l’axe Y.
- Scatter plot : X = avg_cost_per_call, Y = error_rate ou conversion_to_checkout.
Ces graphiques aident à identifier rapidement les candidats à l’optimisation : cher, lent et pas de conversion — on commence par là.
Relier le coût aux revenus est facilité par le fait que nous journalisons checkout_* avec request_id. On peut alors calculer avg_revenue_per_call comme la somme des revenus divisée par le nombre d’appels de l’outil dans les scénarios où il y a eu checkout_success.
8. Prise en compte des coûts d’infrastructure (sans zèle excessif)
Avec les coûts LLM, tout est propre : chaque appel a des jetons, on peut calculer le coût directement dans le log. L’infrastructure est moins directe : vous avez une facture mensuelle pour Vercel, les bases, Redis, etc.
Au début, suivez un chemin simple :
- Prenez le total mensuel d’infrastructure (disons, 200 $).
- Divisez-le par le nombre de workflows du mois (workflow_completed) — vous obtenez un infra_cost_per_task approximatif.
- Ou divisez par le nombre d’utilisateurs actifs — infra_cost_per_user.
Ensuite, ces chiffres s’additionnent avec le coût LLM (calculé finement via les logs) — on obtient la somme de coûts complète approximative d’un scénario ou d’un utilisateur.
Quand l’application grandit, vous pourrez raffiner (ventiler par services et outils), mais pour les premières versions c’est largement suffisant pour ne pas avancer à l’aveugle.
9. Petit exemple de bout en bout pour GiftGenius
Assemblons tout en une mini‑histoire.
L’utilisateur décrit le destinataire du cadeau, ChatGPT propose d’activer GiftGenius. Ensuite :
- Le widget lance le workflow "gift_selection".
- Votre backend décide d’utiliser un agent LLM pour une sélection plus intelligente.
- L’agent effectue 3 étapes :
- analyze_recipient (analyse de la description via LLM).
- suggest_gifts (notre outil MCP).
- rerank_gifts (modèle additionnel pour améliorer la liste).
- L’utilisateur voit les fiches cadeaux, en enregistre quelques‑unes.
- Il clique sur « Acheter », l’ACP démarre et checkout_create_session est lancé.
- checkout_success réussi avec un montant de 79.00 USD.
Ce qu’il reste dans les logs :
- Trois tool_invocation (chacun avec ses tokens_in/tokens_out, cost_estimate_usd, latencyMs).
- Plusieurs agent_step avec workflow = "gift_selection", step_name.
- checkout_started et checkout_success avec amount=7900, currency="USD".
Via request_id, nous relions tout cela et pouvons dire :
- Coût LLM du scénario : somme de cost_estimate_usd des trois outils, disons 0.19 $.
- Part d’infrastructure (depuis les agrégats) environ 0.03 $ par workflow.
- Au total 0.22 $ de coût complet.
- Revenu de la transaction — 79 $ moins la commission Stripe, etc.
C’est déjà une économie unitaire concrète, pas un vague « il semble que GPT‑4 soit cher ».
10. Erreurs typiques avec l’instrumentation des coûts
Erreur n° 1 : ne regarder que la facture mensuelle sans granularité.
Il est tentant de ne voir que le total OpenAI/cloud. Mais sans lien avec tool_name, user_id, workflow, vous ne savez pas où exactement l’argent est dépensé. L’optimisation se transforme alors en « baisser le modèle à l’aveugle » au lieu d’améliorer précisément les scénarios coûteux.
Erreur n° 2 : écrire les données de coût dans des logs texte non structurés.
Des lignes du type "Tool suggest_gifts used 123 tokens" sont impossibles à agréger/filtrer proprement. À un moment, vous comprendrez qu’il faut migrer vers le JSON, et ce sera douloureux. Faites directement des journaux structurés avec request_id, tool_name, tokens_in/tokens_out, cost_estimate_usd.
Erreur n° 3 : ignorer le lien coût ↔ événements commerce.
Journaliser checkout_success sans request_id et sans lien avec les appels d’outils, c’est renoncer à comprendre quels scénarios sont rentables et lesquels ne font que consommer des jetons. Faites l’effort de faire passer request_id de bout en bout, du widget à l’ACP.
Erreur n° 4 : viser une facturation « parfaite » au lieu d’une estimation pragmatique.
Certaines équipes s’enlisent à tenter de reproduire la facturation OpenAI au jeton près. En réalité, il suffit de l’ordre de grandeur : que le scénario coûte 0.02 $ ou 0.021 $ n’est pas critique. L’important est que ce ne soit pas 2 $. N’ayez pas peur d’utiliser des estimations via usage ou même des heuristiques grossières.
Erreur n° 5 : ne regarder que le coût et oublier la qualité.
Parfois, en voyant de jolis chiffres d’économie, on veut basculer partout sur le modèle le moins cher. On peut « optimiser » l’application au point que les utilisateurs cessent de s’en servir. Le coût doit être évalué avec la qualité des réponses et la conversion — ce sera le sujet de la prochaine leçon de ce même module, sur la tarification et les expériences « coût ↔ qualité ».
GO TO FULL VERSION