Memória e Contexto

Histórico de Conversa

Persistir mensagens em banco, Redis ou sessão e reconstruir messages[] a cada chamada.

Intermediário 30 min 30 pontos Leitura 0%

Nesta aula você vai

  • Salvar mensagens user/assistant após cada turno
  • Carregar últimas N mensagens ao montar prompt
  • Manter sessionId estável por conversa

Histórico de Conversa

Objetivos

  • Criar contexto persistente entre mensagens
  • Exercício: salvar e recuperar últimas mensagens

Da amnésia à continuidade

Na aula anterior vimos o problema. Agora a correção: cada turno salva o que foi dito e o próximo turno reconstrói o array messages[] a partir do storage.

É como editar um roteiro de teatro — a cada cena nova, o ator (LLM) recebe o script atualizado, não só a última fala.

Schema SQL mínimo

CREATE TABLE chat_messages (
  id         INTEGER PRIMARY KEY AUTOINCREMENT,
  session_id TEXT NOT NULL,
  role       TEXT NOT NULL CHECK (role IN ('user', 'assistant')),
  content    TEXT NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_session ON chat_messages(session_id, created_at);

Simples de propósito. Produção pode adicionar user_id, tokens, deleted_at — mas o fluxo base é este.

Fluxo no endpoint

app.post('/api/chat', async (req, res) => {
  const sessionId = req.cookies.chat_session;
  const { message } = req.body;

  await db.insertMessage(sessionId, 'user', message);

  const history = await db.getLastMessages(sessionId, 20); // últimas 20

  const messages = [
    { role: 'system', content: SYSTEM_PROMPT },
    ...history.map((m) => ({ role: m.role, content: m.content })),
  ];

  const reply = await chat(messages);

  await db.insertMessage(sessionId, 'assistant', reply);
  res.json({ reply });
});

Ordem importa:

  1. Salva mensagem do usuário
  2. Carrega histórico
  3. Chama LLM
  4. Salva resposta do assistente
  5. Devolve ao frontend

Conversa que passa no teste:

Usuário: "Me chamo Ana."
Assistente: "Olá, Ana! Como posso ajudar?"
Usuário: "Qual meu nome?"
Assistente: "Você se chama Ana."

Se falhar, o bug está no storage ou na montagem de messages[] — não no modelo.

Redis como alternativa

Lista por sessão — rápido, volátil se não persistir:

await redis.rpush(`chat:${sessionId}`, JSON.stringify({ role: 'user', content: message }));
await redis.ltrim(`chat:${sessionId}`, -40, -1); // mantém últimas 20 pares ≈ 40 entries

Bom para MVP; produção costuma usar Redis + PostgreSQL (Redis quente, PG arquivo).

PHP Session (protótipo)

session_start();
$_SESSION['chat'][] = ['role' => 'user', 'content' => $message];
// limite
$_SESSION['chat'] = array_slice($_SESSION['chat'], -20);

Limitação: não escala horizontalmente sem sticky session. Serve para demo local, não para deploy sério.

Exercício

  1. Implemente insertMessage + getLastMessages
  2. Converse: "Me chamo Ana" → "Qual meu nome?" — deve funcionar
  3. Abra nova sessão (novo cookie) — deve esquecer

O terceiro passo confirma que a memória está ligada à sessão, não ao modelo.

Privacidade

  • TTL: apagar sessões > 30 dias
  • LGPD: histórico é dado pessoal se identificar usuário — documente retenção
  • Não logue conteúdo completo em produção sem necessidade

Diálogo jurídico ↔ engenharia:

DPO: "Quanto tempo guardamos conversas?"
Dev: "30 dias, depois job apaga."
DPO: "Documenta na política de privacidade."

Resumo

  • Salve user + assistant após cada turno
  • Reconstrua messages[] do storage, não do cliente
  • Limite N mensagens desde o início — custo e context window

Histórico funcionando — mas conversas longas encarecem e podem estourar limite. Na próxima aula: janela de contexto, resumo e compressão.