SSD Nodes Learn 🎉 VPS desde $4.99/mês
Guias Matt ConnorPor Matt Connor · Atualizado 2026-08-07

Como hospedar o mem0 em um VPS para memória de agentes

Descubra o mínimo real de RAM do mem0, use Compose com bind em localhost, TLS na frente da API e Ollama local, incluindo o caso de modelo 8B com 6 GB.

Quanto realmente custa em RAM alojar o mem0 num VPS

Alojar o mem0 significa executar três contentores: o servidor FastAPI de memória, o Postgres com a extensão pgvector e um dashboard Next.js. O mem0 é uma camada de memória para agentes. Envia-lhe uma conversa, um modelo de linguagem extrai os factos duradouros dessa conversa e esses factos são armazenados como vetores, para que uma consulta posterior possa recuperar os factos relevantes.

Reserve aproximadamente 1 GB de memória residente para os três contentores e 3 a 4 GB de disco depois de criar as imagens. Um VPS com 2 GB executa esta configuração sem problemas quando o modelo de linguagem está noutro local. Quando o modelo é executado no mesmo servidor através do Ollama, o modelo domina o consumo: um modelo 8B quantizado para 4 bits precisa de aproximadamente 6 GB por si só, pelo que a configuração totalmente local começa nos 8 GB.

Não aceite estes valores com base num artigo de blog, incluindo este. Meça a configuração que efetivamente instalou.

docker compose ps
docker stats --no-stream
docker system df -v

docker stats mostra a memória residente por contentor. docker system df -v mostra o espaço em disco ocupado por cada imagem e cada volume.

O consumo em estado estável não corresponde ao pico. docker compose up -d --build compila o dashboard Next.js, e essa compilação do Node é o momento de maior consumo de toda a instalação. Num VPS com 1 GB, o kernel interrompe o processo através do out-of-memory killer e a compilação termina com exit code 137. Confirme a causa antes de procurar um erro no Docker:

dmesg -T | grep -i "killed process"

Se um servidor parecer uma infraestrutura excessiva para o que precisa, existem opções mais pequenas. um armazenamento local de memória para agentes, sem servidor e memória que reside no próprio Claude Code dispensam ambos a base de dados. Volte aqui quando vários agentes ou várias máquinas precisarem de ler as mesmas memórias.

É necessário usar o Neo4j para a memória em grafo do mem0?

Não. Se um guia indicar a adição de um contentor Neo4j, esse guia é anterior ao código atual.

A memória em grafo no mem0 significava anteriormente uma base de dados de grafos externa, configurada sob uma chave graph_store com enable_graph definido como true. O novo algoritmo de memória, lançado em abril de 2026, removeu ambas as chaves do SDK de código aberto. A extração de entidades é agora executada no fluxo normal de adição, e as entidades são gravadas numa segunda coleção pgvector com o nome da coleção principal seguido de _entities. Não é necessário executar nenhuma migração. A associação de entidades integrada começa a funcionar na chamada seguinte a add.

Remover o armazenamento do grafo elimina um contentor JVM, a respetiva heap e várias centenas de megabytes da imagem. Num VPS com 2 GB, isso pode determinar se o sistema funciona ou começa a usar swap.

Eis o que deixa de estar disponível, de forma direta. Os resultados de pesquisa incluíam anteriormente um campo relations que listava as relações entre entidades. Esse campo deixou de existir. As correspondências de entidades aumentam agora a posição de uma memória na pontuação combinada, mas não existe nenhuma estrutura que possa percorrer. Se a sua aplicação percorria essas relações, o mem0 já não as mantém. Nesse caso, terá de manter uma base de dados de grafos própria fora do mem0 e alimentá-la com o seu próprio código.

O ficheiro compose no repositório é um ficheiro compose de desenvolvimento

server/docker-compose.yaml declara name: mem0-dev e é exatamente isso que faz. Leia-o antes de o executar, porque há cinco problemas nele para um servidor.

  • Ele cria a imagem a partir de server/dev.Dockerfile e monta o seu checkout sobre a imagem com .:/app. Assim, o contentor executa o que estiver nesse diretório, em vez do que foi criado na imagem.
  • O comando é rm -rf /app/packages && pip install -q --force-reinstall --no-deps mem0ai && alembic upgrade head && uvicorn main:app --reload. Ele reinstala mem0ai a partir do PyPI em cada arranque. Por isso, a versão executada pelo servidor pode mudar durante um reinício que não pretendia que fosse uma atualização.
  • O mesmo passo do pip faz com que um reinício sem acesso à rede externa falhe antes de o uvicorn arrancar. O servidor de memória fica indisponível porque o PyPI não estava acessível.
  • --reload inicia o observador de ficheiros do uvicorn. Ele existe para reiniciar o processo quando edita código. Em produção, consome memória e inicia um segundo processo sem utilidade. O Dockerfile de produção também inclui --reload no seu CMD. Portanto, tem de substituir o comando de qualquer forma.
  • As portas publicadas são "8888:8000", "8432:5432" e "3000:3000". Uma porta publicada sem um endereço à frente fica associada a 0.0.0.0. Assim, o Postgres fica acessível pela Internet pública na porta 8432 assim que a stack arranca.

Este último ponto merece um aviso próprio. O Docker publica uma porta escrevendo as suas próprias regras antes da cadeia gerida pelo ufw. Por isso, ufw deny 8432 não fecha uma porta publicada de um contentor. Publicar portas do Docker diretamente através do ufw explica as regras envolvidas.

Um ficheiro Compose para um servidor real

Trabalhe dentro de server/, mantenha init-db.sh no lugar e substitua docker-compose.yaml por isto.

name: mem0

services:
  mem0:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    env_file: .env
    ports:
      - "127.0.0.1:8888:8000"
    networks: [mem0_network]
    volumes:
      - mem0_history:/app/history
    depends_on:
      postgres:
        condition: service_healthy
    command: >
      sh -c "alembic upgrade head &&
             uvicorn main:app --host 0.0.0.0 --port 8000"
    environment:
      - PYTHONUNBUFFERED=1
      - DASHBOARD_URL=https://mem0.example.com
      - APP_DB_NAME=mem0_app
      - AUTH_DISABLED=false
      - MEM0_TELEMETRY=false

  postgres:
    image: pgvector/pgvector:pg17
    restart: unless-stopped
    shm_size: "128mb"
    networks: [mem0_network]
    environment:
      - POSTGRES_USER=${POSTGRES_USER:-postgres}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -q -U ${POSTGRES_USER:-postgres}"]
      interval: 5s
      timeout: 5s
      retries: 5
    volumes:
      - postgres_db:/var/lib/postgresql/data
      - ./init-db.sh:/docker-entrypoint-initdb.d/init-db.sh

  mem0-dashboard:
    build: ./dashboard
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"
    networks: [mem0_network]
    environment:
      - NEXT_PUBLIC_API_URL=https://mem0.example.com
      - API_INTERNAL_URL=http://mem0:8000
    depends_on:
      mem0:
        condition: service_started

volumes:
  postgres_db:
  mem0_history:

networks:
  mem0_network:
    driver: bridge

Cinco alterações são importantes, e cada uma tem uma razão.

Cada entrada ports começa com 127.0.0.1, por isso o kernel aceita essas ligações apenas a partir do próprio servidor. Tudo o que vem do exterior passa pelo reverse proxy, que é o único componente que mantém um certificado.

O Postgres não tem qualquer bloco ports. O contentor mem0 acede-lhe através de mem0_network pelo nome do serviço, por isso publicar 8432 não traz qualquer benefício e deixa uma porta aberta. Use docker compose exec postgres psql -U postgres quando precisar de uma shell.

O histórico passa da montagem bind ./history para um volume nomeado. Uma montagem bind associa os dados a um caminho e a um uid neste host, enquanto um volume nomeado é um objeto que o Docker pode copiar para um snapshot e mover. Volumes nomeados e montagens bind explica quando cada opção é adequada.

O comando remove --reload e mantém alembic upgrade head. Mantenha esse passo da migração. Sem ele, a aplicação arranca com uma base de dados sem tabelas e todos os pedidos falham na primeira consulta.

NEXT_PUBLIC_API_URL é o URL que o browser utiliza, por isso tem de ser o endereço HTTPS público e não http://mem0:8000. O Next.js inclui todos os valores NEXT_PUBLIC_ no build, por isso alterá-los requer docker compose up -d --build mem0-dashboard. Um simples restart mantém o valor antigo incorporado no JavaScript e o dashboard contacta o host errado.

Os segredos ficam em .env, e .env não fica exposto à Internet

cd server
cp .env.example .env
openssl rand -hex 32    # paste into JWT_SECRET
openssl rand -hex 32    # paste into ADMIN_API_KEY
chmod 600 .env

Defina POSTGRES_PASSWORD, JWT_SECRET e ADMIN_API_KEY. Deixe AUTH_DISABLED=false sem definir. O nome descreve corretamente o que essa opção faz: quando está ativada, o servidor entrega toda a memória que detém a qualquer pessoa que consiga aceder à porta. Defina MEM0_TELEMETRY=false se não quiser que o evento de onboarding seja enviado para montante.

ADMIN_API_KEY é comparado com o cabeçalho X-API-Key usando secrets.compare_digest, e uma correspondência ignora todas as consultas à base de dados. Trata-se de uma credencial root para toda a API. Proteja-a como tal: não a deixe no histórico da shell, no git nem a cole num prompt. Ficheiros de ambiente do Compose e onde os segredos escapam deles e manter as chaves da API fora do contexto de um agente aplicam-se diretamente, porque os clientes deste servidor são agentes.

Os valores carregados de env_file ficam no ambiente do contentor, e docker inspect apresenta-os na íntegra. Qualquer pessoa no grupo docker pode lê-los, e qualquer pessoa no grupo docker é efetivamente root no host.

Coloque TLS à frente da API em vez de abrir a porta 8888

A API responde em 127.0.0.1:8888 e o dashboard em 127.0.0.1:3000. O nginx termina o TLS (segurança da camada de transporte) na porta 443 e encaminha os pedidos para ambos.

server {
    listen 443 ssl;
    server_name mem0.example.com;

    ssl_certificate     /etc/letsencrypt/live/mem0.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mem0.example.com/privkey.pem;

    location ~ ^/(memories|search|configure|auth|api-keys|docs|openapi.json) {
        proxy_pass http://127.0.0.1:8888;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_read_timeout 180s;
    }

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

proxy_read_timeout é mais importante do que parece. Uma chamada de adição fica bloqueada enquanto o modelo de linguagem lê a conversa e extrai os factos. Um modelo local 8B executado no CPU demora regularmente mais do que os 60 segundos predefinidos do nginx. Nesse caso, o cliente recebe 504 Gateway Time-out enquanto o modelo continua a trabalhar e a memória continua a ser gravada. O resultado é uma memória que foi apresentada como falhada.

Feche o restante com uma política ufw de negação por predefinição, mantendo as portas 22 e 443 abertas. Emita o certificado com certbot no Ubuntu 24.04 atrás do nginx. Se o servidor já encaminhar outras aplicações com Traefik a encaminhar várias aplicações Compose, adicione o mem0 a esse router em vez de instalar outro proxy.

Teste rápido: adicione uma memória e leia-a de volta

export MEM0_KEY='<the ADMIN_API_KEY from .env>'

curl -sS -X POST http://127.0.0.1:8888/memories \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $MEM0_KEY" \
  -d '{"messages":[{"role":"user","content":"I deploy with Docker Compose and I run Postgres 17."}],"user_id":"smoke"}'

Uma resposta válida é um objeto JSON com uma lista results, em que cada entrada contém um id, o texto memory extraído e "event": "ADD". O algoritmo atual devolve apenas eventos ADD. Os eventos UPDATE e DELETE foram removidos, por isso a sua ausência não é um erro.

curl -sS -X POST http://127.0.0.1:8888/search \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $MEM0_KEY" \
  -d '{"query":"which database do I run?","filters":{"user_id":"smoke"},"top_k":5}'

O facto sobre o Postgres 17 deve ser devolvido com uma pontuação. Passe o identificador dentro de filters, como mostrado. Um user_id de nível superior continua a funcionar, e o servidor regista Top-level user_id in /search is deprecated. Use filters={...} instead. sempre que o utiliza.

Limpe os dados criados pelo teste para que não contaminem as pesquisas reais:

curl -sS -X DELETE "http://127.0.0.1:8888/memories?user_id=smoke" \
  -H "X-API-Key: $MEM0_KEY"

Se a pesquisa devolver menos linhas do que esperava, verifique os valores predefinidos antes de culpar a recuperação. Na versão atual, top_k assume 20 por predefinição, em vez de 100, e threshold assume 0.1 em vez de nenhum valor, pelo que as correspondências fracas são agora filtradas automaticamente. Depois de isto funcionar com curl, são estes mesmos endpoints que deve ligar a um agente, diretamente ou através de um servidor MCP em execução no mesmo VPS.

Execute mem0 sem nenhuma chave OpenAI

Comece pelo bloqueio, porque vai encontrá-lo nos primeiros cinco minutos. A imagem do servidor inclui um conjunto fixo de bibliotecas de provedores, e /configure rejeita tudo o que estiver fora desse conjunto:

LLM provider 'ollama' is not bundled in this image. Bundled providers: openai, anthropic, gemini. To use another provider, install its Python package, rebuild the container, and extend BUNDLED_LLM_PROVIDERS in server/main.py.

Não precisa de recompilar nada. O Ollama disponibiliza uma API compatível com OpenAI em /v1, abrangendo /v1/chat/completions e /v1/embeddings, e o provedor openai do mem0 aceita um openai_base_url. Aponte essa chave para o Ollama e a verificação incluída passa, porque o provedor é efetivamente openai. Apenas o endereço muda.

Adicione o Ollama ao mesmo projeto Compose:

  ollama:
    image: ollama/ollama
    restart: unless-stopped
    networks: [mem0_network]
    ports:
      - "127.0.0.1:11434:11434"
    volumes:
      - ollama_models:/root/.ollama

Adicione ollama_models: à chave volumes: de nível superior e, em seguida, descarregue um modelo de chat e um modelo de embeddings:

docker compose up -d ollama
docker compose exec ollama ollama pull llama3.1:8b
docker compose exec ollama ollama pull nomic-embed-text

Se o Ollama já estiver a ser executado no host como uma unidade systemd, tal como em executar o Ollama diretamente numa VPS, não aponte o contentor para 127.0.0.1:11434. Dentro do contentor mem0, 127.0.0.1 é o contentor mem0. Dê ao serviço mem0 extra_hosts: ["host.docker.internal:host-gateway"], defina Environment="OLLAMA_HOST=0.0.0.0:11434" num drop-in do systemd para que o Ollama escute num endereço que a bridge consiga alcançar e mantenha 11434 bloqueada na firewall.

Consulte a dimensão dos embeddings do modelo antes de configurar qualquer coisa

Este passo determina se a recuperação funciona.

O armazenamento pgvector do mem0 cria a tabela com uma largura fixa de vetores, vector vector(1536), porque embedding_model_dims assume por predefinição o valor 1536, a largura de text-embedding-3-small da OpenAI. nomic-embed-text devolve 768 valores. Nada dentro do mem0 compara estes dois números, pelo que a incompatibilidade aparece no Postgres na primeira inserção:

expected 1536 dimensions, not 768

Também não confie no número deste parágrafo. Consulte o modelo:

curl -sS http://127.0.0.1:11434/v1/embeddings \
  -H "Content-Type: application/json" \
  -d '{"model":"nomic-embed-text","input":"dimension check"}' \
  | python3 -c "import json,sys; print(len(json.load(sys.stdin)['data'][0]['embedding']))"

Isto apresenta a largura que a sua coleção tem de utilizar. Escreva a configuração num ficheiro, porque inserir uma palavra-passe do Postgres através das aspas da shell é uma forma de introduzir erros de digitação em produção.

{
  "vector_store": {
    "provider": "pgvector",
    "config": {
      "host": "postgres",
      "port": 5432,
      "dbname": "postgres",
      "user": "postgres",
      "password": "<POSTGRES_PASSWORD from .env>",
      "collection_name": "memories_local_768",
      "embedding_model_dims": 768
    }
  },
  "llm": {
    "provider": "openai",
    "config": {
      "model": "llama3.1:8b",
      "api_key": "ollama",
      "openai_base_url": "http://ollama:11434/v1",
      "temperature": 0.2
    }
  },
  "embedder": {
    "provider": "openai",
    "config": {
      "model": "nomic-embed-text",
      "api_key": "ollama",
      "openai_base_url": "http://ollama:11434/v1"
    }
  }
}
curl -sS -X POST http://127.0.0.1:8888/configure \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $MEM0_KEY" \
  -d @config.json

curl -sS http://127.0.0.1:8888/configure -H "X-API-Key: $MEM0_KEY"

A segunda chamada lê novamente a configuração. Esta é a verificação de que a escrita foi efetuada. Em seguida, repita o teste de funcionamento acima.

Há quatro detalhes nesse JSON que não são óbvios. Cada um deles causa uma falha se for configurado incorretamente.

api_key é a string ollama, e o Ollama ignora o seu valor. Não pode estar vazia, porque a biblioteca cliente OpenAI gera um erro antes de qualquer pedido sair do processo quando nenhuma chave está definida. Qualquer string não vazia funciona.

embedding_model_dims pertence ao armazenamento de vetores, e não existe deliberadamente nenhum embedding_dims no embedder. O mem0 envia o parâmetro OpenAI dimensions apenas quando define embedding_dims, e os backends que não implementam truncamento Matryoshka rejeitam esse parâmetro imediatamente. Defina a largura quando a tabela for criada e deixe o embedder inalterado.

collection_name é novo. O mem0 cria a tabela com CREATE TABLE IF NOT EXISTS, portanto apontar uma largura diferente para uma coleção existente não faz absolutamente nada: a coluna vector(1536) antiga permanece, e todas as inserções falham. Uma alteração de largura requer um nome de coleção novo ou a eliminação manual da tabela antiga.

O host em openai_base_url é o nome do serviço Compose ollama, não localhost. Os contentores resolvem-se entre si pelo nome do serviço na rede partilhada.

O custo do caminho totalmente local

Seja honesto consigo próprio sobre a qualidade. As pontuações dos benchmarks publicados pelo mem0 foram medidas com modelos de vanguarda a fazer a extração. Considere-as um limite superior, não uma previsão para um modelo 8B na sua VPS. Um modelo pequeno escreve factos mais vagos e, por vezes, devolve prosa quando foi pedido JSON. Isto aparece como uma chamada add que devolve uma lista results vazia, sem erro.

A velocidade é o outro custo. A extração apenas com CPU demora segundos por chamada add, e cada mensagem armazenada tem esse custo. Se essa latência for importante, uma VPS com uma GPU ligada é a solução adequada. Adicionar mais núcleos de CPU a um modelo 8B ajuda muito menos do que é habitual esperar.

Há uma regra que se aplica qualquer que seja a sua escolha: nunca misture modelos de embeddings numa coleção. Dois modelos diferentes que por acaso tenham a mesma largura produzem vetores que não são comparáveis. A inserção é concluída, a pesquisa devolve linhas e as linhas estão erradas, sem que nada indique um erro.

Backups: existem duas bases de dados, não uma

O erro mais comum nos backups do mem0 é exportar uma única base de dados. init-db.sh cria mem0_app juntamente com a base de dados predefinida postgres, e ambas armazenam dados diferentes. A base de dados postgres contém as coleções pgvector, que correspondem às memórias. mem0_app contém utilizadores, sessões, chaves de API e logs de pedidos.

Se restaurar apenas postgres, as memórias serão recuperadas, mas todas as contas e chaves de API desaparecerão. Assim, nada poderá autenticar-se para as ler. Exporte ambas, juntamente com as roles, num único comando:

docker compose exec -T postgres pg_dumpall -U postgres --clean \
  | gzip > "mem0-$(date +%F).sql.gz"

O volume de histórico é separado do Postgres e precisa da sua própria cópia:

docker run --rm -v mem0_mem0_history:/data -v "$PWD:/backup" \
  alpine tar czf /backup/mem0-history.tgz -C /data .

O Docker acrescenta o nome do projeto aos nomes dos volumes. Confirme o seu com docker volume ls antes de assumir que é mem0_mem0_history.

Restaure os dados num contentor temporário e verifique o número de linhas antes de considerar o backup válido:

gunzip -c mem0-2026-08-03.sql.gz \
  | docker compose exec -T postgres psql -U postgres -d postgres

Um backup que nunca foi restaurado é apenas uma suposição. Depois de confirmar que os dumps estão corretos, envie-os para fora do servidor com snapshots do restic para armazenamento externo, porque um backup guardado no servidor que deve proteger não protege nada.

Modos de falha e as mensagens exatas que verá

{"detail":"Authentication required. Provide a Bearer token or X-API-Key header."} significa que o cabeçalho está em falta ou foi escrito incorretamente. O nome é X-API-Key, e o curl envia os nomes dos cabeçalhos literalmente.

{"detail":"At least one identifier (user_id, agent_id, run_id) is required."} numa adição significa que o pedido não tinha nenhum desses campos. Uma memória tem de estar associada a algo, porque a pesquisa filtra exatamente por esses campos.

LLM provider 'ollama' is not bundled in this image com HTTP 400 significa que enviou "provider": "ollama". Use "provider": "openai" com openai_base_url apontado para o Ollama.

expected 1536 dimensions, not 768 do Postgres significa que a coleção foi criada com uma dimensão e o embedder devolve outra. Defina embedding_model_dims no armazenamento vetorial e use um novo collection_name.

A pesquisa devolve linhas sem sentido depois de uma alteração de modelo, sem qualquer erro. A dimensão continua a corresponder, por isso a base de dados aceita os dados, mas dois modelos colocam a mesma frase em posições diferentes. Crie uma nova coleção e volte a adicionar os dados.

Connection refused nos logs do mem0 ao aceder ao Ollama normalmente significa 127.0.0.1 em openai_base_url. Dentro do contentor, esse endereço corresponde ao próprio contentor. Use o nome do serviço ou o gateway do host quando o Ollama estiver a ser executado no host.

504 Gateway Time-out do nginx durante uma adição significa que o modelo demorou mais de proxy_read_timeout. Aumente esse valor e confirme se a memória foi escrita antes de repetir o pedido.

exit code 137 durante docker compose up --build é o out-of-memory killer a interromper a compilação do dashboard. Adicione swap ou compile a imagem numa máquina maior e envie-a para um registry.

error: port 3000 is already in use vem do destino make up do repositório, que se recusa a iniciar quando as portas 3000 ou 8888 estão ocupadas. Encontre o processo responsável com lsof -iTCP:3000 -sTCP:LISTEN.

FAQ

Ainda preciso do Neo4j para executar o mem0 com memória em grafo?

Não. O novo algoritmo de memória, lançado em abril de 2026, removeu as chaves de configuração graph_store e enable_graph do SDK de código aberto. A extração de entidades agora é executada durante uma operação normal de adição e grava os dados numa segunda coleção pgvector chamada <collection_name>_entities. Não existe, portanto, uma base de dados de grafos externa, um contentor adicional ou uma etapa de migração. A contrapartida é que o campo relations deixou de existir nos resultados da pesquisa. As entidades agora aumentam a classificação de uma memória, em vez de fornecerem arestas para percorrer. Uma aplicação que percorria essas relações precisa do seu próprio armazenamento de grafos fora do mem0.

Qual é o VPS mais pequeno que executa um servidor mem0 autoalojado?

Com o modelo de linguagem alojado noutro local, 2 GB de RAM e cerca de 4 GB de espaço livre em disco são suficientes para o contentor da API, o Postgres e o dashboard. O momento mais exigente é a primeira compilação, porque compilar o dashboard Next.js usa mais memória do que executá-lo. Num servidor com 1 GB, a compilação é terminada com exit code 137. Se o Ollama for executado no mesmo servidor, dimensione o servidor para o modelo. Um modelo 8B com quantização de 4 bits precisa de aproximadamente 6 GB por si só. Planeie, portanto, 8 GB.

Posso executar o mem0 sem uma chave de API da OpenAI?

Sim, através do endpoint compatível com OpenAI do Ollama. Definir "provider": "ollama" falha porque a imagem do servidor inclui apenas as bibliotecas openai, anthropic e gemini e devolve HTTP 400. Em vez disso, mantenha "provider": "openai" e defina "openai_base_url": "http://ollama:11434/v1" com qualquer valor não vazio de api_key, tanto para o llm como para o embedder. O Ollama ignora a chave. A verificação do fornecedor incluída é aprovada porque o fornecedor é realmente openai.

Por que motivo o mem0 não devolve resultados depois de eu mudar para um modelo de embeddings local?

Porque a tabela pgvector foi criada com uma dimensão fixa. embedding_model_dims tem o valor predefinido 1536, nomic-embed-text devolve 768 e o Postgres rejeita a inserção com expected 1536 dimensions, not 768. O mem0 cria a tabela com CREATE TABLE IF NOT EXISTS. Por isso, alterar apenas o número não tem efeito numa coleção existente. Defina embedding_model_dims para a dimensão real do seu modelo, confirme essa dimensão chamando /v1/embeddings e contando os valores devolvidos e atribua ao armazenamento vetorial um novo collection_name ao mesmo tempo.