La Línea de Registro Que Reformuló Cómo Instrumentamos Agentes
El encabezado cache-reason de Vercel es engañosamente simple: un campo que registra no solo si una solicitud golpeó el caché, sino por qué no lo hizo. MISS, STALE, BYPASS, REVALIDATED — cada uno te dice algo diferente sobre lo que acaba de suceder y qué te costó. La mayoría de los equipos registran llamadas de herramientas como tramos opacos de éxito/fallo. Los que registran la dimensión razón son los que siguen cuerdos a las 2am cuando los picos de latencia ocurren.
Hemos cometido este error antes. Enviamos agentes que registraban llamadas de herramientas con un campo de estado (ok, error) y nada más. Una recuperación devolvió cero resultados — ¿fue una falla de reescritura de consulta? ¿Un filtro de umbral? ¿Una ACL eliminando fragmentos silenciosamente? El estado era el mismo de cualquier forma. Quemaríamos dos horas en Datadog intentando reducirlo, cuando cinco caracteres en el tramo (reason: low_confidence vs reason: filtered_by_acl) habría colapsado eso a treinta segundos.
El cambio del modelo mental es pequeño pero decisivo: registra la decisión, no solo el resultado. Un registro de cache-reason no es una ocurrencia tardía — es una primitiva de observabilidad de primera clase que transforma "esto es lento" en "esto es lento porque la herramienta X se ejecutó de nuevo a pesar de un resultado en caché de 20 segundos de antigüedad, porque el hash del esquema cambió."
Por Qué Los Agentes Son Sistemas de Caché Disfrazados
Los agentes son cachés en capas que nadie trata como cachés. La respuesta del LLM en sí está en caché en hash de solicitud más versión del modelo. Los embeddings están en caché en contenido de fragmento. Las llamadas de herramientas se memorizan en hash de argumento. La ventana de contexto es un caché caliente. El bloc de notas del planificador se reutiliza entre turnos. Cada capa tiene semántica de hit/miss/stale, pero la mayoría de los equipos no exponen nada de esto.
Cuando envías un agente a escala, cada capa redundante te cuesta tokens y tiempo de reloj de pared. Una recuperación que se re-ejecuta porque incrementaste el campo chunk_id pero no cambiaste el embedding quema tokens de embedding y llamadas a API. Una herramienta que se ejecuta dos veces porque el planificador vio una puntuación de confianza diferente la segunda vez y no reconoció la invocación anterior quema latencia y costo. A escala, la orquestación está resuelta — el enrutamiento de tokens es el problema. Pero no puedes enrutar lo que no puedes ver. Los códigos de razón hacen visibles las capas invisibles.
Comienza simple: si puedes describir lo que el agente hizo como un diagrama de flujo, puedes asignar un código de razón a cada punto de decisión. ¿Decidió el planificador llamar a la herramienta? (reason: initial_call o reason: replan_required.) ¿Golpeó el caché? (reason: match, reason: stale, reason: bypass.) ¿Devolvió resultados la recuperación? (reason: hit, reason: low_confidence, reason: filtered.) Cada etiqueta de razón tus tramos con un escalar único que te permite construir histogramas, series de tiempo y desgloses de costos que realmente informan cómo sintonizas el sistema.
Los Códigos de Razón Que Ahora Emitimos en Cada Llamada de Herramienta
Aquí está la taxonomía en la que hemos convergido. Adáptala a tu stack, pero la forma importa más que las palabras exactas.
tool.miss: Sin llamada anterior con este hash de argumento. El caché estaba vacío.tool.stale: TTL expirado. Adjunta ttl_ms y age_ms al tramo para que puedas hacer histogramas de cuándo las cosas se vuelven obsoletas.tool.bypass: El llamador estableció no_cache=true o el usuario es un administrador re-ejecutando un comando. Esperado, no es un problema.tool.revalidated: 304 desde upstream; actualización económica que no re-ejecutó la herramienta.tool.rerun_schema_drift: El hash del esquema de entrada cambió (se agregó un campo, el tipo cambió, la regla de validación se endureció). Misma llamada lógica, firma diferente.tool.rerun_planner_forced: El LLM pidió la llamada de nuevo a pesar del caché. Adjunta planner_temp y model para entender por qué se re-planificó.
Cada razón obtiene cost_ms (cuánto tiempo tomó) y cost_tokens (si aplica) adjuntos. Estos son los datos que alimentan desgloses de costo por acción de agente exitosa — puedes preguntar "¿qué códigos de razón están quemando más tokens esta semana?" y realmente obtener una respuesta respaldada por datos.
Los Fallos de Recuperación Mienten Más Fuerte Que Los Fallos de Herramientas
Los cachés de herramientas son directos porque las llamadas de herramientas son deterministas. La recuperación es donde los códigos de razón ganan su valor, porque los fallos de recuperación son silenciosos por defecto.
Distinguimos retrieval.miss (sin fragmentos intentados), retrieval.low_confidence (fragmentos devueltos pero puntuados por debajo de tu umbral), y retrieval.filtered_by_acl (los fragmentos se habrían devuelto pero los permisos del usuario los eliminaron). Un sistema de producción de un cliente que golpea retrieval.low_confidence el 40% del tiempo es un problema diferente al que golpea retrieval.miss el 5% del tiempo. El primero apunta a la calidad del embedding o los límites de fragmentos. El segundo apunta a la lógica de reescritura de consultas o brechas de índice.
La brecha entre recall de evaluación (0.9) y recall de producción (0.4) es donde los códigos de razón hacen su mejor trabajo. Registra no solo si la recuperación golpeó, sino por qué no lo hizo: below_threshold, deduped (fragmento duplicado ya en contexto), reranker_dropped (pasó similitud semántica pero falló en verificaciones léxicas o de dominio). Y registra la reescritura de consulta que produjo el fallo, no solo la consulta final. Si tu agente reescribió "muéstrame ingresos de Q3 por región" a "revenue region quarter:Q3" y obtuvo cero resultados, ese es el punto de datos que te enseña dónde se rompió la lógica de reescritura.
Ejecuta un informe semanal: las 10 razones de fallo principales por frecuencia. Las que mueven tu aguja alimentan directamente cambios de índice, ajustes de tamaño de fragmento y sintonización de reranker. Así es como diagnosticas la calidad de recuperación antes de que los usuarios presenten tickets.
Los Modos de Fallo Que Esto Detecta Antes de Que Lo Hagan Tus Usuarios
La deriva de esquema silenciosa es el peor tipo. Cambias la estructura de entrada de una API, olvidas aumentar un campo schema_hash en la clave de caché, y de repente una herramienta que solía cachear limpiamente se re-ejecuta en cada turno. Pico de costo de 10x, cero errores, sin alertas hasta que alguien pregunta por qué la factura se duplicó.
Hemos enviado una herramienta con TTL establecido en 60 segundos en datos que cambian cada hora. Resultado: el 98% de los golpes de caché eran obsoletos, y las llamadas de revalidación estaban duplicando la latencia. El panel de control mostraba "cache hit: 98%" y todos celebraban hasta que agregamos códigos de razón. Luego vimos tool.stale dominando el histograma y lo arreglamos en una tarde.
La deriva de temperatura del planificador es sutil. Una ruta de código establece temperature=0.7 para la planificación, otra no anula un valor predeterminado global de 1.0. Misma llamada de herramienta, comportamiento diferente del LLM, decisión diferente de re-ejecutar. El caché parece estar funcionando hasta que haces histogramas por razón — luego ves tool.rerun_planner_forced aumentando en el flujo de trabajo de un cliente.
Los filtros de ACL que silenciosamente eliminan el 30% de los fragmentos recuperados son invisibles sin códigos de razón. El agente aún devuelve una respuesta (del 70% que pasó), así que la llamada se ve exitosa. Pero retrieval.filtered_by_acl en tus histogramas te dice que este cliente está operando en datos obsoletos o incompletos y no lo sabe.
La lógica de reintento puede disfrazarse como un fallo de caché. Un 429 desencadena un reintento, el reintento tiene éxito, y lo registras como tool.miss en lugar de tool.rate_limited. Con el tiempo, tu lógica de sintonización no puede distinguir entre datos genuinamente faltantes y límites de velocidad transitorios. Agrega la razón, y la distinción se vuelve obvia en un histograma.
Conectándolo Sin Reconstruir Tu Stack
No necesitas una nueva plataforma de observabilidad. Agrega un único enum de razón a tus tramos OpenTelemetry existentes: agent.cache.reason como un atributo de cadena. Envuelve tus clientes de herramientas con un decorador delgado que emita la razón en cada retorno, junto con arg_hash y schema_hash para que puedas agrupar por decisión, no por sitio de llamada.
Paneles de control: cardinalidad de código de razón a lo largo del tiempo (nuevas razones = nuevos modos de fallo), costo por razón (qué decisiones queman más), p95 por razón (qué razones te ralentizan). Para muestreo, mantén el 100% de razones que no son golpes (son raras y valiosas) y muestrea golpes al 1% (son ruido a escala).
Comienza pequeño. Elige tus dos llamadas de herramientas más costosas esta semana. Agrega un enum de razón de cinco valores a cada una. Despliega el martes. Para el viernes, tu panel de control te dirá qué códigos de razón están realmente disparándose y cuáles inventaste. Expande desde allí. La verdadera observabilidad de agentes se construye sobre decisiones hechas visibles, no resultados registrados después del hecho.
Elige tu única llamada de herramienta más costosa esta semana, agrega un enum de razón de cinco valores a su tramo, y observa lo que tu panel de control te dice para el viernes — si quieres ayuda conectándolo a un stack de agente existente, estamos en /contact.