Resposta direta

TL;DR: - Memória vetorial (chunks + similaridade) falha em perguntas que cruzam múltiplos fatos - Knowledge graphs resolvem, mas a maioria das implementações deixa o LLM decidir a estrutura, e o resultado é genérico - A solução é definir a ontologia do seu domínio com Pydantic: tipos de entidade, tipos de relação e atributos antes da extração O problema que todo mundo descobre na semana 2 Você constrói um agente de suporte. Alimenta com 50 conversas de clientes. Depois pergunta: "quais clientes enterprise têm tickets sev-1 abertos?" O agente lembra de tudo.

TL;DR: - Memória vetorial (chunks + similaridade) falha em perguntas que cruzam múltiplos fatos - Knowledge graphs resolvem, mas a maioria das implementações deixa o LLM decidir a estrutura, e o resultado é genérico - A solução é definir a ontologia do seu domínio com Pydantic: tipos de entidade, tipos de relação e atributos antes da extração

Capa editorial do artigo Memória de agente com Pydantic: chega de chunk genérico

O problema que todo mundo descobre na semana 2

Você constrói um agente de suporte. Alimenta com 50 conversas de clientes. Depois pergunta: "quais clientes enterprise têm tickets sev-1 abertos?"

O agente lembra de tudo. Mas não entende nada.

Cada ticket virou um nó "Topic". Cada cliente virou um nó "Object". Cada relação virou "RELATES_TO". Severidade? Plano? Tipo de ticket? Tudo perdido numa sopa de nós genéricos.

O agente não esqueceu. Ninguém disse a ele o que prestar atenção.

Por que busca vetorial quebra em perguntas de múltiplos saltos

Memória vetorial armazena fatos como chunks de texto e recupera por similaridade semântica. Funciona até você precisar conectar fatos que não estão no mesmo chunk.

Três fatos:

  • Alice gerencia o Projeto Atlas

  • Projeto Atlas roda em PostgreSQL

  • O cluster PostgreSQL caiu na terça

Pergunta: "O projeto da Alice foi afetado pela queda de terça?"

Busca vetorial recupera os fatos 1 e 3 (ambos mencionam Alice e terça). O fato 2 é a ponte: conecta Alice ao PostgreSQL via Projeto Atlas, mas não menciona nem Alice nem terça. Similaridade perde.

Um grafo de conhecimento armazena entidades como nós e relacionamentos como arestas. Em vez de casar texto, ele traversa conexões: Alice > gerencia > Projeto Atlas > roda em > PostgreSQL. É isso que faz o raciocínio de múltiplos saltos funcionar.

O pipeline de memória (e onde a extração decide tudo)

Ingestão → Extração → Armazenamento → Consulta → Entrega

A extração é onde tudo é decidido. É ela que determina o que seu grafo contém, como é estruturado e o que é consultável.

O problema: na maioria dos frameworks, a extração é uma caixa-preta. Você passa texto, um LLM decide o que é "entidade" e "relacionamento", e você recebe nós e arestas. Zero controle sobre o que ele classifica ou como.

A solução: defina o schema ANTES com Pydantic

O padrão é o mesmo usado em todo o stack de IA:

  • FastAPI usa Pydantic pra response models

  • Function calling usa Pydantic pra schemas

  • Memória de agente também

No Zep (open-source), você define tipos de entidade customizados com EntityModel:

from zep_cloud.external_clients.ontology import EntityModel, EntityText
from pydantic import Field class Projeto(EntityModel): """Representa um projeto de software específico.""" status: EntityText = Field(description="Status: ativo, concluído, pausado ou arquivado") tipo: EntityText = Field(description="Tipo: web app, mobile app, API, CLI tool")

Docstrings e descrições dos campos são essenciais: elas ensinam ao extrator o vocabulário do seu domínio.

Tipos de aresta com EdgeModel:

from zep_cloud.external_clients.ontology import EdgeModel class UsaTecnologia(EdgeModel): proficiencia: EntityText = Field(description="Nível: iniciante, intermediário, avançado, expert")

E então você conecta tudo com a ontologia:

client.graph.set_ontology( entities={"Projeto": Projeto, "Tecnologia": Tecnologia}, edges={ "USA_TECNOLOGIA": ( UsaTecnologia, [EntityEdgeSourceTarget(source="Usuario", target="Tecnologia")], ), },
)

Isso força que USA_TECNOLOGIA só pode conectar um Usuário a uma Tecnologia. Qualquer relação que não bata com essa constraint não vira aresta tipada.

O que acontece por baixo dos panos

Quando uma conversa é ingerida com um schema ativo, o pipeline do Zep executa 5 passos:

1. Extração de entidades: identifica entidades nomeadas no texto 2. Resolução de entidades: mescla duplicatas ("Nexus" e "projeto Nexus" viram um nó) 3. Extração de fatos: identifica relações e gera arestas tipadas 4. Resolução de fatos: detecta contradições e invalida fatos desatualizados 5. Extração temporal: mapeia referências de tempo para janelas de validade

Seu schema Pydantic guia os passos 1 e 3. O resto acontece automaticamente.

Como aplicar hoje em 10 minutos

Passo 1: Defina 3-4 entidades do seu domínio

class Cliente(EntityModel): plano: EntityText = Field(description="Plano: enterprise, pro, starter") setor: EntityText = Field(description="Setor: fintech, saúde, varejo") class Ticket(EntityModel): severidade: EntityText = Field(description="sev-1, sev-2, sev-3") status: EntityText = Field(description="aberto, em_andamento, resolvido")

Passo 2: Defina as relações possíveis

class AbriuTicket(EdgeModel): canal: EntityText = Field(description="Canal: email, chat, telefone") class AfetaProjeto(EdgeModel): impacto: EntityText = Field(description="impacto: crítico, alto, médio, baixo")

Passo 3: Configure a ontologia e comece a ingerir conversas

client.graph.set_ontology( entities={"Cliente": Cliente, "Ticket": Ticket}, edges={ "ABRIU_TICKET": (AbriuTicket, [EntityEdgeSourceTarget(source="Cliente", target="Ticket")]), },
)

Passo 4: Faça perguntas com contexto tipado

context = client.context.create_context_template( template_id="suporte", template="""# TICKETS ABERTOS
%{edges types=[ABRIU_TICKET] limit=10} # CLIENTES
%{entities types=[Cliente] limit=10}""",
)

Por que 10/10/10 é uma vantagem

O Zep limita a 10 tipos de entidade, 10 tipos de aresta e 10 campos por tipo. Isso é intencional: força você a pensar no que realmente importa no seu domínio em vez de modelar tudo.

As constraints source/target também agem como guardrails: se o schema não inclui uma aresta conectando Projeto a Concorrente, o extrator não cria essa relação, mesmo que a conversa mencione os dois.

O schema define o espaço de memórias válidas. É o mesmo princípio de function calling tipado: a gente restringe o espaço de saída do LLM pra ele não produzir argumentos inválidos. Memória com schema faz o mesmo com o que o agente armazena.

Conclusão

Memória de agente sem schema é um grafo que se comporta como vetor store. Você paga o custo da construção do grafo sem receber o benefício da consulta estruturada.

O schema é como você recupera esse benefício. E o fato de ser Pydantic significa que não tem nada novo pra aprender: é o mesmo padrão que você já usa em FastAPI, function calling e validação de dados.

Comece com 3-4 tipos. Depois expanda. A regra é simples: se o LLM decide a estrutura, a estrutura vai ser genérica. Se você decide, ela vai ser útil.

FAQ

Preciso usar Zep ou posso fazer com qualquer graph DB?

Pode fazer com qualquer graph DB (Neo4j, Dgraph, Amazon Neptune). O Zep só oferece o pipeline de extração + ontologia pronto, então você economiza umas 200 linhas de código.

O schema Pydantic funciona com qualquer LLM?

Sim. O Zep usa o provedor que você configurar (OpenAI, Anthropic, local). O schema é convertido em instruções de extração para o modelo.

Quantos tipos de entidade devo começar?

3-4. O limite de 10 do Zep é proposital: força disciplina. Se você tem mais de 10 tipos, talvez esteja modelando detalhes que deveriam ser atributos, não entidades.

E se eu já tenho dados em vector store?

Dá para migrar? Depende. Se seus chunks são atômicos (um fato por chunk), uma migração com extração orientada por schema pode funcionar. Se são parágrafos soltos, melhor começar do zero.

Isso funciona para qualquer tipo de agente?

Sim. Agente de suporte, assistente de vendas, copiloto de desenvolvimento: qualquer domínio com terminologia própria se beneficia de schema. Quanto mais específico o domínio, maior o ganho.

---

*Post inspirado no artigo do Akshay Pachaar sobre memória de agentes com Pydantic + Zep. Código e documentação completos em github.com/getzep/zep.*