Saltar al contenido principal

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​

  1. Un medidor por turno (FlowTurnMeter). Viaja en el configurable del grafo y createFlowGraph lo aplica a todos los nodos con measured(). Los nodos no cambian.

  2. Tablas propias (20261020000001_bot_flow_metering.sql):

    • bot_turns, bot_node_runs y bot_tool_calls para el detalle crudo;
    • bot_turns_daily, bot_node_daily y bot_tool_daily para el resumen por día local del tenant;
    • bot_flow_revisions para las etiquetas de nodo de cada revisión.

    Solo escribe el backend, con el RPC record_bot_turn.

  3. 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.
  4. Extracción por el motor de reportería, sin endpoints a medida. Hay cinco fuentes, bots.*, con vistas security_invoker. El p95 y el embudo por nodo, que el motor no sabe calcular, salen de bot_node_stats: autoriza dentro de la función y admite al backend (service_role). Para plataforma está bot_metering_platform_summary, solo para superadmin.

  5. 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.
    • BotMeteringScheduler recalcula los resúmenes cada hora y purga una vez al día.
  6. 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, ni core_alerts, que vive en una rama sin aprobar.
  7. 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 desde model_pricing, igual que record_token_usage.

Alternativas descartadas​

  • Añadir turn_id a token_usage_events y un parámetro a record_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_metering comprueba 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 bot de los reportes es el id del bot. La pantalla de métricas de cada bot le pone nombre.