CodeGym /Corsi /ChatGPT Apps /Controllo dei costi e strumentazione del costo

Controllo dei costi e strumentazione del costo

ChatGPT Apps
Livello 19 , Lezione 0
Disponibile

1. Perché «funziona» ≠ «si ripaga»

Le applicazioni LLM hanno una caratteristica importante: oltre ai costi fissi di hosting, spesso compaiono costi variabili per l’esecuzione di alcune richieste, legati alle chiamate dei modelli.

È importante distinguere due mondi:

  • quando il modello gira lato ChatGPT (l’utente interagisce con la tua App in ChatGPT e questa chiama mcp-tools) — i token li paga l’utente con il suo abbonamento ChatGPT;
  • quando il tuo backend/server MCP chiama direttamente OpenAI API o altri servizi LLM — quei token li paghi tu.

È proprio nel secondo caso che compaiono classici costi LLM variabili, che dipendono dal numero e dalla «pesantezza» (tokens_in/tokens_out) delle richieste.

Scenario classico:

  1. Rilasci GiftGenius in produzione, tutto vola, gli utenti sono felici.
  2. Dopo un mese arriva la fattura di OpenAI + cloud + commissioni Stripe, e all’improvviso risulta che la «crescita di successo» in realtà significa «paghiamo per ogni regalo più di quanto incassiamo dalla vendita».

L’approccio FinOps (FinOps) dice: il costo è una metrica come la latenza o il tasso di errore. Va registrata, aggregata e usata per prendere decisioni, non «indovinata in Excel».

Obiettivo di questa lezione — metterti in grado di rispondere a domande del tipo:

  • «Quanto è costata questa specifica selezione di regali per l’utente user42
  • «Quanti soldi ha bruciato questa settimana lo strumento suggest_gifts e quanti ordini ha portato?»

E che le risposte vengano non dall’aria, ma da log e metriche.

2. Struttura dei costi di una ChatGPT App

Partiamo dalla mappa delle spese. Senza di essa tutto il resto è una raccolta caotica di numeri.

Costi LLM (variabili)

È tutto ciò che è legato alle chiamate dei modelli dal tuo backend:

  • Chiamate ai modelli OpenAI dal server MCP o dagli agenti: GPT-5.1 / GPT-5-mini / embeddings / rerank / vision / TTS/STT ecc.
  • Modelli aggiuntivi: reranking per la ricerca, embedding per le raccomandazioni, generazione di immagini.

Ricorda un punto sottile: quando costruisci l’interfaccia tramite Apps SDK e usi solo il modello integrato di ChatGPT, non paghi i token — paga l’utente (con il suo abbonamento a ChatGPT). Ma appena il tuo server MCP inizia a chiamare direttamente OpenAI API (Agents, Responses API, embeddings ecc.), i token vanno a carico tuo.

Idea di base: il costo di tali chiamate è proporzionale a tokens_in e tokens_out, moltiplicati per il prezzo per token.

La chiamata a un MCP‑tool di per sé è gratuita per lo sviluppatore dal punto di vista dei token; i costi compaiono solo dove, nel suo handler, decidi di chiamare OpenAI API o un altro LLM.

Infrastruttura

È tutto l’hardware e i servizi intorno:

  • Server MCP: Vercel / AWS / GCP / bare metal.
  • Agenti (se girano come servizi separati).
  • Basi dati: Postgres/MySQL, DB vettoriali, S3/archivi a oggetti.
  • Cache: Redis/KeyDB.
  • Code e worker: ad esempio per generazione in background, ricalcolo dei feed, ecc.

Questi costi sono più spesso fissi al mese (o fissi a scaglioni), quindi di solito si calcolano dai dati aggregati delle spese dei servizi cloud, non per ogni richiesta.

Servizi di pagamento ed esterni

GiftGenius usa ACP/Stripe, e quindi compaiono:

  • Commissioni per ogni pagamento riuscito (Stripe pari a qualche percento + una parte fissa).
  • Perdite per frodi e chargeback.
  • Costo di API esterne: email / SMS / notifiche push, analitiche aggiuntive, ecc.

All’inizio sono spiccioli, ma con la scala iniziano a farsi sentire, quindi è utile evidenziarli almeno a livello di log e report.

Piccola tabella promemoria

Categoria Esempi Come calcoliamo a spanne
LLM GPT‑5.1, GPT‑5‑mini, embeddings, rerank
tokens_in/out × price_per_token
Infrastruttura MCP, Agents, DB, Redis, code, CDN Dividiamo la fattura del provider per traffico/periodo
Pagamenti e servizi Stripe, e‑mail API, SMS, analitica Numero di eventi × tariffa/commissione

Il nostro obiettivo: collegare queste categorie a eventi specifici nel sistema (chiamate ai tool, workflow, checkout), non guardare solo i totali mensili finali.

3. Dove rilevare i dati di usage: tre livelli

Per calcolare i costi non «una volta al mese», ma in tempo reale, bisogna incorporare la strumentazione nel codice. I punti sono tre.

Server MCP: ogni chiamata di uno strumento

Il server MCP è il punto naturale attraverso cui ChatGPT richiama i tuoi tool. Qui possiamo:

  • Catturare l’istante di inizio/fine della chiamata.
  • Misurare duration_ms (o latency_ms).
  • Raccogliere i token dalla risposta OpenAI (se il MCP chiama il nostro modello) o almeno stimarli.
  • Impostare user_id, tenant_id, request_id/trace_id per collegare i log.

Schema di massima dell’evento di log tool_invocation per GiftGenius:

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

Ora lo stesso sotto forma di tipo TypeScript e di un pezzo di codice.

// 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,
  }));
}

E ora un wrapper attorno all’handler del tool MCP (poniamo suggest_gifts).

// mcp/tools/suggestGifts.ts
export async function handleSuggestGifts(ctx: Context, input: Input) {
  const started = Date.now();

  const llmResult = await callGiftModel(input); // qui chiamiamo OpenAI

  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;
}

Anche se i token vengono stimati «a occhio» valutando la lunghezza del testo, è già meglio di nulla.

Livello agente (Agents SDK): step del workflow

Se usi Agents SDK, l’agente può chiamare più tool in sequenza. Qui è importante registrare il contesto dello step: quale compito sta cercando di risolvere l’agente.

Ad esempio, a ogni chiamata di tool del runner dell’agente si possono aggiungere i campi workflow_name e step_name: «ricerca di idee», «filtraggio per budget», «preparazione del checkout».

Questo consentirà poi di costruire report non solo per strumento, ma anche per step dello scenario: magari l’80% del costo se ne va in qualche «step di affinamento aggiuntivo» inutile.

Esempio di piccolo «hook» attorno all’agente:

// 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,
  }));
}

E usarlo dal runner:

// agents/giftAgent.ts
logAgentStep({
  requestId: run.requestId,
  workflow: 'gift_selection',
  step: 'rank_candidates',
  toolName: 'rerank_gifts',
});

Commerce: checkout e denaro

Nel livello commerce ci interessano gli eventi:

  • checkout_started — avvio dell’acquisto.
  • checkout_success — pagamento riuscito.
  • checkout_failed — errore con codice/tipo.

E a questi bisogna collegare:

  • amount, currency.
  • request_id della stessa sessione di tool_invocation.

Così potremo rispondere alla domanda: «Questo acquisto ci è costato N centesimi di costi LLM e ha portato M dollari di ricavi».

Esempio di semplice handler per gli eventi di 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. Log strutturati per i costi (relazione con M17)

Punto chiave: niente log testuali «liberi» del tipo console.log("Tool suggest_gifts used 123 tokens"). Tutto in JSON.

Nel modulo 17 abbiamo già concordato di registrare le richieste in formato JSON con campi di base come request_id, user_id, tool_name ecc. Ora, sopra questo, aggiungiamo i campi di costo.

Campi che devono essere obbligatori nei log legati ai costi:

  • timestamp, level.
  • event (tool_invocation, agent_step, checkout_success ecc.).
  • request_id, trace_id — per collegare la catena di eventi di un workflow.
  • user_id, tenant_id — per aggregare poi per utenti/aziende.
  • tool_name / service.
  • tokens_in, tokens_out, cost_estimate_usd.
  • latency_ms, success/error_code.

Negli esempi chiameremo il campo del costo cost_estimate_usd (costo in dollari USA) e ci atterremo a questo nome nel codice e nei dashboard.

Questa struttura permette di:

  • Costruire aggregati: cost_estimate_usd medio per tool_name, per user_id, per workflow.
  • Correlare richieste «costose» con latenza aumentata o errori e decidere cosa ottimizzare per primo.

Se nel modulo 17 hai già creato un logger.info({...}) di base, aggiungere i campi di costo non è un nuovo framework, ma un paio di proprietà in più nell’oggetto.

5. Come stimare approssimativamente i costi LLM nel codice

Le formule qui non sono affatto spaventose. Ci serve solo l’ordine di grandezza, non una coincidenza perfetta con la fatturazione all’ultimo centesimo.

Prendere l’usage dalla risposta OpenAI

Quando il tuo server MCP chiama OpenAI Response API, in genere riceve un oggetto usage:

{
  "usage": {
    "prompt_tokens": 120,
    "completion_tokens": 350,
    "total_tokens": 470
  }
}

È comodo calcolare i costi su questa base. Modelli diversi hanno prezzi diversi per 1M di token in input/output.

Funzione stimatore più semplice in TypeScript:

// mcp/cost.ts
type Usage = { prompt_tokens?: number; completion_tokens?: number };

const PRICING = {
  inputPerMillion: 2.5,   // dollari per 1M di token in input, esempio
  outputPerMillion: 10.0, // e per l'output
};

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)); // arrotondiamo un po'
}

I prezzi qui sono di esempio; quelli reali li prenderai dal listino aggiornato di OpenAI e li metterai in config. Importante è che questa funzione venga chiamata a ogni chiamata di tool, e il risultato finisca nel campo cost_estimate_usd nel log.

Se l’usage non è disponibile

A volte usi un LLM di terze parti che non invia l’usage, oppure serve un controllo preliminare prima della chiamata reale. Allora si può:

  • Stimare i token con una libreria tipo tiktoken o un analogo per il modello necessario.
  • Prendere valori medi dai log storici (median_tokens_in/median_tokens_out per lo strumento) e moltiplicarli per il prezzo.

Codice segnaposto per stimare la lunghezza:

// mcp/costEstimateFallback.ts
export function roughTokenEstimate(text: string): number {
  // Stima approssimativa: 1 token ≈ 4 caratteri latini
  return Math.ceil(text.length / 4);
}

Non è rocket science, ma permette, per esempio, di non far passare in un piano economico un prompt da 200000 token.

6. Metriche chiave di costo

I log raccolti sono materia prima. Ora vediamo quali aggregati sono vitali.

cost_per_tool_call

Cos’è: costo medio di una chiamata di uno specifico strumento.

Perché:

  • Si vede quali strumenti sono particolarmente costosi.
  • Si possono trovare quelli «costosi e inutili»: avg_cost_per_call alto e bassa conversione nel successo dello scenario.

Come calcolarlo dai log:

  • Prendiamo i log con event = "tool_invocation" per il periodo.
  • Raggruppiamo per tool_name.
  • Per ciascuno calcoliamo avg(cost_estimate_usd) e, volendo, anche p95 (95° percentile del costo).

cost_per_successful_task (o cost_per_workflow)

Task/workflow — è uno scenario completo lato utente:

  • In GiftGenius può essere «selezione dei regali + visualizzazione delle schede + l’utente ha salvato N idee» oppure «selezione → checkout → acquisto riuscito».

Cosa facciamo:

  • Al termine del workflow scriviamo un evento workflow_completed con request_id, workflow_name e flag di successo.
  • Tramite request_id «agganciamo» tutte le tool_invocation di quel workflow e sommiamo i loro cost_estimate_usd.

Così otteniamo «quanto è costata una singola task riuscita» — la chiave per capire la struttura dei costi dello scenario.

cost_per_user / cost_per_tenant

Per scenari B2B è spesso importante la domanda: «Quanto ci costa un singolo utente/un singolo team al mese?»

Calcolo:

  • Raggruppiamo tool_invocation e altri eventi di costo per user_id o tenant_id.
  • Sommiamo cost_estimate_usd per il periodo (giorno, mese).

Poi confrontiamo con il prezzo dell’abbonamento. Se il cost_per_user si avvicina molto al prezzo del piano, è il momento di alzare il prezzo oppure ottimizzare l’usage (ne parleremo nella prossima lezione su pricing ed esperimenti «costo ↔ qualità»).

7. Esempio: formato tool_invocation e dashboard per GiftGenius

Ora facciamo ciò che era nell’esercizio del piano: progettiamo un evento di log e un dashboard minimo per i tool.

Formato dell’evento tool_invocation per GiftGenius

Prima abbiamo guardato il log minimo per un tool MCP. Ora progettiamo un evento tool_invocation più dettagliato, già utilizzabile in produzione e nei dashboard: l’idea è la stessa, si aggiungono campi per servizi, errori e legame con i modelli.

Per cominciare — il tipo in 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;
}

E un comodo helper:

// telemetry/emitToolInvocation.ts
export function emitToolInvocation(e: ToolInvocationEvent) {
  console.log(JSON.stringify(e));
  // Nella vita reale: inviamo a Logtail/Datadog/ELK ecc.
}

A ogni strumento (ad esempio, suggest_gifts, rerank_gifts, fetch_catalog) aggiungiamo la chiamata a emitToolInvocation alla fine dell’handler (o in un blocco finally, così da loggare anche in caso di errore).

Dashboard semplicissimo per gli strumenti

Tabella minima per il dashboard (ad esempio, in Metabase / Grafana / qualunque BI):

Colonna Descrizione
tool_name
Nome dello strumento (suggest_gifts, checkout_create_session, …)
% di traffico
Quota di tutte le tool_invocation attribuite a questo strumento
avg_cost_per_call
Costo medio di una chiamata (da cost_estimate_usd)
error_rate
Percentuale di eventi con success = false
avg_latency_ms
Latenza media
avg_revenue_per_call
Ricavo medio associato a questo strumento (se presente)

Visivamente di solito appare così: in alto una tabella, in basso un paio di grafici:

  • Grafico a barre: tool_name sull’asse X, avg_cost_per_call sull’asse Y.
  • Diagramma di dispersione: X = avg_cost_per_call, Y = error_rate o conversion_to_checkout.

Questi grafici aiutano a trovare rapidamente i candidati all’ottimizzazione: costoso, lento e senza conversione — si parte da lì.

Collegare costi e ricavi è facilitato dal fatto che logghiamo checkout_* insieme a request_id. Così possiamo calcolare avg_revenue_per_call come la somma dei ricavi divisa per il numero di chiamate dello strumento negli scenari in cui si è verificato checkout_success.

8. Considerazione dei costi infrastrutturali (senza fanatismo)

Con i costi LLM tutto è pulito: ogni chiamata ha i token, si può calcolare il costo direttamente nel log. L’infrastruttura non si presta così facilmente: hai una fattura mensile per Vercel, database, Redis, ecc.

All’inizio si può procedere in modo semplice:

  1. Prendi la fattura totale del mese per l’infrastruttura (poniamo 200$).
  2. La dividi per il numero di workflow del mese (workflow_completed) — ottieni un infra_cost_per_task approssimativo.
  3. Oppure dividi per il numero di utenti attivi — infra_cost_per_user.

Poi questi numeri si sommano al costo LLM (che abbiamo calcolato in dettaglio dai log) — e otteniamo il costo pieno approssimativo per scenario o utente.

Quando l’app crescerà, potrai fare di più (ripartire i costi per servizi e strumenti), ma per le prime versioni questo basta per non procedere alla cieca.

9. Piccolo esempio end‑to‑end per GiftGenius

Mettiamo tutto insieme in una mini‑storia.

L’utente descrive il destinatario del regalo, ChatGPT propone di attivare GiftGenius. Poi:

  1. Il widget avvia il workflow "gift_selection".
  2. Il tuo backend decide di usare un agente LLM per selezionare i regali in modo più intelligente.
  3. L’agente esegue 3 step:
  • analyze_recipient (analisi della descrizione con l’LLM).
  • suggest_gifts (il nostro strumento MCP).
  • rerank_gifts (modello aggiuntivo per migliorare la lista).
  1. L’utente vede le schede dei regali, salva alcune idee.
  2. Preme «Compra», si avvia l’ACP e checkout_create_session.
  3. checkout_success riuscito con importo 79.00 USD.

Cosa rimane nei log:

  • Tre tool_invocation (ognuna con i propri tokens_in/tokens_out, cost_estimate_usd, latencyMs).
  • Diverse agent_step con workflow = "gift_selection", step_name.
  • checkout_started e checkout_success con amount=7900, currency="USD".

Tramite request_id colleghiamo tutto questo e possiamo dire:

  • Costo LLM dello scenario: somma di cost_estimate_usd dei tre strumenti, poniamo 0.19$.
  • Quota di infrastruttura (da aggregati) circa 0.03$ per workflow.
  • Totale 0.22$ di costo.
  • Ricavo per la transazione — 79$ meno commissione Stripe e altro.

Questa è già unit economics concreta, non «sembra che GPT‑4 sia costoso».

10. Errori tipici nel lavoro di strumentazione dei costi

Errore n. 1: considerare solo la fattura mensile senza granularità.
È molto allettante guardare solo il totale di OpenAI/cloud. Ma senza il legame con tool_name, user_id, workflow non sai dove esattamente si spendono i soldi. Alla fine l’ottimizzazione diventa «abbassare alla cieca il modello» invece di migliorare miratamente gli scenari costosi.

Errore n. 2: scrivere i dati di costo in log testuali senza struttura.
Righe del tipo "Tool suggest_gifts used 123 tokens" sono impossibili da aggregare e filtrare bene. A un certo punto capirai che devi migrare al JSON, e questa migrazione sarà dolorosa. Fai subito log strutturati con campi request_id, tool_name, tokens_in/tokens_out, cost_estimate_usd.

Errore n. 3: ignorare il legame costi ↔ eventi di commerce.
Loggare checkout_success senza request_id e senza collegarlo alle chiamate dei tool — significa rinunciare volontariamente a capire quali scenari portano profitto e quali solo divorano token. Non essere pigro: porta request_id lungo tutto il percorso dal widget fino all’ACP.

Errore n. 4: cercare di fare una fatturazione «perfetta» invece di una stima pratica.
Alcuni team si impantanano nel tentativo di riprodurre perfettamente la fatturazione OpenAI all’ultimo token. In realtà basta l’ordine di grandezza: se lo scenario costa 0.02$ o 0.021$ non è fondamentale. Importante è che non costi 2$. Non temere di usare stime approssimative tramite usage o anche euristiche grossolane.

Errore n. 5: guardare solo i costi e dimenticare la qualità.
A volte, vedendo bei numeri di risparmio, viene voglia di passare ovunque al modello più economico. Così si può «ottimizzare» l’app fino al punto in cui gli utenti smettono di usarla. Il costo va considerato insieme alla qualità delle risposte e alla conversione — questo collegamento sarà tema della prossima lezione dello stesso modulo — su pricing ed esperimenti «costo ↔ qualità».

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION