En la misma ventana y con una única clave, tres sistemas mostraron tres cifras: $0.20 en el panel del proveedor, $0.07231 en Langfuse, $0.0711 en Grafana. La clave existía solo para este banco de pruebas, así que ningún gasto ajeno pudo colarse. La pregunta parecía «cuál de los tres miente».
La pregunta correcta resultó ser otra.
Cuántas mediciones independientes hay aquí
Langfuse y Grafana no son dos testigos. Ambos tomaban su cifra de la misma fórmula: tokens_de_la_respuesta × precio estático de la configuración. Coincidían entre sí no porque ambos acertaran, sino porque compartían la entrada. Hay exactamente una fuente independiente, y es el registro del proveedor: contra él se cobra el dinero.
| Fuente | De dónde saca la cifra | Independiente |
|---|---|---|
| registro del proveedor | es lo que se te cobra | sí |
| Langfuse | fórmula «tokens × precio» | no |
| Prometheus / Grafana | la misma fórmula | no |
Dos paneles que coinciden se leen como corroboración y no lo son. Los testigos se cuentan por fuente de datos, no por número de paneles. A partir de ahí la investigación se redujo a una pregunta: por qué la única fórmula que hay se queda corta casi por tres.
Los precios eran correctos
Primera sospecha: los precios de la configuración se habían quedado desfasados. Esa hipótesis se descarta barato — el catálogo de modelos del agregador es público y no necesita clave. El cotejo cuadró hasta el tercer decimal: 0.43 / 0.87 frente a 0.435 / 0.87 en un modelo, un 0.10 / 0.40 exacto en el otro.
De paso apareció un detalle que sirvió después: en un modelo de razonamiento el catálogo lleva una línea de precio propia, internal_reasoning, distinta del precio de completion.
La pista: el mismo multiplicador en ambas direcciones
Una prueba directa con la contabilidad de uso activada mostró que la desviación no estaba en todas partes:
| Modelo | Fórmula propia | Cobró el proveedor | Relación |
|---|---|---|---|
| clase gemini | $6.67e-5 | $6.67e-5 | 1.00 |
| clase deepseek | $2.81e-4 | $4.82e-4 | 1.7× |
Un modelo cuadraba al céntimo, el otro se desviaba 1,7 veces. Lo que resuelve el caso no es la brecha, sino su forma. En cost_details la parte del prompt estaba calculada a 0.745/M en lugar de 0.435/M — y el coeficiente 1,7 era idéntico en el prompt y en la completion.
Con precios correctos, un mismo multiplicador en ambas direcciones a la vez solo puede significar una cosa: las cifras de tokens de la respuesta no son las cifras por las que se factura.
Qué había debajo de la discrepancia
El agregador facturaba por los tokens nativos del modelo del proveedor, mientras que usage.prompt_tokens y usage.completion_tokens devolvían los normalizados, llevados a una escala común para poder comparar modelos distintos entre sí. Un modelo tiene un tokenizador nativo más denso, de ahí el 1,7×. En el otro los normalizados coinciden con los nativos, y por eso cuadraba.
El resto es aritmética: el alias más caro y más cargado del banco de pruebas era justo el que se desviaba. Él arrastró el total de $0.20 a $0.07.
La separación entre ambos contadores no ha desaparecido: sigue viva en la API. El endpoint de estadísticas de generación documenta los dos conjuntos como campos separados: tokens_prompt y tokens_completion frente a native_tokens_prompt, native_tokens_completion, native_tokens_reasoning, native_tokens_cached — estos últimos con la nota «as reported by provider».
La regla que sobrevive a cualquier proveedor
Tu propia fórmula de coste es una estimación, no una medición. Su sitio es la vía de respaldo. La cifra autorizada la da quien emite la factura.
En la práctica eso significa cuatro cosas:
- Tomar
usage.costycost_detailsde la respuesta del proveedor como fuente de verdad. - Mantener la tabla estática de precios como red de seguridad: hace falta allí donde el proveedor no devuelve precio alguno — por ejemplo en el tier local, donde el precio es cero de todos modos.
- Escribir en la configuración que los precios son un respaldo. Si no, dentro de medio año volverán a leerse como verdad.
- Comprobar que el SDK cliente no recorta campos no estándar.
costycost_detailsno forman parte del esquemausagede OpenAI y acaban entre los campos extra del modelo de respuesta; en la práctica sobreviven al SDK y se alcanzan congetattr,model_dumpymodel_extra— pero eso se confirma con una prueba, no con fe.
La receta caducó, la regla no
La medición se hizo el 14 de julio de 2026. Antes de publicar cotejé la documentación del proveedor, y describe otro comportamiento.
| Qué | Medición 14.07.2026 | Documentación a 23.08.2026 |
|---|---|---|
| contadores en la respuesta | normalizados | «calculated using the model's native tokenizer» |
| base de facturación | tokens nativos | «pricing are based on these native token counts» |
| activar la contabilidad de uso | parámetro en el cuerpo de la petición | «always included automatically» |
El proveedor alineó el comportamiento: los contadores de la respuesta pasaron a ser nativos, y el antiguo parámetro usage: {include: true} quedó obsoleto y, según la documentación, no tiene efecto. Ya no hay motivo para ponerlo — y es justo el tipo de línea que no conviene copiar de un artículo de hace medio año.
Nada de eso refuta la medición: los precios estaban cotejados con el catálogo en vivo, un modelo cuadraba al céntimo y el otro se desviaba con un multiplicador idéntico en ambas direcciones. Cambió el proveedor, no el hecho. Leer la cifra autorizada en lugar de recalcularla sigue siendo la única forma de no separarse del registro — y eso no dice nada sobre gastar menos tokens, que es una conversación aparte.
Por qué no bastan dos sumandos
La fórmula prompt × precio_in + completion × precio_out da por supuesto que toda la generación se describe con dos números. Para los modelos de razonamiento eso es falso al menos de dos maneras.
La primera es la línea de precio propia: el razonamiento puede facturarse a su propia tarifa, distinta de la de completion. La segunda es un anidamiento nada obvio. En mi prueba los tokens de razonamiento estaban dentro de completion_tokens (261 de 317), pero eso es un hecho sobre un par concreto de proveedor y modelo, no una regla general. Si en otro par van al lado, la fórmula pierde en silencio una partida entera de gasto.
La caché entra aquí también: input_cache_read y cache_write_tokens se facturan por separado. En mi ejecución estaban a cero, así que no probé cómo se comporta la fórmula con la caché caliente.
Hay además una clase vecina donde no existe precio alguno: una generación que falla del lado del proveedor llega con estado 200, sin contenido y sin línea de coste — ninguna capa de protección la ve. Allí falta el precio. Aquí está, solo que calculado sobre los tokens equivocados.
La comprobación también miente
El cotejo no dio cero al primer intento, y los dos fallos estaban en la comprobación misma, no en el sistema.
Primero, un grep fue contra un nombre de métrica que no existía y devolvió cero. El cero parece «el arreglo no funcionó» cuando significa «he preguntado lo que no era».
Después, el delta del registro leído justo tras la ejecución salió menor que lo anotado: los workflows hijos enviaban sus llamadas al final y el contador de cuenta del proveedor se ponía al día con retraso. Un par de minutos más tarde las cifras coincidieron exactamente: $0.019571 frente a $0.019571.
Las dos trampas son de la misma clase: la comprobación mintió sobre sí misma, no sobre su objeto. La misma clase que una cabecera verde de política de seguridad que no demuestra nada, porque se tomó en un estado donde las peticiones relevantes todavía no ocurren.
Qué queda cuando el proveedor lo arregla todo
La separación entre nativo y normalizado es un detalle de un agregador concreto, y ya está limado. El mecanismo que hay debajo, no.
Cualquier capa entre tú y el modelo normaliza: lleva los contadores a una escala común, agrega, redondea, añade sus propias líneas de facturación. Mientras el coste se calcule multiplicando en tu lado, estás midiendo tu modelo del gasto y no el gasto. Empezará a separarse en silencio, justo el día en que algo cambie aguas arriba.
Una regla simple: el coste tiene una sola fuente, y no es tu fórmula.