Puedo decir cuánto cuesta cada petición — y cuántas fallan.
No es una app nueva: es el mismo agente de los Artefactos 4 y 5, que ya vivía en Cloud Run — pero a oscuras. Este artefacto le enciende la luz: un medidor de coste por petición con aislamiento entre peticiones concurrentes, una bitácora JSON que Cloud Logging indexa campo a campo, y la definición de hecho respondida con consultas sobre datos de producción.
Desplegar era lo fácil: lo difícil es saber qué pasa
Cuando arrancó este artefacto, «desplegar en producción» ya estaba hecho dos veces (el RAG y el agente, ambos en Cloud Run). Pero ambos servicios eran cajas cerradas: cada petición gastaba tokens que nadie contaba y los fallos no dejaban rastro. La factura de OpenAI llegaba a fin de mes como un total mudo — sin saber qué viaje costó qué. El trabajo real del artefacto no era el contenedor: era la observabilidad.
Un contador con un carril por petición
Cada respuesta del modelo trae un objeto usage con los tokens exactos que costó esa llamada — y hasta
este artefacto, se tiraba sin mirarlo. El hallazgo que dimensiona el problema: una petición no es una
llamada. Una sola oferta dispara el bucle del agente más las herramientas por dentro: 7 llamadas al LLM
que hay que sumar en la cuenta correcta.
¿Y cuál es «la cuenta correcta» cuando Cloud Run atiende dos peticiones a la vez en el mismo
contenedor? Un contador global mezclaría los tokens de una con los de otra. La solución es un
ContextVar: se usa como una variable global, pero cada petición ve su propio carril —
el mismo principio de aislamiento estructural que los namespaces de Pinecone, ahora aplicado a la concurrencia.
La tabla de precios vive en el propio módulo (entrada y salida se cobran distinto: generar cuesta ~4× más
que leer), y un modelo sin fila devuelve coste: null — un «no sé» honesto antes que un número
inventado. Y la lectura fina que se lleva el CV: la entrada supera a la salida 5 a 1, porque el bucle
stateless reenvía todo el contexto en cada turno. En agentes, el coste dominante es releer
— por eso existe el prompt caching.
Una línea JSON por petición, y stdout es el canal
El contenedor de Cloud Run escala a cero: cuando no hay tráfico, se apaga — y con él muere
cualquier métrica guardada en memoria. La bitácora resuelve el problema con el principio 12-factor de
logs as event streams: cada petición emite una línea JSON por stdout (con flush, que el búfer
también muere), y la plataforma hace el resto. Si la línea es JSON válido, Cloud Logging la indexa
campo a campo — cada clave inventada aquí se vuelve una columna consultable allí.
{"severity":"INFO", "evento":"peticion", "ruta":"/preguntar", "estado":200,
"latencia_ms":23395, "completado":true, "turnos":4,
"llamadas_llm":7, "tokens_entrada":7270, "tokens_salida":1473, "coste_usd":0.001974}
El arreglo de Wald. La revisión pre-deploy cazó que la línea del error 500 salía sin coste — para
las consultas, los fallos eran gratis. Es el sesgo de supervivencia: medir solo a los que vuelven. Ahora
la petición que revienta cierra su cuenta con lo gastado hasta el crash (el ContextVar sigue vivo en
el mismo hilo tras la excepción) — y los fallos, que son justo los viajes caros sin recompensa, declaran lo suyo.
La bitácora, en vivo
Cuatro tipos de petición pueden llegarle al agente. Lánzalas y mira lo que la bitácora escribe — y cómo las dos consultas del DoD se recalculan debajo con cada línea. Los rangos de tokens y latencia son los medidos en producción, y el coste se calcula con la tabla de precios real del medidor.
— la bitácora está vacía: lanza una petición —
Nada llegó a la carretera sin pasar controles
Entre «funciona en el Mac» y «sirve tráfico real» hubo una cadena de puntos de control, cada uno heredado de una lección anterior del roadmap:
cv_denys.md está prohibido en el viaje a GitHub (público) y es obligatorio en el viaje a la imagen (el agente lo necesita). Por eso .gitignore y .gcloudignore son ficheros distintos, revisados antes de cada salida.
.env es una copia que se sincroniza, no un original que se discute.
Lo que este artefacto no usa, y por qué
El plan original decía «MLflow/Airflow si hay pipeline». La condición no se cumple, y la decisión razonada vale más que las siglas: Airflow orquestaría un único trabajo semanal que ya orquesta el cron de GitHub Actions — un servicio permanente de varios contenedores para un solo vuelo que ya despega solo. Y la trazabilidad de experimentos que daría MLflow ya existe a medida: el historial apéndice-solo del Artefacto 5, con el hash de cada prompt y el informe que se regenera de los datos. Saber cuándo no usar una herramienta también es criterio de ingeniería — y se defiende mejor en una entrevista que el adorno.
Los cuatro cuadernos del artefacto
Cada concepto del recorrido, escrito como cuaderno propio en el momento en que se aprendió. Estos cuatro son los de la monitorización; hay dieciocho en total.
Los criterios, cumplidos
medidor.py recoge el usage de las 7 llamadas de cada petición en un carril aislado por ContextVar y cierra la cuenta en dólares con la tabla de precios del modelo.
00004-fvk sirviendo el 100% del tráfico.
coste_usd, fallos duros (estado ≥ 500) y blandos (completado = false) — respondidos sobre Cloud Logging con datos de producción, y el coste local cuadrando con el de prod.
Desplegar lo consigue cualquiera con un tutorial. Saber qué hace tu servicio cuando no estás mirando — cuánto cuesta, cuánto falla, y poder demostrarlo con una consulta — eso es operar en producción.