diff --git a/docs/open-source/features/rest-api.mdx b/docs/open-source/features/rest-api.mdx
index 79d2b349..4198bcac 100644
--- a/docs/open-source/features/rest-api.mdx
+++ b/docs/open-source/features/rest-api.mdx
@@ -7,7 +7,7 @@ iconType: "solid"
Mem0 provides a REST API server (written using FastAPI). Users can perform all operations through REST endpoints. The API also includes OpenAPI documentation, accessible at `/docs` when the server is running.
-
+
## Features
@@ -23,67 +23,90 @@ Mem0 provides a REST API server (written using FastAPI). Users can perform all o
## Running Locally
-
+
+ The Development Docker Compose comes pre-configured with postgres pgvector, neo4j and a `server/history/history.db` volume for the history database.
-1. Create a `.env` file in the current directory and set your environment variables. For example:
+ The only required environment variable to run the server is `OPENAI_API_KEY`.
-```txt
-OPENAI_API_KEY=your-openai-api-key
-```
+ 1. Create a `.env` file in the `server/` directory and set your environment variables. For example:
-2. Either pull the docker image from docker hub or build the docker image locally.
+ ```txt
+ OPENAI_API_KEY=your-openai-api-key
+ ```
-
-
+ 2. Run the Docker container using Docker Compose:
-```bash
-docker pull mem0/mem0-api-server
-```
+ ```bash
+ cd server
+ docker compose up
+ ```
-
+ 3. Access the API at http://localhost:8888.
-
+ 4. Making changes to the server code or the library code will automatically reload the server.
+
-```bash
-docker build -t mem0-api-server .
-```
+
-
-
+ 1. Create a `.env` file in the current directory and set your environment variables. For example:
-3. Run the Docker container:
+ ```txt
+ OPENAI_API_KEY=your-openai-api-key
+ ```
-``` bash
-docker run -p 8000:8000 mem0-api-server --env-file .env
-```
+ 2. Either pull the docker image from docker hub or build the docker image locally.
-4. Access the API at http://localhost:8000.
+
+
-
+ ```bash
+ docker pull mem0/mem0-api-server
+ ```
-
+
-1. Create a `.env` file in the current directory and set your environment variables. For example:
+
-```txt
-OPENAI_API_KEY=your-openai-api-key
-```
+ ```bash
+ docker build -t mem0-api-server .
+ ```
-2. Install dependencies:
+
+
-```bash
-pip install -r requirements.txt
-```
+ 3. Run the Docker container:
-3. Start the FastAPI server:
+ ``` bash
+ docker run -p 8000:8000 mem0-api-server --env-file .env
+ ```
-```bash
-uvicorn main:app --reload
-```
+ 4. Access the API at http://localhost:8000.
-4. Access the API at http://localhost:8000.
+
-
+
+
+ 1. Create a `.env` file in the current directory and set your environment variables. For example:
+
+ ```txt
+ OPENAI_API_KEY=your-openai-api-key
+ ```
+
+ 2. Install dependencies:
+
+ ```bash
+ pip install -r requirements.txt
+ ```
+
+ 3. Start the FastAPI server:
+
+ ```bash
+ uvicorn main:app --reload
+ ```
+
+ 4. Access the API at http://localhost:8000.
+
+
## Usage
diff --git a/mem0/memory/graph_memory.py b/mem0/memory/graph_memory.py
index 7d412281..30de95b3 100644
--- a/mem0/memory/graph_memory.py
+++ b/mem0/memory/graph_memory.py
@@ -167,7 +167,7 @@ class MemoryGraph:
for item in search_results["tool_calls"][0]["arguments"]["entities"]:
entity_type_map[item["entity"]] = item["entity_type"]
except Exception as e:
- logger.error(f"Error in search tool: {e}")
+ logger.exception(f"Error in search tool: {e}, llm_provider={self.llm_provider}, search_results={search_results}")
entity_type_map = {k.lower().replace(" ", "_"): v.lower().replace(" ", "_") for k, v in entity_type_map.items()}
logger.debug(f"Entity type map: {entity_type_map}")
@@ -203,14 +203,13 @@ class MemoryGraph:
tools=_tools,
)
+ entities = []
if extracted_entities["tool_calls"]:
- extracted_entities = extracted_entities["tool_calls"][0]["arguments"]["entities"]
- else:
- extracted_entities = []
+ entities = extracted_entities["tool_calls"][0]["arguments"]["entities"]
- extracted_entities = self._remove_spaces_from_entities(extracted_entities)
- logger.debug(f"Extracted entities: {extracted_entities}")
- return extracted_entities
+ entities = self._remove_spaces_from_entities(entities)
+ logger.debug(f"Extracted entities: {entities}")
+ return entities
def _search_graph_db(self, node_list, filters, limit=100):
"""Search similar nodes among and their respective incoming and outgoing relations."""
@@ -347,14 +346,9 @@ class MemoryGraph:
params = {
"source_id": source_node_search_result[0]["elementId(source_candidate)"],
"destination_name": destination,
- "relationship": relationship,
- "destination_type": destination_type,
"destination_embedding": dest_embedding,
"user_id": user_id,
}
- resp = self.graph.query(cypher, params=params)
- results.append(resp)
-
elif destination_node_search_result and not source_node_search_result:
cypher = f"""
MATCH (destination)
@@ -372,14 +366,9 @@ class MemoryGraph:
params = {
"destination_id": destination_node_search_result[0]["elementId(destination_candidate)"],
"source_name": source,
- "relationship": relationship,
- "source_type": source_type,
"source_embedding": source_embedding,
"user_id": user_id,
}
- resp = self.graph.query(cypher, params=params)
- results.append(resp)
-
elif source_node_search_result and destination_node_search_result:
cypher = f"""
MATCH (source)
@@ -396,12 +385,8 @@ class MemoryGraph:
"source_id": source_node_search_result[0]["elementId(source_candidate)"],
"destination_id": destination_node_search_result[0]["elementId(destination_candidate)"],
"user_id": user_id,
- "relationship": relationship,
}
- resp = self.graph.query(cypher, params=params)
- results.append(resp)
-
- elif not source_node_search_result and not destination_node_search_result:
+ else:
cypher = f"""
MERGE (n:{source_type} {{name: $source_name, user_id: $user_id}})
ON CREATE SET n.created = timestamp(), n.embedding = $source_embedding
@@ -415,15 +400,13 @@ class MemoryGraph:
"""
params = {
"source_name": source,
- "source_type": source_type,
"dest_name": destination,
- "destination_type": destination_type,
"source_embedding": source_embedding,
"dest_embedding": dest_embedding,
"user_id": user_id,
}
- resp = self.graph.query(cypher, params=params)
- results.append(resp)
+ result = self.graph.query(cypher, params=params)
+ results.append(result)
return results
def _remove_spaces_from_entities(self, entity_list):
diff --git a/mem0/vector_stores/pgvector.py b/mem0/vector_stores/pgvector.py
index 095bff22..3997f0af 100644
--- a/mem0/vector_stores/pgvector.py
+++ b/mem0/vector_stores/pgvector.py
@@ -67,6 +67,7 @@ class PGVector(VectorStoreBase):
Args:
embedding_model_dims (int): Dimension of the embedding vector.
"""
+ self.cur.execute("CREATE EXTENSION IF NOT EXISTS vector")
self.cur.execute(
f"""
CREATE TABLE IF NOT EXISTS {self.collection_name} (
diff --git a/server/.env.example b/server/.env.example
new file mode 100644
index 00000000..30c0a48a
--- /dev/null
+++ b/server/.env.example
@@ -0,0 +1,12 @@
+OPENAI_API_KEY=
+NEO4J_URI=
+NEO4J_USERNAME=
+NEO4J_PASSWORD=
+
+
+POSTGRES_HOST=
+POSTGRES_PORT=
+POSTGRES_DB=
+POSTGRES_USER=
+POSTGRES_PASSWORD=
+POSTGRES_COLLECTION_NAME=
diff --git a/server/dev.Dockerfile b/server/dev.Dockerfile
new file mode 100644
index 00000000..852b52c1
--- /dev/null
+++ b/server/dev.Dockerfile
@@ -0,0 +1,25 @@
+FROM python:3.12
+
+WORKDIR /app
+
+# Install Poetry
+RUN curl -sSL https://install.python-poetry.org | python3 -
+ENV PATH="/root/.local/bin:$PATH"
+
+# Copy requirements first for better caching
+COPY server/requirements.txt .
+RUN pip install -r requirements.txt
+
+# Install mem0 in editable mode using Poetry
+WORKDIR /app/packages
+COPY pyproject.toml .
+COPY poetry.lock .
+COPY README.md .
+COPY mem0 ./mem0
+RUN pip install -e .[graph]
+
+# Return to app directory and copy server code
+WORKDIR /app
+COPY server .
+
+CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
diff --git a/server/docker-compose.yaml b/server/docker-compose.yaml
new file mode 100644
index 00000000..1c731afa
--- /dev/null
+++ b/server/docker-compose.yaml
@@ -0,0 +1,74 @@
+name: mem0-dev
+
+services:
+ mem0:
+ build:
+ context: .. # Set context to parent directory
+ dockerfile: server/dev.Dockerfile
+ ports:
+ - "8888:8000"
+ env_file:
+ - .env
+ networks:
+ - mem0_network
+ volumes:
+ - ./history:/app/history # History db location. By default, it creates a history.db file on the server folder
+ - .:/app # Server code. This allows to reload the app when the server code is updated
+ - ../mem0:/app/packages/mem0 # Mem0 library. This allows to reload the app when the library code is updated
+ depends_on:
+ postgres:
+ condition: service_healthy
+ neo4j:
+ condition: service_healthy
+ command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload # Enable auto-reload
+ environment:
+ - PYTHONDONTWRITEBYTECODE=1 # Prevents Python from writing .pyc files
+ - PYTHONUNBUFFERED=1 # Ensures Python output is sent straight to terminal
+
+ postgres:
+ image: ankane/pgvector:v0.5.1
+ restart: on-failure
+ shm_size: "128mb" # Increase this if vacuuming fails with a "no space left on device" error
+ networks:
+ - mem0_network
+ environment:
+ - POSTGRES_USER=postgres
+ - POSTGRES_PASSWORD=postgres
+ healthcheck:
+ test: ["CMD", "pg_isready", "-q", "-d", "postgres", "-U", "postgres"]
+ interval: 5s
+ timeout: 5s
+ retries: 5
+ volumes:
+ - postgres_db:/var/lib/postgresql/data
+ ports:
+ - "8432:5432"
+ neo4j:
+ image: neo4j:5.26.4
+ networks:
+ - mem0_network
+ healthcheck:
+ test: wget http://localhost:7687 || exit 1
+ interval: 1s
+ timeout: 10s
+ retries: 20
+ start_period: 3s
+ ports:
+ - "8474:7474" # HTTP
+ - "8687:7687" # Bolt
+ volumes:
+ - neo4j_data:/data
+ environment:
+ - NEO4J_AUTH=neo4j/mem0graph
+ - NEO4J_PLUGINS=["apoc"] # Add this line to install APOC
+ - NEO4J_apoc_export_file_enabled=true
+ - NEO4J_apoc_import_file_enabled=true
+ - NEO4J_apoc_import_file_use__neo4j__config=true
+
+volumes:
+ neo4j_data:
+ postgres_db:
+
+networks:
+ mem0_network:
+ driver: bridge
\ No newline at end of file
diff --git a/server/main.py b/server/main.py
index af3ce764..165c1af2 100644
--- a/server/main.py
+++ b/server/main.py
@@ -6,10 +6,69 @@ from typing import Optional, List, Any, Dict
from mem0 import Memory
from dotenv import load_dotenv
+import logging
+
+logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
+
# Load environment variables
load_dotenv()
-MEMORY_INSTANCE = Memory()
+
+POSTGRES_HOST = os.environ.get("POSTGRES_HOST", "postgres")
+POSTGRES_PORT = os.environ.get("POSTGRES_PORT", "5432")
+POSTGRES_DB = os.environ.get("POSTGRES_DB", "postgres")
+POSTGRES_USER = os.environ.get("POSTGRES_USER", "postgres")
+POSTGRES_PASSWORD = os.environ.get("POSTGRES_PASSWORD", "postgres")
+POSTGRES_COLLECTION_NAME = os.environ.get("POSTGRES_COLLECTION_NAME", "memories")
+
+NEO4J_URI = os.environ.get("NEO4J_URI", "bolt://neo4j:7687")
+NEO4J_USERNAME = os.environ.get("NEO4J_USERNAME", "neo4j")
+NEO4J_PASSWORD = os.environ.get("NEO4J_PASSWORD", "mem0graph")
+
+OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
+HISTORY_DB_PATH = os.environ.get("HISTORY_DB_PATH", "/app/history/history.db")
+
+DEFAULT_CONFIG = {
+ "version": "v1.1",
+ "vector_store": {
+ "provider": "pgvector",
+ "config": {
+ "host": POSTGRES_HOST,
+ "port": int(POSTGRES_PORT),
+ "dbname": POSTGRES_DB,
+ "user": POSTGRES_USER,
+ "password": POSTGRES_PASSWORD,
+ "collection_name": POSTGRES_COLLECTION_NAME,
+ }
+ },
+ "graph_store": {
+ "provider": "neo4j",
+ "config": {
+ "url": NEO4J_URI,
+ "username": NEO4J_USERNAME,
+ "password": NEO4J_PASSWORD
+ }
+ },
+ "llm": {
+ "provider": "openai",
+ "config": {
+ "api_key": OPENAI_API_KEY,
+ "temperature": 0.2,
+ "model": "gpt-4o"
+ }
+ },
+ "embedder": {
+ "provider": "openai",
+ "config": {
+ "api_key": OPENAI_API_KEY,
+ "model": "text-embedding-3-small"
+ }
+ },
+ "history_db_path": HISTORY_DB_PATH,
+}
+
+
+MEMORY_INSTANCE = Memory.from_config(DEFAULT_CONFIG)
app = FastAPI(
title="Mem0 REST APIs",
@@ -36,6 +95,7 @@ class SearchRequest(BaseModel):
user_id: Optional[str] = None
run_id: Optional[str] = None
agent_id: Optional[str] = None
+ filters: Optional[Dict[str, Any]] = None
@app.post("/configure", summary="Configure Mem0")
@@ -59,6 +119,7 @@ def add_memory(memory_create: MemoryCreate):
response = MEMORY_INSTANCE.add(messages=[m.model_dump() for m in memory_create.messages], **params)
return JSONResponse(content=response)
except Exception as e:
+ logging.exception("Error in add_memory:") # This will log the full traceback
raise HTTPException(status_code=500, detail=str(e))
@@ -75,6 +136,7 @@ def get_all_memories(
params = {k: v for k, v in {"user_id": user_id, "run_id": run_id, "agent_id": agent_id}.items() if v is not None}
return MEMORY_INSTANCE.get_all(**params)
except Exception as e:
+ logging.exception("Error in get_all_memories:")
raise HTTPException(status_code=500, detail=str(e))
@@ -84,6 +146,7 @@ def get_memory(memory_id: str):
try:
return MEMORY_INSTANCE.get(memory_id)
except Exception as e:
+ logging.exception("Error in get_memory:")
raise HTTPException(status_code=500, detail=str(e))
@@ -94,6 +157,7 @@ def search_memories(search_req: SearchRequest):
params = {k: v for k, v in search_req.model_dump().items() if v is not None and k != "query"}
return MEMORY_INSTANCE.search(query=search_req.query, **params)
except Exception as e:
+ logging.exception("Error in search_memories:")
raise HTTPException(status_code=500, detail=str(e))
@@ -103,6 +167,7 @@ def update_memory(memory_id: str, updated_memory: Dict[str, Any]):
try:
return MEMORY_INSTANCE.update(memory_id=memory_id, data=updated_memory)
except Exception as e:
+ logging.exception("Error in update_memory:")
raise HTTPException(status_code=500, detail=str(e))
@@ -112,6 +177,7 @@ def memory_history(memory_id: str):
try:
return MEMORY_INSTANCE.history(memory_id=memory_id)
except Exception as e:
+ logging.exception("Error in memory_history:")
raise HTTPException(status_code=500, detail=str(e))
@@ -122,6 +188,7 @@ def delete_memory(memory_id: str):
MEMORY_INSTANCE.delete(memory_id=memory_id)
return {"message": "Memory deleted successfully"}
except Exception as e:
+ logging.exception("Error in delete_memory:")
raise HTTPException(status_code=500, detail=str(e))
@@ -139,6 +206,7 @@ def delete_all_memories(
MEMORY_INSTANCE.delete_all(**params)
return {"message": "All relevant memories deleted"}
except Exception as e:
+ logging.exception("Error in delete_all_memories:")
raise HTTPException(status_code=500, detail=str(e))
@@ -149,6 +217,7 @@ def reset_memory():
MEMORY_INSTANCE.reset()
return {"message": "All memories reset"}
except Exception as e:
+ logging.exception("Error in reset_memory:")
raise HTTPException(status_code=500, detail=str(e))