Como hospedar o mem0 em um VPS com Ollama local
Veja o limite real de RAM do mem0, um Compose preso ao localhost, TLS na API e o caminho local com Ollama: 2 GB sem modelo e 8 GB com um modelo 8B.
Quanto custa realmente executar o mem0 num VPS em termos de RAM
A execução autónoma do mem0 implica 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 persistentes 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 passa a dominar o consumo: um modelo 8B quantizado para 4 bits precisa de aproximadamente 6 GB por si só, por isso uma instalação totalmente local começa nos 8 GB.
Não aceite estes valores com base numa publicação de blog, incluindo esta. Meça a instalação que efetivamente criou.
docker compose ps
docker stats --no-stream
docker system df -vdocker stats apresenta a memória residente por contentor. docker system df -v apresenta 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 termina o processo através do mecanismo out-of-memory killer e a compilação termina com exit code 137. Confirme a causa antes de procurar um problema no Docker:
dmesg -T | grep -i "killed process"Se um servidor parecer uma solução demasiado complexa para as suas necessidades, existem opções mais pequenas. um armazenamento local de memória para agentes, sem servidor e memória integrada no próprio Claude Code dispensam ambos a base de dados. Volte a esta configuração quando vários agentes ou vários computadores precisarem de ler as mesmas memórias.
Preciso do Neo4j para a memória de grafo do mem0?
Não. Se um guia disser para adicionar um contentor Neo4j, esse guia é anterior ao código atual.
A memória de grafo do mem0 costumava significar uma base de dados de grafos externa, configurada numa chave graph_store com enable_graph definida 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 durante o 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 ligação de entidades integrada começa a funcionar na próxima chamada de adição.
Remover o armazenamento de grafos elimina um contentor JVM, a respetiva heap e várias centenas de megabytes de 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. Os resultados de pesquisa costumavam incluir um campo relations com as ligaçõ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 uma 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 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 que o tornam inadequado para um servidor.
- Compila a partir de
server/dev.Dockerfilee monta o seu checkout sobre a imagem com.:/app. Assim, o contentor executa o que estiver nesse diretório, e não o que foi compilado. - O comando é
rm -rf /app/packages && pip install -q --force-reinstall --no-deps mem0ai && alembic upgrade head && uvicorn main:app --reload. Isto reinstalamem0aia partir do PyPI em cada arranque. Por isso, a versão executada pelo servidor pode mudar durante um reinício que não pretendia usar para atualizar nada. - O mesmo passo do pip faz com que um reinício sem rede de saída falhe antes de o uvicorn arrancar. O seu servidor de memória fica então indisponível porque o PyPI não estava acessível.
--reloadinicia o monitor de ficheiros do uvicorn. Esta opção existe para reiniciar o processo quando edita código. Em produção, apenas consome memória e mantém um segundo processo sem utilidade. ODockerfilede produção também inclui--reloadno seuCMD, por isso terá 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 à sua frente fica associada a0.0.0.0. Assim, o Postgres fica acessível a partir da Internet pública na porta 8432 assim que a stack arranca.
Este último ponto merece um aviso próprio. O Docker publica uma porta ao inserir as suas próprias regras antes da cadeia gerida pelo ufw. Por isso, ufw deny 8432 não fecha uma porta de um contentor publicada. Publicação de 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 mesmo local 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: bridgeHá cinco alterações importantes, e cada uma tem uma razão.
Todas as entradas ports começam 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 chega através do reverse proxy, que é o único componente que mantém um certificado.
O Postgres não tem qualquer bloco ports. O contentor mem0 liga-se a ele 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 do bind mount ./history para um volume nomeado. Um bind mount associa os dados a um caminho e a um uid neste host, enquanto um volume nomeado é um objeto que o Docker pode guardar num snapshot e mover. Volumes nomeados e bind mounts explica quando usar cada um.
O comando remove --reload e mantém alembic upgrade head. Mantenha esta etapa da migração. Sem ela, 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 chama, 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 momento da compilação, por isso alterá-lo requer docker compose up -d --build mem0-dashboard. Um simples reinício mantém o valor antigo incorporado no JavaScript, e o dashboard chama 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 .envDefina 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á ativa, o servidor entrega toda a memória que detém a qualquer pessoa que consiga alcançar a porta. Defina MEM0_TELEMETRY=false se não quiser que o evento de onboarding seja enviado para o serviço upstream.
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. Esta é uma credencial root para toda a API. Trate-a como tal: não a deixe no histórico da shell, não a guarde no git e não a cole num prompt. Ficheiros de ambiente do Compose e onde os segredos vazam 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 do grupo docker pode lê-los, e qualquer pessoa do grupo docker tem, na prática, privilégios root no host.
Configure o TLS na 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 de 8B executado no CPU demora regularmente mais do que o valor predefinido de 60 segundos do nginx. Nesse caso, o chamador recebe 504 Gateway Time-out enquanto o modelo ainda está a trabalhar e a memória continua a ser gravada. O resultado é uma memória que foi apresentada como falhada.
Bloqueie o restante com uma política ufw de negação por predefinição, mantendo abertas as portas 22 e 443. Emita o certificado com o certbot no Ubuntu 24.04 atrás do nginx. Se o servidor já publicar outras aplicações através do Traefik para encaminhar várias aplicações Compose, adicione o mem0 a esse router em vez de instalar um segundo 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 saudável é um objeto JSON com uma lista results, e cada entrada contém um id, o texto memory extraído e "event": "ADD". O algoritmo atual retorna apenas eventos ADD. Os eventos UPDATE e DELETE foram removidos, portanto a ausência deles 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 voltar com uma pontuação. Passe o identificador dentro de filters, como mostrado. Um user_id no 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 de teste para não poluírem 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 retornar menos linhas do que esperava, verifique os valores predefinidos antes de culpar a recuperação. Na versão atual, top_k tem o valor predefinido 20, em vez de 100, e threshold tem o valor predefinido 0.1 em vez de nenhum valor, por isso as correspondências fracas são 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 o mem0 sem qualquer 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 qualquer elemento que esteja 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 é necessário reconstruir nada. O Ollama disponibiliza uma API compatível com OpenAI em /v1, que cobre /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. Só 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/.ollamaAdicione ollama_models: à chave de nível superior volumes: 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-textSe o Ollama já for executado no host como uma unidade systemd, como em executar o Ollama diretamente numa VPS, não aponte o contentor para 127.0.0.1:11434. Dentro do contentor do mem0, 127.0.0.1 é o contentor do 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 fechado 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 no mem0 compara estes dois números, por isso a incompatibilidade aparece no Postgres na primeira inserção:
expected 1536 dimensions, not 768També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 colar uma palavra-passe do Postgres através do quoting 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. Essa é a verificação de que a escrita foi aplicada. Depois, repita o teste de funcionamento anterior.
Há quatro detalhes nesse JSON que não são óbvios. Cada um causa uma falha se for configurado incorretamente.
api_key é a string ollama, e o Ollama ignora o seu valor. Não pode ficar vazio, 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 é definido no armazenamento de vetores, e deliberadamente não existe embedding_dims no embedder. O mem0 envia o parâmetro dimensions da OpenAI apenas quando define embedding_dims, e os backends que não implementam truncamento Matryoshka rejeitam esse parâmetro diretamente. Defina a largura quando a tabela for criada e deixe o embedder sem alterações.
collection_name é novo. O mem0 cria a tabela com CREATE TABLE IF NOT EXISTS. Por isso, apontar uma largura diferente para uma coleção existente não produz efeito: 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 remoçã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 de executar tudo localmente
Seja realista quanto à qualidade. Os resultados dos benchmarks publicados pelo mem0 foram medidos com modelos de fronteira a fazer a extração. Por isso, trate-os como um limite superior, não como uma previsão para um modelo 8B na sua VPS. Um modelo pequeno escreve factos mais vagos e, por vezes, devolve prosa quando foi solicitado JSON. Isto aparece como uma chamada add que devolve uma lista results vazia, sem erro.
A velocidade é outro custo. A extração apenas com CPU demora segundos por chamada add, e cada mensagem armazenada tem esse custo. Um modelo que continua a gerar texto depois do JSON solicitado agrava o problema. Por isso, limitar a resposta com num_predict define um limite para a duração de cada chamada add. 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 se espera. Alterar o modelo é uma opção mais barata do que alterar a máquina, e o Nemotron 3.5 Lightning numa VPS fornece a tag a descarregar, a RAM necessária e indica se a execução apenas com CPU é suficientemente rápida para ser aceitável.
Há uma regra que se aplica a qualquer escolha: nunca misture modelos de embeddings na mesma coleção. Dois modelos diferentes que, por acaso, tenham a mesma largura produzem vetores que não são comparáveis. A inserção é bem-sucedida, a pesquisa devolve linhas e essas linhas estão erradas, sem que exista qualquer erro reportado.
Backups: existem duas bases de dados, não uma
O erro mais comum ao fazer backup do mem0 é descarregar uma única base de dados. init-db.sh cria mem0_app juntamente com a base de dados predefinida postgres, e cada uma armazena dados diferentes. A base de dados postgres armazena as coleções pgvector, que correspondem às memórias. mem0_app armazena utilizadores, sessões, chaves de API e logs de pedidos. Cada aplicação self-hosted divide o seu estado de forma diferente. Por isso, dois servidores de fotografias que fazem o mesmo trabalho continuam a precisar de comandos de backup diferentes. Leia o que a sua aplicação armazena antes de confiar num dump. No extremo oposto está algo como uma biblioteca Jellyfin reconstruída como uma videoteca dos anos 90, que lê todo o seu catálogo a partir de outro serviço e, por isso, precisa sobretudo que a sua própria configuração seja copiada. O mem0, por outro lado, precisa das duas bases de dados; caso contrário, o restauro fica inutilizado.
Restaure apenas postgres e as memórias serão recuperadas, mas todas as contas e chaves de API desaparecerão. Nesse caso, nada poderá autenticar-se para as ler. Faça o dump das duas bases de dados, incluindo as roles, com um único comando:
docker compose exec -T postgres pg_dumpall -U postgres --clean \
| gzip > "mem0-$(date +%F).sql.gz"O volume do 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. Por isso, confirme o seu com docker volume ls antes de assumir que é mem0_mem0_history.
Restaure os dados num contentor temporário e confirme 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 postgresUm 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 deveria proteger não protege nada.
Modos de falha e as strings exatas que verá
{"detail":"Authentication required. Provide a Bearer token or X-API-Key header."} significa que o cabeçalho está ausente ou 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."} num add 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 Ollama.
expected 1536 dimensions, not 768 do Postgres significa que a collection foi criada com uma largura e o embedder devolve outra. Defina embedding_model_dims no vector store e use uma nova collection_name.
A pesquisa devolve linhas sem sentido depois de uma alteração de modelo, sem qualquer erro. A largura continua a corresponder, por isso a base de dados aceita os dados, mas dois modelos colocam a mesma frase em posições diferentes. Comece uma nova collection e adicione os dados novamente.
Connection refused nos logs do mem0 ao contactar o 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 é executado no host.
504 Gateway Time-out do nginx num add significa que o modelo demorou mais do que proxy_read_timeout. Aumente esse valor e verifique 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 target make up do repositório, que recusa 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 April 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. Por isso, não existe uma base de dados de grafos externa, um contentor adicional nem 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. Por isso, 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. Numa máquina com 1 GB, a compilação é terminada com exit code 137. Se o Ollama for executado no mesmo servidor, dimensione o sistema para o modelo. Um modelo 8B com quantização de 4 bits precisa de aproximadamente 6 GB por si só. Nesse caso, planeie usar 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 api_key não vazio, tanto para o llm como para o embedder. O Ollama ignora a chave, e a verificação do provider incluído é bem-sucedida porque o provider é realmente openai.
Porque é que o mem0 não devolve resultados depois de mudar para um modelo local de embeddings?
Porque a tabela pgvector foi criada com uma largura fixa. embedding_model_dims usa 1536 por predefinição, 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 com a largura real do seu modelo, confirme essa largura chamando /v1/embeddings e contando os valores devolvidos, e atribua simultaneamente ao armazenamento vetorial um novo collection_name.