Aucune description

Lukas Goldschmidt 89f104a6e9 fixes il y a 2 jours
.gitignore 2dddbab7cf initial commit il y a 4 jours
Dockerfile 3cbca7e125 improvements, separate knowledge base, tests il y a 3 jours
README.md 3cbca7e125 improvements, separate knowledge base, tests il y a 3 jours
docker-compose.yml 3cbca7e125 improvements, separate knowledge base, tests il y a 3 jours
mem0server.py 89f104a6e9 fixes il y a 2 jours
requirements.txt 2dddbab7cf initial commit il y a 4 jours
reset_memory.py 89f104a6e9 fixes il y a 2 jours
tests.sh 3cbca7e125 improvements, separate knowledge base, tests il y a 3 jours

README.md

mem0server

A lightweight FastAPI wrapper around mem0 providing persistent memory over a REST API, with dual-collection storage, metadata passthrough, and local reranking.

Architecture

Component Provider Address
LLM Groq (meta-llama/llama-4-scout-17b-16e-instruct) cloud
Vector store Chroma 192.168.0.200:8001
Embedder Ollama (nomic-embed-text) 192.168.0.200:11434
Reranker local REST server 192.168.0.200:5200

Collections

The server maintains two independent Chroma collections with separate extraction prompts:

Collection Chroma name Endpoint prefix Used by Extraction style
Conversational openclaw_mem /memories OpenClaw agent User-centric facts ("User prefers…")
Knowledge knowledge_mem /knowledge book-ingestor Objective, encyclopedic facts

Prompts for both collections are defined in the PROMPTS dict at the top of mem0server.py and are easy to edit without touching routing code.

Environment variables

Variable Required Default Description
GROQ_API_KEY ✅ yes Groq API key
RERANKER_URL no http://192.168.0.200:5200/rerank Local reranker endpoint

Create a .env file (never commit it):

GROQ_API_KEY=your_key_here
RERANKER_URL=http://192.168.0.200:5200/rerank

Docker (recommended)

The recommended setup mounts mem0server.py as a volume so code changes are picked up by uvicorn's --reload without rebuilding the image. Only touch docker compose build when requirements.txt changes.

docker-compose.yml

services:
  mem0server:
    build: .
    image: mem0server:latest
    container_name: mem0server
    ports:
      - "8420:8420"
    volumes:
      - ./mem0server.py:/app/mem0server.py:ro
    env_file:
      - .env
    restart: unless-stopped

Dockerfile

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# mem0server.py is mounted at runtime via docker-compose volume
EXPOSE 8420
CMD ["uvicorn", "mem0server:app", "--host", "0.0.0.0", "--port", "8420", "--reload"]

Workflow

Situation Command
First time / deps changed docker compose up --build
Code edit Just save — uvicorn reloads automatically
Restart container docker compose restart
View logs docker compose logs -f

⚠️ Never bake .env into the image. Always pass secrets at runtime via env_file or -e.


API

GET /health

Returns server status, active collection names, and a preview of each extraction prompt.

{
  "status": "ok",
  "reranker_url": "http://192.168.0.200:5200/rerank",
  "collections": {
    "conversational": "openclaw_mem",
    "knowledge": "knowledge_mem"
  },
  "prompts": {
    "conversational": "You are a personal memory assistant…",
    "knowledge": "You are a knowledge extraction assistant…"
  }
}

/memories — conversational collection (OpenClaw)

POST /memories

Add a memory. Accepts plain text or a messages array.

{ "text": "I prefer Python over JavaScript.", "user_id": "alice" }
{
  "messages": [{"role": "user", "content": "I've used Vim for 10 years."}],
  "user_id": "alice"
}

POST /memories/search

Search with reranking. Fetches limit × 3 candidates, reranks locally, returns top limit.

{ "query": "editor preferences", "user_id": "alice", "limit": 5 }

Results include a rerank_score field.

POST /memories/recent

Return the most recently created memories for a user.

{ "user_id": "alice", "limit": 5 }

DELETE /memories

Delete memories by filter.

{ "filter": { "user_id": "alice" } }

/knowledge — knowledge collection (book-ingestor)

Same shape as /memories with two additions:

metadata — arbitrary key/value dict stored alongside the memory. Useful for provenance tagging:

{
  "text": "Silvio Gesell proposed demurrage to discourage currency hoarding.",
  "user_id": "knowledge_base",
  "metadata": { "source_file": "gesell_neo.pdf", "chapter": 3, "page": 47 }
}

infer: false — bypasses LLM extraction entirely and stores the text verbatim. Use for pre-summarised chunks where you want exact content preserved:

{
  "text": "MIDI SysEx messages use a 7-bit checksum.",
  "user_id": "knowledge_base",
  "infer": false,
  "metadata": { "source_file": "midi_spec.pdf", "chapter": 9, "page": 112 }
}

POST /knowledge/search

POST /knowledge/recent

DELETE /knowledge

Same request/response shape as their /memories counterparts.


POST /search — merged search (both collections)

Queries both collections simultaneously, tags each result with _source, then runs a single rerank pass over the merged pool. Intended for OpenClaw's autorecall webhook when it should draw on both conversational memory and ingested knowledge.

{ "query": "Gesell economic theory", "user_id": "knowledge_base", "limit": 8 }

Each result includes "_source": "conversational" or "_source": "knowledge".


Reranker contract

The server expects a reranker at RERANKER_URL accepting:

{
  "query": "...",
  "documents": ["doc1", "doc2"],
  "top_k": 5
}

And returning:

{
  "results": [
    { "text": "doc1", "score": 0.99 },
    { "text": "doc2", "score": 0.87 }
  ]
}

If the reranker is unreachable the server falls back gracefully to raw mem0 results — no crash, no error returned to the caller.


Resetting a collection

reset_memory.py deletes and recreates a collection directly via the Chroma HTTP API, then restarts the mem0server container so it re-acquires the fresh collection object:

python reset_memory.py openclaw_mem   # wipe conversational
python reset_memory.py knowledge_mem  # wipe knowledge
# reset_memory.py
import sys, subprocess, requests

base = "http://192.168.0.200:8001/api/v1"
name = sys.argv[1]
requests.delete(f"{base}/collections/{name}")
requests.post(f"{base}/collections", json={"name": name})
print(f"collection reset: {name}")
subprocess.run(["docker", "compose", "restart", "mem0server"])
print("mem0server restarted")

Testing

tests.sh exercises every endpoint. Run it after any server change:

bash tests.sh

Key assertions to eyeball manually:

  • /knowledge/search query "Gesell demurrage" → Gesell result ranked #1, MIDI result near the bottom
  • /knowledge/search query "free money currency hoarding" → Gesell rerank_score should be > 0.9, MIDI < 0.001
  • /search (merged) results should include _source field on every item
  • infer: false response body should show the text stored verbatim, not an LLM-rewritten version