Tu agente de IA acaba de tener una conversación de 30 minutos con un usuario. Discutieron los requisitos del proyecto, compartieron preferencias y tomaron decisiones. Luego, el usuario escribe /new para comenzar una nueva sesión.
El agente intenta consolidar esa conversación en la memoria a largo plazo. La llamada al LLM falla. Rate limit. Timeout. O el modelo devuelve texto en lugar de llamar a la tool requerida.
La memoria ha desaparecido. Treinta minutos de contexto, evaporados.
Esto sucede más a menudo de lo que piensas. Lo rastreamos en nuestras instancias de LemonClaw: la consolidación de memoria tenía una tasa de fallo de aproximadamente el 15% en cualquier modelo individual. Para una funcionalidad que se supone que es infraestructura invisible, eso es inaceptable.
Si estás construyendo la superficie del producto circundante en lugar de solo el subsistema de memoria, combina esta página con la guía de chatbot de una sola clave y la guía de LemonClaw auto-alojado. La durabilidad de la memoria solo importa cuando el agente realmente vive dentro de una aplicación utilizable.
Cómo lo manejan otros frameworks (No lo hacen)
La mayoría de los frameworks de agentes de IA tratan la consolidación de memoria como una simple llamada de LLM. Si funciona, genial. Si no, la memoria se pierde.
El framework predecesor de LemonClaw utiliza el mismo modelo para la consolidación que para la conversación. Una llamada a Claude Sonnet que cuesta $0.003 y tarda más de 8 segundos, solo para resumir un chat que el usuario nunca verá. Cuando esa llamada falla (rate limit, timeout, error del modelo), el framework registra una advertencia y continúa. El contexto del usuario desaparece.
nanobot, otro framework popular, tiene la misma arquitectura. Un modelo, un intento, sin fallback. La función de consolidación ni siquiera tiene un timeout. Un upstream lento (los errores Cloudflare 524 son comunes) bloquea toda la sesión hasta que se cae la conexión.
Ninguno de los frameworks separa la consolidación del modelo principal. Ninguno tiene lógica de fallback para las operaciones de memoria. Ninguno distingue entre "la llamada a la API falló" y "la llamada a la API tuvo éxito pero el modelo no hizo lo que pedimos".
Estos no son casos aislados. Con tasas de fallo del 15% en cualquier modelo individual, un framework que ejecuta 100 consolidaciones por día pierde la memoria en 15 de ellas. En una semana, son 105 conversaciones donde el agente olvida todo.
El problema es más profundo que la lógica de reintento (Retry Logic)
La solución obvia es el reintento con backoff exponencial. Teníamos eso. Maneja bien los errores HTTP transitorios:
# Bucle de reintento: backoff de 1s → 2s → 4s
for attempt in range(3):
try:
response = await acompletion(**kwargs)
return await self._collect_stream(response)
except (RateLimitError, APIConnectionError) as e:
await asyncio.sleep(RETRY_DELAYS[attempt])
Esto captura los 429 y los problemas de red. Pero dos modos de fallo se escapan:
Modo de fallo 1: el modelo no puede realizar tool calling. Algunos modelos, especialmente los más pequeños que se ejecutan en motores de inferencia rápidos, ocasionalmente fallan al generar llamadas a funciones válidas en prompts complejos. La API devuelve un 200 con un ServiceUnavailableError envuelto dentro de un MidStreamFallbackError. Tu lógica de reintento ve una excepción, reintenta con el mismo modelo y obtiene el mismo error.
Modo de fallo 2: el modelo "tiene éxito" pero no llama a la tool. El LLM devuelve una respuesta perfectamente válida. HTTP 200. Sin errores. Pero en lugar de llamar a save_memory con datos estructurados, escribe un resumen en texto plano. Tu motor de reintento considera esto un éxito. La función de consolidación busca llamadas a tools, no encuentra ninguna y se rinde.
El segundo modo de fallo es el más insidioso. La capa de transporte cree que todo funcionó. La capa de negocio sabe que no fue así. Ninguna cantidad de reintentos a nivel HTTP solucionará un modelo que no entiende el esquema de tu tool.
Arquitectura de Fallback de Doble Capa
Resolvimos esto con dos bucles de fallback independientes que operan a diferentes niveles:
El usuario envía /new
│
▼
consolidate() ─── Fallback de Capa de Negocio
│ "¿El modelo llamó a save_memory?"
│ No → intentar con el siguiente modelo en la cadena
│
▼
_chat_with_retry() ─── Fallback de Capa de Transporte
│ Errores HTTP → backoff exponencial
│ Reintentos agotados → recorrer cadena de fallback
│
▼
Cadena de fallback MODEL_MAP:
llama-3.3-70b ─$0.59/M─→ qwen3-32b ─$0.29/M─→ llama-4-scout ─$0.11/M─→ gpt-4.1-mini ─→ claude-haiku
(394 TPS) (662 TPS) (594 TPS) (confiable) (último recurso)
La Capa 1 maneja los fallos de transporte. La Capa 2 maneja los fallos de lógica de negocio. La cadena de fallback se comparte entre ambas capas, definida una vez en un catálogo central.
Este es un enfoque fundamentalmente diferente al de reintentar con el mismo modelo. Cuando un modelo falla al llamar a una tool, reintentar con el mismo prompt rara vez ayuda. Cambiar a un modelo diferente con diferentes pesos y un comportamiento de tool calling distinto, sí lo hace.
El Catálogo de Modelos: Una Fuente Única de Verdad
Cada modelo en nuestro catálogo tiene un campo opcional fallback que apunta al siguiente modelo a probar:
@dataclass(frozen=True)
class ModelEntry:
id: str
label: str
tier: str
description: str
fallback: str | None = None
hidden: bool = False # Oculto de la lista /model orientada al usuario
MODEL_CATALOG = [
# Modelos visibles para el usuario (16 modelos entre los que los usuarios pueden cambiar)
ModelEntry("claude-sonnet-4-6", "Claude Sonnet 4.6", "standard",
"Recomendado", fallback="claude-sonnet-4-5"),
ModelEntry("gpt-4.1-mini", "GPT-4.1 Mini", "economy",
"Stable tool calling", fallback="claude-haiku-4-5"),
# Modelos de consolidación ocultos (solo uso interno)
ModelEntry("llama-3.3-70b-versatile", "Llama 3.3 70B (Groq)", "economy",
"394 TPS", fallback="qwen3-32b", hidden=True),
ModelEntry("qwen3-32b", "Qwen3 32B (Groq)", "economy",
"662 TPS", fallback="llama-4-scout-17b-16e-instruct", hidden=True),
# ...
]
El flag hidden=True mantiene los modelos internos fuera del comando /model orientado al usuario, mientras siguen participando en las cadenas de fallback. Los usuarios ven 16 modelos entre los que pueden elegir. El sistema utiliza 19. Los tres modelos ocultos existen únicamente para tareas en segundo plano como la consolidación de memoria, donde la velocidad y el costo importan más que la calidad conversacional.
Este catálogo es la fuente única de verdad para todo el enrutamiento de modelos. Añadir un nuevo modelo a la cadena de fallback significa añadir una línea. Sin archivos de configuración que sincronizar, sin variables de entorno que actualizar, sin scripts de despliegue que modificar.
Capa de Transporte: Fallback Encadenado con Detección de Ciclos
El motor de reintento recorre la cadena de fallback utilizando un conjunto de visitados para evitar bucles infinitos:
async def _chat_with_retry(self, kwargs, original_model):
# Fase 1: Backoff exponencial en el modelo primario
for attempt in range(3):
try:
response = await acompletion(**kwargs)
return await self._collect_stream(response)
except (RateLimitError, APIConnectionError, APIError) as e:
await asyncio.sleep(RETRY_DELAYS[attempt])
except AuthenticationError:
return LLMResponse(content="API key invalid.", finish_reason="error")
# Fase 2: Recorrer la cadena de fallback
visited = {original_model}
current = original_model
while True:
entry = MODEL_MAP.get(current)
if not entry or not entry.fallback or entry.fallback in visited:
break
current = entry.fallback
visited.add(current)
# Resolver el gateway correcto para este modelo
gw = self._resolve_gateway_for_model(current)
resolved = self._resolve_model(current, gateway=gw)
fb_kwargs = {**kwargs, "model": resolved}
# Corregir api_base para el protocolo del modelo de destino
if gw and gw.default_api_base:
fb_kwargs["api_base"] = gw.default_api_base
try:
response = await acompletion(**fb_kwargs)
return await self._collect_stream(response)
except Exception:
continue # Intentar con el siguiente en la cadena
return LLMResponse(content="Service unavailable.", finish_reason="error")
El conjunto visited es crítico. Sin él, una cadena como A→B→A entraría en un bucle infinito. Con él, el motor prueba cada modelo exactamente una vez.
La resolución del gateway también es importante. Diferentes modelos necesitan diferentes formatos de API. Los modelos de Claude se enrutan a través de un gateway con formato Anthropic (sin el sufijo /v1). Los modelos GPT se enrutan a través de un gateway compatible con OpenAI (con /v1). Los modelos de Groq utilizan otro endpoint. El motor de fallback resuelve el gateway correcto para cada modelo en la cadena, evitando desajustes de protocolo como enviar solicitudes de Anthropic a un endpoint de OpenAI.
Este es un detalle que la mayoría de los frameworks ignoran por completo. Asumen que todos los modelos hablan el mismo protocolo. En producción, con 19 modelos en 4 formatos de API diferentes, esa suposición se rompe de inmediato.
Capa de Negocio: Verificación de Tool Call
La función de consolidación añade su propio bucle de fallback por encima:
async def consolidate(self, session, provider, model, **kwargs):
visited = set()
current_model = model
while current_model and current_model not in visited and len(visited) <= 3:
visited.add(current_model)
response = await asyncio.wait_for(
provider.chat(messages=messages, tools=SAVE_MEMORY_TOOL, model=current_model),
timeout=30,
)
if response.has_tool_calls:
# Éxito: extraer y guardar memoria
args = response.tool_calls[0].arguments
self.write_long_term(args["memory_update"])
self.append_history(args["history_entry"])
return True
# El modelo no llamó a la tool — intentar con el siguiente en la cadena
entry = MODEL_MAP.get(current_model)
next_model = entry.fallback if entry else None
if next_model and next_model not in visited:
current_model = next_model
continue
return False # No hay más fallbacks
return False
Esto captura el caso en el que _chat_with_retry devuelve una respuesta exitosa (HTTP 200, contenido válido) pero el modelo no utilizó la tool. La función de consolidación verifica has_tool_calls y, si falta, pasa al siguiente modelo de la cadena.
El wrapper de timeout (asyncio.wait_for) también activa el fallback. Si un modelo tarda más de 30 segundos (común con los errores Cloudflare 524 en upstreams lentos), la función captura el TimeoutError e intenta con el siguiente modelo en lugar de bloquear la sesión del usuario indefinidamente.
Por qué Groq para la Consolidación
La consolidación de memoria es una tarea en segundo plano. El usuario no ve el resultado. Solo necesita que funcione. Esto la convierte en una candidata perfecta para modelos rápidos y económicos.
La mayoría de los frameworks utilizan el mismo modelo costoso para todo. Si estás usando Claude Sonnet para la conversación, también estás usando Claude Sonnet para la consolidación de memoria. Eso son $3/M de tokens de entrada y más de 8 segundos por consolidación, para una tarea que produce un resultado que ningún humano lee jamás.
Desvinculamos completamente la consolidación del modelo de conversación. La conversación utiliza cualquier modelo que el usuario haya seleccionado. La consolidación utiliza una cadena dedicada de modelos alojados en Groq:
| Modelo | Velocidad | Costo de Entrada | Costo de Salida |
|---|---|---|---|
| llama-3.3-70b-versatile | 394 TPS | $0.59/M | $0.79/M |
| qwen3-32b | 662 TPS | $0.29/M | $0.59/M |
| llama-4-scout-17b-16e | 594 TPS | $0.11/M | $0.34/M |
| gpt-4.1-mini (anterior) | ~150 TPS | $0.40/M | $1.60/M |
El modelo primario (llama-3.3-70b) consolida una sesión de 60 mensajes en unos 5 segundos. El valor predeterminado anterior (gpt-4.1-mini) tardaba más de 8 segundos. El costo por consolidación bajó de ~$0.003 a ~$0.001.
La contrapartida: los modelos de Groq tienen un tool calling menos confiable en prompts complejos. Por eso precisamente existe el fallback de doble capa. Cuando llama-3.3-70b falla al llamar a la tool, qwen3-32b toma el relevo. Si eso también falla, lo intenta llama-4-scout. Si los tres modelos de Groq fallan, gpt-4.1-mini lo maneja con una confiabilidad de tool calling cercana al 100%.
En producción, vemos que el modelo primario tiene éxito en un ~85% de las veces. La cadena llega a gpt-4.1-mini en menos del 2% de las consolidaciones. Tasa de fallo total: efectivamente cero.
Resultados en Producción
Desplegamos esto en dos instancias de LemonClaw y lo probamos con conversaciones reales de Telegram.
Primer despliegue (solo fallback de una capa):
Memory consolidation (archive_all): 56 messages
llama-3.3-70b-versatile → "Failed to call a function"
Falling back → qwen3-32b
qwen3-32b: LLM did not call save_memory, skipping
→ "Memory archival failed, session not cleared."
La capa de transporte capturó el primer fallo y realizó el fallback. Pero qwen3-32b devolvió texto sin llamar a la tool. El fallback de una sola capa no pudo manejar esto. Este es el escenario exacto en el que cualquier otro framework perdería la memoria silenciosamente.
Segundo despliegue (fallback de doble capa):
Memory consolidation (archive_all): 60 messages
model=llama-3.3-70b-versatile → success
Memory consolidation done: 60 messages remaining
Mismo modelo, mismo volumen de mensajes. Esta vez funcionó al primer intento. La naturaleza intermitente del fallo en el tool calling es precisamente por lo que se necesita una cadena de fallback en lugar de un único modelo de respaldo.
Cuando el modelo primario falla, la cadena lo captura:
llama-3.3-70b → tool call failed
→ consolidate() fallback → qwen3-32b
→ qwen3-32b didn't call tool
→ consolidate() fallback → llama-4-scout
→ llama-4-scout didn't call tool
→ consolidate() fallback → gpt-4.1-mini
→ gpt-4.1-mini called save_memory ✓
Memory consolidation done
Cuatro modelos probados, memoria guardada. El usuario ve "Nueva sesión iniciada" y no tiene idea de que nada de esto sucedió.
La Brecha Arquitectónica
El sistema de memoria de LemonClaw frente a las alternativas, característica por característica:
| Capacidad | Framework de Agente de IA Típico | LemonClaw |
|---|---|---|
| Modelo de consolidación | Igual al de conversación (costoso, lento) | Cadena de modelos independiente, acelerada por Groq |
| Manejo de fallos | Registrar advertencia, perder memoria | Fallback de doble capa, 5 modelos de profundidad |
| Fallback de transporte | Reintentar mismo modelo 3x | Fallback encadenado a través de diferentes modelos |
| Fallback de lógica de negocio | Ninguno | Verificación de tool call + cambio de modelo |
| Protección de timeout | Ninguna (Cloudflare 524 bloquea la sesión) | asyncio.wait_for(timeout=30) + fallback |
| Truncamiento de sesión | Ninguno (el contexto crece infinitamente) | Truncar mensajes antiguos tras la consolidación |
| Búsqueda en el historial | Ninguna | Ventana rodante HISTORY.md, buscable con grep |
| Modelos internos | No soportado | hidden=True para modelos solo del sistema |
| Prevención de ciclos | No necesaria (sin cadenas) | Conjunto visited evita bucles A→B→A |
| Resolución de gateway | Se asume un único formato de API | Gateway por modelo con detección de protocolo |
Cada fila de esta tabla representa un fallo en producción que experimentamos nosotros mismos u observamos en los rastreadores de problemas de otros frameworks. El fallback de doble capa, el catálogo de modelos ocultos, la resolución de gateway por modelo, el fallback activado por timeout: nada de esto existe en nanobot ni en ningún otro framework de agentes de código abierto que hayamos examinado.
Lo que aprendimos
"Solicitud exitosa" no es "tarea exitosa". Los motores de reintento genéricos operan a nivel HTTP. No pueden saber que una respuesta 200 con JSON válido es en realidad un fallo porque el modelo no utilizó la tool que pediste. Las operaciones críticas para el negocio necesitan sus propios criterios de éxito y su propia lógica de fallback.
Los modelos pequeños fallan de manera diferente a los modelos grandes. Los modelos grandes (GPT-4.1, Claude Sonnet) casi siempre llaman a las tools cuando se les pide. Los modelos pequeños en motores de inferencia rápidos a veces generan respuestas que parecen válidas pero ignoran por completo el esquema de la tool. Este no es un error que se pueda solucionar con ingeniería de prompts. Es una brecha de capacidad que requiere mitigación arquitectónica.
Prueba con datos de producción, no con datos sintéticos. Nuestra prueba inicial con 6 mensajes sintéticos pasó en todos los modelos. La sesión real de 60 mensajes con historial de llamadas a tools, marcas de tiempo e idiomas mixtos falló en dos de los tres modelos de Groq. La complejidad de los datos reales expone modos de fallo que los datos de prueba limpios nunca mostrarán.
Esta es también la razón por la que la guía de rate limiting de APIs de IA es importante aquí. El sistema de memoria no solo necesita un "mejor modelo". Necesita una política de transporte, una verificación de éxito de la lógica de negocio y una escalera de fallback que no se derrumbe ante un fallo ordinario del proveedor.
LemonClaw es un framework de agentes de IA de código abierto con enrutamiento multimodelo integrado, memoria persistente e integraciones con más de 10 plataformas de chat. Todo el sistema de fallback de doble capa descrito aquí se incluye en la versión de código abierto. Ejecútalo en tu propio servidor: github.com/hedging8563/lemonclaw
¿Necesitas más de 300 modelos de IA a través de una sola API key? lemondata.cc proporciona acceso unificado a OpenAI, Anthropic, Google, DeepSeek, Groq y más.
