diff --git a/docker/.env.example b/docker/.env.example new file mode 100644 index 000000000..33f7ae853 --- /dev/null +++ b/docker/.env.example @@ -0,0 +1,29 @@ +# MemOS Environment Variables Configuration + +# Path to memory storage (e.g. /tmp/data_test) +MOS_CUBE_PATH= + +# OpenAI Configuration +OPENAI_API_KEY= # Your OpenAI API key +OPENAI_API_BASE= # OpenAI API base URL (default: https://api.openai.com/v1) + +# MemOS Feature Toggles +MOS_ENABLE_DEFAULT_CUBE_CONFIG= # Enable default cube config (true/false) +MOS_ENABLE_SCHEDULER= # Enable background scheduler (true/false) + +# Neo4j Configuration +NEO4J_URI= # Neo4j connection URI (e.g. bolt://localhost:7687) +NEO4J_USER= # Neo4j username +NEO4J_PASSWORD= # Neo4j password +MOS_NEO4J_SHARED_DB= # Shared Neo4j database name (if using multi-db) + +# MemOS User Configuration +MOS_USER_ID= # Unique user ID +MOS_SESSION_ID= # Session ID for current chat +MOS_MAX_TURNS_WINDOW= # Max number of turns to keep in memory + +# Ollama Configuration (for local embedding models) +OLLAMA_API_BASE= # Ollama API base URL (e.g. http://localhost:11434) + +# Embedding Configuration +MOS_EMBEDDER_BACKEND= # Embedding backend: openai, ollama, etc. diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 000000000..ea158b111 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,25 @@ +FROM python:3.11-slim + +RUN apt-get update && apt-get install -y \ + gcc \ + g++ \ + build-essential \ + libffi-dev \ + python3-dev \ + curl \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +ENV HF_ENDPOINT=https://hf-mirror.com + +COPY requirements.txt . +RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt + +RUN pip install chonkie + +COPY ../. . +ENV PYTHONPATH=/app/src + +EXPOSE 8000 +CMD ["uvicorn", "memos.api.product_api:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 000000000..64d383a25 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,65 @@ +name: memos-dev + +services: + memos: + container_name: memos-api-server + build: + context: .. + dockerfile: Dockerfile + ports: + - "8000:8000" + env_file: + - ../.env + depends_on: + - neo4j + - qdrant + environment: + - PYTHONPATH=/app/src + volumes: + - .:/app + networks: + - memos_network + + neo4j: + image: neo4j:5.26.4 + container_name: neo4j-server + ports: + - "7474:7474" # HTTP + - "7687:7687" # Bolt + healthcheck: + test: wget http://localhost:7687 || exit 1 + interval: 1s + timeout: 10s + retries: 20 + start_period: 3s + environment: + NEO4J_ACCEPT_LICENSE_AGREEMENT: "yes" + NEO4J_AUTH: "neo4j/12345678" + volumes: + - neo4j_data:/data + - neo4j_logs:/logs + networks: + - memos_network + + qdrant: + image: qdrant/qdrant:v1.15.0 + container_name: qdrant-server + ports: + - "6333:6333" # REST API + - "6334:6334" # gRPC API + volumes: + - ./qdrant_data:/qdrant/storage + environment: + QDRANT__SERVICE__GRPC_PORT: 6334 + QDRANT__SERVICE__HTTP_PORT: 6333 + restart: unless-stopped + networks: + - memos_network + +volumes: + neo4j_data: + neo4j_logs: + +networks: + memos_network: + driver: bridge diff --git a/docker/requirements.txt b/docker/requirements.txt new file mode 100644 index 000000000..596e94b47 --- /dev/null +++ b/docker/requirements.txt @@ -0,0 +1,92 @@ +annotated-types==0.7.0 ; python_version >= "3.10" and python_version < "4.0" +anyio==4.9.0 ; python_version >= "3.10" and python_version < "4.0" +attrs==25.3.0 ; python_version >= "3.10" and python_version < "4.0" +authlib==1.6.0 ; python_version >= "3.10" and python_version < "4.0" +certifi==2025.7.14 ; python_version >= "3.10" and python_version < "4.0" +cffi==1.17.1 ; python_version >= "3.10" and python_version < "4.0" and platform_python_implementation != "PyPy" +charset-normalizer==3.4.2 ; python_version >= "3.10" and python_version < "4.0" +click==8.2.1 ; python_version >= "3.10" and python_version < "4.0" +colorama==0.4.6 ; python_version >= "3.10" and python_version < "4.0" and (platform_system == "Windows" or sys_platform == "win32") +cryptography==45.0.5 ; python_version >= "3.10" and python_version < "4.0" +cyclopts==3.22.2 ; python_version >= "3.10" and python_version < "4.0" +distro==1.9.0 ; python_version >= "3.10" and python_version < "4.0" +dnspython==2.7.0 ; python_version >= "3.10" and python_version < "4.0" +docstring-parser==0.16 ; python_version >= "3.10" and python_version < "4.0" +docutils==0.21.2 ; python_version >= "3.10" and python_version < "4.0" +email-validator==2.2.0 ; python_version >= "3.10" and python_version < "4.0" +exceptiongroup==1.3.0 ; python_version >= "3.10" and python_version < "4.0" +fastapi-cli==0.0.8 ; python_version >= "3.10" and python_version < "4.0" +fastapi-cloud-cli==0.1.4 ; python_version >= "3.10" and python_version < "4.0" +fastapi==0.115.14 ; python_version >= "3.10" and python_version < "4.0" +fastmcp==2.10.5 ; python_version >= "3.10" and python_version < "4.0" +filelock==3.18.0 ; python_version >= "3.10" and python_version < "4.0" +fsspec==2025.7.0 ; python_version >= "3.10" and python_version < "4.0" +greenlet==3.2.3 ; python_version >= "3.10" and python_version < "3.14" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") +h11==0.16.0 ; python_version >= "3.10" and python_version < "4.0" +hf-xet==1.1.5 ; python_version >= "3.10" and python_version < "4.0" and (platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "arm64" or platform_machine == "aarch64") +httpcore==1.0.9 ; python_version >= "3.10" and python_version < "4.0" +httptools==0.6.4 ; python_version >= "3.10" and python_version < "4.0" +httpx-sse==0.4.1 ; python_version >= "3.10" and python_version < "4.0" +httpx==0.28.1 ; python_version >= "3.10" and python_version < "4.0" +huggingface-hub==0.33.4 ; python_version >= "3.10" and python_version < "4.0" +idna==3.10 ; python_version >= "3.10" and python_version < "4.0" +itsdangerous==2.2.0 ; python_version >= "3.10" and python_version < "4.0" +jinja2==3.1.6 ; python_version >= "3.10" and python_version < "4.0" +jiter==0.10.0 ; python_version >= "3.10" and python_version < "4.0" +joblib==1.5.1 ; python_version >= "3.10" and python_version < "4.0" +jsonschema-specifications==2025.4.1 ; python_version >= "3.10" and python_version < "4.0" +jsonschema==4.24.1 ; python_version >= "3.10" and python_version < "4.0" +markdown-it-py==3.0.0 ; python_version >= "3.10" and python_version < "4.0" +markupsafe==3.0.2 ; python_version >= "3.10" and python_version < "4.0" +mcp==1.12.0 ; python_version >= "3.10" and python_version < "4.0" +mdurl==0.1.2 ; python_version >= "3.10" and python_version < "4.0" +numpy==2.2.6 ; python_version == "3.10" +numpy==2.3.1 ; python_version >= "3.11" and python_version < "4.0" +ollama==0.4.9 ; python_version >= "3.10" and python_version < "4.0" +openai==1.97.0 ; python_version >= "3.10" and python_version < "4.0" +openapi-pydantic==0.5.1 ; python_version >= "3.10" and python_version < "4.0" +orjson==3.11.0 ; python_version >= "3.10" and python_version < "4.0" +packaging==25.0 ; python_version >= "3.10" and python_version < "4.0" +pycparser==2.22 ; python_version >= "3.10" and python_version < "4.0" and platform_python_implementation != "PyPy" +pydantic-core==2.33.2 ; python_version >= "3.10" and python_version < "4.0" +pydantic-extra-types==2.10.5 ; python_version >= "3.10" and python_version < "4.0" +pydantic-settings==2.10.1 ; python_version >= "3.10" and python_version < "4.0" +pydantic==2.11.7 ; python_version >= "3.10" and python_version < "4.0" +pygments==2.19.2 ; python_version >= "3.10" and python_version < "4.0" +pyperclip==1.9.0 ; python_version >= "3.10" and python_version < "4.0" +python-dotenv==1.1.1 ; python_version >= "3.10" and python_version < "4.0" +python-multipart==0.0.20 ; python_version >= "3.10" and python_version < "4.0" +pywin32==311 ; python_version >= "3.10" and python_version < "4.0" and (platform_system == "Windows" or sys_platform == "win32") +pyyaml==6.0.2 ; python_version >= "3.10" and python_version < "4.0" +referencing==0.36.2 ; python_version >= "3.10" and python_version < "4.0" +regex==2024.11.6 ; python_version >= "3.10" and python_version < "4.0" +requests==2.32.4 ; python_version >= "3.10" and python_version < "4.0" +rich-rst==1.3.1 ; python_version >= "3.10" and python_version < "4.0" +rich-toolkit==0.14.8 ; python_version >= "3.10" and python_version < "4.0" +rich==14.0.0 ; python_version >= "3.10" and python_version < "4.0" +rignore==0.6.2 ; python_version >= "3.10" and python_version < "4.0" +rpds-py==0.26.0 ; python_version >= "3.10" and python_version < "4.0" +safetensors==0.5.3 ; python_version >= "3.10" and python_version < "4.0" +scikit-learn==1.7.0 ; python_version >= "3.10" and python_version < "4.0" +scipy==1.15.3 ; python_version == "3.10" +scipy==1.16.0 ; python_version >= "3.11" and python_version < "4.0" +sentry-sdk==2.33.0 ; python_version >= "3.10" and python_version < "4.0" +shellingham==1.5.4 ; python_version >= "3.10" and python_version < "4.0" +sniffio==1.3.1 ; python_version >= "3.10" and python_version < "4.0" +sqlalchemy==2.0.41 ; python_version >= "3.10" and python_version < "4.0" +sse-starlette==2.4.1 ; python_version >= "3.10" and python_version < "4.0" +starlette==0.46.2 ; python_version >= "3.10" and python_version < "4.0" +tenacity==9.1.2 ; python_version >= "3.10" and python_version < "4.0" +threadpoolctl==3.6.0 ; python_version >= "3.10" and python_version < "4.0" +tokenizers==0.21.2 ; python_version >= "3.10" and python_version < "4.0" +tqdm==4.67.1 ; python_version >= "3.10" and python_version < "4.0" +transformers==4.53.2 ; python_version >= "3.10" and python_version < "4.0" +typer==0.16.0 ; python_version >= "3.10" and python_version < "4.0" +typing-extensions==4.14.1 ; python_version >= "3.10" and python_version < "4.0" +typing-inspection==0.4.1 ; python_version >= "3.10" and python_version < "4.0" +ujson==5.10.0 ; python_version >= "3.10" and python_version < "4.0" +urllib3==2.5.0 ; python_version >= "3.10" and python_version < "4.0" +uvicorn==0.35.0 ; python_version >= "3.10" and python_version < "4.0" +uvloop==0.21.0 ; python_version >= "3.10" and python_version < "4.0" and platform_python_implementation != "PyPy" and sys_platform != "win32" and sys_platform != "cygwin" +watchfiles==1.1.0 ; python_version >= "3.10" and python_version < "4.0" +websockets==15.0.1 ; python_version >= "3.10" and python_version < "4.0" diff --git a/src/memos/api/config.py b/src/memos/api/config.py index feb267ccb..75bec5569 100644 --- a/src/memos/api/config.py +++ b/src/memos/api/config.py @@ -115,6 +115,32 @@ def get_embedder_config() -> dict[str, Any]: }, } + @staticmethod + def get_neo4j_community_config(user_id: str | None = None) -> dict[str, Any]: + """Get Neo4j community configuration.""" + return { + "uri": os.getenv("NEO4J_URI", "bolt://localhost:7687"), + "user": os.getenv("NEO4J_USER", "neo4j"), + "db_name": os.getenv("NEO4J_DB_NAME", "shared-tree-textual-memory"), + "password": os.getenv("NEO4J_PASSWORD", "12345678"), + "user_name": f"memos{user_id.replace('-', '')}", + "auto_create": True, + "use_multi_db": False, + "embedding_dimension": 3072, + "vec_config": { + # Pass nested config to initialize external vector DB + # If you use qdrant, please use Server instead of local mode. + "backend": "qdrant", + "config": { + "collection_name": "neo4j_vec_db", + "vector_dimension": 3072, + "distance_metric": "cosine", + "host": "localhost", + "port": 6333, + }, + }, + } + @staticmethod def get_neo4j_config(user_id: str | None = None) -> dict[str, Any]: """Get Neo4j configuration.""" @@ -274,7 +300,7 @@ def get_start_default_config() -> dict[str, Any]: def create_user_config(user_name: str, user_id: str) -> tuple[MOSConfig, GeneralMemCube]: """Create configuration for a specific user.""" openai_config = APIConfig.get_openai_config() - neo4j_config = APIConfig.get_neo4j_config(user_id) + qwen_config = APIConfig.qwen_config() vllm_config = APIConfig.vllm_config() backend = os.getenv("MOS_CHAT_MODEL_PROVIDER", "openai") @@ -325,29 +351,56 @@ def create_user_config(user_name: str, user_id: str) -> tuple[MOSConfig, General default_config = MOSConfig(**config_dict) - # Create MemCube config - default_cube_config = GeneralMemCubeConfig.model_validate( - { - "user_id": user_id, - "cube_id": f"{user_name}_default_cube", - "text_mem": { - "backend": "tree_text", - "config": { - "extractor_llm": {"backend": "openai", "config": openai_config}, - "dispatcher_llm": {"backend": "openai", "config": openai_config}, - "graph_db": { - "backend": "neo4j", - "config": neo4j_config, + if os.getenv("NEO4J_BACKEND", "neo4j_community").lower() == "neo4j_community": + neo4j_community_config = APIConfig.get_neo4j_community_config(user_id) + # Create MemCube config + default_cube_config = GeneralMemCubeConfig.model_validate( + { + "user_id": user_id, + "cube_id": f"{user_name}_default_cube", + "text_mem": { + "backend": "tree_text", + "config": { + "extractor_llm": {"backend": "openai", "config": openai_config}, + "dispatcher_llm": {"backend": "openai", "config": openai_config}, + "graph_db": { + "backend": "neo4j-community", + "config": neo4j_community_config, + }, + "embedder": APIConfig.get_embedder_config(), }, - "embedder": APIConfig.get_embedder_config(), }, - }, - "act_mem": {} - if os.getenv("ENABLE_ACTIVATION_MEMORY", "false").lower() == "false" - else APIConfig.get_activation_vllm_config(), - "para_mem": {}, - } - ) + "act_mem": {} + if os.getenv("ENABLE_ACTIVATION_MEMORY", "false").lower() == "false" + else APIConfig.get_activation_vllm_config(), + "para_mem": {}, + } + ) + else: + neo4j_config = APIConfig.get_neo4j_config(user_id) + # Create MemCube config + default_cube_config = GeneralMemCubeConfig.model_validate( + { + "user_id": user_id, + "cube_id": f"{user_name}_default_cube", + "text_mem": { + "backend": "tree_text", + "config": { + "extractor_llm": {"backend": "openai", "config": openai_config}, + "dispatcher_llm": {"backend": "openai", "config": openai_config}, + "graph_db": { + "backend": "neo4j", + "config": neo4j_config, + }, + "embedder": APIConfig.get_embedder_config(), + }, + }, + "act_mem": {} + if os.getenv("ENABLE_ACTIVATION_MEMORY", "false").lower() == "false" + else APIConfig.get_activation_vllm_config(), + "para_mem": {}, + } + ) default_mem_cube = GeneralMemCube(default_cube_config) return default_config, default_mem_cube @@ -363,28 +416,56 @@ def get_default_cube_config() -> GeneralMemCubeConfig | None: return None openai_config = APIConfig.get_openai_config() - neo4j_config = APIConfig.get_neo4j_config(user_id="default") - - return GeneralMemCubeConfig.model_validate( - { - "user_id": "default", - "cube_id": "default_cube", - "text_mem": { - "backend": "tree_text", - "config": { - "extractor_llm": {"backend": "openai", "config": openai_config}, - "dispatcher_llm": {"backend": "openai", "config": openai_config}, - "graph_db": { - "backend": "neo4j", - "config": neo4j_config, + + if os.getenv("NEO4J_BACKEND", "neo4j_community").lower() == "neo4j_community": + neo4j_community_config = APIConfig.get_neo4j_community_config(user_id="default") + return GeneralMemCubeConfig.model_validate( + { + "user_id": "default", + "cube_id": "default_cube", + "text_mem": { + "backend": "tree_text", + "config": { + "extractor_llm": {"backend": "openai", "config": openai_config}, + "dispatcher_llm": {"backend": "openai", "config": openai_config}, + "graph_db": { + "backend": "neo4j-community", + "config": neo4j_community_config, + }, + "embedder": APIConfig.get_embedder_config(), + "reorganize": os.getenv("MOS_ENABLE_REORGANIZE", "false").lower() + == "true", }, - "embedder": APIConfig.get_embedder_config(), - "reorganize": os.getenv("MOS_ENABLE_REORGANIZE", "false").lower() == "true", }, - }, - "act_mem": {} - if os.getenv("ENABLE_ACTIVATION_MEMORY", "false").lower() == "false" - else APIConfig.get_activation_vllm_config(), - "para_mem": {}, - } - ) + "act_mem": {} + if os.getenv("ENABLE_ACTIVATION_MEMORY", "false").lower() == "false" + else APIConfig.get_activation_vllm_config(), + "para_mem": {}, + } + ) + else: + neo4j_config = APIConfig.get_neo4j_config(user_id="default") + return GeneralMemCubeConfig.model_validate( + { + "user_id": "default", + "cube_id": "default_cube", + "text_mem": { + "backend": "tree_text", + "config": { + "extractor_llm": {"backend": "openai", "config": openai_config}, + "dispatcher_llm": {"backend": "openai", "config": openai_config}, + "graph_db": { + "backend": "neo4j", + "config": neo4j_config, + }, + "embedder": APIConfig.get_embedder_config(), + "reorganize": os.getenv("MOS_ENABLE_REORGANIZE", "false").lower() + == "true", + }, + }, + "act_mem": {} + if os.getenv("ENABLE_ACTIVATION_MEMORY", "false").lower() == "false" + else APIConfig.get_activation_vllm_config(), + "para_mem": {}, + } + )