Configurações

Idioma

Por que seu agente de IA continua perdendo a memória (e como resolvemos isso)

L
LemonData
·28 de fevereiro de 2026·25 visualizações
#agentes de IA#memória#fallback#arquitetura#LemonClaw
Por que seu agente de IA continua perdendo a memória (e como resolvemos isso)

Por que seu agente de AI continua perdendo a memória (e como resolvemos isso)

Seu agente de AI acabou de ter uma conversa de 30 minutos com um usuário. Eles discutiram requisitos de projeto, compartilharam preferências, tomaram decisões. Então o usuário digita /new para iniciar uma nova sessão.

O agente tenta consolidar essa conversa na memória de longo prazo. A chamada de LLM falha. Rate limit. Timeout. Ou o modelo retorna texto em vez de chamar a tool necessária.

A memória se foi. Trinta minutos de contexto, evaporados.

Isso acontece com mais frequência do que você imagina. Monitoramos isso em nossas instâncias do LemonClaw: a consolidação de memória tinha uma taxa de falha de ~15% em qualquer modelo individual. Para um recurso que deveria ser uma infraestrutura invisível, isso é inaceitável.

Como outros frameworks lidam com isso (eles não lidam)

A maioria dos frameworks de agentes de AI trata a consolidação de memória como uma simples chamada de LLM. Se funcionar, ótimo. Se não, a memória é perdida.

OpenClaw, o framework de agentes open-source mais popular, usa o mesmo modelo para consolidação e para a conversa. Uma chamada do Claude Sonnet que custa $0.003 e leva mais de 8 segundos, apenas para resumir um chat que o usuário nunca verá. Quando essa chamada falha (rate limit, timeout, erro do modelo), o framework registra um aviso e segue em frente. O contexto do usuário desapareceu.

nanobot, outro framework popular, tem a mesma arquitetura. Um modelo, uma tentativa, sem fallback. A função de consolidação nem sequer tem um timeout. Um upstream lento (erros Cloudflare 524 são comuns) bloqueia toda a sessão até que a conexão caia.

Nenhum dos frameworks separa a consolidação do modelo principal. Nenhum possui lógica de fallback para operações de memória. Nenhum distingue entre "a chamada da API falhou" e "a chamada da API teve sucesso, mas o modelo não fez o que pedimos".

Estes não são casos isolados. Com taxas de falha de 15% em qualquer modelo único, um framework executando 100 consolidações por dia perde a memória em 15 delas. Ao longo de uma semana, são 105 conversas onde o agente esquece tudo.

O problema é mais profundo do que a lógica de repetição (retry)

A correção óbvia é o retry com exponential backoff. Nós tínhamos isso. Ele lida bem com erros HTTP transitórios:

# Loop de repetição: 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])

Isso captura 429s e instabilidades de rede. Mas dois modos de falha passam despercebidos:

Modo de falha 1: O modelo não consegue executar tool calling. Alguns modelos, especialmente os menores rodando em mecanismos de inferência rápidos, ocasionalmente falham ao gerar chamadas de função válidas em prompts complexos. A API retorna um 200 com um ServiceUnavailableError envolto em um MidStreamFallbackError. Sua lógica de retry vê uma exceção, tenta o mesmo modelo novamente e recebe o mesmo erro.

Modo de falha 2: O modelo "tem sucesso", mas não chama a tool. O LLM retorna uma resposta perfeitamente válida. HTTP 200. Sem erros. Mas, em vez de chamar save_memory com dados estruturados, ele escreve um resumo em texto simples. Seu mecanismo de retry considera isso um sucesso. A função de consolidação verifica as chamadas de tool, não encontra nenhuma e desiste.

O segundo modo de falha é o mais insidioso. A camada de transporte acha que tudo funcionou. A camada de negócio sabe que não. Nenhuma quantidade de retries em nível HTTP corrigirá um modelo que não entende o esquema da sua tool.

Arquitetura de Fallback em Duas Camadas

Resolvemos isso com dois loops de fallback independentes operando em níveis diferentes:

Usuário envia /new
    │
    ▼
consolidate() ─── Fallback da Camada de Negócio
    │               "O modelo chamou save_memory?"
    │               Não → tenta o próximo modelo na cadeia
    │
    ▼
_chat_with_retry() ─── Fallback da Camada de Transporte
    │                    Erros HTTP → exponential backoff
    │                    Retries esgotados → percorre a cadeia de fallback
    │
    ▼
Cadeia 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)                  (confiável)        (último recurso)

A Camada 1 lida com falhas de transporte. A Camada 2 lida com falhas de lógica de negócio. A cadeia de fallback é compartilhada entre ambas as camadas, definida uma única vez em um catálogo central.

Esta é uma abordagem fundamentalmente diferente de repetir o mesmo modelo. Quando um modelo falha ao chamar uma tool, repeti-lo com o mesmo prompt raramente ajuda. Mudar para um modelo diferente, com pesos diferentes e comportamento de tool calling diferente, ajuda.

O Catálogo de Modelos: Uma Única Fonte de Verdade

Cada modelo em nosso catálogo possui um campo opcional fallback apontando para o próximo modelo a ser tentado:

@dataclass(frozen=True)
class ModelEntry:
    id: str
    label: str
    tier: str
    description: str
    fallback: str | None = None
    hidden: bool = False  # Oculto da lista /model para o usuário

MODEL_CATALOG = [
    # Modelos visíveis ao usuário (16 modelos que os usuários podem alternar)
    ModelEntry("claude-sonnet-4-6", "Claude Sonnet 4.6", "standard",
               "Recommended", 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 consolidação ocultos (apenas 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),
    # ...
]

A flag hidden=True mantém os modelos internos fora do comando /model voltado para o usuário, permitindo que ainda participem das cadeias de fallback. Os usuários veem 16 modelos que podem escolher. O sistema usa 19. Os três modelos ocultos existem exclusivamente para tarefas de segundo plano, como a consolidação de memória, onde velocidade e custo importam mais do que a qualidade conversacional.

Este catálogo é a única fonte de verdade para todo o roteamento de modelos. Adicionar um novo modelo à cadeia de fallback significa adicionar apenas uma linha. Sem arquivos de configuração para sincronizar, sem variáveis de ambiente para atualizar, sem scripts de deploy para modificar.

Camada de Transporte: Fallback Encadeado com Detecção de Ciclos

O mecanismo de retry percorre a cadeia de fallback usando um conjunto de modelos visitados para evitar loops infinitos:

async def _chat_with_retry(self, kwargs, original_model):
    # Fase 1: Exponential backoff no modelo primário
    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: Percorrer a cadeia 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 o gateway correto para este modelo
        gw = self._resolve_gateway_for_model(current)
        resolved = self._resolve_model(current, gateway=gw)
        fb_kwargs = {**kwargs, "model": resolved}

        # Ajustar api_base para o protocolo do modelo alvo
        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  # Tenta o próximo na cadeia

    return LLMResponse(content="Service unavailable.", finish_reason="error")

O conjunto visited é crítico. Sem ele, uma cadeia como A→B→A entraria em loop eterno. Com ele, o mecanismo tenta cada modelo exatamente uma vez.

A resolução do gateway também é importante. Modelos diferentes precisam de formatos de API diferentes. Modelos Claude são roteados através de um gateway no formato Anthropic (sem o sufixo /v1). Modelos GPT passam por um gateway compatível com OpenAI (com /v1). Modelos Groq usam ainda outro endpoint. O mecanismo de fallback resolve o gateway correto para cada modelo na cadeia, evitando incompatibilidades de protocolo, como enviar requisições Anthropic para um endpoint OpenAI.

Este é um detalhe que a maioria dos frameworks ignora completamente. Eles assumem que todos os modelos falam o mesmo protocolo. Em produção, com 19 modelos em 4 formatos de API diferentes, essa suposição quebra imediatamente.

Camada de Negócio: Verificação de Tool Call

A função de consolidação adiciona seu próprio loop de fallback por cima:

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:
            # Sucesso: extrair e salvar memória
            args = response.tool_calls[0].arguments
            self.write_long_term(args["memory_update"])
            self.append_history(args["history_entry"])
            return True

        # O modelo não chamou a tool — tenta o próximo na cadeia
        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  # Sem mais fallbacks

    return False

Isso captura o caso em que o _chat_with_retry retorna uma resposta bem-sucedida (HTTP 200, conteúdo válido), mas o modelo não usou a tool. A função de consolidação verifica por has_tool_calls e, se estiver faltando, passa para o próximo modelo na cadeia.

O wrapper de timeout (asyncio.wait_for) também aciona o fallback. Se um modelo demorar mais de 30 segundos (comum com erros Cloudflare 524 em upstreams lentos), a função captura o TimeoutError e tenta o próximo modelo em vez de bloquear a sessão do usuário indefinidamente.

Por que Groq para Consolidação

A consolidação de memória é uma tarefa de segundo plano. O usuário não vê a saída. Ele apenas precisa que funcione. Isso a torna uma candidata perfeita para modelos rápidos e baratos.

A maioria dos frameworks usa o mesmo modelo caro para tudo. Se você está usando Claude Sonnet para a conversa, também está usando Claude Sonnet para a consolidação de memória. Isso custa $3/M de tokens de entrada e leva mais de 8 segundos por consolidação, para uma tarefa que produz uma saída que nenhum humano lerá.

Nós desacoplamos totalmente a consolidação do modelo de conversa. A conversa usa qualquer modelo que o usuário selecionou. A consolidação usa uma cadeia dedicada de modelos hospedados no Groq:

Modelo Velocidade Custo de Entrada Custo de Saída
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

O modelo primário (llama-3.3-70b) consolida uma sessão de 60 mensagens em ~5 segundos. O padrão anterior (gpt-4.1-mini) levava mais de 8 segundos. O custo por consolidação caiu de ~$0.003 para ~$0.001.

O tradeoff: modelos Groq têm tool calling menos confiável em prompts complexos. É exatamente por isso que o fallback de camada dupla existe. Quando o llama-3.3-70b falha ao chamar a tool, o qwen3-32b assume. Se ele também falhar, o llama-4-scout tenta. Se todos os três modelos Groq falharem, o gpt-4.1-mini resolve com quase 100% de confiabilidade no tool calling.

Em produção, vemos o modelo primário ter sucesso em ~85% das vezes. A cadeia chega ao gpt-4.1-mini em menos de 2% das consolidações. A taxa de falha total é efetivamente zero.

Resultados em Produção

Fizemos o deploy disso em duas instâncias do LemonClaw e testamos com conversas reais do Telegram.

Primeiro deploy (apenas fallback de camada única):

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."

A camada de transporte capturou a primeira falha e acionou o fallback. Mas o qwen3-32b retornou texto sem chamar a tool. O fallback de camada única não conseguiu lidar com isso. Este é o cenário exato onde qualquer outro framework perderia a memória silenciosamente.

Segundo deploy (fallback de camada dupla):

Memory consolidation (archive_all): 60 messages
model=llama-3.3-70b-versatile → success
Memory consolidation done: 60 messages remaining

Mesmo modelo, mesmo volume de mensagens. Desta vez funcionou na primeira tentativa. A natureza intermitente da falha de tool calling é exatamente por que você precisa de uma cadeia de fallback em vez de um único modelo de backup.

Quando o modelo primário falha, a cadeia o 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

Quatro modelos tentados, memória salva. O usuário vê "Nova sessão iniciada." e não tem ideia de que nada disso aconteceu.

A Lacuna de Arquitetura

Sistema de memória do LemonClaw vs. alternativas, recurso por recurso:

Capacidade Framework Típico de Agente AI LemonClaw
Modelo de consolidação Igual ao da conversa (caro, lento) Cadeia de modelos independente, Groq-accelerated
Tratamento de falhas Registra aviso, perde memória Fallback de camada dupla, 5 modelos de profundidade
Fallback de transporte Repete o mesmo modelo 3x Fallback encadeado entre modelos diferentes
Fallback de lógica de negócio Nenhum Verificação de tool call + troca de modelo
Proteção de timeout Nenhum (Cloudflare 524 bloqueia a sessão) asyncio.wait_for(timeout=30) + fallback
Trunfamento de sessão Nenhum (contexto cresce infinitamente) Trunca mensagens antigas após consolidação
Busca no histórico Nenhum Janela deslizante HISTORY.md, pesquisável por grep
Modelos internos Não suportado hidden=True para modelos apenas de sistema
Prevenção de ciclo Não necessário (sem cadeias) Conjunto visited previne loops A→B→A
Resolução de gateway Assume formato de API único Gateway por modelo com detecção de protocolo

Cada linha nesta tabela representa uma falha de produção que nós mesmos experimentamos ou observamos nos rastreadores de problemas de outros frameworks. O fallback de camada dupla, o catálogo de modelos ocultos, a resolução de gateway por modelo, o fallback acionado por timeout: nada disso existe no OpenClaw, nanobot ou qualquer outro framework de agentes open-source que examinamos.

O que aprendemos

"Requisição bem-sucedida" não é "tarefa bem-sucedida". Mecanismos de retry genéricos operam no nível HTTP. Eles não conseguem saber que uma resposta 200 com JSON válido é na verdade uma falha porque o modelo não usou a tool que você pediu. Operações críticas para o negócio precisam de seus próprios critérios de sucesso e sua própria lógica de fallback.

Modelos pequenos falham de forma diferente dos modelos grandes. Modelos grandes (GPT-4.1, Claude Sonnet) quase sempre chamam as tools quando solicitados. Modelos pequenos em mecanismos de inferência rápidos às vezes geram respostas que parecem válidas, mas ignoram completamente o esquema da tool. Isso não é um bug que você possa corrigir com prompt engineering. É uma lacuna de capacidade que requer mitigação arquitetural.

Teste com dados de produção, não dados sintéticos. Nosso teste inicial com 6 mensagens sintéticas passou em todos os modelos. A sessão real de 60 mensagens com histórico de tool calls, carimbos de data/hora e idiomas mistos falhou em dois dos três modelos Groq. A complexidade dos dados reais expõe modos de falha que dados de teste limpos nunca mostrarão.


LemonClaw é um framework de agentes de AI open-source com roteamento multi-modelo integrado, memória persistente e mais de 10 integrações com plataformas de chat. Todo o sistema de fallback de camada dupla descrito aqui está disponível na versão open-source. Rode no seu próprio servidor: github.com/hedging8563/lemonclaw

Precisa de mais de 300 modelos de AI através de uma única API key? lemondata.cc fornece acesso unificado a OpenAI, Anthropic, Google, DeepSeek, Groq e muito mais.

Share: