Pular para o conteúdo
astorlm
Idioma: Português
← Mapa

Nível 9

Memória

O histórico morre com a sessão. A memória de longo prazo é o que você salvar fora dela, mais um jeito de encontrar isso de novo na próxima vez.
1/33 Dobras do bandoneón:
  • user
  • assistant
  • tool_result
O assistente da loja de uma torrefação de café, numa pequena fazenda. O galpão da torrefação processa os pedidos. O diário ao lado da cama é a memória de longo prazo: ele vive fora da sessão, então a noite não consegue tocá-lo.

EventBus

O problema

Tudo o que um agente sabe sobre uma conversa vive no histórico dele, as mensagens que o loop reenvia a cada turno. Quando a sessão termina, o histórico vai junto. Amanhã o mesmo cliente volta, diz “o mesmo da última vez”, e o agente não faz ideia do que foi isso.

Esse é o Espectro Apagador, a amnésia de sessão. Ele não quebra nada. Só faz o agente repetir as mesmas perguntas todo dia, esquecer as preferências que ouviu e tratar um cliente fiel como um estranho.

Guardar o histórico inteiro para sempre também não resolve. Ele cresce sem fim, e o Gulp, do nível 7, está esperando por ele.

A solução

Salve o que importa fora do histórico, onde o fim de uma sessão não alcança: um arquivo, um banco de dados. Depois dê ao agente um jeito de recuperar isso. Há três jeitos comuns, e agentes de verdade muitas vezes os misturam:

  • Retomar a sessão

    Salvar o histórico inteiro sob um id de sessão e carregá-lo de volta na próxima vez.

    Nada se perde, mas tudo volta: a próxima requisição já começa pesada, e a conversa antiga lota o contexto. Bom para retomar uma tarefa inacabada, não para lembrar de um cliente por meses.

  • Notas no prompt

    O agente salva notas curtas num arquivo, e o arquivo inteiro vai no system prompt no início de cada sessão.

    Simples e previsível: o modelo sempre vê todas as notas. Para de escalar quando as notas passam de uma página. O CLAUDE.md e os arquivos de memória do Claude Code funcionam assim.

  • Busca por significado

    Cada nota é salva com o seu embedding. Uma ferramenta recall calcula o embedding da pergunta e devolve só as notas mais próximas.

    Escala para milhares de notas, e as encontra mesmo quando as palavras não batem. Custa uma chamada de embedding por nota e por busca, e o modelo precisa se lembrar de chamar o recall.

A animação mostra o terceiro. Um embedding é uma lista de números que um modelo calcula para um texto, de forma que textos com significados parecidos recebam números parecidos. “O que o cliente pediu da última vez” e “Compra 1 kg de Colômbia” não têm nenhuma palavra em comum, mas os embeddings deles apontam na mesma direção, e é assim que o recall encontra a página certa.

O elenco

O mesmo elenco de sempre, desta vez numa fazenda.

Um dia uma sessão
Uma conversa, do primeiro pedido até a resposta. A noite a encerra.
O bandoneón o histórico
Cada mensagem da sessão de hoje. Ele começa vazio toda manhã.
O diário memória de longo prazo
Notas salvas fora de qualquer sessão, uma linha por nota. remember escreve uma página, recall busca entre elas.
O Espectro Apagador fim da sessão
Vem toda noite e esvazia o bandoneón. Não consegue tocar no diário.
O ranking similaridade
O quão perto o significado de cada nota está da consulta, de 0 a 1. O modelo só recebe as melhores.
O torrador place_order
Uma ferramenta comum.

No painel EventBus, remember e recall são chamadas de ferramenta comuns. O loop não sabe nada sobre memória: são as suas ferramentas, e um arquivo em que elas escrevem.

O código

Com astorlm: createSemanticIndex com createOpenAIEmbedder ordena as notas por significado. O índice vive em memória, então a ferramenta remember também o grava num arquivo, e a sessão seguinte o carrega de volta com addVector.

Do zero: Uma chamada de embedding, uma similaridade de cosseno, um arquivo JSON e duas ferramentas. O loop do nível 2 não muda.

import { OpenAIProvider, createLocalAgent, createOpenAIEmbedder, createSemanticIndex, tool } from 'astorlm'
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
import { z } from 'zod'

// Any OpenAI-compatible endpoint: OpenAI, Ollama, LM Studio, vLLM, a proxy…
const LLM = { baseURL: 'http://localhost:11434/v1', apiKey: 'YOUR_API_KEY' } // local servers usually ignore the key

// The diary: one file of notes per customer, each saved with its embedding.
// The semantic index lives in memory, so load the saved vectors into it at startup.
const customerId = 'c-2291'
const file = `./memory/${customerId}.json`
const diary = createSemanticIndex({
  embedder: createOpenAIEmbedder({ ...LLM, model: 'your-embedding-model' }), // e.g. 'nomic-embed-text'
})
if (existsSync(file)) JSON.parse(readFileSync(file, 'utf8')).forEach(diary.addVector)

const remember = tool({
  name: 'remember',
  description: 'Save a short note about this customer for future conversations.',
  schema: z.object({ note: z.string().describe('One fact, in your own words.') }),
  execute: async ({ note }) => {
    await diary.add(`note-${diary.size + 1}`, note) // embeds it, then stores it
    writeFileSync(file, JSON.stringify(diary.list())) // outlives the session
    return `Saved. ${diary.size} notes about this customer.`
  },
})

const recall = tool({
  name: 'recall',
  description: 'Search the saved notes about this customer by meaning. Returns the closest ones.',
  schema: z.object({ query: z.string() }),
  execute: async ({ query }) => {
    const hits = await diary.query(query, { topK: 2, threshold: 0.3 })
    return hits.map((hit) => `${hit.score.toFixed(2)} ${hit.text}`).join('\n') || 'Nothing saved about that.'
  },
})

const agent = await createLocalAgent({
  provider: new OpenAIProvider({ ...LLM, model: 'your-model' }), // e.g. 'llama3.1', 'gpt-4o-mini'
  systemPrompt:
    'You are the shop assistant of a coffee roaster. When a customer tells you something worth keeping ' +
    '(what they buy, how they like it), save it with remember. If they refer to the past, use recall first.',
  tools: [placeOrder, remember, recall], // placeOrder: your code
  maxTurns: 10,
})

// A new session every day: the history starts empty, the diary doesn't.
const last = await agent.run('Hi again! Send me the same as last time.')
console.log(last.content)

O que observar

  • Decida o que vale a pena guardar. Salve fatos que vão importar da próxima vez: preferências, decisões, endereços. Não o chat inteiro. Diga isso no system prompt, ou o modelo vai salvar nada ou salvar tudo.
  • Mantenha as memórias separadas. Um diário por cliente, e confira de quem é a cada chamada. Uma busca de memória que cruza clientes é um vazamento de dados esperando para acontecer.
  • Memórias ficam desatualizadas. O cliente se muda de Rosário para Córdoba. Salve uma data com cada nota, deixe as notas mais novas vencerem, e dê às pessoas um jeito de ver e apagar o que está guardado sobre elas.
  • Uma correspondência não é prova. Uma busca sempre devolve as notas mais próximas, mesmo quando nenhuma serve. Defina uma pontuação mínima e, quando a melhor correspondência for fraca, faça o modelo perguntar em vez de chutar.
  • O que ele lê, ele pode obedecer. Uma nota salva volta para o prompt depois. Nunca deixe o texto de um cliente virar instrução para outra sessão (nível 14).