May 20266 min de lecturaJohan Bretonneau

Tu gateway de LLM no sabe que estás ejecutando un agente
Cómo el routing sin estado rompe silenciosamente tus agentes multi-turno

Cada gateway LLM enruta cada request de forma independiente. Para una app de un solo disparo, perfecto. Para un agente multi-turno, es un desastre anunciado. Así es como el routing session-aware lo cambia todo.

Hay una categoría de bugs que solo aparece en producción, con cargas reales, después de que ya has hecho el deploy. No lanza ninguna excepción. No devuelve un 500. El agente simplemente va empeorando en silencio, y haciéndose más caro, con el tiempo.

El bug es este: tu gateway LLM es stateless, y tu agente no lo es.

El problema del routing stateless

Cuando un request llega a una gateway LLM, la gateway toma una decisión de routing: qué modelo, qué proveedor, en función del coste, la carga y la latencia. Luego reenvía el request y lo olvida. El siguiente request empieza de cero.

Este modelo funciona perfectamente para el 90 % de los casos de uso LLM. Un usuario manda un mensaje, recibes una respuesta, listo. Cada llamada es independiente. El routing stateless es rápido, sencillo y correcto.

Pero los agentes no son llamadas independientes. Un agente es una conversación: una secuencia de turnos que se construyen unos sobre otros. El context window en el turno 5 contiene todo lo que pasó desde el turno 1 hasta el turno 4. El comportamiento del modelo en el turno 5 depende de cómo interpretó los turnos 1 a 4.

Cuando la gateway enruta el turno 1 a claude-sonnet-3-7 y luego enruta el turno 5 a claude-haiku-3-5 porque el clúster está bajo carga, no has cambiado solo de modelo. Has introducido una inteligencia diferente en medio de un proceso de razonamiento en curso.

El agente estaba construyendo un plan. Ahora un modelo diferente lee ese plan y decide qué hacer a continuación, un modelo con puntos fuertes diferentes, tendencias diferentes, modos de fallo diferentes. El contexto de la conversación fue escrito por un modelo y ahora lo está interpretando otro.

Lo que pasa en concreto

Para que quede claro. Imagina un agente de investigación: hace fetch de datos, razona sobre ellos, llama a herramientas, construye una respuesta a través de varios turnos.

Turno 1. La gateway ve un request nuevo, sin historial de conversación. Enruta al mejor modelo disponible para la tarea, digamos claude-sonnet-3-7.

Turno 2. Sonnet genera un plan. Detalla 4 pasos, hace algunas llamadas de herramientas intermedias, hace una pregunta de aclaración. La respuesta es coherente y encaminada.

Turno 3. Una oleada de tráfico llega a tu gateway. El load balancing se activa, y el tier más barato se convierte en la opción de routing inteligente. Tu turno 3 va a claude-haiku-3-5.

Turno 4. Haiku lee el contexto acumulado, una conversación de 12K tokens escrita por Sonnet, llena de cadenas de razonamiento matizadas y pasos parcialmente completados. Haiku hace lo que puede, pero su interpretación es diferente. Se pierde una sutileza del paso 3. Da una respuesta más corta, más plana.

Turno 5. Sonnet vuelve (la carga ha bajado). Lee la respuesta del turno 4 e intenta continuar, pero el turno 4 se desvió. Sonnet intenta reconciliar. El agente entra en bucle. Los costes se disparan.

Sin error lanzado. Sin 500. Solo un agente que pierde coherencia tranquilamente a través de cambios de modelo de los que nunca tuvo noticia.

El arreglo: un solo header

La solución más simple posible es también la correcta: session locking mediante un conversation ID.

X-Conversation-ID: <tu-session-id>

Manda ese header con cada request de la conversación de un agente. La gateway lo reconoce y recuerda: el turno 1 fue al modelo X, así que los turnos 2 a N también van al modelo X. La sesión queda fijada.

Sin cambios en la lógica de tu agente. Sin migraciones de esquema. Sin ningún SDK nuevo que aprender. Un header HTTP en cada llamada.

En HiWay2LLM, añadimos esto como parte del Router Profile AGENT. Cuando usas el perfil AGENT, la gateway aplica el session lock automáticamente, una vez que se selecciona un modelo para un conversation ID, se mantiene seleccionado durante toda la vida de esa conversación. También aplica un floor de calidad, impidiendo los downgrades de ahorro de costes a mitad de tarea, incluso bajo carga.

Los Router Profiles: adaptar el comportamiento al workload

Un insight que tuvimos pronto: distintos workloads necesitan comportamientos de routing fundamentalmente diferentes. Un router de talla única es un compromiso equivocado para todos.

Llegamos a cuatro perfiles principales:

CHAT. Interactivo, sensible a la latencia, ligero. Un humano está esperando la respuesta. Optimiza para la velocidad percibida, prefiere modelos con baja latencia al primer token, acepta cierta varianza en la profundidad de los outputs. No hace falta session locking porque cada intercambio es corto.

AGENT. De larga duración, multi-turno, crítico en exactitud. Una máquina está orquestando una tarea compleja. Session lock obligatorio. Floor de calidad aplicado. Sin downgrade de modelo a mitad de sesión independientemente de la carga.

BATCH. Procesamiento async, no urgente. Una job queue trabaja sobre un gran backlog. La latencia no importa, importan el throughput y el coste. Maximiza la eficiencia en el lado del proveedor, reparte la carga.

SAVINGS. Routing cost-first. Has puesto una restricción de presupuesto dura y quieres exprimir cada euro. El router encuentra el modelo más barato capaz de manejar la tarea, sin floor de calidad, sin extras.

El header para fijar un perfil es igual de simple:

X-Router-Profile: AGENT

Eliges el perfil una vez, al nivel del system prompt o de la inicialización del agente. La gateway se encarga de todo lo demás.

Detección de divergencia: la capa por debajo

El session locking resuelve el problema del cambio de modelo. Pero hay un modo de fallo relacionado que el locking solo no arregla: el agente que se queda en el mismo modelo pero empieza a entrar en bucle de todas formas.

Esto pasa cuando el modelo entra en un estado en el que repite acciones similares una y otra vez, volver a lanzar una llamada de herramienta, volver a hacer la misma pregunta, retroceder a un paso ya completado. El modelo es consistente, pero el agente está atascado.

A esto lo llamamos divergencia. Cuando el perfil AGENT la detecta, un patrón de conversación que cicla sin progresar, puede inyectar una señal correctiva. No un hard stop, no un kill-switch, sino un pequeño reencuadre: un mensaje de sistema que la gateway inyecta en el siguiente turno para reorientar al modelo.

Deliberadamente no publicamos aquí la lógica de detección. Lo que importa es el comportamiento observable: los agentes que usan el perfil AGENT divergen significativamente menos a menudo que los mismos agentes sin él. Cuando divergen igualmente, la recuperación es automática.

Por qué lo construimos

La respuesta honesta building-in-public: lo necesitábamos para nuestros propios agentes en Mytm.ai.

Tenemos agentes corriendo para varios flujos de trabajo internos, pipelines de investigación, procesos de contenido, análisis de documentos. Al principio, antes de tener una instrumentación decente, empezamos a notar que algunos de nuestros agentes producían outputs sutilmente peores en ciertos runs. El debug era un dolor. Los agentes no crasheaban, simplemente… derivaban.

Cuando excavamos en los logs de requests, el patrón era obvio: las conversaciones donde bajaba la calidad eran aquellas en las que el modelo había cambiado a mitad de sesión. No siempre, pero lo suficientemente consistente para que importara.

El arreglo, X-Conversation-ID y una política de routing bloqueada, fue la solución más simple posible. Lo construimos para nosotros primero, lo deployamos internamente, y vimos desaparecer el problema de la deriva. Luego lo convertimos en una feature de primera clase en HiWay2LLM porque sabíamos que cualquier equipo que ejecute agentes chocaría contra el mismo muro.

El header era la respuesta obvia. La parte difícil fue darse cuenta de que el problema existía.

En la práctica

Si hoy corres agentes a través de cualquier gateway LLM, comprueba si soporta algún tipo de afinidad de sesión a nivel de conversación. La mayoría no lo hace. Fueron construidas para el routing one-shot, y eso es una elección arquitectónica legítima para ese caso de uso.

Pero si tus agentes hacen algo complejo, razonamiento multi-turno, uso de herramientas, planificación iterativa, necesitas consciencia de sesión en la capa gateway, no solo dentro del código de tu aplicación.

El workaround a nivel de aplicación es pasar siempre model=claude-sonnet-3-7 (o lo que uses) explícitamente y saltarte la lógica de selección de modelo del router. Funciona, pero pierdes los beneficios del routing inteligente: fallback automático en caídas de proveedor, optimización de costes entre tareas, load balancing en tus claves API.

La solución correcta es una gateway que entiende la diferencia entre un request y una conversación, y enruta en consecuencia.

// Antes: stateless, cada turno enruta de forma independiente
const response = await hiway.chat({
  messages: conversationHistory,
});

// Después: session-aware, modelo bloqueado para toda la conversación
const response = await hiway.chat({
  messages: conversationHistory,
  headers: {
    "X-Conversation-ID": sessionId,
    "X-Router-Profile": "AGENT",
  },
});

Dos headers. Sin otros cambios. Tu agente tiene ahora una identidad de modelo consistente durante toda la conversación.

Ese es el arreglo completo. Lo sorprendente no es lo complejo que es. Lo sorprendente es que tan pocas gateways lo ofrezcan.

Probar HiWay2LLM →

Sin tarjeta bancaria


Lecturas relacionadas: Vimos a un agente de IA quemar 200 $ a las 3 de la mañana, 5 patrones de coste LLM a escala.

Compartir

LinkedInXEmail

Was this useful?

Comments

Be the first to comment.