1. Por que “funciona” ≠ “se paga”
Aplicativos LLM têm uma característica importante: além dos custos fixos de hospedagem, eles frequentemente apresentam custos variáveis para a execução de algumas solicitações, relacionados às chamadas de modelos.
É importante distinguir dois mundos:
- quando o modelo roda do lado do ChatGPT (o usuário interage com seu App no ChatGPT e ele chama mcp-tools) — os tokens são pagos pelo usuário com sua assinatura do ChatGPT;
- quando seu backend/servidor MCP chama por conta própria o OpenAI API ou outros serviços de LLM — esses tokens são pagos por você.
É exatamente no segundo caso que surgem os custos variáveis clássicos de LLM, que dependem da quantidade e da “pesadez” (tokens_in/tokens_out) das solicitações.
Cenário clássico:
- Você lança o GiftGenius em produção, tudo voa, os usuários estão felizes.
- Um mês depois chega a fatura da OpenAI + nuvem + comissões da Stripe e, de repente, descobre-se que “crescimento bem-sucedido” na verdade significa “estamos pagando por cada presente mais do que ganhamos com a venda”.
A abordagem FinOps (FinOps) diz: custo é uma métrica tal como latência ou taxa de erro. Ela deve ser registrada, agregada e usada para tomada de decisão — não “adivinhada no Excel”.
O objetivo desta aula é fazer com que você consiga responder perguntas do tipo:
- “Quanto custou esta seleção específica de presentes para o usuário user42?”
- “Quanto dinheiro este instrumento suggest_gifts queimou nesta semana e quantos pedidos ele trouxe?”
E que as respostas venham não do nada, mas de logs e métricas.
2. Estrutura de custos do ChatGPT App
Vamos começar com o mapa de despesas. Sem ele, todo o resto vira coleta caótica de números.
Custos de LLM (variáveis)
É tudo que está ligado a chamadas de modelos a partir do seu backend:
- Chamadas a modelos da OpenAI a partir do servidor MCP ou de agentes: GPT-5.1 / GPT-5-mini / embeddings / rerank / visão / TTS/STT etc.
- Modelos adicionais: reranking para busca, embeddings para recomendações, geração de imagens.
É importante lembrar um detalhe: quando você constrói a interface via Apps SDK e usa apenas o modelo embutido do ChatGPT, você não paga pelos tokens — quem paga é o usuário (pela assinatura do ChatGPT). Mas assim que seu servidor MCP começa a chamar o OpenAI API por conta própria (Agents, Responses API, embeddings etc.), os tokens passam a ser cobrados na sua conta.
Ideia básica: o custo dessas chamadas é proporcional a tokens_in e tokens_out, multiplicados pelo preço por token.
A chamada de uma ferramenta MCP em si é gratuita para o desenvolvedor do ponto de vista de tokens; os custos aparecem apenas onde, no handler, você decide chamar o OpenAI API ou outro LLM.
Infraestrutura
É todo o hardware e serviços ao redor:
- Servidores MCP: Vercel / AWS / GCP / bare metal.
- Agentes (se rodam como serviços separados).
- Bancos de dados: Postgres/MySQL, bancos vetoriais, S3/armazenamento de objetos.
- Caches: Redis/KeyDB.
- Filas e workers: por exemplo, para geração em segundo plano, reprocessamento de feeds etc.
Esses custos são geralmente fixos por mês (ou fixos por degraus), então normalmente são calculados por dados agregados de despesas com provedores de nuvem, e não por solicitação.
Pagamentos e serviços externos
O GiftGenius usa ACP/Stripe, então surgem:
- Comissões por cada pagamento bem-sucedido (Stripe na ordem de alguns por cento + uma parte fixa).
- Perdas com fraude e chargebacks.
- Custo de APIs externas: e‑mail / SMS / notificações push, análises adicionais etc.
No começo isso é troco, mas com escala começa a pesar; por isso, ao menos no nível de logs e relatórios, é útil destacá-los.
Pequena tabela para lembrar
| Categoria | Exemplos | Como estimar grosso modo |
|---|---|---|
| LLM | GPT‑5.1, GPT‑5‑mini, embeddings, rerank | |
| Infraestrutura | MCP, Agents, DB, Redis, filas, CDN | Dividimos a fatura do provedor por tráfego/período |
| Pagamentos e serviços | Stripe, e‑mail API, SMS, analytics | Qtd. de eventos × tarifa/comissão |
Nossa meta: vincular essas categorias a eventos específicos no sistema (chamadas de tools, workflow, checkout), e não olhar apenas os valores mensais finais.
3. Onde coletar dados de uso: três camadas
Para calcular custo não “uma vez por mês”, mas em tempo real, é preciso incorporar instrumentação no código. Existem apenas três lugares.
Servidor MCP: cada chamada de ferramenta
O servidor MCP é um ponto natural através do qual o ChatGPT chama suas ferramentas. Aqui podemos:
- Capturar o momento de início/fim da chamada.
- Medir duration_ms (ou latency_ms).
- Coletar tokens da resposta da OpenAI (se o MCP chamar nosso modelo) ou ao menos estimá-los.
- Definir user_id, tenant_id, request_id/trace_id para ligar os logs.
Esquematicamente, o evento de log tool_invocation para o GiftGenius fica assim:
{
"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
}
Agora o mesmo como um tipo TypeScript e um trecho de código.
// 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,
}));
}
Agora, um wrapper em torno do handler da ferramenta MCP (digamos suggest_gifts).
// mcp/tools/suggestGifts.ts
export async function handleSuggestGifts(ctx: Context, input: Input) {
const started = Date.now();
const llmResult = await callGiftModel(input); // aqui chamamos a 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;
}
Mesmo que você estime os tokens “no olho” pela duração do texto, isso já é melhor do que nada.
Nível do agente (Agents SDK): etapas do workflow
Se você usa o Agents SDK, o agente pode chamar várias ferramentas em sequência. Aqui é importante registrar o contexto da etapa: qual tarefa o agente tenta resolver.
Por exemplo, a cada chamada de ferramenta do runner do agente, você pode adicionar os campos workflow_name e step_name: “busca de ideias”, “filtragem por orçamento”, “preparação do checkout”.
Isso permitirá depois construir relatórios não apenas por ferramentas, mas também por etapas do cenário: talvez 80% do custo esteja indo para alguma “etapa adicional de esclarecimento” inútil.
Exemplo de um pequeno “hook” ao redor do 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 usar isso a partir do runner:
// agents/giftAgent.ts
logAgentStep({
requestId: run.requestId,
workflow: 'gift_selection',
step: 'rank_candidates',
toolName: 'rerank_gifts',
});
Commerce: checkout e dinheiro
No nível de commerce, nos interessam os eventos:
- checkout_started — início da compra.
- checkout_success — pagamento aprovado.
- checkout_failed — erro com código/tipo.
E a eles é preciso anexar:
- amount, currency.
- request_id da mesma sessão que o tool_invocation.
Assim poderemos responder à pergunta: “Esta compra nos custou N centavos de custo de LLM e trouxe M dólares de receita”.
Exemplo de um handler simples de eventos 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. Logs estruturados para custos (relação com M17)
Ponto-chave: nada de logs “livres” como texto do tipo console.log("Tool suggest_gifts used 123 tokens"). Tudo em JSON.
No módulo 17 nós já combinamos registrar solicitações em JSON com campos básicos como request_id, user_id, tool_name etc. Agora, por cima disso, vamos adicionar os campos de custo.
Campos que precisam estar obrigatoriamente em logs relacionados a custos:
- timestamp, level.
- event (tool_invocation, agent_step, checkout_success etc.).
- request_id, trace_id — para ligar a cadeia de eventos de um workflow.
- user_id, tenant_id — para depois agregar por usuários/empresas.
- tool_name / service.
- tokens_in, tokens_out, cost_estimate_usd.
- latency_ms, success/error_code.
Nos exemplos, chamaremos o campo de custo de cost_estimate_usd (custo em dólares americanos) e vamos manter esse nome no código e nos dashboards.
Essa estrutura permite:
- Construir agregações: cost_estimate_usd médio por tool_name, por user_id, por workflow.
- Correlacionar solicitações “caras” com latência elevada ou erros e decidir o que otimizar primeiro.
Se você já fez no M17 um logger.info({...}) básico, adicionar os campos de custo não é um novo framework, é apenas um punhado de propriedades a mais no objeto.
5. Como estimar o custo de LLM no código
As fórmulas aqui não têm mistério. Precisamos apenas da ordem de grandeza, não de bater com a cobrança no centavo.
Usar usage da resposta da OpenAI
Quando seu servidor MCP chama o OpenAI Responses API, ele normalmente recebe um objeto usage:
{
"usage": {
"prompt_tokens": 120,
"completion_tokens": 350,
"total_tokens": 470
}
}
É conveniente calcular o custo com base nele. Modelos diferentes têm preços distintos por 1M de tokens de entrada/saída.
Função de estimativa simples em TypeScript:
// mcp/cost.ts
type Usage = { prompt_tokens?: number; completion_tokens?: number };
const PRICING = {
inputPerMillion: 2.5, // dólares por 1M de tokens de entrada, exemplo
outputPerMillion: 10.0, // e por tokens de saída
};
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)); // arredondamos um pouco
}
Os preços aqui são de exemplo; os reais você pegará do pricing atual da OpenAI e colocará no config. O importante é que essa função seja chamada a cada chamada de tool, e o resultado vá para o campo cost_estimate_usd no log.
Se usage não estiver disponível
Às vezes você usa um LLM de terceiros que não envia usage, ou precisa de um controle prévio antes da chamada real. Então você pode:
- Estimar tokens usando uma biblioteca como tiktoken ou análoga para o modelo desejado.
- Pegar valores médios de logs históricos (median_tokens_in/median_tokens_out para a ferramenta) e multiplicá-los pelo preço.
Código placeholder para estimar comprimento:
// mcp/costEstimateFallback.ts
export function roughTokenEstimate(text: string): number {
// Estimativa grosseira: 1 token ≈ 4 caracteres latinos
return Math.ceil(text.length / 4);
}
Isso não é ciência de foguetes, mas permite, por exemplo, não deixar entrar em um plano barato um prompt com 200000 tokens.
6. Métricas chave de custo
Os logs coletados são a matéria-prima. Agora vejamos quais agregações são vitais.
cost_per_tool_call
O que é: custo médio de uma chamada de uma ferramenta específica.
Por que:
- Dá para ver quais ferramentas são especialmente caras.
- Permite buscar “caras e inúteis”: avg_cost_per_call alto e baixa conversão no sucesso do cenário.
Como calcular a partir dos logs:
- Pegue os logs com event = "tool_invocation" no período.
- Agregue por tool_name.
- Para cada um, calcule avg(cost_estimate_usd) e, se quiser, p95 (95º percentil de custo).
cost_per_successful_task (ou cost_per_workflow)
Task/workflow — um cenário concluído no nível do usuário:
- No GiftGenius isso pode ser “seleção de presente + exibição de cards + o usuário salvou N ideias” ou “seleção → checkout → compra bem-sucedida”.
O que fazemos:
- Ao concluir o workflow, escrevemos um evento workflow_completed com request_id, workflow_name e um flag de sucesso.
- Através do request_id “puxamos” todas as tool_invocation desse workflow e somamos seus cost_estimate_usd.
Assim obtemos “quanto custou uma tarefa bem-sucedida” — chave para entender o custo unitário do cenário.
cost_per_user / cost_per_tenant
Para cenários B2B, muitas vezes importa: “Quanto custa para nós um usuário/uma equipe por mês?”
Como calcular:
- Agregue tool_invocation e outros eventos de custo por user_id ou tenant_id.
- Some cost_estimate_usd no período (dia, mês).
Depois compare com o preço do plano. Se o cost_per_user ficar muito próximo do preço do plano, é hora de aumentar o preço ou otimizar o uso (falaremos disso na próxima aula sobre pricing e experimentos “custo ↔ qualidade”).
7. Exemplo: formato tool_invocation e dashboard para o GiftGenius
Agora vamos fazer o que estava no exercício do plano: projetar um evento de log e um dashboard mínimo por ferramentas.
Formato do evento tool_invocation para o GiftGenius
Antes vimos um log mínimo para uma ferramenta do MCP. Agora vamos projetar um evento mais detalhado tool_invocation, já utilizável em produção e nos dashboards: a ideia é a mesma, só adicionamos campos para serviços, erros e vínculo com modelos.
Primeiro — o tipo em 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 um helper conveniente:
// telemetry/emitToolInvocation.ts
export function emitToolInvocation(e: ToolInvocationEvent) {
console.log(JSON.stringify(e));
// Na vida real: enviar para Logtail/Datadog/ELK etc.
}
Para cada ferramenta (por exemplo, suggest_gifts, rerank_gifts, fetch_catalog) adicionamos uma chamada a emitToolInvocation no final do handler (ou no bloco finally, para que o log exista mesmo em caso de erro).
Dashboard mais simples por ferramentas
Tabela mínima para o dashboard (por exemplo, no Metabase / Grafana / qualquer BI):
| Coluna | Descrição |
|---|---|
|
Nome da ferramenta (suggest_gifts, checkout_create_session, …) |
|
Parcela de todas as tool_invocation que coube a essa ferramenta |
|
Custo médio de uma chamada (a partir de cost_estimate_usd) |
|
Percentual de eventos com success = false |
|
Latência média |
|
Receita média associada a essa ferramenta (se houver) |
Visualmente, isso costuma parecer assim: em cima uma tabela, embaixo — dois gráficos:
- Gráfico de barras: tool_name no eixo X, avg_cost_per_call no eixo Y.
- Diagrama de dispersão: X = avg_cost_per_call, Y = error_rate ou conversion_to_checkout.
Esses gráficos ajudam a encontrar rapidamente candidatos à otimização: caro, lento e não converte — comece por ali.
Vincular custo a receita é facilitado pelo fato de registrarmos checkout_* junto com request_id. Assim, podemos calcular avg_revenue_per_call como a soma da receita dividida pelo número de chamadas da ferramenta nos cenários em que ocorreu checkout_success.
8. Alocação dos custos de infraestrutura (sem fanatismo)
Com custos de LLM tudo é bonito: cada chamada tem tokens, dá para calcular o custo diretamente no log. Infraestrutura não é tão direta: você tem a fatura mensal da Vercel, bancos, Redis etc.
No início, dá para ir pelo caminho simples:
- Pegue a fatura total do mês para infraestrutura (por exemplo, 200 USD).
- Divida pelo número de workflows no mês (workflow_completed) — e obtenha um infra_cost_per_task aproximado.
- Ou divida pelo número de usuários ativos — infra_cost_per_user.
Depois, some esses números ao custo de LLM (que calculamos detalhadamente pelos logs) — e obtenha o custo total aproximado do cenário ou do usuário.
Quando o aplicativo crescer, você poderá fazer algo mais refinado (distribuir despesas por serviços e ferramentas), mas para as primeiras versões isso é mais do que suficiente para não andar às cegas.
9. Pequeno exemplo end‑to‑end para o GiftGenius
Vamos juntar tudo em uma mini-história.
O usuário descreve a pessoa presenteada, o ChatGPT sugere ativar o GiftGenius. Em seguida:
- O widget inicia o workflow "gift_selection".
- Seu backend decide usar um agente de LLM para selecionar presentes com mais inteligência.
- O agente faz 3 etapas:
- analyze_recipient (análise da descrição com LLM).
- suggest_gifts (nossa ferramenta MCP).
- rerank_gifts (modelo adicional para melhorar a lista).
- O usuário vê os cards de presentes e salva algumas ideias.
- Clica em “Comprar”, inicia o ACP e checkout_create_session.
- checkout_success bem-sucedido com o valor de 79.00 USD.
O que fica nos logs:
- Três tool_invocation (cada uma com seus tokens_in/tokens_out, cost_estimate_usd, latencyMs).
- Vários agent_step com workflow = "gift_selection", step_name.
- checkout_started e checkout_success com amount=7900, currency="USD".
Com request_id vinculamos tudo isso e podemos dizer:
- Custo de LLM do cenário: soma de cost_estimate_usd das três ferramentas, por exemplo, 0.19 USD.
- Parcela de infraestrutura (a partir dos agregados) de aproximadamente 0.03 USD por workflow.
- Total de 0.22 USD de custo.
- Receita pela transação — 79 USD menos a comissão da Stripe e afins.
Isso já é economia unitária concreta, e não “parece que GPT‑4 é caro”.
10. Erros comuns ao trabalhar com instrumentação de custos
Erro nº 1: olhar apenas a fatura mensal e não ter granularidade.
É muito tentador olhar só a fatura total da OpenAI/nuvem. Mas sem vínculo com tool_name, user_id, workflow, você não sabe onde exatamente o dinheiro está sendo gasto. No fim, a otimização vira “rebaixar o modelo às cegas” em vez de melhorar pontualmente os cenários caros.
Erro nº 2: escrever dados de custo em logs de texto sem estrutura.
Linhas como "Tool suggest_gifts used 123 tokens" são impossíveis de agregar e filtrar com qualidade. Em algum momento você vai perceber que precisa migrar para JSON — e essa migração será dolorosa. Faça desde o início logs estruturados com campos request_id, tool_name, tokens_in/tokens_out, cost_estimate_usd.
Erro nº 3: ignorar a ligação custo ↔ eventos de commerce.
Registrar checkout_success sem request_id e sem vínculo com as chamadas de ferramentas é abrir mão, voluntariamente, de entender quais cenários geram lucro e quais só consomem tokens. Não tenha preguiça de passar o request_id por todo o caminho, do widget ao ACP.
Erro nº 4: tentar fazer uma cobrança “perfeita” em vez de uma estimativa prática.
Algumas equipes se enterram tentando reproduzir perfeitamente a cobrança da OpenAI token a token. Na realidade, basta a ordem de grandeza: se o cenário custa 0.02 USD ou 0.021 USD, isso não é crucial. O importante é que não seja 2 USD. Não tenha medo de usar estimativas aproximadas via usage ou até heurísticas grosseiras.
Erro nº 5: olhar só para custo e esquecer a qualidade.
Às vezes, ao ver números bonitos de economia, dá vontade de trocar tudo para o modelo mais barato. Assim você pode “otimizar” o app a ponto de os usuários pararem de usá-lo. Custo deve ser considerado junto com a qualidade das respostas e a conversão — esse elo será o tema da próxima aula deste mesmo módulo — sobre pricing e experimentos “custo ↔ qualidade”.
GO TO FULL VERSION