Pular para conteúdo

Construindo um Assistente Pessoal

Este guia mostra como dar a um agente de IA memória igual a de um humano usando o Arandu — pra ele lembrar o que ouviu, o que falou, quem falou o quê, e o que fez.

O Problema

Pensa em como a tua memória funciona. Quando tu conversa com alguém:

  • Tu lembra o que a pessoa te contou — "Pedro me disse que mora em Porto Alegre"
  • Tu lembra o que tu falou — "Eu recomendei um restaurante pra ele"
  • Tu lembra o que aprendeu sobre outra pessoa na conversa — "Pedro mencionou que a namorada Ana é designer"
  • Tu lembra o que tu fez — "Eu criei um documento pro Pedro"
  • Quando alguém pergunta sobre qualquer assunto, tu lembra tudo que sabe, independente de quem te contou ou quando

Um agente de IA não tem nada disso por padrão. Cada sessão começa em branco. O agente não sabe o que aconteceu antes, quem disse o quê, ou o que ele fez. Ele não tem memória.

O Arandu dá ao seu agente o mesmo tipo de memória que um humano tem. Cada fato carrega sobre quem é, quem disse, e quando — e a busca funciona como a lembrança humana: pergunta sobre um assunto, recebe tudo que é relevante.

Arquitetura

Um assistente pessoal precisa de três camadas de memória:

Camada Ferramenta O que guarda
Memória semântica Arandu Fatos sobre pessoas, preferências, ações, conhecimento
Memória procedimental System prompt / config Como o assistente deve se comportar
Memória episódica Sistema de arquivos / banco Logs diários, boards de tarefas, registros estruturados

O Arandu cuida da memória semântica — a camada de "o que eu sei". Ele extrai fatos de linguagem natural, reconcilia com o conhecimento existente, e recupera por relevância.

Padrão Principal: Gravar as Duas Pontas

O segredo: gravar tanto as mensagens do usuário quanto as respostas do assistente.

# Usuário falou → gravar com speaker_name do usuário
await memory.write(
    agent_id="meu-assistente",
    message="Minha namorada Ana é designer, ela trabalha na Stone",
    speaker_name="Pedro",
)
# Fatos extraídos:
#   "Ana é designer" (entidade: person:ana)
#   "Ana trabalha na Stone" (entidade: person:ana, organization:stone)
#   "Ana é namorada do Pedro" (entidade: person:ana)

# Assistente respondeu → gravar com speaker_name do assistente
await memory.write(
    agent_id="meu-assistente",
    message="Criei a nota 'Ana.md' no vault e agendei lembrete de aniversário pro dia 15/05",
    speaker_name="Assistente",
)
# Fatos extraídos:
#   "Assistente criou nota Ana.md no vault" (entidade: person:assistente)
#   "Assistente agendou lembrete de aniversário pro dia 15/05" (entidade: person:assistente)

O speaker_name resolve pronomes: "eu criei" com speaker_name="Assistente" vira "Assistente criou". Cada fato carrega o metadado speaker dizendo quem falou.

Fluxo de Sessão

┌─────────────────────────────────────────────────────┐
│ Início da sessão                                     │
│                                                      │
│  1. retrieve(query="contexto recente")               │
│     → Recupera o que o assistente sabe e fez         │
│     → Injeta no system prompt como contexto          │
│                                                      │
├─────────────────────────────────────────────────────┤
│ Durante a conversa                                   │
│                                                      │
│  2. Usuário manda mensagem                           │
│     → write(message=msg, speaker_name="Pedro")       │
│                                                      │
│  3. Assistente processa e responde                   │
│     → retrieve(query=msg) pra buscar contexto        │
│     → Gera resposta                                  │
│     → write(message=resposta, speaker_name="Assist") │
│                                                      │
├─────────────────────────────────────────────────────┤
│ Fim da sessão                                        │
│                                                      │
│  Nada especial. Tudo já foi gravado durante          │
│  a conversa. A próxima sessão começa com retrieve.   │
└─────────────────────────────────────────────────────┘

Exemplos de Retrieval

Uma vez que as duas pontas estão gravadas, o assistente consegue responder perguntas sobre qualquer pessoa e assunto:

# "O que eu sei sobre a Ana?"
result = await memory.retrieve(agent_id="meu-assistente", query="Ana")
# Retorna TODOS os fatos sobre Ana, independente de quem falou:
#   - Ana é designer (Pedro contou)
#   - Ana trabalha na Stone (Pedro contou)
#   - Assistente criou nota Ana.md (Assistente fez)

# "O que eu fiz recentemente?"
result = await memory.retrieve(agent_id="meu-assistente", query="o que foi feito recentemente")
# Retorna ações do assistente:
#   - Criou nota Ana.md
#   - Agendou lembrete de aniversário

# Restrito a uma entidade específica
result = await memory.retrieve(
    agent_id="meu-assistente",
    query="trabalho",
    entity_keys=["person:ana"],
)
# Retorna só fatos sobre trabalho relacionados à Ana

entity_keys aceita aliases também — passa "person:ana" mesmo que a canônica seja person:ana_silva, que o SDK resolve via memory_entity_aliases. Chaves que não resolvem aparecem em result.warnings pro caller distinguir "sem matches" de "chave errada":

result = await memory.retrieve(
    agent_id="meu-assistente",
    query="trabalho",
    entity_keys=["person:ana", "person:unknown"],
)
# result.warnings → ["entity_key 'person:unknown' not found (not canonical, no matching alias)"]

Provenance do Speaker

Todo fato carrega o campo speaker dizendo quem falou a mensagem de onde foi extraído. Disponível tanto no FactDetail (via get/get_all) quanto no ScoredFact (via retrieve):

result = await memory.retrieve(agent_id="meu-assistente", query="Stone")
for fact in result.facts:
    print(f"[{fact.speaker}] {fact.fact_text}")
# Output:
#   [Pedro] Ana trabalha na Stone
#   [Assistente] Stone é uma fintech brasileira, fundada em 2012

Nenhum concorrente persiste provenance do speaker nativamente — isso é um diferencial do Arandu.

Gerenciando a Memória

O assistente pode inspecionar e gerenciar sua própria memória:

# Listar entidades conhecidas
entities = await memory.entities(agent_id="meu-assistente")
for e in entities:
    print(f"[{e.entity_type}] {e.display_name} ({e.fact_count} fatos)")
# [person] Pedro (12 fatos)
# [person] Ana (5 fatos)
# [organization] Stone (3 fatos)
# [person] Assistente (8 fatos)

# Listar fatos de uma entidade específica
ana_facts = await memory.get_all(
    agent_id="meu-assistente",
    entity_keys=["person:ana"],
)

# Deletar um fato específico
await memory.delete(agent_id="meu-assistente", fact_id="algum-uuid")

# Resetar toda a memória (use com cuidado)
await memory.delete_all(agent_id="meu-assistente")

Exemplo Completo

Um assistente pessoal mínimo com memória:

import asyncio
from arandu import MemoryClient
from arandu.providers.openai import OpenAIProvider


async def handle_message(
    memory: MemoryClient,
    agent_id: str,
    user_msg: str,
    speaker: str,
) -> str:
    # 1. Gravar mensagem do usuário
    await memory.write(
        agent_id=agent_id,
        message=user_msg,
        speaker_name=speaker,
    )

    # 2. Recuperar contexto relevante
    context = await memory.retrieve(agent_id=agent_id, query=user_msg)

    # 3. Gerar resposta (substitua pela sua chamada LLM)
    llm = memory._llm  # reutiliza o provider do SDK
    response = await llm.complete(
        messages=[
            {
                "role": "system",
                "content": f"Você é um assistente pessoal. Contexto da memória:\n{context.context}",
            },
            {"role": "user", "content": user_msg},
        ]
    )

    # 4. Gravar resposta do assistente
    await memory.write(
        agent_id=agent_id,
        message=response.text,
        speaker_name="Assistente",
    )

    return response.text


async def main():
    provider = OpenAIProvider(api_key="sk-...")
    memory = MemoryClient(
        database_url="postgresql+psycopg://memory:memory@localhost:5432/memory",
        llm=provider,
        embeddings=provider,
    )
    await memory.initialize()

    agent_id = "assistente-pedro"

    try:
        # Início da sessão: verificar o que aconteceu antes
        recent = await memory.retrieve(agent_id=agent_id, query="contexto recente")
        if recent.facts:
            print("Retomando com contexto de sessões anteriores...")

        # Simular conversa
        reply = await handle_message(
            memory, agent_id,
            "Minha namorada Ana é designer na Stone",
            speaker="Pedro",
        )
        print(f"Assistente: {reply}")

        reply = await handle_message(
            memory, agent_id,
            "O que você sabe sobre a Ana?",
            speaker="Pedro",
        )
        print(f"Assistente: {reply}")
    finally:
        await memory.close()


asyncio.run(main())

Como o Arandu se Compara

Capacidade Mem0 Zep Letta Arandu
Extração de fatos do texto Sim Sim Não (manual) Sim
Filtro no retrieve user_id/agent_id group_ids Labels de bloco entity_keys
Provenance do speaker Não Indireta (lookup no episódio) Não Sim (nativo)
Grafo de entidades Opcional Sim Não Sim
Operações CRUD Sim Sim Sim Sim
Reconciliação (dedup) Sim Automática Manual Sim (via LLM)