0007 — Medición de bots por flujo, nodo y herramienta
Estado: aceptada · Fecha: 2026-09-19
Contexto
Para seguir, vigilar y controlar los bots hacía falta saber, por flujo y por nodo:
- cuántas veces corre cada paso;
- cuánto tarda;
- cuánto gasta;
- dónde falla;
- dónde abandona la gente.
Lo único que existía era token_usage_events, que da el consumo por turno y modelo para
facturar, sin ninguna relación con el flujo.
Decisión
-
Un medidor por turno (
FlowTurnMeter). Viaja en elconfigurabledel grafo ycreateFlowGraphlo aplica a todos los nodos conmeasured(). Los nodos no cambian. -
Tablas propias (
20261020000001_bot_flow_metering.sql):bot_turns,bot_node_runsybot_tool_callspara el detalle crudo;bot_turns_daily,bot_node_dailyybot_tool_dailypara el resumen por día local del tenant;bot_flow_revisionspara las etiquetas de nodo de cada revisión.
Solo escribe el backend, con el RPC
record_bot_turn. -
Sin PHI. Se lee con
bot.view, un permiso más amplio que el de la historia clínica. Por eso solo se guardan identificadores, tipos, tiempos y contadores:- nada de texto de mensajes, ni argumentos o resultados de herramientas, ni valores de variables;
- los errores se guardan como código, nunca como mensaje.
-
Extracción por el motor de reportería, sin endpoints a medida. Hay cinco fuentes,
bots.*, con vistassecurity_invoker. El p95 y el embudo por nodo, que el motor no sabe calcular, salen debot_node_stats: autoriza dentro de la función y admite al backend (service_role). Para plataforma estábot_metering_platform_summary, solo para superadmin. -
Retención.
- El detalle crudo se guarda
BOT_METERING_RAW_RETENTION_DAYS(180 por defecto, mínimo 7). - Los resúmenes diarios se guardan siempre.
BotMeteringSchedulerrecalcula los resúmenes cada hora y purga una vez al día.
- El detalle crudo se guarda
-
Control por alertas, no por corte.
- Hay umbrales por tenant o por bot (
20261020000002_bot_alerts.sql), con defectos de plataforma para errores, flujos cortados y lentitud. - El tope diario de costo o tokens solo avisa. El usuario lo decidió el 2026-09-19: un pico de mensajes no puede dejar a los pacientes sin respuesta.
- Las alertas se ven en la tarjeta Métricas y en la campana, en una tabla propia. No se reutiliza
usage_alerts, que solo sabe de consumo, nicore_alerts, que vive en una rama sin aprobar.
- Hay umbrales por tenant o por bot (
-
Facturación intacta. El consumo por nodo usa un collector hijo (
TokenUsageCollector.scope()) que suma también en el del turno, así que lo que se cobra no cambia. Cada tabla calcula su costo desdemodel_pricing, igual querecord_token_usage.
Alternativas descartadas
- Añadir
turn_idatoken_usage_eventsy un parámetro arecord_token_usage. Añadir un parámetro con valor por defecto crea una segunda versión de la función. Las llamadas existentes con parámetros por nombre pasarían a ser ambiguas (function is not unique) en pleno cobro. Se prefirió dejar facturación intacta y cuadrar por tenant, bot y día. - LangSmith como fuente de métricas. Envía entradas y salidas a un tercero, hay PHI y no existe acuerdo de datos. Queda como traza opcional, sin contenido (fase 6 del plan del motor).
- Una columna JSON con todo el turno. Imposible de agregar desde el motor de reportería sin una función por consulta.
Consecuencias
- Toda columna nueva de estas tablas tiene que respetar la regla de "sin PHI". El test
supabase/tests/bot_meteringcomprueba que ningún campo de reporte apunta a texto. - El guardado es best-effort. Si el RPC falla, el turno se atiende igual y la métrica se pierde, con un aviso en el log.
- La dimensión
botde los reportes es el id del bot. La pantalla de métricas de cada bot le pone nombre.