Ship a memory layer to a self-hosted Mac Mini environment — async embed-on-write, in-memory LRU caching, observability, and graceful degradation when the vector store is unavailable.
Antes de escrever qualquer código novo, ajuda nomear exatamente o que pode dar errado. Um sistema de memória que funciona em um notebook ou teste de turno único ainda pode falhar um agente em produção de três maneiras específicas.
Gerar um embedding é uma chamada de API — tipicamente 100–400 ms dependendo do modelo e provedor. Se você embebe texto de forma síncrona antes de retornar uma resposta, cada turno que o agente executa paga esse custo como atraso visível. Em uma conversa multi-turno isso se acumula rapidamente: o agente parece lento mesmo quando o Claude responde rapidamente.
O insight chave é que o embedding não precisa estar pronto antes da resposta atual. Ele precisa estar pronto antes da próxima recuperação. Escreva a mensagem no contexto agora; agende embed-e-armazenamento em segundo plano.
Pense no banco de dados vetorial como um catálogo de biblioteca: rápido para uma coleção pequena, visivelmente mais lento quando as prateleiras estão cheias e você está buscando em milhares de entradas. A recuperação em um armazenamento pequeno leva alguns milissegundos. Com 50.000 memórias e um modelo de embedding maior pode chegar a 800 ms. Em uma conversa de dez turnos isso adiciona vários segundos de tempo de espera que o usuário nunca pediu.
A resposta é um cache na frente do armazenamento. Perguntas repetidas — e em qualquer implantação real, certas perguntas recorrem constantemente — atingem o cache com velocidade de microssegundos em vez de consultar o banco de dados vetorial.
Um banco de dados vetorial auto-hospedado cai: o disco enche, o banco de dados trava, uma atualização de SO rotineira reinicia o processo. Um agente que trata o banco de dados vetorial como dependência rígida falha completamente quando isso acontece. A regra de produção é: a recuperação de memória deve degradar graciosamente, nunca bloquear o agente. Sem memórias a resposta é mais fraca; sem disponibilidade o agente está quebrado para todos.
Essas três preocupações — latência de escrita, latência de recuperação, indisponibilidade do armazenamento — mapeiam diretamente para três decisões de engenharia: escrita assíncrona, cache LRU, caminho de fallback. O resto deste módulo implementa cada uma.
Reflexão: Antes de continuar, pense nos modos de falha do seu próprio agente. O que acontece hoje se o banco de dados vetorial estiver inacessível por trinta segundos? Qual é a experiência do usuário? Conhecer essa resposta molda o quão agressivamente você investe no caminho de fallback.
O padrão de escrita assíncrona de embeddings é uma corrotina que você dispara com asyncio.create_task. O agente não a aguarda — ele continua gerando a resposta enquanto o embedding executa em segundo plano.
Isso requer duas coisas: sua aplicação já executa um loop de eventos assíncrono (padrão em qualquer serviço FastAPI ou Python assíncrono), e você usa um cliente de embedding que suporta async. O SDK da Anthropic não expõe um endpoint de embedding dedicado, então este módulo usa o AsyncClient do Voyage AI — o Voyage AI é o parceiro de embedding recomendado pela Anthropic, e voyage-3 é o modelo de propósito geral atual deles.
Aqui está o padrão central de escrita. Note que store_memory_async é síncrono do ponto de vista do chamador — ele dispara a corrotina e retorna imediatamente:
O chamador — seu handler de turno do agente — chama store_memory_async com a mensagem do usuário e retorna imediatamente. Se a chamada de embedding falhar, o erro é registrado e o agente nunca fica bloqueado esperando por ele.
Passe no mínimo um ID de conversa e um timestamp em metadata para que as memórias possam ser filtradas por sessão e recência mais tarde. Além disso, inclua qualquer coisa que suas consultas de recuperação precisem filtrar.
asyncio.create_task só funciona dentro de um loop de eventos em execução. Para ferramentas CLI síncronas ou scripts, use um pool de threads (asyncio.run_in_executor) ou uma fila de tarefas em segundo plano como ARQ.
Um cache LRU (least-recently-used) despeja as entradas mais antigas quando atinge a capacidade. Para resultados de recuperação de memória, dois parâmetros adicionais importam: capacidade (quantos resultados de consulta manter) e TTL (quanto tempo um resultado permanece válido antes de expirar).
O collections.OrderedDict do Python é o bloco de construção padrão. move_to_end promove a entrada acessada recentemente; popitem(last=False) remove a menos acessada recentemente quando o cache está cheio:
A chave do cache é a string de consulta bruta. Se o agente fizer a mesma pergunta duas vezes dentro da janela de TTL, a segunda chamada retorna o resultado em cache sem tocar no banco de dados vetorial ou gastar tokens de embedding.
Uma capacidade de 100 e um TTL de 5 minutos são valores iniciais. Se estão corretos depende de quantas consultas distintas seu agente realmente recebe e com que frequência novas memórias chegam que tornariam resultados em cache obsoletos. A próxima seção fornece os dados para responder isso.
Você não consegue ajustar o que não consegue medir. Os três números que valem a pena rastrear são latência de recuperação, taxa de acerto do cache e uso de tokens de embedding. Nenhum desses requer um dashboard ou serviço externo — linhas de log estruturadas para stdout são suficientes.
Conecte o cache e as métricas em uma única função de recuperação. Esta é a versão final que vai para produção:
Após algumas centenas de turnos você terá dados reais: taxa de acerto do cache, latência mediana de recuperação e gasto diário de tokens de embedding. Uma taxa de acerto abaixo de 20% geralmente significa que as consultas são muito variadas para se beneficiar do cache com correspondência exata — considere converter para minúsculas e remover pontuação da consulta antes de usá-la como chave.
Use um formatador de log JSON para que seu agregador possa indexar cada campo independentemente. As linhas de log se tornam uma série temporal consultável em vez de uma parede de texto para fazer grep.
Há duas formas de implantar a camada de memória: como uma biblioteca importada diretamente no processo do agente, ou como um serviço HTTP independente que o agente chama via localhost.
Padrão de biblioteca — o cache, o cliente do banco de dados vetorial e o cliente de embedding todos vivem no mesmo processo que o agente. Mais simples para começar. Funciona bem para um único agente no Mac Mini. Principal desvantagem: cada reinicialização do agente limpa o cache em memória.
Padrão de serviço independente — a camada de memória executa como um processo FastAPI separado. Vantagens: o cache sobrevive às reinicializações do agente; múltiplas instâncias do agente podem compartilhar um armazenamento de memória; você pode reimplantar o agente sem tocar no serviço de memória. Vale a complexidade extra quando você tem mais de um processo de agente ou quando a persistência do cache entre reinicializações se torna importante.
Independentemente do padrão, exponha a configuração que importa como variáveis de ambiente para que você possa ajustar sem mudanças de código. As quatro que mais importam são o nome do modelo de embedding, capacidade do cache, TTL do cache e URL do banco de dados vetorial.
A degradação graciosa pertence a toda implantação. Envolva sua função de recuperação em um fallback que retorna uma lista vazia quando o banco de dados vetorial está indisponível:
Um agente que funciona sem memórias é mais fraco. Um agente que trava em um problema de banco de dados está quebrado para todos até ser reiniciado manualmente.
1. Por que a geração de embeddings na escrita deve acontecer de forma assíncrona em vez de síncrona?
Correct Answer: B
Os embeddings precisam ser armazenados antes da próxima recuperação, não antes da resposta atual. Executar embed-e-armazenamento como uma tarefa em segundo plano remove o custo de latência do caminho crítico do agente.
2. O que `OrderedDict.move_to_end(key)` realiza no cache LRU em um acerto de cache?
Correct Answer: B
move_to_end promove a entrada acessada recentemente. Quando o cache está cheio, popitem(last=False) remove a entrada na frente — a que ficou mais tempo sem ser acessada. Juntas elas implementam o despejo LRU.
3. Uma taxa de acerto do cache de 15% indica o quê sobre sua estratégia de cache?
Correct Answer: B
Uma taxa de acerto baixa geralmente significa que as chaves de cache são muito diversas para colidir. Normalizar consultas — minúsculas, remover pontuação, deduplicar espaços em branco — aumenta a probabilidade de que perguntas semanticamente idênticas reutilizem o mesmo resultado em cache.
4. O que deve acontecer quando o banco de dados vetorial lança uma exceção durante a recuperação?
Correct Answer: C
Degradação graciosa significa que o agente retorna uma resposta mais fraca em vez de nenhuma resposta. Registre a falha para visibilidade, retorne uma lista vazia e deixe o agente continuar gerando saída.
5. Qual preocupação de produção um cache LRU aborda diretamente?
Correct Answer: C
O cache aborda a latência de recuperação: consultas repetidas são servidas em microssegundos da memória em vez de milissegundos do banco de dados vetorial. Não ajuda com latência no caminho de escrita (isso é async) ou indisponibilidade do armazenamento (isso requer um caminho de fallback).
6. Qual é a principal vantagem do padrão de implantação de serviço independente em relação ao padrão de biblioteca?
Correct Answer: B
Quando o cache vive em um processo separado, reiniciar o agente não limpa o cache. Múltiplas instâncias do agente também podem compartilhar o mesmo serviço de memória, o que não é possível com uma biblioteca por processo.
7. Qual é a primeira coisa que você adicionaria a este sistema de memória antes de colocá-lo em produção?
Correct Answer: B
Antes de ajustar capacidade, TTL ou mudar de modelos de embedding, você precisa de visibilidade sobre o que o sistema está realmente fazendo. Logs estruturados para latência e taxa de acerto do cache são a linha de base mínima — não custam nada e respondem as perguntas que surgem na primeira semana de tráfego real.
Muito bem. Veja o que vem a seguir.
Voltar para esta trilhaOu agende a chamada de diagnóstico