Skip to content
Open
1 change: 0 additions & 1 deletion src/memos/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
MAX_RETRY_COUNT = 3



class MemOSClient:
"""MemOS API client"""

Expand Down
4 changes: 2 additions & 2 deletions src/memos/api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,8 @@ def get_nebular_config(user_id: str | None = None) -> dict[str, Any]:
"user": os.getenv("NEBULAR_USER", "root"),
"password": os.getenv("NEBULAR_PASSWORD", "xxxxxx"),
"space": os.getenv("NEBULAR_SPACE", "shared-tree-textual-memory"),
"user_name": f"memos{user_id.replace('-', '')}",
"use_multi_db": False,
# "user_name": f"memos{user_id.replace('-', '')}",
# "use_multi_db": False,
"auto_create": True,
"embedding_dimension": int(os.getenv("EMBEDDING_DIMENSION", 3072)),
}
Expand Down
2 changes: 1 addition & 1 deletion src/memos/api/product_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@

parser = argparse.ArgumentParser()
parser.add_argument("--port", type=int, default=8001)
parser.add_argument("--workers", type=int, default=32)
parser.add_argument("--workers", type=int, default=1)
args = parser.parse_args()
uvicorn.run("memos.api.product_api:app", host="0.0.0.0", port=args.port, workers=args.workers)
5 changes: 3 additions & 2 deletions src/memos/api/product_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,16 @@ class MemoryCreateRequest(BaseRequest):
mem_cube_id: str | None = Field(None, description="Cube ID")
source: str | None = Field(None, description="Source of the memory")
user_profile: bool = Field(False, description="User profile memory")
session_id: str | None = Field(None, description="Session id")
session_id: str | None = Field(
default_factory=lambda: str(uuid.uuid4()), description="Session id"
)


class SearchRequest(BaseRequest):
"""Request model for searching memories."""

user_id: str = Field(..., description="User ID")
query: str = Field(..., description="Search query")
mem_cube_id: str | None = Field(None, description="Cube ID to search in")
top_k: int = Field(10, description="Number of results to return")
session_id: str | None = Field(None, description="Session ID for soft-filtering memories")

Expand Down
137 changes: 137 additions & 0 deletions src/memos/api/routers/server_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import json
import os
import time
from fastapi import APIRouter
from memos import log
from memos.api.product_models import (
MemoryCreateRequest,
SearchRequest,
SearchResponse,
MemoryResponse,
)
from memos.chunkers.sentence_chunker import SentenceChunker
from memos.configs.chunker import SentenceChunkerConfig
from memos.configs.embedder import UniversalAPIEmbedderConfig
from memos.configs.graph_db import NebulaGraphDBConfig
from memos.configs.llm import OpenAILLMConfig
from memos.embedders.universal_api import UniversalAPIEmbedder
from memos.graph_dbs.nebular import NebulaGraphDB
from memos.llms.openai import OpenAILLM
from memos.mem_reader.simple_struct import SimpleStructMemReader
from memos.memories.textual.tree_text_memory.organize.manager import MemoryManager
from memos.memories.textual.tree_text_memory.retrieve.searcher import Searcher
from memos.reranker.cosine_local import CosineLocalReranker

logger = log.get_logger(__name__)
router = APIRouter()


def init_model():
llm = OpenAILLM(
OpenAILLMConfig(
model_schema="memos.configs.llm.OpenAILLMConfig",
model_name_or_path="gpt-4o-mini",
temperature=0.8,
max_tokens=4096,
top_p=0.9,
top_k=50,
remove_think_prefix=True,
api_key=os.getenv("OPENAI_API_KEY"),
api_base=os.getenv("OPENAI_API_BASE"),
extra_body=None,
)
)
embedder = UniversalAPIEmbedder(
UniversalAPIEmbedderConfig(
model_schema="memos.configs.embedder.UniversalAPIEmbedderConfig",
model_name_or_path="bge-m3",
embedding_dims=None,
provider="openai",
api_key="EMPTY",
base_url=os.getenv("MOS_EMBEDDER_API_BASE"),
)
)

reranker = CosineLocalReranker(
level_weights={"topic": 1.0, "concept": 1.0, "fact": 1.0}, level_field="background"
)

graph_store = NebulaGraphDB(
NebulaGraphDBConfig(
model_schema="memos.configs.graph_db.NebulaGraphDBConfig",
uri=json.loads(os.getenv("NEBULAR_HOSTS")),
user=os.getenv("NEBULAR_USER"),
password=os.getenv("NEBULAR_PASSWORD"),
space=os.getenv("NEBULAR_SPACE"),
auto_create=True,
max_client=1000,
embedding_dimension=1024,
)
)
search_obj = Searcher(
llm, graph_store, embedder, reranker, internet_retriever=None, moscube=False
)
chunker = SentenceChunker(
SentenceChunkerConfig(
model_schema="memos.configs.chunker.SentenceChunkerConfig",
tokenizer_or_token_counter="gpt2",
chunk_size=512,
chunk_overlap=128,
min_sentences_per_chunk=1,
)
)
mem_reader = SimpleStructMemReader(llm, embedder, chunker)
memory_add_obj = MemoryManager(
graph_store,
embedder,
llm,
memory_size={"WorkingMemory": 20, "LongTermMemory": 1500, "UserMemory": 480},
is_reorganize=False,
)

return search_obj, memory_add_obj, mem_reader


search_obj, memory_add_obj, mem_reader = init_model()


@router.post("/search", summary="Search memories", response_model=SearchResponse)
def search_memories(search_req: SearchRequest):
"""Search memories for a specific user."""
# try:
# user_id = f"memos{search_req.user_id.replace('-', '')}"
user_id = search_req.user_id
res = search_obj.search(
query=search_req.query,
user_id=user_id,
top_k=search_req.top_k,
mode="fast",
search_filter=None,
info={"user_id": user_id, "session_id": "root_session", "chat_history": []},
)
res = {"d": res}
# print(res)
return SearchResponse(message="Search completed successfully", data=res)


@router.post("/add", summary="add memories", response_model=MemoryResponse)
def add_memories(add_req: MemoryCreateRequest):
"""Add memories for a specific user."""
time_start = time.time()
memories = mem_reader.get_memory(
[add_req.messages],
type="chat",
info={"user_id": add_req.user_id, "session_id": add_req.session_id},
)
memories = [mm for m in memories for mm in m]
logger.info(
f"time add: get mem_reader time user_id: {add_req.user_id} time is: {time.time() - time_start:.2f}s"
)
mem_id_list: list[str] = memory_add_obj.add(memories, user_name=add_req.user_id)
logger.info(
f"Added memory for user {add_req.user_id} in session {add_req.session_id}: {mem_id_list}"
)
data = []
for m_id, m in zip(mem_id_list, memories):
data.append({"memory": m.memory, "memory_id": m_id, "memory_type": m.metadata.memory_type})
return MemoryResponse(message="Memory added successfully", data=data)
38 changes: 38 additions & 0 deletions src/memos/api/server_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import logging

from fastapi import FastAPI

from memos.api.exceptions import APIExceptionHandler
from memos.api.middleware.request_context import RequestContextMiddleware
from memos.api.routers.server_router import router as server_router


# Configure logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)

app = FastAPI(
title="MemOS Product REST APIs",
description="A REST API for managing multiple users with MemOS Product.",
version="1.0.1",
)

app.add_middleware(RequestContextMiddleware)
# Include routers
app.include_router(server_router)

# Exception handlers
app.exception_handler(ValueError)(APIExceptionHandler.value_error_handler)
app.exception_handler(Exception)(APIExceptionHandler.global_exception_handler)


if __name__ == "__main__":
import argparse

import uvicorn

parser = argparse.ArgumentParser()
parser.add_argument("--port", type=int, default=8001)
parser.add_argument("--workers", type=int, default=1)
args = parser.parse_args()
uvicorn.run("memos.api.server_api:app", host="0.0.0.0", port=args.port, workers=args.workers)
12 changes: 0 additions & 12 deletions src/memos/configs/graph_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,22 +124,10 @@ class NebulaGraphDBConfig(BaseGraphDBConfig):
space: str = Field(
..., description="The name of the target NebulaGraph space (like a database)"
)
user_name: str | None = Field(
default=None,
description="Logical user or tenant ID for data isolation (optional, used in metadata tagging)",
)
auto_create: bool = Field(
default=False,
description="Whether to auto-create the space if it does not exist",
)
use_multi_db: bool = Field(
default=True,
description=(
"If True: use Neo4j's multi-database feature for physical isolation; "
"each user typically gets a separate database. "
"If False: use a single shared database with logical isolation by user_name."
),
)
max_client: int = Field(
default=1000,
description=("max_client"),
Expand Down
Loading
Loading