1. Por qué «funciona» ≠ «se rentabiliza»
Las aplicaciones LLM tienen una característica importante: además de los costes fijos de hosting, a menudo aparecen costes variables por ejecutar ciertas solicitudes, relacionados con las llamadas a modelos.
Es importante distinguir dos mundos:
- cuando el modelo funciona del lado de ChatGPT (el usuario interactúa con tu App en ChatGPT y esta llama a mcp-tools) — los tokens los paga el usuario con su propia suscripción de ChatGPT;
- cuando tu backend/servidor MCP llama por sí mismo a OpenAI API u otros servicios LLM — esos tokens ya los pagas tú.
Precisamente en el segundo caso aparecen los costes LLM clásicos y variables, que dependen de la cantidad y la «pesadez» (tokens_in/tokens_out) de las solicitudes.
Escenario clásico:
- Lanzas GiftGenius a producción felizmente, todo vuela, los usuarios están contentos.
- Un mes después llega la factura de OpenAI + la nube + las comisiones de Stripe, y de repente resulta que «crecimiento exitoso» en realidad significa «pagamos por cada regalo más de lo que ganamos con la venta».
El enfoque FinOps (FinOps) dice: el coste es una métrica igual que la latencia o la tasa de error. Hay que registrarlo, agregarlo y tomar decisiones a partir de él, no «adivinarlo en Excel».
El objetivo de esta lección es que puedas responder preguntas del tipo:
- «¿Cuánto costó esta selección concreta de regalos para el usuario user42?»
- «¿Cuánto dinero quemó esta semana la herramienta suggest_gifts y cuántos pedidos generó?»
Y que las respuestas no salgan del aire, sino de logs y métricas.
2. Estructura de costes de la app de ChatGPT
Empecemos con el mapa de gastos. Sin él, lo demás es un acopio caótico de números.
Costes de LLM (variables)
Es todo lo relacionado con llamadas a modelos desde tu backend:
- Llamadas a modelos de OpenAI desde el servidor MCP o agentes: GPT-5.1 / GPT-5-mini / embeddings / rerank / vision / TTS/STT, etc.
- Modelos adicionales: reranking para búsqueda, embeddings para recomendaciones, generación de imágenes.
Es importante recordar un matiz: cuando construyes la interfaz con Apps SDK y usas solo el modelo integrado de ChatGPT, no pagas por los tokens — paga el usuario (con su suscripción de ChatGPT). Pero en cuanto tu servidor MCP empieza a llamar por sí mismo a OpenAI API (Agents, Responses API, embeddings, etc.), los tokens ya van a tu cuenta.
La idea básica: el coste de esas llamadas es proporcional a tokens_in y tokens_out, multiplicados por el precio por token.
La llamada a un MCP‑tool en sí es gratuita para el desarrollador en términos de tokens; los gastos aparecen solo donde, en su manejador, decides llamar a OpenAI API u otro LLM.
Infraestructura
Todo el hardware y servicios alrededor:
- Servidores MCP: Vercel / AWS / GCP / bare metal.
- Agentes (si se ejecutan como servicios separados).
- Bases de datos: Postgres/MySQL, bases vectoriales, S3/almacenamiento de objetos.
- Cachés: Redis/KeyDB.
- Colas y workers: por ejemplo, para generación en background, recálculo de feeds, etc.
Estos costes suelen ser fijos por mes (o escalonados fijos), por lo que normalmente se calculan con datos agregados de gastos en la nube, no por cada solicitud.
Pagos y servicios externos
GiftGenius tiene ACP/Stripe, por lo que aparecen:
- Comisiones por cada pago exitoso (Stripe del orden de varios porcentajes + una parte fija).
- Pérdidas por fraude y chargebacks.
- Coste de APIs externas: e‑mail / SMS / notificaciones push, analítica adicional, etc.
Al principio son céntimos, pero al escalar se empiezan a notar; por eso, al menos a nivel de logs e informes, conviene destacarlos.
Pequeña tabla para recordar
| Categoría | Ejemplos | Cálculo aproximado |
|---|---|---|
| LLM | GPT‑5.1, GPT‑5‑mini, embeddings, rerank | |
| Infraestructura | MCP, Agents, DB, Redis, colas, CDN | Dividimos la factura del proveedor entre el tráfico/periodo |
| Pagos y servicios | Stripe, e‑mail API, SMS, analítica | N.º de eventos × tarifa/comisión |
Nuestro objetivo: vincular estas categorías a eventos concretos del sistema (invocaciones de tools, workflow, checkout), y no mirar solo los importes mensuales finales.
3. Dónde capturar datos de uso: tres capas
Para calcular el coste no «una vez al mes», sino en tiempo real, hay que instrumentar el código. Solo hay tres lugares.
Servidor MCP: cada invocación de herramienta
El servidor MCP es el punto natural a través del cual ChatGPT llama a tus tools. Aquí podemos:
- Capturar el momento de inicio/fin de la llamada.
- Medir duration_ms (o latency_ms).
- Recoger los tokens de la respuesta de OpenAI (si el MCP llama a nuestro modelo) o al menos estimarlos.
- Establecer user_id, tenant_id, request_id/trace_id para enlazar los logs.
De forma esquemática, el evento de log tool_invocation para GiftGenius se ve así:
{
"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
}
Ahora lo mismo como un tipo de TypeScript y un trozo 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,
}));
}
Y ahora, un wrapper alrededor del manejador del MCP‑tool (digamos suggest_gifts).
// mcp/tools/suggestGifts.ts
export async function handleSuggestGifts(ctx: Context, input: Input) {
const started = Date.now();
const llmResult = await callGiftModel(input); // aquí llamamos 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;
}
Incluso si calculas los tokens «a ojo» mediante una estimación de longitud del texto, ya es mejor que nada.
Nivel del agente (Agents SDK): pasos del workflow
Si utilizas Agents SDK, el agente puede llamar él mismo a varias tools seguidas. Aquí es importante registrar el contexto del paso: qué tarea intenta resolver el agente.
Por ejemplo, en cada invocación de tool del runner del agente puedes añadir los campos workflow_name y step_name: «búsqueda de ideas», «filtrado por presupuesto», «preparación del checkout».
Esto permitirá luego construir informes no solo por herramientas, sino también por pasos del escenario: quizá el 80% del coste se vaya en algún «paso adicional de aclaración» inútil.
Ejemplo de un pequeño «hook» alrededor del 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,
}));
}
Y usarlo desde el runner:
// agents/giftAgent.ts
logAgentStep({
requestId: run.requestId,
workflow: 'gift_selection',
step: 'rank_candidates',
toolName: 'rerank_gifts',
});
Commerce: checkout y dinero
En la capa de commerce nos interesan los eventos:
- checkout_started — compra iniciada.
- checkout_success — pago realizado.
- checkout_failed — error con código/tipo.
Y hay que adjuntarles:
- amount, currency.
- request_id de la misma sesión que tool_invocation.
Entonces podremos responder a la pregunta: «Esta compra nos costó N céntimos de costes LLM y trajo M dólares de ingresos».
Ejemplo de un handler sencillo 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 estructurados para costes (relación con M17)
Punto clave: nada de logs «libres» de texto del tipo console.log("Tool suggest_gifts used 123 tokens"). Todo en JSON.
En el módulo 17 ya acordamos registrar las solicitudes en JSON con campos básicos como request_id, user_id, tool_name, etc. Ahora, sobre eso, añadimos los campos de coste.
Campos que deben estar sí o sí en los logs relacionados con costes:
- timestamp, level.
- event (tool_invocation, agent_step, checkout_success, etc.).
- request_id, trace_id — para enlazar la cadena de eventos de un mismo workflow.
- user_id, tenant_id — para luego agregar por usuarios/empresas.
- tool_name / service.
- tokens_in, tokens_out, cost_estimate_usd.
- latency_ms, success/error_code.
En los ejemplos llamaremos al campo de coste cost_estimate_usd (coste en dólares estadounidenses) y nos ceñiremos a ese nombre en el código y en los paneles.
Esta estructura permite:
- Construir agregados: cost_estimate_usd medio por tool_name, por user_id, por workflow.
- Correlacionar solicitudes «caras» con latencia elevada o errores y decidir qué optimizar primero.
Si ya en el M17 hiciste un logger.info({...}) básico, añadir campos de coste no es un framework nuevo, sino un par de propiedades más en el objeto.
5. Cómo estimar aproximadamente el coste LLM en el código
Las fórmulas no son nada temibles. Solo necesitamos el orden de magnitud aproximado, no cuadrar con la facturación al último céntimo.
Tomar usage de la respuesta de OpenAI
Cuando tu servidor MCP llama al OpenAI Response API, normalmente recibe un objeto usage:
{
"usage": {
"prompt_tokens": 120,
"completion_tokens": 350,
"total_tokens": 470
}
}
Con él es cómodo calcular el coste. Distintos modelos tienen precios distintos por 1 M de tokens de entrada/salida.
Función estimadora sencilla en TypeScript:
// mcp/cost.ts
type Usage = { prompt_tokens?: number; completion_tokens?: number };
const PRICING = {
inputPerMillion: 2.5, // dólares por 1 M de tokens de entrada, ejemplo
outputPerMillion: 10.0, // y por los de salida
};
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)); // redondeamos un poco
}
Los precios aquí son de ejemplo; los reales los tomarás del pricing actual de OpenAI y los pondrás en la configuración. Lo importante es que esta función se llame en cada invocación de tool, y el resultado vaya al campo cost_estimate_usd del log.
Si usage no está disponible
A veces usas un LLM de terceros que no envía usage, o necesitas un control previo antes de la llamada real. Entonces puedes:
- Estimar tokens con una librería tipo tiktoken o su análogo para el modelo necesario.
- Tomar valores medios de logs históricos (median_tokens_in/median_tokens_out para la herramienta) y multiplicarlos por el precio.
Código de relleno para estimar la longitud:
// mcp/costEstimateFallback.ts
export function roughTokenEstimate(text: string): number {
// Estimación gruesa: 1 token ≈ 4 caracteres latinos
return Math.ceil(text.length / 4);
}
No es ciencia espacial, pero permite, por ejemplo, no dejar pasar a una tarifa barata un prompt de 200000 tokens.
6. Métricas clave de coste
Los logs recopilados son materia prima. Veamos qué agregados son vitales.
cost_per_tool_call
Qué es: coste medio de una invocación de una herramienta concreta.
Para qué:
- Se ve qué herramientas son especialmente caras.
- Permite buscar «caras e inútiles»: avg_cost_per_call alto y baja conversión al éxito del escenario.
Cómo calcular a partir de los logs:
- Tomamos los logs con event = "tool_invocation" del periodo.
- Agrupamos por tool_name.
- Para cada uno calculamos avg(cost_estimate_usd) y también p95 (percentil 95 del coste).
cost_per_successful_task (o cost_per_workflow)
Una tarea/workflow es un escenario terminado a nivel de usuario:
- En GiftGenius puede ser «selección de regalo + mostrar tarjetas + el usuario guardó N ideas» o «selección → checkout → compra exitosa».
Qué hacemos:
- Al finalizar el workflow escribimos un evento workflow_completed con request_id, workflow_name y un indicador de éxito.
- Con el request_id «traemos» todos los tool_invocation de ese workflow y sumamos sus cost_estimate_usd.
Así obtenemos «cuánto costó una tarea exitosa»: la clave para comprender el coste unitario del escenario.
cost_per_user / cost_per_tenant
Para escenarios B2B, a menudo importa: «¿Cuánto nos cuesta un usuario/un equipo al mes?»
Cálculo:
- Agrupamos tool_invocation y otros eventos de coste por user_id o tenant_id.
- Sumamos cost_estimate_usd en el periodo (día, mes).
Luego lo comparamos con el precio de la suscripción. Si cost_per_user se acerca mucho al precio del plan, toca o subir precio o optimizar el uso (de esto hablaremos en la siguiente lección sobre pricing y experimentos «coste ↔ calidad»).
7. Ejemplo: formato de tool_invocation y dashboard para GiftGenius
Ahora haremos lo que había en el ejercicio del plan: diseñar el evento de log y un dashboard mínimo por tools.
Formato del evento tool_invocation para GiftGenius
Antes vimos el log mínimo para un MCP‑tool. Ahora diseñemos un evento más detallado tool_invocation, ya usable en producción y paneles: la idea es la misma, solo añadimos campos para servicios, errores y enlace con modelos.
Primero, el tipo en 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;
}
Y un helper cómodo:
// telemetry/emitToolInvocation.ts
export function emitToolInvocation(e: ToolInvocationEvent) {
console.log(JSON.stringify(e));
// En la vida real: lo enviaremos a Logtail/Datadog/ELK, etc.
}
A cada herramienta (por ejemplo, suggest_gifts, rerank_gifts, fetch_catalog) le añadimos una llamada a emitToolInvocation al final del handler (o en un bloque finally, para que haya log incluso si falla).
Dashboard más simple por herramientas
Tabla mínima para el dashboard (por ejemplo, en Metabase / Grafana / cualquier BI):
| Columna | Descripción |
|---|---|
|
Nombre de la herramienta (suggest_gifts, checkout_create_session, …) |
|
Porcentaje de todos los tool_invocation que corresponden a esta herramienta |
|
Coste medio de una llamada (a partir de cost_estimate_usd) |
|
Porcentaje de eventos con success = false |
|
Latencia media |
|
Ingresos medios asociados a esta herramienta (si los hay) |
Visualmente suele verse así: arriba la tabla y abajo un par de gráficos:
- Gráfico de barras: tool_name en el eje X, avg_cost_per_call en el eje Y.
- Diagrama de dispersión: X = avg_cost_per_call, Y = error_rate o conversion_to_checkout.
Estos gráficos ayudan a encontrar rápido candidatos a optimización: caro, lento y sin conversión — primero ahí.
Vincular el coste con los ingresos ayuda el hecho de que registramos checkout_* junto con request_id. Así podemos calcular avg_revenue_per_call como la suma de ingresos dividida por el número de llamadas de la herramienta en los escenarios donde hubo checkout_success.
8. Imputación de costes de infraestructura (sin fanatismos)
Con los costes LLM todo es bonito: cada llamada tiene tokens, se puede calcular el coste directamente en el log. La infraestructura no se deja tan fácilmente: tienes una factura mensual por Vercel, bases, Redis, etc.
Al principio puedes ir por el camino simple:
- Tomas la factura total del mes por infraestructura (digamos, 200 $).
- La divides entre el número de workflows del mes (workflow_completed) — obtienes un infra_cost_per_task aproximado.
- O la divides entre el número de usuarios activos — infra_cost_per_user.
Luego estas cifras se suman al coste LLM (que calculamos en detalle a partir de los logs) — obtenemos la coste total unitario aproximado del escenario o del usuario.
Cuando la aplicación crezca, se podrá hacer más fino (repartir gastos por servicios y herramientas), pero para las primeras versiones basta para no ir a ciegas.
9. Pequeño ejemplo end‑to‑end para GiftGenius
Juntemos todo en una mini‑historia.
El usuario describe a la persona destinataria del regalo, ChatGPT propone activar GiftGenius. Después:
- El widget lanza el workflow "gift_selection".
- Tu backend decide usar un agente LLM para seleccionar regalos de forma más inteligente.
- El agente da 3 pasos:
- analyze_recipient (análisis de la descripción con ayuda de LLM).
- suggest_gifts (nuestro MCP‑tool).
- rerank_gifts (modelo adicional para mejorar la lista).
- El usuario ve las tarjetas de regalos y guarda varias ideas.
- Pulsa «Comprar», se inicia ACP y checkout_create_session.
- checkout_success exitoso con importe 79.00 USD.
¿Qué nos queda en los logs?
- Tres tool_invocation (cada uno con sus tokens_in/tokens_out, cost_estimate_usd, latencyMs).
- Varios agent_step con workflow = "gift_selection", step_name.
- checkout_started y checkout_success con amount=7900, currency="USD".
Por request_id enlazamos todo y podemos decir:
- Coste LLM del escenario: suma de cost_estimate_usd de las tres herramientas, supongamos 0.19 $.
- Parte de infraestructura (a partir de agregados) aproximadamente 0.03 $ por workflow.
- En total 0.22 $ de coste unitario.
- Ingresos por la transacción — 79 $ menos la comisión de Stripe y demás.
Esto ya es economía unitaria concreta, y no «parece que GPT‑4 es caro».
10. Errores típicos al trabajar con la instrumentación de costes
Error n.º 1: mirar solo la factura mensual sin granularidad.
Es muy tentador mirar solo la factura total de OpenAI/la nube. Pero sin conexión con tool_name, user_id, workflow no sabes dónde exactamente se gasta el dinero. Al final, la optimización se convierte en «bajar el modelo a ciegas» en lugar de mejorar con precisión los escenarios caros.
Error n.º 2: escribir datos de coste en logs de texto sin estructura.
Líneas del tipo "Tool suggest_gifts used 123 tokens" no se pueden agregar ni filtrar bien. En algún momento verás que hay que migrar a JSON, y esa migración será dolorosa. Haz desde el inicio logs estructurados con campos request_id, tool_name, tokens_in/tokens_out, cost_estimate_usd.
Error n.º 3: ignorar el vínculo coste ↔ eventos de commerce.
Registrar checkout_success sin request_id y sin relación con las invocaciones de tools equivale a renunciar voluntariamente a entender qué escenarios generan beneficio y cuáles solo devoran tokens. No te dé pereza propagar request_id a lo largo de todo el recorrido, del widget al ACP.
Error n.º 4: intentar hacer una facturación «perfecta» en lugar de una estimación práctica.
Algunos equipos se pierden intentando reproducir la facturación de OpenAI al último token. En la realidad basta con el orden de magnitud: si un escenario cuesta 0.02 $ o 0.021 $, no es crucial. Lo importante es que no sea 2 $. No temas usar estimaciones aproximadas mediante usage o incluso heurísticas gruesas.
Error n.º 5: mirar solo el coste y olvidarse de la calidad.
A veces, al ver cifras bonitas de ahorro, apetece cambiar en todas partes al modelo más barato. Así puedes «optimizar» la aplicación hasta que los usuarios dejen de usarla. El coste debe considerarse junto con la calidad de las respuestas y la conversión — este vínculo será el tema de la siguiente lección de este mismo módulo: sobre pricing y experimentos «coste ↔ calidad».
GO TO FULL VERSION