SSD Nodes Learn 🎉 VPS dès $4.99/mois
Guides Matt ConnorPar Matt Connor · Mis à jour le 2026-08-07

Auto-héberger mem0 sur un VPS pour la mémoire d’agent

Découvrez la RAM réelle de mem0, le fichier Compose lié à localhost, le TLS devant l’API et le mode Ollama local, avec un minimum de 8 Go pour un modèle 8B.

Ce que coûte réellement mem0 en RAM lorsqu’il est auto-hébergé sur un VPS

L’auto-hébergement de mem0 consiste à exécuter trois conteneurs : le serveur mémoire FastAPI, Postgres avec l’extension pgvector et un tableau de bord Next.js. mem0 est une couche mémoire pour les agents. Vous lui envoyez une conversation, un modèle de langage en extrait les faits durables, puis ces faits sont stockés sous forme de vecteurs afin qu’une requête ultérieure puisse récupérer les éléments pertinents.

Prévoyez environ 1 GB de mémoire résidente pour les trois conteneurs et 3 à 4 GB d’espace disque une fois les images construites. Un VPS de 2 GB exécute correctement cet ensemble lorsque le modèle de langage fonctionne ailleurs. Si le modèle s’exécute sur la même machine avec Ollama, il consomme bien plus que le reste : un modèle 8B quantifié sur 4 bits demande environ 6 GB à lui seul. Une installation entièrement locale nécessite donc au minimum 8 GB.

Ne reprenez pas ces chiffres d’un article de blog, y compris celui-ci. Mesurez la pile que vous avez réellement construite.

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

docker stats affiche la mémoire résidente de chaque conteneur. docker system df -v affiche l’espace disque occupé par chaque image et chaque volume.

L’utilisation en régime stable ne correspond pas au pic. docker compose up -d --build compile le tableau de bord Next.js, et cette compilation Node constitue le moment le plus gourmand de toute l’installation. Sur un VPS de 1 GB, le kernel out-of-memory killer l’arrête et la compilation se termine avec exit code 137. Confirmez la cause avant de chercher un bug Docker :

dmesg -T | grep -i "killed process"

Si un serveur semble trop complexe pour votre besoin, des options plus légères existent. un stockage mémoire local pour agent, sans serveur et une mémoire intégrée directement à Claude Code évitent tous deux la base de données. Revenez ici lorsque plusieurs agents ou plusieurs machines devront lire les mêmes mémoires.

Ai-je besoin de Neo4j pour la mémoire en graphe de mem0 ?

Non. Si un guide vous demande d’ajouter un conteneur Neo4j, il est antérieur au code actuel.

La mémoire en graphe de mem0 désignait auparavant une base de données de graphes externe, configurée avec une clé graph_store et enable_graph définie sur true. Le nouvel algorithme de mémoire, publié en avril 2026, a supprimé ces deux clés du SDK open source. L’extraction des entités s’exécute désormais dans le chemin d’ajout standard, et les entités sont écrites dans une seconde collection pgvector dont le nom reprend celui de la collection principale, suivi de _entities. Aucune migration n’est nécessaire. La mise en relation intégrée des entités commence à fonctionner lors du prochain appel d’ajout.

La suppression du graph store évite d’exécuter un conteneur JVM, ainsi que de lui réserver de la mémoire heap et plusieurs centaines de mégaoctets pour l’image. Sur un VPS de 2 GB, cela peut faire la différence entre un système qui fonctionne et un système qui commence à utiliser le swap.

Voici clairement ce que vous perdez. Les résultats de recherche contenaient auparavant un champ relations qui listait les relations entre les entités. Ce champ a disparu. Les correspondances d’entités augmentent désormais la position d’une mémoire dans le score combiné, mais aucune structure ne peut être parcourue. Si votre application parcourait ces relations, mem0 ne les conserve plus. Vous devez maintenir votre propre graph database en dehors de mem0 et l’alimenter avec votre propre code.

Le fichier Compose du dépôt est un fichier de développement

server/docker-compose.yaml déclare name: mem0-dev, et il l’utilise réellement. Lisez-le avant de l’exécuter, car cinq éléments ne conviennent pas à un serveur.

  • Il construit l’image à partir de server/dev.Dockerfile et monte votre checkout par-dessus l’image avec .:/app. Le conteneur exécute donc ce qui se trouve dans ce répertoire, et non ce que vous avez construit.
  • Sa commande est rm -rf /app/packages && pip install -q --force-reinstall --no-deps mem0ai && alembic upgrade head && uvicorn main:app --reload. Elle réinstalle mem0ai depuis PyPI à chaque démarrage. La version exécutée par votre serveur peut donc changer lors d’un redémarrage que vous ne pensiez pas être une mise à niveau.
  • Cette étape pip signifie également qu’un redémarrage sans accès au réseau sortant échoue avant même le lancement d’uvicorn. Votre serveur de mémoire est alors indisponible parce que PyPI n’était pas joignable.
  • --reload active le file watcher d’uvicorn. Il sert à redémarrer le processus lorsque vous modifiez le code. En production, il consomme de la mémoire et lance un second processus sans utilité. Le Dockerfile de production contient également --reload dans son CMD. Vous devez donc remplacer la commande dans les deux cas.
  • Les ports publiés sont "8888:8000", "8432:5432" et "3000:3000". Un port publié sans adresse devant lui est lié à 0.0.0.0. Postgres est donc accessible depuis Internet sur le port 8432 dès le démarrage de la stack.

Ce dernier point mérite un avertissement distinct. Docker publie un port en plaçant ses propres règles avant la chaîne gérée par ufw. Ainsi, ufw deny 8432 ne ferme pas un port de conteneur publié. La publication des ports Docker contourne directement ufw présente les règles concernées.

Un fichier Compose pour un vrai serveur

Travaillez dans server/, conservez init-db.sh à son emplacement et remplacez docker-compose.yaml par ceci.

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

Cinq changements sont importants ici, et chacun a une raison.

Chaque entrée ports commence par 127.0.0.1. Le noyau n’accepte donc ces connexions que depuis le serveur lui-même. Toutes les connexions externes passent par le reverse proxy, qui est le seul composant à détenir un certificat.

Postgres ne comporte aucun bloc ports. Le conteneur mem0 y accède via mem0_network en utilisant le nom du service. Publier le port 8432 ne vous apporte donc rien et vous impose un port ouvert. Utilisez docker compose exec postgres psql -U postgres lorsque vous avez besoin d’un shell.

L’historique passe du bind mount ./history à un volume nommé. Un bind mount lie les données à un chemin et à un uid précis sur cet hôte, tandis qu’un volume nommé est un objet que Docker peut sauvegarder et déplacer. Volumes nommés et bind mounts explique dans quels cas utiliser l’un ou l’autre.

La commande supprime --reload et conserve alembic upgrade head. Conservez cette étape de migration. Sans elle, l’application démarre avec une base de données dépourvue de tables et chaque requête échoue lors de la première requête SQL.

NEXT_PUBLIC_API_URL est l’URL appelée par votre navigateur. Elle doit donc être l’adresse HTTPS publique, et non http://mem0:8000. Next.js intègre chaque valeur NEXT_PUBLIC_ au moment du build. Pour la modifier, vous devez donc exécuter docker compose up -d --build mem0-dashboard. Un simple redémarrage conserve l’ancienne valeur intégrée au JavaScript et le dashboard appelle le mauvais hôte.

Les secrets sont stockés dans .env, et .env ne doit pas être exposé sur 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

Définissez POSTGRES_PASSWORD, JWT_SECRET et ADMIN_API_KEY. Laissez AUTH_DISABLED=false désactivé. Le nom indique clairement le rôle de cette option : lorsqu’elle est activée, le serveur transmet toute la mémoire qu’il contient à quiconque peut atteindre le port. Définissez MEM0_TELEMETRY=false si vous ne voulez pas que l’événement d’intégration soit envoyé en amont.

ADMIN_API_KEY est comparé à l’en-tête X-API-Key avec secrets.compare_digest ; en cas de correspondance, toutes les recherches dans la base de données sont ignorées. Il s’agit d’un identifiant root pour toute l’API. Traitez-le comme tel : pas d’historique shell, pas de git, et ne le collez pas dans une invite. Fichiers d’environnement Compose et fuites de secrets et garder les clés d’API hors du contexte d’un agent s’appliquent directement, car les clients de ce serveur sont des agents.

Les valeurs chargées depuis env_file se trouvent dans l’environnement du conteneur, et docker inspect les affiche intégralement. Toute personne appartenant au groupe docker peut les lire, et toute personne appartenant au groupe docker dispose en pratique des privilèges root sur l’hôte.

Placez TLS devant l’API au lieu d’ouvrir 8888

L’API répond sur 127.0.0.1:8888 et le dashboard sur 127.0.0.1:3000. nginx termine TLS (transport layer security) sur 443 et transmet les requêtes aux deux services.

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 est plus important qu’il n’y paraît. Un appel d’ajout reste bloqué pendant que le modèle de langage lit la conversation et en extrait les faits. Avec un modèle local de 8B exécuté sur le CPU, le traitement dépasse régulièrement le délai par défaut de 60 secondes de nginx. L’appelant reçoit alors 504 Gateway Time-out alors que le modèle travaille encore et que la mémoire est malgré tout enregistrée. Vous obtenez donc une mémoire signalée comme ayant échoué alors qu’elle a bien été créée.

Bloquez le reste avec une stratégie ufw par défaut en deny, en laissant 22 et 443 ouverts. Émettez le certificat avec certbot sur Ubuntu 24.04 derrière nginx. Si le serveur expose déjà d’autres applications avec Traefik pour router plusieurs applications Compose, ajoutez mem0 à ce routeur au lieu d’installer un second proxy.

Test de bon fonctionnement : ajouter une mémoire et la relire

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"}'

Une réponse correcte est un objet JSON contenant une liste results. Chaque entrée contient un id, le texte memory extrait et "event": "ADD". L’algorithme actuel renvoie uniquement les événements ADD. Les événements UPDATE et DELETE ont été supprimés ; leur absence n’est donc pas un bug.

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}'

Le fait concernant Postgres 17 doit être renvoyé avec un score. Transmettez l’identifiant dans filters, comme indiqué. Un champ user_id au niveau supérieur fonctionne toujours, et le serveur journalise Top-level user_id in /search is deprecated. Use filters={...} instead. à chaque utilisation.

Nettoyez les données après le test afin qu’elles ne faussent pas les recherches réelles :

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

Si la recherche renvoie moins de lignes que prévu, vérifiez les valeurs par défaut avant de mettre en cause la récupération. Dans la version actuelle, top_k vaut 20 par défaut, contre 100 auparavant, et threshold vaut 0.1 plutôt que none ; les correspondances faibles sont donc désormais filtrées automatiquement. Une fois que cela fonctionne avec curl, ce sont les mêmes endpoints que vous branchez à un agent, directement ou via un serveur MCP exécuté sur le même VPS.

Exécuter mem0 sans aucune clé OpenAI

Commencez par le blocage, car vous le rencontrerez dans les cinq premières minutes. L’image du serveur contient un ensemble fixe de bibliothèques de providers, et /configure rejette tout ce qui n’en fait pas partie :

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.

Vous n’avez rien à reconstruire. Ollama expose une API compatible avec OpenAI sur /v1, qui prend en charge /v1/chat/completions et /v1/embeddings, et le provider openai de mem0 accepte un openai_base_url. Pointez cette clé vers Ollama : la vérification intégrée réussit, car le provider est bien openai. Seule l’adresse change.

Ajoutez Ollama au même projet Compose :

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

Ajoutez ollama_models: sous la clé de niveau supérieur volumes:, puis téléchargez un modèle de chat et un modèle d’embedding :

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

Si Ollama s’exécute déjà sur l’hôte comme unité systemd, comme dans exécuter Ollama directement sur un VPS, ne pointez pas le conteneur vers 127.0.0.1:11434. Dans le conteneur mem0, 127.0.0.1 désigne le conteneur mem0. Donnez au service mem0 extra_hosts: ["host.docker.internal:host-gateway"], définissez Environment="OLLAMA_HOST=0.0.0.0:11434" dans un drop-in systemd afin qu’Ollama écoute sur une adresse accessible depuis le bridge, et laissez le port 11434 fermé dans le firewall.

Demandez la dimension des embeddings au modèle avant toute configuration

Cette étape détermine à elle seule si la recherche fonctionne.

Le store pgvector de mem0 crée sa table avec une largeur de vecteur fixe, vector vector(1536), car embedding_model_dims vaut 1536 par défaut, soit la largeur de text-embedding-3-small d’OpenAI. nomic-embed-text renvoie 768 valeurs. Rien dans mem0 ne compare ces deux nombres. L’incompatibilité apparaît donc dans Postgres lors de la première insertion :

expected 1536 dimensions, not 768

Ne vous fiez pas non plus au nombre indiqué dans ce paragraphe. Demandez-le au modèle :

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']))"

Cette commande affiche la largeur à utiliser pour votre collection. Écrivez la configuration dans un fichier, car le fait de coller un mot de passe Postgres avec les guillemets du shell est une source d’erreurs en production.

{
  "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"

Le deuxième appel relit la configuration. C’est la vérification que l’écriture a bien été effectuée. Répétez ensuite le smoke test ci-dessus.

Quatre détails de ce JSON ne sont pas évidents. Chacun provoque un dysfonctionnement s’il est incorrect.

api_key est la chaîne ollama, et Ollama ignore sa valeur. Elle ne peut pas être vide, car la bibliothèque cliente OpenAI lève une erreur avant l’envoi de toute requête si aucune clé n’est définie. N’importe quelle chaîne non vide convient.

embedding_model_dims se place sur le vector store. Il n’y a volontairement aucun embedding_dims sur l’embedder. mem0 envoie le paramètre OpenAI dimensions uniquement lorsque vous définissez embedding_dims, et les backends qui n’implémentent pas la troncature Matryoshka rejettent directement ce paramètre. Définissez la largeur lors de la création de la table et laissez l’embedder tel quel.

collection_name est nouveau. mem0 crée sa table avec CREATE TABLE IF NOT EXISTS. Indiquer une autre largeur pour une collection existante ne change donc rien : l’ancienne colonne vector(1536) reste en place et chaque insertion échoue. Pour changer de largeur, vous devez utiliser un nouveau nom de collection ou supprimer manuellement l’ancienne table.

L’hôte dans openai_base_url est le nom du service Compose ollama, et non localhost. Les conteneurs se résolvent entre eux par leur nom de service sur leur réseau commun.

Le coût d’une architecture entièrement locale

Soyez réaliste concernant la qualité. Les scores des benchmarks publiés par mem0 ont été mesurés avec des modèles de pointe pour l’extraction. Considérez-les donc comme une limite supérieure, et non comme une prévision pour un modèle 8B sur votre VPS. Un petit modèle produit des faits plus vagues et renvoie parfois du texte alors qu’un JSON était demandé. Cela se traduit par un appel add qui renvoie une liste results vide, sans erreur.

La vitesse est l’autre coût. L’extraction sur CPU uniquement prend plusieurs secondes par appel add, et chaque message enregistré doit être traité. Si cette latence est importante, un VPS avec un GPU attaché est la solution réaliste. Ajouter des cœurs CPU à un modèle 8B apporte beaucoup moins de gain qu’on ne le pense.

Une règle reste valable quel que soit votre choix : ne mélangez jamais des modèles d’embeddings dans une même collection. Deux modèles différents qui ont par hasard la même largeur produisent des vecteurs qui ne sont pas comparables. L’insertion réussit, la recherche renvoie des lignes et ces lignes sont incorrectes, sans qu’aucun élément ne signale une erreur.

Sauvegardes : il y a deux bases de données, pas une seule

L’erreur la plus fréquente lors de la sauvegarde de mem0 consiste à exporter une seule base de données. init-db.sh crée mem0_app en plus de la base de données par défaut postgres, et elles contiennent des données différentes. La base postgres contient les collections pgvector, c’est-à-dire les mémoires. mem0_app contient les utilisateurs, les sessions, les clés API et les journaux des requêtes.

Si vous restaurez uniquement postgres, les mémoires réapparaissent, mais tous les comptes et toutes les clés API ont disparu. Plus rien ne peut donc s’authentifier pour les lire. Exportez les deux bases, ainsi que les rôles, avec une seule commande :

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

Le volume d’historique est distinct de Postgres et nécessite sa propre copie :

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

Docker préfixe les noms de volume avec le nom du projet. Vérifiez donc le vôtre avec docker volume ls avant de supposer qu’il s’agit de mem0_mem0_history.

Restaurez les sauvegardes dans un conteneur temporaire et vérifiez le nombre de lignes avant de considérer les données comme fiables :

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

Une sauvegarde que vous n’avez jamais restaurée n’est qu’une hypothèse. Une fois les exports validés, envoyez-les hors du serveur avec des snapshots restic vers un stockage externe, car une sauvegarde conservée sur le serveur qu’elle protège ne protège rien.

Modes d’échec et chaînes exactes affichées

{"detail":"Authentication required. Provide a Bearer token or X-API-Key header."} signifie que l’en-tête est absent ou mal orthographié. Son nom est X-API-Key, et curl envoie les noms d’en-têtes littéralement.

{"detail":"At least one identifier (user_id, agent_id, run_id) is required."} lors d’un ajout signifie que la requête n’en contenait aucun. Une mémoire doit être rattachée à un élément, car la recherche filtre exactement sur ces champs.

LLM provider 'ollama' is not bundled in this image avec HTTP 400 signifie que vous avez envoyé "provider": "ollama". Utilisez "provider": "openai" avec openai_base_url dirigé vers Ollama.

expected 1536 dimensions, not 768 provenant de Postgres signifie que la collection a été créée avec une dimension et que l’embedder en renvoie une autre. Définissez embedding_model_dims sur le vector store et utilisez un nouveau collection_name.

La recherche renvoie des lignes incohérentes après un changement de modèle, sans aucune erreur. La dimension correspond toujours, donc la base de données considère la requête comme valide, mais deux modèles placent la même phrase à des positions différentes. Créez une nouvelle collection et ajoutez-y de nouveau les données.

Connection refused dans les journaux mem0 lors de la connexion à Ollama signifie généralement que 127.0.0.1 dans openai_base_url. Dans le conteneur, cette adresse désigne le conteneur lui-même. Utilisez le nom du service, ou la passerelle vers l’hôte lorsque Ollama s’exécute sur l’hôte.

504 Gateway Time-out provenant de nginx lors d’un ajout signifie que le modèle a mis plus de proxy_read_timeout. Augmentez cette valeur et vérifiez si la mémoire a tout de même été enregistrée avant de relancer la requête.

exit code 137 pendant docker compose up --build est dû à l’out-of-memory killer, qui arrête la construction du dashboard. Ajoutez du swap, ou construisez l’image sur une machine plus puissante avant de l’envoyer vers un registry.

error: port 3000 is already in use provient de la cible make up du dépôt, qui refuse de démarrer lorsque les ports 3000 ou 8888 sont déjà utilisés. Trouvez le processus propriétaire avec lsof -iTCP:3000 -sTCP:LISTEN.

FAQ

Ai-je encore besoin de Neo4j pour utiliser mem0 avec la mémoire en graphe ?

Non. Le nouvel algorithme de mémoire, publié en avril 2026, a supprimé les clés de configuration graph_store et enable_graph du SDK open source. L’extraction des entités s’exécute désormais lors d’un ajout normal et écrit dans une seconde collection pgvector nommée <collection_name>_entities. Il n’y a donc plus de base de données de graphe externe, de conteneur supplémentaire ni d’étape de migration. En contrepartie, le champ relations n’existe plus dans les résultats de recherche. Les entités améliorent maintenant le classement d’une mémoire au lieu de fournir des relations à parcourir. Une application qui parcourait ces relations doit donc utiliser son propre graph store en dehors de mem0.

Quel est le plus petit VPS capable d’exécuter un serveur mem0 auto-hébergé ?

Si le modèle de langage est hébergé ailleurs, 2 GB de RAM et environ 4 GB d’espace disque libre suffisent pour le conteneur API, Postgres et le dashboard. Le moment le plus exigeant est le premier build, car la compilation du dashboard Next.js utilise plus de mémoire que son exécution. Sur une machine de 1 GB, le build est tué avec exit code 137. Si Ollama s’exécute sur le même serveur, dimensionnez la machine en fonction du modèle : un modèle 8B avec une quantification 4-bit nécessite à lui seul environ 6 GB. Prévoyez donc 8 GB.

Puis-je exécuter mem0 sans clé API OpenAI ?

Oui, avec l’endpoint compatible OpenAI d’Ollama. La définition de "provider": "ollama" échoue, car l’image serveur inclut uniquement les bibliothèques openai, anthropic et gemini et renvoie HTTP 400. Conservez plutôt "provider": "openai" et définissez "openai_base_url": "http://ollama:11434/v1" avec n’importe quel api_key non vide, pour le llm comme pour l’embedder. Ollama ignore la clé et le contrôle du provider intégré réussit, car le provider est bien openai.

Pourquoi mem0 ne renvoie-t-il aucun résultat après le remplacement du modèle d’embedding par un modèle local ?

Parce que la table pgvector a été créée avec une dimension fixe. embedding_model_dims vaut 1536 par défaut, nomic-embed-text renvoie 768 et Postgres refuse l’insertion avec expected 1536 dimensions, not 768. mem0 crée la table avec CREATE TABLE IF NOT EXISTS. Modifier uniquement ce nombre ne change donc rien dans une collection existante. Définissez embedding_model_dims sur la dimension réelle de votre modèle, vérifiez cette dimension en appelant /v1/embeddings et en comptant les valeurs renvoyées, puis attribuez en même temps un nouveau collection_name au vector store.