Voltar aos materiaisTutorialgrafosagentes-de-iasurrealdb

Memória de agentes em uma query SurrealQL: vetores, grafos e texto para infoprodutos

Como combinar busca vetorial HNSW, texto completo BM25 e travessia de grafos em uma única query SurrealQL para dar memória real a agentes de IA no WhatsApp, recuperação de Pix/cartão e suporte de infoprodutos no Brasil.
MM
Por Mauro Mequelussi
16 min
Memória de agentes em uma query SurrealQL: vetores, grafos e texto para infoprodutos
Em resumo

Como combinar busca vetorial HNSW, texto completo BM25 e travessia de grafos em uma única query SurrealQL para dar memória real a agentes de IA no WhatsApp, recuperação de Pix/cartão e suporte de infoprodutos no Brasil.

Uma única query SELECT no SurrealQL consegue buscar memórias de um agente de IA simultaneamente pelo significado semântico (índice vetorial HNSW), pelas palavras exatas (índice Full-Text BM25), pelas conexões de grafo (RELATE) e por filtros temporais dentro de uma mesma transação ACID. Em vez de manter quatro sistemas separados — um banco relacional, um banco vetorial, um motor de busca textual e um banco de grafos — e juntar tudo no código da aplicação, o SurrealDB executa os quatro índices sobre o mesmo registro.

No mercado brasileiro de infoprodutos, lançamentos, perpétuos e áreas de membros, essa arquitetura resolve o maior gargalo dos agentes de vendas e suporte no WhatsApp: lembrar o que o lead quis dizer, qual palavra específica ele usou (como "Pix", "boleto", "reembolso" ou o nome de um cupom), a qual produto, Order Bump ou gateway aquela conversa está ligada e quem é o responsável comercial pela oferta.

Quer dominar memória de agentes na prática? Assista gratuitamente ao nosso Curso de SurrealDB Agent Memory (15 aulas em português) e ao Curso SurrealDB para Engenheiros de IA (11 aulas).

Assista na prática: como funciona a memória de agentes no SurrealDB

Sua IA tem amnésia? SurrealDB lançou o Agent Memory

O que um agente de IA para infoprodutos precisa lembrar ao mesmo tempo?

Quando um lead ou aluno manda mensagem no WhatsApp comercial durante uma abertura de carrinho ou recuperação de vendas, a memória do agente precisa responder a quatro perguntas simultâneas antes de gerar a resposta no LLM:

Pergunta da MemóriaRecurso Nativo no SurrealQLExemplo Real em Infoproduto no Brasil
1. O que isso significa? (Semântica)Índice vetorial HNSW + operador KNN embedding <|3,COSINE|> $qLead diz "meu limite do cartão tá tomado pelo valor cheio", que tem significado próximo de parcelamento inteligente ou Pix parcelado.
2. O que foi dito exatamente? (Léxico)Índice FULLTEXT com ANALYZER em português + BM25 (text @@ 'parcelado')Localizar menções exatas a "Pix", "Kiwify", "Hotmart", "garantia", código de cupom ou regra de lote.
3. A que isso se conecta? (Grafo)Arestas RELATE e travessia ->mentions->entity->relates_to->entitySaber que a oferta mencionada inclui um Order Bump específico, usa checkout Pagar.me e pertence à closer Marina.
4. Quando, por quem e de que tipo? (Metadados)Campos tipados do documento (kind, speaker, said_at)Distinguir uma fala recente do lead (kind = 'turn') de um documento fixo de política de reembolso (kind = 'document').

Na maioria das stacks tradicionais de IA, cada uma dessas perguntas mora em um serviço diferente (PostgreSQL + Pinecone/Qdrant + Elasticsearch + Neo4j), exigindo múltiplas chamadas de rede e reconciliação manual em TypeScript ou Python. No SurrealDB, elas são quatro índices operando na mesma tabela memory.

Inspirado na engenharia oficial do SurrealDB, adaptado ao cenário brasileiro
Este guia adapta o padrão arquitetural apresentado por Tobie Morgan Hitchcock no artigo oficial Agent memory in one SurrealQL query: vectors, graph, text para a realidade de operações digitais brasileiras: funis de WhatsApp, recuperação de checkout e suporte a alunos.

Como modelar a memória do agente no SurrealQL?

A modelagem inteira exige apenas duas tabelas de registros (memory e entity) e duas tabelas de relação em grafo (mentions e relates_to).

Cada registro em memory armazena um embedding: um array de números de ponto flutuante que representa o significado semântico do texto. Em produção, modelos reais geram vetores longos para capturar nuances finas de significado:

  • text-embedding-3-small (OpenAI): 1536 dimensões;
  • text-embedding-3-large (OpenAI): 3072 dimensões;
  • voyage-3.5 (Voyage AI, recomendado pela Anthropic): 1024 dimensões por padrão.

Como vetores de 1024 ou 1536 números seriam ilegíveis em um exemplo didático, os blocos abaixo utilizam vetores de 3 números (array<float, 3>). Para levar o mesmo código para produção, basta alterar o tamanho do tipo e o parâmetro DIMENSION do índice HNSW.

-- 1. Memórias: o que foi dito, por quem, quando e o que significa DEFINE TABLE memory SCHEMAFULL; DEFINE FIELD text ON memory TYPE string; DEFINE FIELD kind ON memory TYPE 'turn' | 'document'; DEFINE FIELD speaker ON memory TYPE string; DEFINE FIELD said_at ON memory TYPE datetime; DEFINE FIELD embedding ON memory TYPE array<float, 3>;

-- Índice vetorial HNSW: encontra memórias por significado semântico DEFINE INDEX memory_embedding ON memory FIELDS embedding HNSW DIMENSION 3 DIST COSINE;

-- Analisador de texto em português: separa palavras, remove acentos -- e reduz cada termo ao radical (ex.: "parcelamentos" -> "parcel") DEFINE ANALYZER memo_text TOKENIZERS blank, punct FILTERS lowercase, ascii, snowball(portuguese);

-- Índice Full-Text BM25: encontra memórias pelas palavras exatas DEFINE INDEX memory_text ON memory FIELDS text FULLTEXT ANALYZER memo_text BM25 HIGHLIGHTS;

-- 2. Entidades: especialistas, infoprodutos, order bumps e sistemas DEFINE TABLE entity SCHEMAFULL; DEFINE FIELD name ON entity TYPE string; DEFINE FIELD kind ON entity TYPE string;

-- 3. Arestas de grafo: de uma memória para a entidade que ela menciona DEFINE TABLE mentions TYPE RELATION IN memory OUT entity;

-- Garante que cada memória mencione a mesma entidade apenas uma vez DEFINE INDEX unique_mention ON mentions FIELDS in, out UNIQUE;

-- 4. Arestas entre entidades (ex.: especialista -> produto -> checkout) DEFINE TABLE relates_to TYPE RELATION IN entity OUT entity; DEFINE FIELD label ON relates_to TYPE string;

Repare em um detalhe crucial para produtos no Brasil: no DEFINE ANALYZER memo_text, combinamos o filtro ascii (que normaliza acentuação como "inscrição" e "inscricao") com snowball(portuguese). Assim, quando o agente busca por "parcelamento", o índice BM25 encontra automaticamente variações como "parcelamentos", "parcelado" ou "parcelar".

Carregando o grafo de um lançamento e as memórias do agente

Imagine um agente de IA que auxilia o time comercial de uma escola online ou infoprodutor durante a semana de abertura de carrinho. Na nossa operação existem quatro entidades conectadas:

  • entity:marina: Marina, head comercial e responsável pela oferta;
  • entity:mentoria_ia: o produto principal (Mentoria IA para Infoprodutores);
  • entity:checkout_pagarme: o motor de pagamentos com split e retentativa (veja quanto você economiza na nossa Calculadora de Taxas Hotmart, Kiwify e Pagar.me);
  • entity:bump_prompts: o Order Bump (Pack de Prompts de Copy e Vendas) oferecido no checkout.

Primeiro, criamos as entidades e conectamos o grafo de negócio com RELATE:

-- Pessoas, infoprodutos e sistemas do funil CREATE entity:marina CONTENT { name: 'Marina', kind: 'person' }; CREATE entity:mentoria_ia CONTENT { name: 'Mentoria IA', kind: 'product' }; CREATE entity:checkout_pagarme CONTENT { name: 'Checkout Pagar.me', kind: 'system' }; CREATE entity:bump_prompts CONTENT { name: 'Order Bump Prompts', kind: 'product' };

-- Como as entidades se relacionam no ecossistema do infoproduto RELATE entity:marina->relates_to->entity:mentoria_ia SET label = 'owns'; RELATE entity:mentoria_ia->relates_to->entity:checkout_pagarme SET label = 'uses'; RELATE entity:mentoria_ia->relates_to->entity:bump_prompts SET label = 'includes_bump';

Em seguida, gravamos quatro memórias e ligamos cada uma às entidades mencionadas. Três são turnos de conversa (kind: 'turn') registrados na operação e uma é um trecho da documentação técnica do checkout (kind: 'document'):

CREATE memory:m1 CONTENT { text: 'Marina liberou o parcelamento via Pix em 4x na Mentoria IA até sexta antes de fechar o carrinho', kind: 'turn', speaker: 'user', said_at: d'2026-11-02T09:00:00Z', embedding: 0.9, 0.1, 0.0 };

CREATE memory:m2 CONTENT { text: 'O Checkout Pagar.me repete pagamentos recusados no cartão três vezes com backoff automático', kind: 'document', speaker: 'docs', said_at: d'2026-10-20T09:00:00Z', embedding: 0.8, 0.3, 0.1 };

CREATE memory:m3 CONTENT { text: 'O criativo de Reels do lote 2 precisa de um gancho novo nos primeiros 3 segundos', kind: 'turn', speaker: 'user', said_at: d'2026-11-01T12:00:00Z', embedding: 0.0, 0.2, 0.9 };

CREATE memory:m4 CONTENT { text: 'O Order Bump Prompts entra junto no parcelamento especial da Mentoria IA sem taxa extra', kind: 'turn', speaker: 'user', said_at: d'2026-11-03T10:00:00Z', embedding: 0.7, 0.2, 0.2 };

-- Conecta cada memória às entidades que ela menciona RELATE memory:m1->mentions->entity:marina; RELATE memory:m1->mentions->entity:mentoria_ia; RELATE memory:m2->mentions->entity:checkout_pagarme; RELATE memory:m4->mentions->entity:bump_prompts; RELATE memory:m4->mentions->entity:mentoria_ia;

O que cada índice retorna quando consultado separadamente?

Antes de unir tudo em uma única query, vale observar como o índice vetorial e o índice de texto completo se comportam isoladamente quando um lead pergunta sobre as condições de pagamento da mentoria (vetor $q = [0.85, 0.15, 0.05]):

  • O operador KNN <|3,COSINE|> busca as três memórias mais próximas em significado semântico, e vector::distance::knn() calcula a distância de cosseno de cada uma.
  • O operador Full-Text @0@ busca a palavra analisada no texto, enquanto search::score(0) e search::highlight('<b>', '</b>', 0) entregam a pontuação de relevância BM25 e o trecho exato destacado.

-- Embedding da pergunta do lead/agente LET $q = 0.85, 0.15, 0.05;

-- 1. Busca apenas por significado (HNSW KNN): as 3 memórias mais próximas SELECT id, text, vector::distance::knn() AS distance FROM memory WHERE embedding <|3,COSINE|> $q ORDER BY distance;

-- 2. Busca apenas por palavras (Full-Text BM25): memórias com radical de "parcelamento" SELECT id, text, search::score(0) AS score, search::highlight('', '', 0) AS hit FROM memory WHERE text @0@ 'parcelamento' ORDER BY score DESC;

A busca vetorial pura traz memory:m1 (distância 0.0037), memory:m2 (o documento técnico de retentativa de cartão, distância 0.0185) e memory:m4 (distância 0.0272), deixando de fora memory:m3 (sobre criativo de Reels). Já a busca por palavras com BM25 encontra exatamente memory:m1 e memory:m4, que contêm o termo "parcelamento" destacado em <b>parcelamento</b>.

Embeddings e busca vetorial por significado no SurrealDB

Como recuperar significado, palavras e grafo em uma única query SurrealQL?

Agora juntamos todas as condições na mesma cláusula WHERE e atravessamos o grafo diretamente na projeção do SELECT.

A consulta abaixo localiza memórias próximas ao vetor da pergunta (<|3,COSINE|>), que contenham o termo "parcelamento" (@@), que sejam turnos reais de conversa (kind = 'turn') ocurridos após 1º de novembro de 2026. Para cada memória encontrada, a mesma query percorre as arestas mentions e relates_to para descobrir quais sistemas e Order Bumps são afetados e quem é a pessoa dona daquela oferta — tudo dentro de um bloco BEGIN ... COMMIT com leitura consistente:

BEGIN;

LET $q = 0.85, 0.15, 0.05;

SELECT id, text, speaker, said_at, vector::distance::knn() AS distance, -- Entidades mencionadas diretamente na memória ->mentions->entity.name AS mentions, -- Sistemas e produtos conectados às entidades mencionadas (2º salto do grafo) ->mentions->entity->relates_to->entity.name AS connected_to, -- Quem é dono do produto mencionado (travessia reversa filtrada por aresta) ->mentions->entity<-(relates_to WHERE label = 'owns')<-entity.name AS owners FROM memory WHERE embedding <|3,COSINE|> $q -- por significado (vetor HNSW) AND text @@ 'parcelamento' -- por palavras exatas (BM25) AND kind = 'turn' -- por tipo de registro AND said_at > d'2026-11-01T00:00:00Z' -- por janela de tempo ORDER BY distance;

COMMIT;

O resultado devolvido ao agente em uma única viagem ao banco é um pacote de contexto completo e auditável:

[
  {
    "id": "memory:m1",
    "text": "Marina liberou o parcelamento via Pix em 4x na Mentoria IA até sexta antes de fechar o carrinho",
    "speaker": "user",
    "said_at": "2026-11-02T09:00:00Z",
    "distance": 0.0037,
    "mentions": ["Mentoria IA", "Marina"],
    "connected_to": ["Checkout Pagar.me", "Order Bump Prompts", "Mentoria IA"],
    "owners": ["Marina"]
  },
  {
    "id": "memory:m4",
    "text": "O Order Bump Prompts entra junto no parcelamento especial da Mentoria IA sem taxa extra",
    "speaker": "user",
    "said_at": "2026-11-03T10:00:00Z",
    "distance": 0.0272,
    "mentions": ["Order Bump Prompts", "Mentoria IA"],
    "connected_to": ["Checkout Pagar.me", "Order Bump Prompts"],
    "owners": ["Marina"]
  }
]

Observe como cada item retornado entrega ao agente de vendas do WhatsApp exatamente o que ele precisa para responder com segurança:

  1. Significado: a memória tem altíssima proximidade semântica (distance: 0.0037) com a dúvida do lead;
  2. Palavras exatas: o filtro léxico garante que a regra fala especificamente sobre parcelamento na conversa atual;
  3. Conexões de negócio: o grafo revela que a Mentoria IA usa o Checkout Pagar.me, possui o Order Bump Prompts vinculado e tem Marina como responsável comercial (owners).

O documento genérico sobre retentativa de cartão (memory:m2) ficou de fora porque não é um turno de conversa nem trata do parcelamento liberado, e a conversa sobre Reels (memory:m3) nunca teve proximidade semântica suficiente para poluir o prompt.

O agente pode gravar uma nova memória e consultá-la na mesma transação?

Sim. No SurrealDB, uma transação enxerga imediatamente suas próprias escritas tanto no índice vetorial HNSW quanto no índice Full-Text BM25 e nas arestas do grafo.

Imagine que, no meio do plantão de fechamento de carrinho, a Marina avisa no grupo da operação que prorrogou o corte do Pix parcelado para segunda-feira às 23h59. O webhook grava memory:m5, cria os vínculos RELATE e já executa o recall antes mesmo do COMMIT:

BEGIN;

CREATE memory:m5 CONTENT { text: 'Marina prorrogou o encerramento do parcelamento da Mentoria IA para segunda-feira', kind: 'turn', speaker: 'user', said_at: d'2026-11-04T08:00:00Z', embedding: 0.88, 0.12, 0.02 };

RELATE memory:m5->mentions->entity:marina; RELATE memory:m5->mentions->entity:mentoria_ia;

-- A nova memória já está visível nos três índices dentro da mesma transação SELECT id, text, vector::distance::knn() AS distance, ->mentions->entity.name AS mentions FROM memory WHERE embedding <|2,COSINE|> 0.85, 0.15, 0.05 AND text @@ 'encerramento' ORDER BY distance;

COMMIT;

O SELECT retorna imediatamente memory:m5 com distância 0.0014 e mentions: ["Mentoria IA", "Marina"].

Proteção contra duplicidade de entidades extraídas por LLM

Quando um agente usa um LLM para extrair entidades de uma mensagem de áudio ou texto no WhatsApp, é muito comum que duas expressões na mesma frase apontem para a mesma entidade do banco. Por exemplo, o cliente diz "quero entrar na Mentoria IA e aproveitar o desconto do programa de inteligência artificial" — e o extrator resolve ambas as expressões para entity:mentoria_ia.

Como definimos o índice DEFINE INDEX unique_mention ON mentions FIELDS in, out UNIQUE, a segunda tentativa de RELATE memory:m6->mentions->entity:mentoria_ia é recusada pelo banco e cancela a transação inteira, impedindo que memórias órfãs ou arestas duplicadas sujem o grafo:

BEGIN;

CREATE memory:m6 CONTENT { text: 'A Mentoria IA exige confirmação de webhook no Checkout Pagar.me para liberar o programa', kind: 'turn', speaker: 'user', said_at: d'2026-11-05T08:00:00Z', embedding: 0.8, 0.25, 0.05 };

RELATE memory:m6->mentions->entity:mentoria_ia; RELATE memory:m6->mentions->entity:checkout_pagarme;

-- Se "o programa" também foi resolvido para entity:mentoria_ia pelo LLM, -- o índice unique_mention barra a aresta duplicada e desfaz toda a transação: RELATE memory:m6->mentions->entity:mentoria_ia;

COMMIT;

Dica prática para deduplicar no código da aplicação
Ao extrair entidades com IA (usando Cloudflare Workers AI, Claude ou OpenAI), aplique um Array.from(new Set(entityIds)) no back-end antes de disparar os comandos RELATE (ou utilize nosso Conversor de JSON para SurrealQL para validar o formato dos registros) e mantenha o índice UNIQUE no banco como guarda final de integridade.

Prompt de sistema para o agente comercial usar a memória híbrida

Recuperar os dados certos em uma query SurrealQL é metade do trabalho; a outra metade é instruir o LLM a respeitar a hierarquia entre regras atuais da oferta, conexões do grafo e documentação fixa:

Agente Closer / Suporte de Infoproduto com Memória SurrealQL

Você é o assistente comercial e de suporte da operação. Responda à mensagem do lead no WhatsApp usando exclusivamente as memórias recuperadas pelo SurrealDB.

MEMÓRIAS RECUPERADAS (ordenadas por proximidade semântica):

  • Cada item contém: text (o que foi dito), said_at (data/hora), mentions (produtos/pessoas citados), connected_to (Order Bumps e sistemas de checkout vinculados) e owners (responsável pela oferta).

REGRAS DE DECISÃO:

  1. Priorize fatos mais recentes (said_at) quando houver prorrogação de lote, cupom ou parcelamento.
  2. Se o produto mencionado tiver um Order Bump em connected_to, apresente o benefício complementar de forma natural quando o lead perguntar sobre condições de pagamento.
  3. Nunca invente links de pagamento, descontos ou prazos que não estejam explícitos em text.
  4. Se a dúvida exigir aprovação de exceção comercial, informe que vai acionar a pessoa listada em owners.

Trilha recomendada para implementar no seu produto

Se você quer sair de agentes que esquecem tudo a cada mensagem e construir sua própria infraestrutura de IA e automação comercial em português, siga estes próximos passos no portal:

  1. Curso Grátis: SurrealDB Agent Memory (15 aulas): aprenda a usar fatos com fonte, reconciliação temporal (remember, recall, reflect) e servidor MCP no Claude Code;
  2. Curso Grátis: SurrealDB para Engenheiros de IA (11 aulas): domine índices HNSW, BM25, RAG híbrido e Graph RAG passo a passo;
  3. SurrealDB 4 e SurrealDB Earth na Cloudflare: entenda como rodar instâncias serverless do SurrealDB na borda da Cloudflare integradas aos seus Workers;
  4. Crie sua conta no SurrealDB Cloud pelo link da Escola: teste todas as queries deste artigo diretamente no Surrealist Studio.

Perguntas frequentes sobre memória de agentes em uma query SurrealQL

Como buscar vetores, grafos e texto na mesma query no SurrealDB?

Basta combinar na cláusula WHERE de um único SELECT o operador de vizinhança vetorial KNN (embedding <|K,COSINE|> $vetor), o operador de busca textual BM25 (text @@ 'termo') e filtros em campos escalares (kind, said_at), projetando as travessias de grafo (->mentions->entity->relates_to->entity.name) nas colunas retornadas dentro de uma transação BEGIN ... COMMIT.

Por que usar o analisador snowball(portuguese) no índice Full-Text do SurrealDB?

O filtro snowball(portuguese) aliado ao filtro ascii no DEFINE ANALYZER remove acentos e reduz palavras em português ao seu radical morfológico (stemming). Com isso, uma busca por "parcelamento" encontra automaticamente memórias que contenham "parcelamentos", "parcelado" ou "parcelar", aumentando a precisão do ranking BM25 em conversas reais de WhatsApp.

Qual a vantagem de usar SurrealDB em vez de Postgres + Pinecone + Neo4j para agentes de IA?

Unificar documentos, vetores HNSW, busca textual BM25 e relações de grafo no SurrealDB elimina a latência e a complexidade de sincronizar três ou quatro bancos diferentes no código da aplicação. Além disso, todas as escritas e leituras ocorrem na mesma transação ACID, garantindo que uma memória recém-gravada e suas conexões de grafo fiquem imediatamente visíveis para o agente.

Como evitar arestas duplicadas quando o LLM extrai entidades de uma conversa?

Crie uma tabela de relação tipada (DEFINE TABLE mentions TYPE RELATION IN memory OUT entity) acompanhada de um índice composto único (DEFINE INDEX unique_mention ON mentions FIELDS in, out UNIQUE). Se o modelo de linguagem resolver duas expressões do mesmo texto para a mesma entidade, o índice bloqueia a duplicata e preserva a integridade transacional do grafo.

Qual dimensão de embedding devo configurar no índice HNSW em produção?

O parâmetro DIMENSION do índice HNSW deve corresponder exatamente ao tamanho do vetor gerado pelo seu modelo de embedding: use 1536 para text-embedding-3-small da OpenAI, 3072 para text-embedding-3-large ou 1024 para voyage-3.5 da Voyage AI.