From c3472c41ec0c6f91bd65f5af04c9ef53d4c7503d Mon Sep 17 00:00:00 2001 From: chentang Date: Thu, 25 Dec 2025 15:45:16 +0800 Subject: [PATCH 01/19] fix: fix bugs when running local queue for memos --- .../task_schedule_modules/dispatcher.py | 2 - .../task_schedule_modules/local_queue.py | 139 +++++++++++++++--- .../task_schedule_modules/redis_queue.py | 95 +++++++----- .../task_schedule_modules/task_queue.py | 23 +-- .../mem_scheduler/utils/status_tracker.py | 20 ++- 5 files changed, 200 insertions(+), 79 deletions(-) diff --git a/src/memos/mem_scheduler/task_schedule_modules/dispatcher.py b/src/memos/mem_scheduler/task_schedule_modules/dispatcher.py index 35df3db64..cdd491183 100644 --- a/src/memos/mem_scheduler/task_schedule_modules/dispatcher.py +++ b/src/memos/mem_scheduler/task_schedule_modules/dispatcher.py @@ -129,8 +129,6 @@ def status_tracker(self) -> TaskStatusTracker | None: try: self._status_tracker = TaskStatusTracker(self.redis) # Propagate to submodules when created lazily - if self.dispatcher: - self.dispatcher.status_tracker = self._status_tracker if self.memos_message_queue: self.memos_message_queue.set_status_tracker(self._status_tracker) except Exception as e: diff --git a/src/memos/mem_scheduler/task_schedule_modules/local_queue.py b/src/memos/mem_scheduler/task_schedule_modules/local_queue.py index 69cfc0af9..72dccfd98 100644 --- a/src/memos/mem_scheduler/task_schedule_modules/local_queue.py +++ b/src/memos/mem_scheduler/task_schedule_modules/local_queue.py @@ -4,9 +4,18 @@ the local memos_message_queue functionality in BaseScheduler. """ +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from collections.abc import Callable + from memos.log import get_logger from memos.mem_scheduler.general_modules.misc import AutoDroppingQueue as Queue from memos.mem_scheduler.schemas.message_schemas import ScheduleMessageItem +from memos.mem_scheduler.schemas.task_schemas import DEFAULT_STREAM_KEY_PREFIX +from memos.mem_scheduler.task_schedule_modules.orchestrator import SchedulerOrchestrator +from memos.mem_scheduler.utils.status_tracker import TaskStatusTracker from memos.mem_scheduler.webservice_modules.redis_service import RedisSchedulerModule @@ -16,26 +25,38 @@ class SchedulerLocalQueue(RedisSchedulerModule): def __init__( self, - maxsize: int, + maxsize: int = 0, + stream_key_prefix: str = DEFAULT_STREAM_KEY_PREFIX, + orchestrator: SchedulerOrchestrator | None = None, + status_tracker: TaskStatusTracker | None = None, ): """ Initialize the SchedulerLocalQueue with a maximum queue size limit. + Arguments match SchedulerRedisQueue for compatibility. Args: - maxsize (int): Maximum number of messages allowed - in each individual queue. - If exceeded, subsequent puts will block - or raise an exception based on `block` parameter. + maxsize (int): Maximum number of messages allowed in each individual queue. + stream_key_prefix (str): Prefix for stream keys (simulated). + orchestrator: SchedulerOrchestrator instance (ignored). + status_tracker: TaskStatusTracker instance (ignored). """ super().__init__() - self.stream_key_prefix = "local_queue" + self.stream_key_prefix = stream_key_prefix or "local_queue" self.max_internal_message_queue_size = maxsize + # Dictionary to hold per-stream queues: key = stream_key, value = Queue[ScheduleMessageItem] self.queue_streams: dict[str, Queue[ScheduleMessageItem]] = {} + + self.orchestrator = orchestrator + self.status_tracker = status_tracker + + self._is_listening = False + self._message_handler: Callable[[ScheduleMessageItem], None] | None = None + logger.info( - f"SchedulerLocalQueue initialized with max_internal_message_queue_size={maxsize}" + f"SchedulerLocalQueue initialized with max_internal_message_queue_size={self.max_internal_message_queue_size}" ) def get_stream_key(self, user_id: str, mem_cube_id: str, task_label: str) -> str: @@ -62,7 +83,7 @@ def put( Exception: Any underlying error during queue.put() operation. """ stream_key = self.get_stream_key( - user_id=message.user_id, mem_cube_id=message.mem_cube_id, task_label=message.task_label + user_id=message.user_id, mem_cube_id=message.mem_cube_id, task_label=message.label ) message.stream_key = stream_key @@ -86,7 +107,7 @@ def get( stream_key: str, block: bool = True, timeout: float | None = None, - batch_size: int | None = None, + batch_size: int | None = 1, ) -> list[ScheduleMessageItem]: if batch_size is not None and batch_size <= 0: logger.warning( @@ -99,47 +120,85 @@ def get( logger.error(f"Stream {stream_key} does not exist when trying to get messages.") return [] + # Ensure we always request a batch so we get a list back + effective_batch_size = batch_size if batch_size is not None else 1 + # Note: Assumes custom Queue implementation supports batch_size parameter res = self.queue_streams[stream_key].get( - block=block, timeout=timeout, batch_size=batch_size + block=block, timeout=timeout, batch_size=effective_batch_size ) logger.debug( f"Retrieved {len(res)} messages from queue '{stream_key}'. Current size: {self.queue_streams[stream_key].qsize()}" ) return res - def get_nowait(self, batch_size: int | None = None) -> list[ScheduleMessageItem]: + def get_nowait(self, stream_key: str, batch_size: int | None = 1) -> list[ScheduleMessageItem]: """ - Non-blocking version of get(). Equivalent to get(block=False, batch_size=batch_size). + Non-blocking version of get(). Equivalent to get(stream_key, block=False, batch_size=batch_size). Returns immediately with available messages or an empty list if queue is empty. Args: + stream_key (str): The stream/queue identifier. batch_size (int | None): Number of messages to retrieve in a batch. If None, retrieves one message. Returns: List[ScheduleMessageItem]: Retrieved messages or empty list if queue is empty. """ - logger.debug(f"get_nowait() called with batch_size: {batch_size}") - return self.get(block=False, batch_size=batch_size) + logger.debug(f"get_nowait() called for {stream_key} with batch_size: {batch_size}") + return self.get(stream_key=stream_key, block=False, batch_size=batch_size) + + def get_messages(self, batch_size: int) -> list[ScheduleMessageItem]: + """ + Get messages from all streams in round-robin or sequential fashion. + Equivalent to SchedulerRedisQueue.get_messages. + """ + messages = [] + # Snapshot keys to avoid runtime modification issues + stream_keys = list(self.queue_streams.keys()) + + # Simple strategy: try to get up to batch_size messages across all streams + # We can just iterate and collect. + + # Calculate how many to get per stream to be fair? + # Or just greedy? Redis implementation uses a complex logic. + # For local, let's keep it simple: just iterate and take what's available (non-blocking) + + for stream_key in stream_keys: + if len(messages) >= batch_size: + break + + needed = batch_size - len(messages) + # Use get_nowait to avoid blocking + fetched = self.get_nowait(stream_key=stream_key, batch_size=needed) + messages.extend(fetched) + + return messages def qsize(self) -> dict: """ Return the current size of all internal queues as a dictionary. Each key is the stream name, and each value is the number of messages in that queue. + Also includes 'total_size'. Returns: Dict[str, int]: Mapping from stream name to current queue size. """ sizes = {stream: queue.qsize() for stream, queue in self.queue_streams.items()} + total_size = sum(sizes.values()) + sizes["total_size"] = total_size logger.debug(f"Current queue sizes: {sizes}") return sizes - def clear(self) -> None: - for queue in self.queue_streams.values(): - queue.clear() + def clear(self, stream_key: str | None = None) -> None: + if stream_key: + if stream_key in self.queue_streams: + self.queue_streams[stream_key].clear() + else: + for queue in self.queue_streams.values(): + queue.clear() @property def unfinished_tasks(self) -> int: @@ -151,6 +210,50 @@ def unfinished_tasks(self) -> int: Returns: int: Sum of all message counts in all internal queues. """ - total = sum(self.qsize().values()) + # qsize() now includes "total_size", so we need to be careful not to double count if we use qsize() values + # But qsize() implementation above sums values from queue_streams, then adds total_size. + # So sum(self.queue_streams.values().qsize()) is safer. + total = sum(queue.qsize() for queue in self.queue_streams.values()) logger.debug(f"Total unfinished tasks across all queues: {total}") return total + + def get_stream_keys(self, stream_key_prefix: str | None = None) -> list[str]: + """ + Return list of active stream keys. + """ + prefix = stream_key_prefix or self.stream_key_prefix + return [k for k in self.queue_streams if k.startswith(prefix)] + + def size(self) -> int: + """ + Total size of all queues. + """ + return sum(q.qsize() for q in self.queue_streams.values()) + + def empty(self) -> bool: + """ + Check if all queues are empty. + """ + return self.size() == 0 + + def full(self) -> bool: + """ + Check if any queue is full (approximate). + """ + if self.max_internal_message_queue_size <= 0: + return False + return any( + q.qsize() >= self.max_internal_message_queue_size for q in self.queue_streams.values() + ) + + def ack_message( + self, + user_id: str, + mem_cube_id: str, + task_label: str, + redis_message_id, + message: ScheduleMessageItem | None, + ) -> None: + """ + Acknowledge a message (no-op for local queue as messages are popped immediately). + """ diff --git a/src/memos/mem_scheduler/task_schedule_modules/redis_queue.py b/src/memos/mem_scheduler/task_schedule_modules/redis_queue.py index 1c57f18f0..c27eb5f50 100644 --- a/src/memos/mem_scheduler/task_schedule_modules/redis_queue.py +++ b/src/memos/mem_scheduler/task_schedule_modules/redis_queue.py @@ -5,7 +5,6 @@ the local memos_message_queue functionality in BaseScheduler. """ -import contextlib import os import re import threading @@ -201,6 +200,20 @@ def _refresh_stream_keys( recent_seconds=DEFAULT_STREAM_RECENT_ACTIVE_SECONDS, now_sec=now_sec, ) + + # Ensure consumer groups for newly discovered active streams + with self._stream_keys_lock: + # Identify keys we haven't seen yet + new_streams = [k for k in active_stream_keys if k not in self.seen_streams] + + # Create groups outside the lock to avoid blocking + for key in new_streams: + self._ensure_consumer_group(key) + + if new_streams: + with self._stream_keys_lock: + self.seen_streams.update(new_streams) + deleted_count = self._delete_streams(keys_to_delete) self._update_stream_cache_with_log( stream_key_prefix=stream_key_prefix, @@ -560,10 +573,7 @@ def _read_new_messages_batch( return {} # Pre-ensure consumer groups to avoid NOGROUP during batch reads - for stream_key in stream_keys: - with contextlib.suppress(Exception): - self._ensure_consumer_group(stream_key=stream_key) - + # (Optimization: rely on put() and _refresh_stream_keys() to ensure groups) pipe = self._redis_conn.pipeline(transaction=False) for stream_key in stream_keys: pipe.xreadgroup( @@ -676,13 +686,6 @@ def _batch_claim_pending_messages( Returns: A list of (stream_key, claimed_entries) pairs for all successful claims. """ - if not self._redis_conn or not claims_spec: - return [] - - # Ensure consumer groups exist to avoid NOGROUP errors during batch claim - for stream_key, _need_count, _label in claims_spec: - with contextlib.suppress(Exception): - self._ensure_consumer_group(stream_key=stream_key) pipe = self._redis_conn.pipeline(transaction=False) for stream_key, need_count, label in claims_spec: @@ -696,26 +699,42 @@ def _batch_claim_pending_messages( justid=False, ) - results = [] try: - results = pipe.execute() - except Exception: - # Fallback: attempt sequential xautoclaim for robustness - for stream_key, need_count, label in claims_spec: - try: - self._ensure_consumer_group(stream_key=stream_key) - res = self._redis_conn.xautoclaim( - name=stream_key, - groupname=self.consumer_group, - consumername=self.consumer_name, - min_idle_time=self.orchestrator.get_task_idle_min(task_label=label), - start_id="0-0", - count=need_count, - justid=False, - ) - results.append(res) - except Exception: - continue + # Execute with raise_on_error=False so we get exceptions in the results list + # instead of aborting the whole batch. + results = pipe.execute(raise_on_error=False) + except Exception as e: + logger.error(f"Pipeline execution critical failure: {e}") + results = [e] * len(claims_spec) + + # Handle individual failures (e.g. NOGROUP) by retrying just that stream + final_results = [] + for i, res in enumerate(results): + if isinstance(res, Exception): + err_msg = str(res).lower() + if "nogroup" in err_msg or "no such key" in err_msg: + stream_key, need_count, label = claims_spec[i] + try: + self._ensure_consumer_group(stream_key=stream_key) + retry_res = self._redis_conn.xautoclaim( + name=stream_key, + groupname=self.consumer_group, + consumername=self.consumer_name, + min_idle_time=self.orchestrator.get_task_idle_min(task_label=label), + start_id="0-0", + count=need_count, + justid=False, + ) + final_results.append(retry_res) + except Exception as retry_err: + logger.warning(f"Retry xautoclaim failed for {stream_key}: {retry_err}") + final_results.append(None) + else: + final_results.append(None) + else: + final_results.append(res) + + results = final_results claimed_pairs: list[tuple[str, list[tuple[str, dict]]]] = [] for (stream_key, _need_count, _label), claimed_result in zip( @@ -1159,10 +1178,14 @@ def _delete_streams(self, keys_to_delete: list[str]) -> int: del_pipe.delete(key) del_pipe.execute() deleted_count = len(keys_to_delete) - # Clean up empty-tracking state for deleted keys + # Clean up empty-tracking state and seen_streams for deleted keys with self._empty_stream_seen_lock: for key in keys_to_delete: self._empty_stream_seen_times.pop(key, None) + + with self._stream_keys_lock: + for key in keys_to_delete: + self.seen_streams.discard(key) except Exception: for key in keys_to_delete: try: @@ -1170,6 +1193,8 @@ def _delete_streams(self, keys_to_delete: list[str]) -> int: deleted_count += 1 with self._empty_stream_seen_lock: self._empty_stream_seen_times.pop(key, None) + with self._stream_keys_lock: + self.seen_streams.discard(key) except Exception: pass return deleted_count @@ -1190,8 +1215,6 @@ def _update_stream_cache_with_log( self._stream_keys_last_refresh = time.time() cache_count = len(self._stream_keys_cache) logger.info( - f"[REDIS_QUEUE] Stream keys refresh: prefix='{stream_key_prefix}', " - f"total={len(candidate_keys)}, active={len(active_stream_keys)}, cached={cache_count}, " - f"active_threshold_sec={int(active_threshold_sec)}, deleted={deleted_count}, " - f"inactive_threshold_sec={int(DEFAULT_STREAM_INACTIVITY_DELETE_SECONDS)}" + f"Refreshed stream keys cache: {cache_count} active keys, " + f"{deleted_count} deleted, {len(candidate_keys)} candidates examined." ) diff --git a/src/memos/mem_scheduler/task_schedule_modules/task_queue.py b/src/memos/mem_scheduler/task_schedule_modules/task_queue.py index c20243242..b49db2b36 100644 --- a/src/memos/mem_scheduler/task_schedule_modules/task_queue.py +++ b/src/memos/mem_scheduler/task_schedule_modules/task_queue.py @@ -153,28 +153,7 @@ def submit_messages(self, messages: ScheduleMessageItem | list[ScheduleMessageIt ) def get_messages(self, batch_size: int) -> list[ScheduleMessageItem]: - if isinstance(self.memos_message_queue, SchedulerRedisQueue): - return self.memos_message_queue.get_messages(batch_size=batch_size) - stream_keys = self.get_stream_keys() - - if len(stream_keys) == 0: - return [] - - messages: list[ScheduleMessageItem] = [] - - for stream_key in stream_keys: - fetched = self.memos_message_queue.get( - stream_key=stream_key, - block=False, - batch_size=batch_size, - ) - - messages.extend(fetched) - if len(messages) > 0: - logger.debug( - f"Fetched {len(messages)} messages across users with per-user batch_size={batch_size}" - ) - return messages + return self.memos_message_queue.get_messages(batch_size=batch_size) def clear(self): self.memos_message_queue.clear() diff --git a/src/memos/mem_scheduler/utils/status_tracker.py b/src/memos/mem_scheduler/utils/status_tracker.py index d8c8d2cee..c42ef0d0f 100644 --- a/src/memos/mem_scheduler/utils/status_tracker.py +++ b/src/memos/mem_scheduler/utils/status_tracker.py @@ -13,10 +13,13 @@ class TaskStatusTracker: @require_python_package(import_name="redis", install_command="pip install redis") - def __init__(self, redis_client: "redis.Redis"): + def __init__(self, redis_client: "redis.Redis | None"): self.redis = redis_client def _get_key(self, user_id: str) -> str: + if not self.redis: + return + return f"memos:task_meta:{user_id}" def _get_task_items_key(self, user_id: str, task_id: str) -> str: @@ -61,6 +64,9 @@ def task_submitted( self.redis.expire(key, timedelta(days=7)) def task_started(self, task_id: str, user_id: str): + if not self.redis: + return + key = self._get_key(user_id) existing_data_json = self.redis.hget(key, task_id) if not existing_data_json: @@ -77,6 +83,9 @@ def task_started(self, task_id: str, user_id: str): self.redis.expire(key, timedelta(days=7)) def task_completed(self, task_id: str, user_id: str): + if not self.redis: + return + key = self._get_key(user_id) existing_data_json = self.redis.hget(key, task_id) if not existing_data_json: @@ -108,11 +117,17 @@ def task_failed(self, task_id: str, user_id: str, error_message: str): self.redis.expire(key, timedelta(days=7)) def get_task_status(self, task_id: str, user_id: str) -> dict | None: + if not self.redis: + return None + key = self._get_key(user_id) data = self.redis.hget(key, task_id) return json.loads(data) if data else None def get_all_tasks_for_user(self, user_id: str) -> dict[str, dict]: + if not self.redis: + return {} + key = self._get_key(user_id) all_tasks = self.redis.hgetall(key) return {tid: json.loads(t_data) for tid, t_data in all_tasks.items()} @@ -180,6 +195,9 @@ def get_all_tasks_global(self) -> dict[str, dict[str, dict]]: Returns: dict: {user_id: {task_id: task_data, ...}, ...} """ + if not self.redis: + return {} + all_users_tasks = {} cursor: int | str = 0 while True: From 4ef6fec2ea98bf61c90ec1993c17c851a73aec65 Mon Sep 17 00:00:00 2001 From: chentang Date: Thu, 25 Dec 2025 16:30:02 +0800 Subject: [PATCH 02/19] fix: remove an unnecessary function --- .../task_schedule_modules/local_queue.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/memos/mem_scheduler/task_schedule_modules/local_queue.py b/src/memos/mem_scheduler/task_schedule_modules/local_queue.py index 72dccfd98..791cedf41 100644 --- a/src/memos/mem_scheduler/task_schedule_modules/local_queue.py +++ b/src/memos/mem_scheduler/task_schedule_modules/local_queue.py @@ -245,15 +245,3 @@ def full(self) -> bool: return any( q.qsize() >= self.max_internal_message_queue_size for q in self.queue_streams.values() ) - - def ack_message( - self, - user_id: str, - mem_cube_id: str, - task_label: str, - redis_message_id, - message: ScheduleMessageItem | None, - ) -> None: - """ - Acknowledge a message (no-op for local queue as messages are popped immediately). - """ From a2152f1f6da190c1aacf232ebe81944fbfbfbb1f Mon Sep 17 00:00:00 2001 From: Elvis <1693372324@qq.com> Date: Thu, 25 Dec 2025 16:39:16 +0800 Subject: [PATCH 03/19] fix: update README.md --- README.md | 210 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 120 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 634b38dec..0252ef086 100644 --- a/README.md +++ b/README.md @@ -117,98 +117,8 @@ showcasing its capabilities in **information extraction**, **temporal and cross- - **Textual Memory**: For storing and retrieving unstructured or structured text knowledge. - **Activation Memory**: Caches key-value pairs (`KVCacheMemory`) to accelerate LLM inference and context reuse. - **Parametric Memory**: Stores model adaptation parameters (e.g., LoRA weights). - - **Tool Memory** πŸ†•: Records Agent tool call trajectories and experiences to improve planning capabilities. -- **πŸ“š Knowledge Base System** πŸ†•: Build multi-dimensional knowledge bases with automatic document/URL parsing, splitting, and cross-project sharing capabilities. -- **πŸ”§ Memory Controllability** πŸ†•: - - **Feedback Mechanism**: Use `add_feedback` API to correct, supplement, or replace existing memories with natural language. - - **Precise Deletion**: Delete specific memories by User ID or Memory ID via API or MCP tools. -- **πŸ‘οΈ Multi-Modal Support** πŸ†•: Support for image understanding and memory, including chart parsing in documents. -- **⚑ Advanced Architecture**: - - **DB Optimization**: Enhanced connection management and batch insertion for high-concurrency scenarios. - - **Advanced Retrieval**: Custom tag and info field filtering with complex logical operations. - - **Redis Streams Scheduler**: Multi-level queue architecture with intelligent orchestration for fair multi-tenant scheduling. - - **Stream & Non-Stream Chat**: Ready-to-use streaming and non-streaming chat interfaces. - **πŸ”Œ Extensible**: Easily extend and customize memory modules, data sources, and LLM integrations. -- **πŸ‚ Lightweight Deployment** πŸ†•: Support for quick mode and complete mode deployment options. -## πŸš€ Getting Started - -### ⭐️ MemOS online API -The easiest way to use MemOS. Equip your agent with memory **in minutes**! - -Sign up and get started on[`MemOS dashboard`](https://memos-dashboard.openmem.net/cn/quickstart/?source=landing). - - -### Self-Hosted Server -1. Get the repository. -```bash -git clone https://github.com/MemTensor/MemOS.git -cd MemOS -pip install -r ./docker/requirements.txt -``` - -2. Configure `docker/.env.example` and copy to `MemOS/.env` -3. Start the service. -```bash -uvicorn memos.api.server_api:app --host 0.0.0.0 --port 8001 --workers 8 -``` - -### Local SDK -Here's a quick example of how to create a **`MemCube`**, load it from a directory, access its memories, and save it. - -```python -from memos.mem_cube.general import GeneralMemCube - -# Initialize a MemCube from a local directory -mem_cube = GeneralMemCube.init_from_dir("examples/data/mem_cube_2") - -# Access and print all memories -print("--- Textual Memories ---") -for item in mem_cube.text_mem.get_all(): - print(item) - -print("\n--- Activation Memories ---") -for item in mem_cube.act_mem.get_all(): - print(item) - -# Save the MemCube to a new directory -mem_cube.dump("tmp/mem_cube") -``` - -**`MOS`** (Memory Operating System) is a higher-level orchestration layer that manages multiple MemCubes and provides a unified API for memory operations. Here's a quick example of how to use MOS: - -```python -from memos.configs.mem_os import MOSConfig -from memos.mem_os.main import MOS - - -# init MOS -mos_config = MOSConfig.from_json_file("examples/data/config/simple_memos_config.json") -memory = MOS(mos_config) - -# create user -user_id = "b41a34d5-5cae-4b46-8c49-d03794d206f5" -memory.create_user(user_id=user_id) - -# register cube for user -memory.register_mem_cube("examples/data/mem_cube_2", user_id=user_id) - -# add memory for user -memory.add( - messages=[ - {"role": "user", "content": "I like playing football."}, - {"role": "assistant", "content": "I like playing football too."}, - ], - user_id=user_id, -) - -# Later, when you want to retrieve memory for user -retrieved_memories = memory.search(query="What do you like?", user_id=user_id) -# output text_memories: I like playing football, act_memories, para_memories -print(f"text_memories: {retrieved_memories['text_mem']}") -``` - -For more detailed examples, please check out the [`examples`](./examples) directory. ## πŸ“¦ Installation @@ -259,6 +169,126 @@ To download example code, data and configurations, run the following command: memos download_examples ``` +## πŸš€ Getting Started + +### ⭐️ MemOS online API +The easiest way to use MemOS. Equip your agent with memory **in minutes**! + +Sign up and get started on[`MemOS dashboard`](https://memos-dashboard.openmem.net/cn/quickstart/?source=landing). + + +### Self-Hosted Server +1. Get the repository. +```bash +git clone https://github.com/MemTensor/MemOS.git +cd MemOS +pip install -r ./docker/requirements.txt +``` + +2. Configure `docker/.env.example` and copy to `MemOS/.env` +3. Start the service. +```bash +uvicorn memos.api.server_api:app --host 0.0.0.0 --port 8001 --workers 8 +``` + +### Interface SDK +#### Here is a quick example showing how to create interface SDK + +This interface is used to add messages, supporting multiple types of content and batch additions. MemOS will automatically parse the messages and handle memory for reference in subsequent conversations. +```python +# Please make sure MemoS is installed (pip install MemoryOS -U) +from memos.api.client import MemOSClient + +# Initialize the client using the API Key +client = MemOSClient(api_key="YOUR_API_KEY") + +messages = [ + {"role": "user", "content": "I have planned to travel to Guangzhou during the summer vacation. What chain hotels are available for accommodation?"}, + {"role": "assistant", "content": "You can consider [7 Days, All Seasons, Hilton], and so on."}, + {"role": "user", "content": "I'll choose 7 Days"}, + {"role": "assistant", "content": "Okay, ask me if you have any other questions."} +] +user_id = "memos_user_123" +conversation_id = "0610" +res = client.add_message(messages=messages, user_id=user_id, conversation_id=conversation_id) + +print(f"result: {res}") +``` + +This interface is used to retrieve the memories of a specified user, returning the memory fragments most relevant to the input query for Agent use. The recalled memory fragments include 'factual memory', 'preference memory', and 'tool memory'. +```python +# Please make sure MemoS is installed (pip install MemoryOS -U) +from memos.api.client import MemOSClient + +# Initialize the client using the API Key +client = MemOSClient(api_key="YOUR_API_KEY") + +query = "I want to go out to play during National Day. Can you recommend a city I haven't been to and a hotel brand I haven't stayed at?" +user_id = "memos_user_123" +conversation_id = "0928" +res = client.search_memory(query=query, user_id=user_id, conversation_id=conversation_id) + +print(f"result: {res}") +``` + +This interface is used to delete the memory of specified users and supports batch deletion. +```python +# Please make sure MemoS is installed (pip install MemoryOS -U) +from memos.api.client import MemOSClient + +# Initialize the client using the API Key +client = MemOSClient(api_key="YOUR_API_KEY") + +user_ids = ["memos_user_123"] +# Replace with the memory ID +memory_ids = ["6b23b583-f4c4-4a8f-b345-58d0c48fea04"] +res = client.delete_memory(user_ids=user_ids, memory_ids=memory_ids) + +print(f"result: {res}") +``` + +This interface is used to add feedback to messages in the current session, allowing MemOS to correct its memory based on user feedback. +```python +# Please make sure MemoS is installed (pip install MemoryOS -U) +from memos.api.client import MemOSClient + +# Initialize the client using the API Key +client = MemOSClient(api_key="YOUR_API_KEY") + +user_id = "memos_user_123" +conversation_id = "memos_feedback_conv" +feedback_content = "No, let's change it now to a meal allowance of 150 yuan per day and a lodging subsidy of 700 yuan per day for first-tier cities; for second- and third-tier cities, it remains the same as before." +# Replace with the knowledgebase ID +allow_knowledgebase_ids = ["basee5ec9050-c964-484f-abf1-ce3e8e2aa5b7"] + +res = client.add_feedback( + user_id=user_id, + conversation_id=conversation_id, + feedback_content=feedback_content, + allow_knowledgebase_ids=allow_knowledgebase_ids +) + +print(f"result: {res}") +``` + +This interface is used to create a knowledgebase associated with a project +```python +# Please make sure MemoS is installed (pip install MemoryOS -U) +from memos.api.client import MemOSClient + +# Initialize the client using the API Key +client = MemOSClient(api_key="YOUR_API_KEY") + +knowledgebase_name = "Financial Reimbursement Knowledge Base" +knowledgebase_description = "A compilation of all knowledge related to the company's financial reimbursements." + +res = client.create_knowledgebase( + knowledgebase_name=knowledgebase_name, + knowledgebase_description=knowledgebase_description +) +print(f"result: {res}") +``` + ## πŸ’¬ Community & Support Join our community to ask questions, share your projects, and connect with other developers. From 9e6f846f8bfebb5cbe2ae4a00e8af1ba7136a4b2 Mon Sep 17 00:00:00 2001 From: pursues <15180521816@163.com> Date: Thu, 25 Dec 2025 16:46:40 +0800 Subject: [PATCH 04/19] update requirement,Dockerfile --- docker/.env.example | 162 ++++++++++++++++--------- docker/Dockerfile | 2 +- docker/requirements-full.txt | 186 ++++++++++++++++++++++++++++ docker/requirements.txt | 228 +++++++++++++++-------------------- 4 files changed, 384 insertions(+), 194 deletions(-) create mode 100644 docker/requirements-full.txt diff --git a/docker/.env.example b/docker/.env.example index 85d9080a5..3397a8029 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -3,32 +3,31 @@ ## Base TZ=Asia/Shanghai -ENV_NAME=PLAYGROUND_OFFLINE # Tag shown in DingTalk notifications (e.g., PROD_ONLINE/TEST); no runtime effect unless ENABLE_DINGDING_BOT=true MOS_CUBE_PATH=/tmp/data_test # local data path MEMOS_BASE_PATH=. # CLI/SDK cache path MOS_ENABLE_DEFAULT_CUBE_CONFIG=true # enable default cube config MOS_ENABLE_REORGANIZE=false # enable memory reorg +# MOS Text Memory Type MOS_TEXT_MEM_TYPE=general_text # general_text | tree_text ASYNC_MODE=sync # async/sync, used in default cube config ## User/session defaults -MOS_USER_ID=root -MOS_SESSION_ID=default_session -MOS_MAX_TURNS_WINDOW=20 +# Top-K for LLM in the Product API(old versionοΌ‰ MOS_TOP_K=50 ## Chat LLM (main dialogue) +# LLM model name for the Product API MOS_CHAT_MODEL=gpt-4o-mini +# Temperature for LLM in the Product API MOS_CHAT_TEMPERATURE=0.8 -MOS_MAX_TOKENS=8000 +# Max tokens for LLM in the Product API +MOS_MAX_TOKENS=2048 +# Top-P for LLM in the Product API MOS_TOP_P=0.9 +# LLM for the Product API backend MOS_CHAT_MODEL_PROVIDER=openai # openai | huggingface | vllm -MOS_MODEL_SCHEMA=memos.configs.llm.VLLMLLMConfig # vllm only: config class path; keep default unless you extend it OPENAI_API_KEY=sk-xxx # [required] when provider=openai OPENAI_API_BASE=https://api.openai.com/v1 # [required] base for the key -OPENAI_BASE_URL= # compatibility for eval/scheduler -VLLM_API_KEY= # required when provider=vllm -VLLM_API_BASE=http://localhost:8088/v1 # required when provider=vllm ## MemReader / retrieval LLM MEMRADER_MODEL=gpt-4o-mini @@ -37,31 +36,60 @@ MEMRADER_API_BASE=http://localhost:3000/v1 # [required] base for the key MEMRADER_MAX_TOKENS=5000 ## Embedding & rerank +# embedding dim EMBEDDING_DIMENSION=1024 +# set default embedding backend MOS_EMBEDDER_BACKEND=universal_api # universal_api | ollama +# set openai style MOS_EMBEDDER_PROVIDER=openai # required when universal_api +# embedding model MOS_EMBEDDER_MODEL=bge-m3 # siliconflow β†’ use BAAI/bge-m3 +# embedding url MOS_EMBEDDER_API_BASE=http://localhost:8000/v1 # required when universal_api +# embedding model key MOS_EMBEDDER_API_KEY=EMPTY # required when universal_api OLLAMA_API_BASE=http://localhost:11434 # required when backend=ollama +# reanker config MOS_RERANKER_BACKEND=http_bge # http_bge | http_bge_strategy | cosine_local +# reanker url MOS_RERANKER_URL=http://localhost:8001 # required when backend=http_bge* +# reranker model MOS_RERANKER_MODEL=bge-reranker-v2-m3 # siliconflow β†’ use BAAI/bge-reranker-v2-m3 MOS_RERANKER_HEADERS_EXTRA= # extra headers, JSON string, e.g. {"Authorization":"Bearer your_token"} +# use source MOS_RERANKER_STRATEGY=single_turn -MOS_RERANK_SOURCE= # optional rerank scope, e.g., history/stream/custom + + +# External Services (for evaluation scripts) +# API key for reproducting Zep(compertitor product) evaluation +ZEP_API_KEY=your_zep_api_key_here +# API key for reproducting Mem0(compertitor product) evaluation +MEM0_API_KEY=your_mem0_api_key_here +# API key for reproducting MemU(compertitor product) evaluation +MEMU_API_KEY=your_memu_api_key_here +# API key for reproducting MEMOBASE(compertitor product) evaluation +MEMOBASE_API_KEY=your_memobase_api_key_here +# Project url for reproducting MEMOBASE(compertitor product) evaluation +MEMOBASE_PROJECT_URL=your_memobase_project_url_here +# LLM for evaluation +MODEL=gpt-4o-mini +# embedding model for evaluation +EMBEDDING_MODEL=nomic-embed-text:latest ## Internet search & preference memory +# Enable web search ENABLE_INTERNET=false +# API key for BOCHA Search BOCHA_API_KEY= # required if ENABLE_INTERNET=true +# default search mode SEARCH_MODE=fast # fast | fine | mixture -FAST_GRAPH=false -BM25_CALL=false -VEC_COT_CALL=false +# Slow retrieval strategy configuration, rewrite is the rewrite strategy FINE_STRATEGY=rewrite # rewrite | recreate | deep_search -ENABLE_ACTIVATION_MEMORY=false +# Whether to enable preference memory ENABLE_PREFERENCE_MEMORY=true +# Preference Memory Add Mode PREFERENCE_ADDER_MODE=fast # fast | safe +# Whether to deduplicate explicit preferences based on factual memory DEDUP_PREF_EXP_BY_TEXTUAL=false ## Reader chunking @@ -72,63 +100,71 @@ MEM_READER_CHAT_CHUNK_SESS_SIZE=10 # sessions per chunk (default mode) MEM_READER_CHAT_CHUNK_OVERLAP=2 # overlap between chunks ## Scheduler (MemScheduler / API) +# Enable or disable the main switch for configuring the memory scheduler during MemOS class initialization MOS_ENABLE_SCHEDULER=false +# Determine the number of most relevant memory entries that the scheduler retrieves or processes during runtime (such as reordering or updating working memory) MOS_SCHEDULER_TOP_K=10 +# The time interval (in seconds) for updating "Activation Memory" (usually referring to caching or short-term memory mechanisms) MOS_SCHEDULER_ACT_MEM_UPDATE_INTERVAL=300 +# The size of the context window considered by the scheduler when processing tasks (such as the number of recent messages or conversation rounds) MOS_SCHEDULER_CONTEXT_WINDOW_SIZE=5 +# The maximum number of working threads allowed in the scheduler thread pool for concurrent task execution MOS_SCHEDULER_THREAD_POOL_MAX_WORKERS=10000 +# The polling interval (in seconds) for the scheduler to consume new messages/tasks from the queue. The smaller the value, the faster the response, but the CPU usage may be higher MOS_SCHEDULER_CONSUME_INTERVAL_SECONDS=0.01 +# Whether to enable the parallel distribution function of the scheduler to improve the throughput of concurrent operations MOS_SCHEDULER_ENABLE_PARALLEL_DISPATCH=true +# The specific switch to enable or disable the "Activate Memory" function in the scheduler logic MOS_SCHEDULER_ENABLE_ACTIVATION_MEMORY=false +# Control whether the scheduler instance is actually started during server initialization. If false, the scheduler object may be created but its background loop will not be started API_SCHEDULER_ON=true +# Specifically define the window size for API search operations in OptimizedScheduler. It is passed to the ScherderrAPIModule to control the scope of the search context API_SEARCH_WINDOW_SIZE=5 +# Specify how many rounds of previous conversations (history) to retrieve and consider during the 'hybrid search' (fast search+asynchronous fine search). This helps provide context aware search results API_SEARCH_HISTORY_TURNS=5 ## Graph / vector stores +# Neo4j database selection mode NEO4J_BACKEND=neo4j-community # neo4j-community | neo4j | nebular | polardb +# Neo4j database url NEO4J_URI=bolt://localhost:7687 # required when backend=neo4j* +# Neo4j database user NEO4J_USER=neo4j # required when backend=neo4j* +# Neo4j database password NEO4J_PASSWORD=12345678 # required when backend=neo4j* +# Neo4j database name NEO4J_DB_NAME=neo4j # required for shared-db mode +# Neo4j database data sharing with Memos MOS_NEO4J_SHARED_DB=false QDRANT_HOST=localhost QDRANT_PORT=6333 # For Qdrant Cloud / remote endpoint (takes priority if set): QDRANT_URL=your_qdrant_url QDRANT_API_KEY=your_qdrant_key +# milvus server uri MILVUS_URI=http://localhost:19530 # required when ENABLE_PREFERENCE_MEMORY=true MILVUS_USER_NAME=root # same as above MILVUS_PASSWORD=12345678 # same as above -NEBULAR_HOSTS=["localhost"] -NEBULAR_USER=root -NEBULAR_PASSWORD=xxxxxx -NEBULAR_SPACE=shared-tree-textual-memory -NEBULAR_WORKING_MEMORY=20 -NEBULAR_LONGTERM_MEMORY=1000000 -NEBULAR_USER_MEMORY=1000000 - -## Relational DB (user manager / PolarDB) -MOS_USER_MANAGER_BACKEND=sqlite # sqlite | mysql -MYSQL_HOST=localhost # required when backend=mysql -MYSQL_PORT=3306 -MYSQL_USERNAME=root -MYSQL_PASSWORD=12345678 -MYSQL_DATABASE=memos_users -MYSQL_CHARSET=utf8mb4 + +# PolarDB endpoint/host POLAR_DB_HOST=localhost +# PolarDB port POLAR_DB_PORT=5432 +# PolarDB username POLAR_DB_USER=root +# PolarDB password POLAR_DB_PASSWORD=123456 +# PolarDB database name POLAR_DB_DB_NAME=shared_memos_db +# PolarDB Server Mode: +# If set to true, use Multi-Database Mode where each user has their own independent database (physical isolation). +# If set to false (default), use Shared Database Mode where all users share one database with logical isolation via username. POLAR_DB_USE_MULTI_DB=false +# PolarDB connection pool size +POLARDB_POOL_MAX_CONN=100 -## Redis (scheduler queue) β€” fill only if you want scheduler queues in Redis; otherwise in-memory queue is used -REDIS_HOST=localhost # global Redis endpoint (preferred over MEMSCHEDULER_*) -REDIS_PORT=6379 -REDIS_DB=0 -REDIS_PASSWORD= -REDIS_SOCKET_TIMEOUT= -REDIS_SOCKET_CONNECT_TIMEOUT= +## Related configurations of Redis +# Reddimq sends scheduling information and synchronization information for some variables MEMSCHEDULER_REDIS_HOST= # fallback keys if not using the global ones MEMSCHEDULER_REDIS_PORT= MEMSCHEDULER_REDIS_DB= @@ -136,37 +172,43 @@ MEMSCHEDULER_REDIS_PASSWORD= MEMSCHEDULER_REDIS_TIMEOUT= MEMSCHEDULER_REDIS_CONNECT_TIMEOUT= -## MemScheduler LLM -MEMSCHEDULER_OPENAI_API_KEY= # LLM key for scheduler’s own calls (OpenAI-compatible); leave empty if scheduler not using LLM -MEMSCHEDULER_OPENAI_BASE_URL= # Base URL for the above; can reuse OPENAI_API_BASE -MEMSCHEDULER_OPENAI_DEFAULT_MODEL=gpt-4o-mini ## Nacos (optional config center) +# Nacos turns off long polling listening, defaults to true NACOS_ENABLE_WATCH=false +# The monitoring interval for long rotation training is 60 seconds, and the default 30 seconds can be left unconfigured NACOS_WATCH_INTERVAL=60 +# nacos server address NACOS_SERVER_ADDR= +# nacos dataid NACOS_DATA_ID= +# nacos group NACOS_GROUP=DEFAULT_GROUP +# nacos namespace NACOS_NAMESPACE= +# nacos ak AK= +# nacos sk SK= -## DingTalk bot & OSS upload -ENABLE_DINGDING_BOT=false # set true -> fields below required -DINGDING_ACCESS_TOKEN_USER= -DINGDING_SECRET_USER= -DINGDING_ACCESS_TOKEN_ERROR= -DINGDING_SECRET_ERROR= -DINGDING_ROBOT_CODE= -DINGDING_APP_KEY= -DINGDING_APP_SECRET= -OSS_ENDPOINT= # bot image upload depends on OSS -OSS_REGION= -OSS_BUCKET_NAME= -OSS_ACCESS_KEY_ID= -OSS_ACCESS_KEY_SECRET= -OSS_PUBLIC_BASE_URL= - -## SDK / external client -MEMOS_API_KEY= -MEMOS_BASE_URL=https://memos.memtensor.cn/api/openmem/v1 +# chat model for chat api +CHAT_MODEL_LIST='[{ + "backend": "deepseek", + "api_base": "http://localhost:1234", + "api_key": "your-api-key", + "model_name_or_path": "deepseek-r1", + "support_models": ["deepseek-r1"] +}]' + +# RabbitMQ host name for message-log pipeline +MEMSCHEDULER_RABBITMQ_HOST_NAME= +# RabbitMQ user name for message-log pipeline +MEMSCHEDULER_RABBITMQ_USER_NAME= +# RabbitMQ password for message-log pipeline +MEMSCHEDULER_RABBITMQ_PASSWORD= +# RabbitMQ virtual host for message-log pipeline +MEMSCHEDULER_RABBITMQ_VIRTUAL_HOST=memos +# Erase connection state on connect for message-log pipeline +MEMSCHEDULER_RABBITMQ_ERASE_ON_CONNECT=true +# RabbitMQ port for message-log pipeline +MEMSCHEDULER_RABBITMQ_PORT=5672 \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index 29636881c..76be1709d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -32,4 +32,4 @@ ENV PYTHONPATH=/app/src EXPOSE 8000 # Start the docker -CMD ["uvicorn", "memos.api.product_api:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] +CMD ["uvicorn", "memos.api.server_api:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] diff --git a/docker/requirements-full.txt b/docker/requirements-full.txt new file mode 100644 index 000000000..538f5e578 --- /dev/null +++ b/docker/requirements-full.txt @@ -0,0 +1,186 @@ +# Generated from poetry.lock - Main dependencies +# This file contains all transitive dependencies for the production build. + +annotated-types==0.7.0 +anyio==4.9.0 +async-timeout==5.0.1 +attrs==25.3.0 +authlib==1.6.0 +beautifulsoup4==4.13.4 +cachetools==6.2.1 +certifi==2025.7.14 +cffi==1.17.1 +charset-normalizer==3.4.2 +chonkie==1.1.1 +click==8.2.1 +cobble==0.1.4 +colorama==0.4.6 +coloredlogs==15.0.1 +concurrent-log-handler==0.9.28 +cryptography==45.0.5 +cyclopts==3.22.2 +datasketch==1.6.5 +defusedxml==0.7.1 +distro==1.9.0 +dnspython==2.7.0 +docstring-parser==0.16 +docutils==0.21.2 +email-validator==2.2.0 +et-xmlfile==2.0.0 +exceptiongroup==1.3.0 +fastapi==0.115.14 +fastapi-cli==0.0.8 +fastapi-cloud-cli==0.1.4 +fastmcp==2.10.5 +filelock==3.18.0 +flatbuffers==25.2.10 +fsspec==2025.7.0 +greenlet==3.2.3 +grpcio==1.73.1 +h11==0.16.0 +h2==4.2.0 +hf-xet==1.1.5 +hpack==4.1.0 +httpcore==1.0.9 +httptools==0.6.4 +httpx==0.28.1 +httpx-sse==0.4.1 +huggingface-hub==0.33.4 +humanfriendly==10.0 +hyperframe==6.1.0 +idna==3.10 +itsdangerous==2.2.0 +jieba==0.42 +jinja2==3.1.6 +jiter==0.10.0 +joblib==1.5.1 +jsonpatch==1.33 +jsonpointer==3.0.0 +jsonschema==4.24.1 +jsonschema-specifications==2025.4.1 +langchain-core==1.1.0 +langchain-text-splitters==1.0.0 +langsmith==0.4.7 +lxml==6.0.0 +magika==0.6.2 +mammoth==1.9.1 +markdown-it-py==3.0.0 +markdownify==1.1.0 +markitdown==0.1.2 +markupsafe==3.0.2 +mcp==1.12.0 +mdurl==0.1.2 +mpmath==1.3.0 +neo4j==5.28.1 +networkx==3.5 +nltk==3.9.1 +numpy==2.3.1 +nvidia-cublas-cu12==12.6.4.1 +nvidia-cuda-cupti-cu12==12.6.80 +nvidia-cuda-nvrtc-cu12==12.6.77 +nvidia-cuda-runtime-cu12==12.6.77 +nvidia-cudnn-cu12==9.5.1.17 +nvidia-cufft-cu12==11.3.0.4 +nvidia-cufile-cu12==1.11.1.6 +nvidia-curand-cu12==10.3.7.77 +nvidia-cusolver-cu12==11.7.1.2 +nvidia-cusparse-cu12==12.5.4.2 +nvidia-cusparselt-cu12==0.6.3 +nvidia-nccl-cu12==2.26.2 +nvidia-nvjitlink-cu12==12.6.85 +nvidia-nvtx-cu12==12.6.77 +ollama==0.4.9 +onnxruntime==1.22.1 +openai==1.97.0 +openapi-pydantic==0.5.1 +openpyxl==3.1.5 +orjson==3.11.0 +packaging==25.0 +pandas==2.3.1 +pdfminer-six==20250506 +pika==1.3.2 +pillow==11.3.0 +portalocker==2.10.1 +prometheus-client==0.23.1 +protobuf==6.31.1 +pycparser==2.22 +pydantic==2.11.7 +pydantic-core==2.33.2 +pydantic-extra-types==2.10.5 +pydantic-settings==2.10.1 +pygments==2.19.2 +pymilvus==2.6.2 +pymysql==1.1.2 +python-dateutil==2.9.0.post0 +python-dotenv==1.1.1 +python-multipart==0.0.20 +python-pptx==1.0.2 +pytz==2025.2 +pyyaml==6.0.2 +qdrant-client==1.14.3 +rake-nltk==1.0.6 +rank-bm25==0.2.2 +redis==6.2.0 +referencing==0.36.2 +regex==2024.11.6 +requests==2.32.4 +requests-toolbelt==1.0.0 +rich==14.0.0 +rich-rst==1.3.1 +rich-toolkit==0.14.8 +rignore==0.6.2 +rpds-py==0.26.0 +safetensors==0.5.3 +schedule==1.2.2 +scikit-learn==1.7.0 +scipy==1.16.0 +sentence-transformers==4.1.0 +sentry-sdk==2.33.0 +setuptools==80.9.0 +shellingham==1.5.4 +six==1.17.0 +sniffio==1.3.1 +soupsieve==2.7 +sqlalchemy==2.0.41 +sse-starlette==2.4.1 +starlette==0.46.2 +sympy==1.14.0 +tenacity==9.1.2 +threadpoolctl==3.6.0 +tokenizers==0.21.2 +torch +tqdm==4.67.1 +transformers==4.53.2 +triton==3.5.0 +typer==0.16.0 +typing-extensions +typing-inspection==0.4.1 +tzdata==2025.2 +ujson==5.10.0 +urllib3==2.5.0 +uvicorn==0.35.0 +uvloop==0.21.0 +volcengine-python-sdk==4.0.6 +watchfiles==1.1.0 +websockets==15.0.1 +xlrd==2.0.2 +xlsxwriter==3.2.5 +zstandard==0.23.0 +prometheus_client==0.23.1 +beartype==0.22.5 +diskcache==5.6.3 +iniconfig==2.3.0 +jaraco.classes==3.4.0 +jaraco.context==6.0.1 +jaraco.functools==4.3.0 +keyring==25.6.0 +more-itertools==10.8.0 +pathable==0.4.4 +pathvalidate==3.3.1 +platformdirs==4.5.0 +pluggy==1.6.0 +psycopg2-binary==2.9.9 +py-key-value-aio==0.2.8 +py-key-value-shared==0.2.8 +PyJWT==2.10.1 +pytest==9.0.2 \ No newline at end of file diff --git a/docker/requirements.txt b/docker/requirements.txt index 8890ce679..7d2b76734 100644 --- a/docker/requirements.txt +++ b/docker/requirements.txt @@ -2,164 +2,126 @@ # Excludes Windows-specific and heavy GPU packages for faster builds annotated-types==0.7.0 -anyio==4.9.0 -async-timeout==5.0.1 -attrs==25.3.0 -authlib==1.6.0 -beautifulsoup4==4.13.4 -certifi==2025.7.14 -cffi==1.17.1 -charset-normalizer==3.4.2 -chonkie==1.1.1 -click==8.2.1 -cobble==0.1.4 -colorama==0.4.6 -coloredlogs==15.0.1 +anyio==4.11.0 +attrs==25.4.0 +Authlib==1.6.5 +beartype==0.22.5 +cachetools==6.2.2 +certifi==2025.11.12 +cffi==2.0.0 +charset-normalizer==3.4.4 +chonkie==1.1.0 +click==8.3.0 concurrent-log-handler==0.9.28 -cryptography==45.0.5 -cyclopts==3.22.2 -defusedxml==0.7.1 +cryptography==46.0.3 +cyclopts==4.2.3 +diskcache==5.6.3 distro==1.9.0 -dnspython==2.7.0 -docstring-parser==0.16 -docutils==0.21.2 -email-validator==2.2.0 -et-xmlfile==2.0.0 +dnspython==2.8.0 +docstring_parser==0.17.0 +docutils==0.22.3 +email-validator==2.3.0 exceptiongroup==1.3.0 -fastapi-cli==0.0.8 -fastapi-cloud-cli==0.1.4 fastapi==0.115.14 -fastmcp==2.10.5 -filelock==3.18.0 -flatbuffers==25.2.10 -fsspec==2025.7.0 -greenlet==3.2.3 -grpcio==1.73.1 +fastapi-cli==0.0.16 +fastapi-cloud-cli==0.3.1 +fastmcp==2.13.0.2 +filelock==3.20.0 +fsspec==2025.10.0 +grpcio==1.76.0 h11==0.16.0 -h2==4.2.0 -hf-xet==1.1.5 -hpack==4.1.0 +hf-xet==1.2.0 httpcore==1.0.9 -httptools==0.6.4 -httpx-sse==0.4.1 +httptools==0.7.1 httpx==0.28.1 -huggingface-hub==0.33.4 -humanfriendly==10.0 -hyperframe==6.1.0 -idna==3.10 +httpx-sse==0.4.3 +huggingface-hub==0.36.0 +idna==3.11 +iniconfig==2.3.0 itsdangerous==2.2.0 -jinja2==3.1.6 -jiter==0.10.0 -joblib==1.5.1 -jsonschema-specifications==2025.4.1 -jsonschema==4.24.1 -lxml==6.0.0 -magika==0.6.2 -mammoth==1.9.1 -markdown-it-py==3.0.0 -markdownify==1.1.0 -markitdown==0.1.2 -markupsafe==3.0.2 -mcp==1.12.0 +jaraco.classes==3.4.0 +jaraco.context==6.0.1 +jaraco.functools==4.3.0 +jieba==0.42 +Jinja2==3.1.6 +jiter==0.12.0 +joblib==1.5.2 +jsonschema==4.25.1 +jsonschema-path==0.3.4 +jsonschema-specifications==2025.9.1 +keyring==25.6.0 +markdown-it-py==4.0.0 +MarkupSafe==3.0.3 +mcp==1.21.1 mdurl==0.1.2 -mpmath==1.3.0 -neo4j==5.28.1 -networkx==3.5 -numpy==2.3.1 -# NVIDIA CUDA packages excluded for lighter Docker images -# If GPU support is needed, uncomment relevant packages below: -# nvidia-cublas-cu12==12.6.4.1 -# nvidia-cuda-cupti-cu12==12.6.80 -# nvidia-cuda-nvrtc-cu12==12.6.77 -# nvidia-cuda-runtime-cu12==12.6.77 -# nvidia-cudnn-cu12==9.5.1.17 -# nvidia-cufft-cu12==11.3.0.4 -# nvidia-cufile-cu12==1.11.1.6 -# nvidia-curand-cu12==10.3.7.77 -# nvidia-cusolver-cu12==11.7.1.2 -# nvidia-cusparse-cu12==12.5.4.2 -# nvidia-cusparselt-cu12==0.6.3 -# nvidia-nccl-cu12==2.26.2 -# nvidia-nvjitlink-cu12==12.6.85 -# nvidia-nvtx-cu12==12.6.77 +more-itertools==10.8.0 +numpy==2.3.4 ollama==0.4.9 -onnxruntime==1.22.1 -openai==1.97.0 +openai==1.109.1 openapi-pydantic==0.5.1 -openpyxl==3.1.5 -orjson==3.11.0 +orjson==3.11.4 packaging==25.0 -pandas==2.3.1 -pdfminer-six==20250506 +pandas==2.3.3 +pathable==0.4.4 +pathvalidate==3.3.1 pika==1.3.2 -pillow==11.3.0 -portalocker==2.10.1 -protobuf==6.31.1 -pycparser==2.22 -pydantic-core==2.33.2 -pydantic-extra-types==2.10.5 -pydantic-settings==2.10.1 -pydantic==2.11.7 -pygments==2.19.2 -pymysql==1.1.1 -pyperclip==1.9.0 -# Windows-specific packages excluded: -# pyreadline3==3.5.4 # Windows only -# pywin32==311 # Windows only +platformdirs==4.5.0 +pluggy==1.6.0 +portalocker==2.8.0 +prometheus_client==0.23.1 +protobuf==6.33.1 +psycopg2-binary==2.9.9 +py-key-value-aio==0.2.8 +py-key-value-shared==0.2.8 +pycparser==2.23 +pydantic==2.12.4 +pydantic-extra-types==2.10.6 +pydantic-settings==2.12.0 +pydantic_core==2.41.5 +Pygments==2.19.2 +PyJWT==2.10.1 +pymilvus==2.6.5 +PyMySQL==1.1.2 +pyperclip==1.11.0 +pytest==9.0.2 python-dateutil==2.9.0.post0 -python-dotenv==1.1.1 +python-dotenv==1.2.1 python-multipart==0.0.20 -python-pptx==1.0.2 pytz==2025.2 -pyyaml==6.0.2 +PyYAML==6.0.3 qdrant-client==1.14.3 -redis==6.2.0 +redis==6.4.0 referencing==0.36.2 -regex==2024.11.6 -requests==2.32.4 -rich-rst==1.3.1 -rich-toolkit==0.14.8 -rich==14.0.0 -rignore==0.6.2 -rpds-py==0.26.0 -safetensors==0.5.3 -schedule==1.2.2 -scikit-learn==1.7.0 -scipy==1.16.0 -sentence-transformers==4.1.0 -sentry-sdk==2.33.0 +regex==2025.11.3 +requests==2.32.5 +rich==14.2.0 +rich-rst==1.3.2 +rich-toolkit==0.15.1 +rignore==0.7.6 +rpds-py==0.28.0 +safetensors==0.6.2 +scikit-learn==1.7.2 +scipy==1.16.3 +sentry-sdk==2.44.0 setuptools==80.9.0 shellingham==1.5.4 six==1.17.0 sniffio==1.3.1 -soupsieve==2.7 -sqlalchemy==2.0.41 -sse-starlette==2.4.1 +SQLAlchemy==2.0.44 +sse-starlette==3.0.3 starlette==0.46.2 -sympy==1.14.0 tenacity==9.1.2 threadpoolctl==3.6.0 -tokenizers==0.21.2 -# Torch excluded for lighter Docker images (very large package ~2GB) -# If needed for ML/AI features, uncomment: -# torch==2.7.1 -# triton==3.3.1 +tokenizers==0.22.1 tqdm==4.67.1 -transformers==4.53.2 -typer==0.16.0 -typing-extensions==4.14.1 -typing-inspection==0.4.1 +transformers==4.57.1 +typer==0.20.0 +typing-inspection==0.4.2 +typing_extensions==4.15.0 tzdata==2025.2 -ujson==5.10.0 +ujson==5.11.0 urllib3==2.5.0 -uvicorn==0.35.0 -uvloop==0.21.0 -volcengine-python-sdk==4.0.6 -watchfiles==1.1.0 -websockets==15.0.1 -xlrd==2.0.2 -xlsxwriter==3.2.5 -prometheus-client==0.23.1 -pymilvus==2.5.12 -nltk==3.9.1 -rake-nltk==1.0.6 +uvicorn==0.38.0 +uvloop==0.22.1 +watchfiles==1.1.1 +websockets==15.0.1 \ No newline at end of file From 1670ef316722ce15b96ba4f0d7efb6c469582ac5 Mon Sep 17 00:00:00 2001 From: Elvis <1693372324@qq.com> Date: Thu, 25 Dec 2025 17:04:56 +0800 Subject: [PATCH 05/19] fix: update README.md --- README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0252ef086..29a8fc9c8 100644 --- a/README.md +++ b/README.md @@ -117,8 +117,19 @@ showcasing its capabilities in **information extraction**, **temporal and cross- - **Textual Memory**: For storing and retrieving unstructured or structured text knowledge. - **Activation Memory**: Caches key-value pairs (`KVCacheMemory`) to accelerate LLM inference and context reuse. - **Parametric Memory**: Stores model adaptation parameters (e.g., LoRA weights). + - **Tool Memory** πŸ†•: Records Agent tool call trajectories and experiences to improve planning capabilities. +- **πŸ“š Knowledge Base System** πŸ†•: Build multi-dimensional knowledge bases with automatic document/URL parsing, splitting, and cross-project sharing capabilities. +- **πŸ”§ Memory Controllability** πŸ†•: + - **Feedback Mechanism**: Use `add_feedback` API to correct, supplement, or replace existing memories with natural language. + - **Precise Deletion**: Delete specific memories by User ID or Memory ID via API or MCP tools. +- **πŸ‘οΈ Multi-Modal Support** πŸ†•: Support for image understanding and memory, including chart parsing in documents. +- **⚑ Advanced Architecture**: + - **DB Optimization**: Enhanced connection management and batch insertion for high-concurrency scenarios. + - **Advanced Retrieval**: Custom tag and info field filtering with complex logical operations. + - **Redis Streams Scheduler**: Multi-level queue architecture with intelligent orchestration for fair multi-tenant scheduling. + - **Stream & Non-Stream Chat**: Ready-to-use streaming and non-streaming chat interfaces. - **πŸ”Œ Extensible**: Easily extend and customize memory modules, data sources, and LLM integrations. - +- **πŸ‚ Lightweight Deployment** πŸ†•: Support for quick mode and complete mode deployment options. ## πŸ“¦ Installation @@ -346,4 +357,4 @@ We welcome contributions from the community! Please read our [contribution guide ## πŸ“„ License -MemOS is licensed under the [Apache 2.0 License](./LICENSE). +MemOS is licensed under the [Apache 2.0 License](./LICENSE). \ No newline at end of file From ede5e818bf6bc120cb8aca908e66fe5e1e2f47f9 Mon Sep 17 00:00:00 2001 From: Elvis <1693372324@qq.com> Date: Thu, 25 Dec 2025 21:09:28 +0800 Subject: [PATCH 06/19] fix: update README.md --- README.md | 156 +++++++++++++++++++++--------------------------------- 1 file changed, 60 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index 29a8fc9c8..dc3039e17 100644 --- a/README.md +++ b/README.md @@ -131,88 +131,32 @@ showcasing its capabilities in **information extraction**, **temporal and cross- - **πŸ”Œ Extensible**: Easily extend and customize memory modules, data sources, and LLM integrations. - **πŸ‚ Lightweight Deployment** πŸ†•: Support for quick mode and complete mode deployment options. -## πŸ“¦ Installation -### Install via pip - -```bash -pip install MemoryOS -``` - -### Optional Dependencies - -MemOS provides several optional dependency groups for different features. You can install them based on your needs. - -| Feature | Package Name | -| --------------------- | ------------------------- | -| Tree Memory | `MemoryOS[tree-mem]` | -| Memory Reader | `MemoryOS[mem-reader]` | -| Memory Scheduler | `MemoryOS[mem-scheduler]` | - -Example installation commands: - -```bash -pip install MemoryOS[tree-mem] -pip install MemoryOS[tree-mem,mem-reader] -pip install MemoryOS[mem-scheduler] -pip install MemoryOS[tree-mem,mem-reader,mem-scheduler] -``` - -### External Dependencies - -#### Ollama Support - -To use MemOS with [Ollama](https://ollama.com/), first install the Ollama CLI: - -```bash -curl -fsSL https://ollama.com/install.sh | sh -``` - -#### Transformers Support - -To use functionalities based on the `transformers` library, ensure you have [PyTorch](https://pytorch.org/get-started/locally/) installed (CUDA version recommended for GPU acceleration). - -#### Download Examples +## πŸš€ Quickstart Guide -To download example code, data and configurations, run the following command: +### Get API Key + - Sign up and get started on[`MemOS dashboard`](https://memos-dashboard.openmem.net/cn/quickstart/?source=landing) + - Open the API Keys Console in the MemOS dashboard and copy the API Key into the initialization code -```bash -memos download_examples -``` - -## πŸš€ Getting Started - -### ⭐️ MemOS online API -The easiest way to use MemOS. Equip your agent with memory **in minutes**! - -Sign up and get started on[`MemOS dashboard`](https://memos-dashboard.openmem.net/cn/quickstart/?source=landing). - - -### Self-Hosted Server -1. Get the repository. -```bash -git clone https://github.com/MemTensor/MemOS.git -cd MemOS -pip install -r ./docker/requirements.txt -``` +### Install via pip -2. Configure `docker/.env.example` and copy to `MemOS/.env` -3. Start the service. ```bash -uvicorn memos.api.server_api:app --host 0.0.0.0 --port 8001 --workers 8 +pip install MemoryOS -U ``` -### Interface SDK -#### Here is a quick example showing how to create interface SDK +### Basic Usage -This interface is used to add messages, supporting multiple types of content and batch additions. MemOS will automatically parse the messages and handle memory for reference in subsequent conversations. +- Initialize MemOS client with API Key to start sending requests ```python # Please make sure MemoS is installed (pip install MemoryOS -U) from memos.api.client import MemOSClient # Initialize the client using the API Key client = MemOSClient(api_key="YOUR_API_KEY") +``` +- This API allows you to add one or more messages to a specific conversation. As illustrated in the examples bellow, you can add messages in real time during a user-assistant interaction, import historical messages in bulk, or enrich the conversation with user preferences and behavior data. All added messages are transformed into memories by MemOS, enabling their retrieval in future conversations to support chat history management, user behavior tracking, and personalized interactions. +```python messages = [ {"role": "user", "content": "I have planned to travel to Guangzhou during the summer vacation. What chain hotels are available for accommodation?"}, {"role": "assistant", "content": "You can consider [7 Days, All Seasons, Hilton], and so on."}, @@ -226,30 +170,19 @@ res = client.add_message(messages=messages, user_id=user_id, conversation_id=con print(f"result: {res}") ``` -This interface is used to retrieve the memories of a specified user, returning the memory fragments most relevant to the input query for Agent use. The recalled memory fragments include 'factual memory', 'preference memory', and 'tool memory'. +- This API allows you to query a user’s memory and returns the fragments most relevant to the input. These can serve as references for the model when generating responses. As shown in the examples bellow, You can retrieve memory in real time during a user’s conversation with the AI, or perform a global search across their entire memory to create user profiles or support personalized recommendations, improving both dialogue coherence and personalization. +In the latest update, in addition to β€œFact Memory”, the system now supports β€œPreference Memory”, enabling LLM to respond in a way that better understands the user. ```python -# Please make sure MemoS is installed (pip install MemoryOS -U) -from memos.api.client import MemOSClient - -# Initialize the client using the API Key -client = MemOSClient(api_key="YOUR_API_KEY") - query = "I want to go out to play during National Day. Can you recommend a city I haven't been to and a hotel brand I haven't stayed at?" user_id = "memos_user_123" -conversation_id = "0928" +conversation_id = "0610" res = client.search_memory(query=query, user_id=user_id, conversation_id=conversation_id) print(f"result: {res}") ``` -This interface is used to delete the memory of specified users and supports batch deletion. +- This API is used to delete specified user memories, supporting batch deletion. ```python -# Please make sure MemoS is installed (pip install MemoryOS -U) -from memos.api.client import MemOSClient - -# Initialize the client using the API Key -client = MemOSClient(api_key="YOUR_API_KEY") - user_ids = ["memos_user_123"] # Replace with the memory ID memory_ids = ["6b23b583-f4c4-4a8f-b345-58d0c48fea04"] @@ -258,14 +191,8 @@ res = client.delete_memory(user_ids=user_ids, memory_ids=memory_ids) print(f"result: {res}") ``` -This interface is used to add feedback to messages in the current session, allowing MemOS to correct its memory based on user feedback. +- This API is used to add feedback to current session messages, allowing MemOS to correct memories based on user feedback. ```python -# Please make sure MemoS is installed (pip install MemoryOS -U) -from memos.api.client import MemOSClient - -# Initialize the client using the API Key -client = MemOSClient(api_key="YOUR_API_KEY") - user_id = "memos_user_123" conversation_id = "memos_feedback_conv" feedback_content = "No, let's change it now to a meal allowance of 150 yuan per day and a lodging subsidy of 700 yuan per day for first-tier cities; for second- and third-tier cities, it remains the same as before." @@ -282,14 +209,8 @@ res = client.add_feedback( print(f"result: {res}") ``` -This interface is used to create a knowledgebase associated with a project +- This API is used to create a knowledgebase associated with a project ```python -# Please make sure MemoS is installed (pip install MemoryOS -U) -from memos.api.client import MemOSClient - -# Initialize the client using the API Key -client = MemOSClient(api_key="YOUR_API_KEY") - knowledgebase_name = "Financial Reimbursement Knowledge Base" knowledgebase_description = "A compilation of all knowledge related to the company's financial reimbursements." @@ -300,6 +221,49 @@ res = client.create_knowledgebase( print(f"result: {res}") ``` +### Self-Hosted Server +1. Get the repository. +```bash +git clone https://github.com/MemTensor/MemOS.git +cd MemOS +pip install -r ./docker/requirements.txt +``` +2. Configure `docker/.env.example` and copy to `MemOS/.env` + - The `OPENAI_API_KEY`,`MOS_EMBEDDER_API_KEY`,`MEMRADER_API_KEY` and others can be applied for through [`BaiLian`](https://bailian.console.aliyun.com/?spm=a2c4g.11186623.0.0.2f2165b08fRk4l&tab=api#/api). + - Fill in the corresponding configuration in the `MemOS/.env` file. +3. Start the service. +```bash +uvicorn memos.api.server_api:app --host 0.0.0.0 --port 8001 --workers 2 +``` + +For detailed integration steps, see the [`API Reference`](https://docs-pre.openmem.net/cn/open_source/getting_started/rest_api_server/#fork-memos-%E4%BB%93%E5%BA%93%E4%BB%A3%E7%A0%81httpsgithubcommemtensormemos-%E5%88%B0%E8%87%AA%E5%B7%B1%E7%9A%84%E4%BB%93%E5%BA%93). + +Example + - Add User Memory http://localhost:8000/product/add (POST) +```json + // Request params + { + "user_id": "8736b16e-1d20-4163-980b-a5063c3facdc", + "mem_cube_id": "b32d0977-435d-4828-a86f-4f47f8b55bca", + "messages": [ + { + "role": "user", + "content": "I like strawberry" + } + ], + "async_mode": "sync" + } + ``` + - Query User Memory http://localhost:8000/product/search (POST) + ```json + // Request params + { + "query": "What do I like", + "user_id": "8736b16e-1d20-4163-980b-a5063c3facdc", + "mem_cube_id": "b32d0977-435d-4828-a86f-4f47f8b55bca" + } + ``` + ## πŸ’¬ Community & Support Join our community to ask questions, share your projects, and connect with other developers. From 760dd47e50e52603d67c2e2c1b7addb0b71bff21 Mon Sep 17 00:00:00 2001 From: liji <532311301@qq.com> Date: Thu, 25 Dec 2025 22:01:17 +0800 Subject: [PATCH 07/19] feat: update readme --- README.md | 51 ++++++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index dc3039e17..03d6d22c0 100644 --- a/README.md +++ b/README.md @@ -236,32 +236,37 @@ pip install -r ./docker/requirements.txt uvicorn memos.api.server_api:app --host 0.0.0.0 --port 8001 --workers 2 ``` -For detailed integration steps, see the [`API Reference`](https://docs-pre.openmem.net/cn/open_source/getting_started/rest_api_server/#fork-memos-%E4%BB%93%E5%BA%93%E4%BB%A3%E7%A0%81httpsgithubcommemtensormemos-%E5%88%B0%E8%87%AA%E5%B7%B1%E7%9A%84%E4%BB%93%E5%BA%93). +For detailed integration steps, see the [`API Reference`](https://docs.openmem.net/open_source/getting_started/rest_api_server/#run-locally). + +#### If you prefer to deploy using Docker, please refer to the [`Docker Reference`](https://docs.openmem.net/open_source/getting_started/rest_api_server/#method-1-docker-use-repository-dependency-package-imagestart-recommended-use). + Example - - Add User Memory http://localhost:8000/product/add (POST) -```json - // Request params - { - "user_id": "8736b16e-1d20-4163-980b-a5063c3facdc", - "mem_cube_id": "b32d0977-435d-4828-a86f-4f47f8b55bca", - "messages": [ - { - "role": "user", - "content": "I like strawberry" - } - ], - "async_mode": "sync" - } + - Add User Message + ```bash + curl -X POST http://localhost:8000/product/add \ + -H "Content-Type: application/json" \ + -d '{ + "user_id": "8736b16e-1d20-4163-980b-a5063c3facdc", + "mem_cube_id": "b32d0977-435d-4828-a86f-4f47f8b55bca", + "messages": [ + { + "role": "user", + "content": "I like strawberry" + } + ], + "async_mode": "sync" + }' ``` - - Query User Memory http://localhost:8000/product/search (POST) - ```json - // Request params - { - "query": "What do I like", - "user_id": "8736b16e-1d20-4163-980b-a5063c3facdc", - "mem_cube_id": "b32d0977-435d-4828-a86f-4f47f8b55bca" - } + - Search User Memory + ```bash + curl -X POST http://localhost:8000/product/search \ + -H "Content-Type: application/json" \ + -d '{ + "query": "What do I like", + "user_id": "8736b16e-1d20-4163-980b-a5063c3facdc", + "mem_cube_id": "b32d0977-435d-4828-a86f-4f47f8b55bca" + }' ``` ## πŸ’¬ Community & Support From 335d426a31f33152db6beda5d0e7e16b548ea6df Mon Sep 17 00:00:00 2001 From: liji <532311301@qq.com> Date: Thu, 25 Dec 2025 22:17:41 +0800 Subject: [PATCH 08/19] feat: fix NACOS --- src/memos/api/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/memos/api/config.py b/src/memos/api/config.py index 48a16a6e2..5fef51ca0 100644 --- a/src/memos/api/config.py +++ b/src/memos/api/config.py @@ -201,7 +201,7 @@ def init(cls) -> None: sk = os.getenv("SK") if not (server_addr and data_id and ak and sk): - logger.warning("❌ missing NACOS_SERVER_ADDR / AK / SK / DATA_ID") + logger.warning("missing NACOS_SERVER_ADDR / AK / SK / DATA_ID") return base_url = f"http://{server_addr}/nacos/v1/cs/configs" From 1c7ef048f162860eb8dbc4c6210226345cf5ebab Mon Sep 17 00:00:00 2001 From: harvey_xiang Date: Fri, 26 Dec 2025 10:30:06 +0800 Subject: [PATCH 09/19] feat: add timer log --- src/memos/utils.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/memos/utils.py b/src/memos/utils.py index b57967db0..594180e8f 100644 --- a/src/memos/utils.py +++ b/src/memos/utils.py @@ -1,5 +1,6 @@ import functools import time +import traceback from memos.log import get_logger @@ -35,6 +36,7 @@ def decorator(fn): def wrapper(*args, **kwargs): start = time.perf_counter() exc_type = None + exc_message = None result = None success_flag = False @@ -44,6 +46,7 @@ def wrapper(*args, **kwargs): return result except Exception as e: exc_type = type(e) + exc_message = traceback.format_exc() success_flag = False if fallback is not None and callable(fallback): @@ -76,13 +79,15 @@ def wrapper(*args, **kwargs): status = "SUCCESS" if success_flag else "FAILED" status_info = f", status: {status}" - if not success_flag and exc_type is not None: - status_info += f", error: {exc_type.__name__}" + status_info += ( + f", error_type: {exc_type.__name__}, error_message: {exc_message}" + ) msg = ( f"[TIMER_WITH_STATUS] {log_prefix or fn.__name__} " f"took {elapsed_ms:.0f} ms{status_info}, args: {ctx_str}" + f", result: {result}" ) logger.info(msg) From 4de2d301cc0a7eea8fd3c8fe627ea0e392ee1be4 Mon Sep 17 00:00:00 2001 From: pursues <15180521816@163.com> Date: Fri, 26 Dec 2025 10:43:18 +0800 Subject: [PATCH 10/19] update requirements --- docker/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/requirements.txt b/docker/requirements.txt index 7d2b76734..e7a2b2d9a 100644 --- a/docker/requirements.txt +++ b/docker/requirements.txt @@ -55,6 +55,7 @@ MarkupSafe==3.0.3 mcp==1.21.1 mdurl==0.1.2 more-itertools==10.8.0 +neo4j==5.28.1 numpy==2.3.4 ollama==0.4.9 openai==1.109.1 From 5152f29c2307e6da14efd4752261eaa799bd4081 Mon Sep 17 00:00:00 2001 From: Elvis <1693372324@qq.com> Date: Fri, 26 Dec 2025 14:29:21 +0800 Subject: [PATCH 11/19] fix: 12.26 update README.md --- README.md | 133 ++++++++++++++++++++++++------------------------------ 1 file changed, 59 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 03d6d22c0..a2f713b51 100644 --- a/README.md +++ b/README.md @@ -181,93 +181,78 @@ res = client.search_memory(query=query, user_id=user_id, conversation_id=convers print(f"result: {res}") ``` -- This API is used to delete specified user memories, supporting batch deletion. -```python -user_ids = ["memos_user_123"] -# Replace with the memory ID -memory_ids = ["6b23b583-f4c4-4a8f-b345-58d0c48fea04"] -res = client.delete_memory(user_ids=user_ids, memory_ids=memory_ids) - -print(f"result: {res}") -``` - -- This API is used to add feedback to current session messages, allowing MemOS to correct memories based on user feedback. -```python -user_id = "memos_user_123" -conversation_id = "memos_feedback_conv" -feedback_content = "No, let's change it now to a meal allowance of 150 yuan per day and a lodging subsidy of 700 yuan per day for first-tier cities; for second- and third-tier cities, it remains the same as before." -# Replace with the knowledgebase ID -allow_knowledgebase_ids = ["basee5ec9050-c964-484f-abf1-ce3e8e2aa5b7"] - -res = client.add_feedback( - user_id=user_id, - conversation_id=conversation_id, - feedback_content=feedback_content, - allow_knowledgebase_ids=allow_knowledgebase_ids -) - -print(f"result: {res}") -``` - -- This API is used to create a knowledgebase associated with a project -```python -knowledgebase_name = "Financial Reimbursement Knowledge Base" -knowledgebase_description = "A compilation of all knowledge related to the company's financial reimbursements." - -res = client.create_knowledgebase( - knowledgebase_name=knowledgebase_name, - knowledgebase_description=knowledgebase_description -) -print(f"result: {res}") -``` - ### Self-Hosted Server 1. Get the repository. -```bash -git clone https://github.com/MemTensor/MemOS.git -cd MemOS -pip install -r ./docker/requirements.txt -``` + ```bash + git clone https://github.com/MemTensor/MemOS.git + cd MemOS + pip install -r ./docker/requirements.txt + ``` 2. Configure `docker/.env.example` and copy to `MemOS/.env` - The `OPENAI_API_KEY`,`MOS_EMBEDDER_API_KEY`,`MEMRADER_API_KEY` and others can be applied for through [`BaiLian`](https://bailian.console.aliyun.com/?spm=a2c4g.11186623.0.0.2f2165b08fRk4l&tab=api#/api). - Fill in the corresponding configuration in the `MemOS/.env` file. 3. Start the service. -```bash -uvicorn memos.api.server_api:app --host 0.0.0.0 --port 8001 --workers 2 -``` -For detailed integration steps, see the [`API Reference`](https://docs.openmem.net/open_source/getting_started/rest_api_server/#run-locally). +- Launch via Docker + ###### Tips: Please ensure that Docker Compose is installed successfully and that you have navigated to the docker directory (via `cd docker`) before executing the following command. + ```bash + # Enter docker directory + docker compose up + ``` + ##### If you prefer to deploy using Docker, please refer to the [`Docker Reference`](https://docs.openmem.net/open_source/getting_started/rest_api_server/#method-1-docker-use-repository-dependency-package-imagestart-recommended-use). + +- Launch via the uvicorn command line interface (CLI) + ###### Tips: Please ensure that Neo4j and Qdrant are running before executing the following command. + ```bash + uvicorn memos.api.server_api:app --host 0.0.0.0 --port 8001 --workers 1 + ``` + ##### For detailed integration steps, see the [`CLI Reference`](https://docs.openmem.net/open_source/getting_started/rest_api_server/#method-3client-install-with-CLI). -#### If you prefer to deploy using Docker, please refer to the [`Docker Reference`](https://docs.openmem.net/open_source/getting_started/rest_api_server/#method-1-docker-use-repository-dependency-package-imagestart-recommended-use). Example - Add User Message - ```bash - curl -X POST http://localhost:8000/product/add \ - -H "Content-Type: application/json" \ - -d '{ - "user_id": "8736b16e-1d20-4163-980b-a5063c3facdc", - "mem_cube_id": "b32d0977-435d-4828-a86f-4f47f8b55bca", - "messages": [ - { - "role": "user", - "content": "I like strawberry" - } - ], - "async_mode": "sync" - }' - ``` + ```python + import requests + import json + + data = { + "user_id": "8736b16e-1d20-4163-980b-a5063c3facdc", + "mem_cube_id": "b32d0977-435d-4828-a86f-4f47f8b55bca", + "messages": [ + { + "role": "user", + "content": "I like strawberry" + } + ], + "async_mode": "sync" + } + headers = { + "Content-Type": "application/json" + } + url = "http://localhost:8000/product/add" + + res = requests.post(url=url, headers=headers, data=json.dumps(data)) + print(f"result: {res.json()}") + ``` - Search User Memory - ```bash - curl -X POST http://localhost:8000/product/search \ - -H "Content-Type: application/json" \ - -d '{ - "query": "What do I like", - "user_id": "8736b16e-1d20-4163-980b-a5063c3facdc", - "mem_cube_id": "b32d0977-435d-4828-a86f-4f47f8b55bca" - }' - ``` + ```python + import requests + import json + + data = { + "query": "What do I like", + "user_id": "8736b16e-1d20-4163-980b-a5063c3facdc", + "mem_cube_id": "b32d0977-435d-4828-a86f-4f47f8b55bca" + } + headers = { + "Content-Type": "application/json" + } + url = "http://localhost:8000/product/search" + + res = requests.post(url=url, headers=headers, data=json.dumps(data)) + print(f"result: {res.json()}") + ``` ## πŸ’¬ Community & Support From 7cdd74072e88e9fb58a3700ff2cc30525277a8dd Mon Sep 17 00:00:00 2001 From: pursues <15180521816@163.com> Date: Fri, 26 Dec 2025 15:28:47 +0800 Subject: [PATCH 12/19] change local server name --- src/memos/api/server_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/memos/api/server_api.py b/src/memos/api/server_api.py index 0dfef99d9..ac9ed8d88 100644 --- a/src/memos/api/server_api.py +++ b/src/memos/api/server_api.py @@ -13,8 +13,8 @@ logger = logging.getLogger(__name__) app = FastAPI( - title="MemOS Product REST APIs", - description="A REST API for managing multiple users with MemOS Product.", + title="MemOS Server REST APIs", + description="A REST API for managing multiple users with MemOS Server.", version="1.0.1", ) From eb11e0a2f316328b41867641025cad2f8823f829 Mon Sep 17 00:00:00 2001 From: pursues <15180521816@163.com> Date: Fri, 26 Dec 2025 17:44:57 +0800 Subject: [PATCH 13/19] update docker-compose.yml --- docker/docker-compose.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 0f680505f..0a8e2c634 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -53,7 +53,7 @@ services: - "6333:6333" # REST API - "6334:6334" # gRPC API volumes: - - ./qdrant_data:/qdrant/storage + - qdrant_data:/qdrant/storage environment: QDRANT__SERVICE__GRPC_PORT: 6334 QDRANT__SERVICE__HTTP_PORT: 6333 @@ -64,6 +64,7 @@ services: volumes: neo4j_data: neo4j_logs: + qdrant_data: networks: memos_network: From 9f71f0a63dd45111d4e06f0d8b30666c51aa634d Mon Sep 17 00:00:00 2001 From: chentang Date: Fri, 26 Dec 2025 18:00:41 +0800 Subject: [PATCH 14/19] fix: issues caused by no reading default use_redis from env --- src/memos/mem_scheduler/schemas/general_schemas.py | 4 +++- src/memos/mem_scheduler/task_schedule_modules/redis_queue.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/memos/mem_scheduler/schemas/general_schemas.py b/src/memos/mem_scheduler/schemas/general_schemas.py index f4ad9fe48..06910ba17 100644 --- a/src/memos/mem_scheduler/schemas/general_schemas.py +++ b/src/memos/mem_scheduler/schemas/general_schemas.py @@ -1,3 +1,5 @@ +import os + from pathlib import Path @@ -21,7 +23,7 @@ DEFAULT_MAX_INTERNAL_MESSAGE_QUEUE_SIZE = -1 DEFAULT_TOP_K = 5 DEFAULT_CONTEXT_WINDOW_SIZE = 5 -DEFAULT_USE_REDIS_QUEUE = True +DEFAULT_USE_REDIS_QUEUE = os.getenv("MEMSCHEDULER_USE_REDIS_QUEUE", "False").lower() == "true" DEFAULT_MULTI_TASK_RUNNING_TIMEOUT = 30 DEFAULT_SCHEDULER_RETRIEVER_BATCH_SIZE = 20 DEFAULT_SCHEDULER_RETRIEVER_RETRIES = 1 diff --git a/src/memos/mem_scheduler/task_schedule_modules/redis_queue.py b/src/memos/mem_scheduler/task_schedule_modules/redis_queue.py index c27eb5f50..78b38aa80 100644 --- a/src/memos/mem_scheduler/task_schedule_modules/redis_queue.py +++ b/src/memos/mem_scheduler/task_schedule_modules/redis_queue.py @@ -785,7 +785,7 @@ def qsize(self) -> dict: Total number of messages across all matching streams. """ if not self._redis_conn: - return 0 + return {} total_size = 0 try: From 20a0ac79604e0a5a04d6e2c6eb053b3cb2d0e317 Mon Sep 17 00:00:00 2001 From: liji <532311301@qq.com> Date: Fri, 26 Dec 2025 21:39:53 +0800 Subject: [PATCH 15/19] feat: fix requirements --- docker/requirements-full.txt | 4 ++-- docker/requirements.txt | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docker/requirements-full.txt b/docker/requirements-full.txt index 538f5e578..e8911cbb9 100644 --- a/docker/requirements-full.txt +++ b/docker/requirements-full.txt @@ -159,7 +159,7 @@ tzdata==2025.2 ujson==5.10.0 urllib3==2.5.0 uvicorn==0.35.0 -uvloop==0.21.0 +uvloop==0.22.1; sys_platform != 'win32' volcengine-python-sdk==4.0.6 watchfiles==1.1.0 websockets==15.0.1 @@ -179,7 +179,7 @@ pathable==0.4.4 pathvalidate==3.3.1 platformdirs==4.5.0 pluggy==1.6.0 -psycopg2-binary==2.9.9 +psycopg2-binary==2.9.11 py-key-value-aio==0.2.8 py-key-value-shared==0.2.8 PyJWT==2.10.1 diff --git a/docker/requirements.txt b/docker/requirements.txt index e7a2b2d9a..dd4b089b8 100644 --- a/docker/requirements.txt +++ b/docker/requirements.txt @@ -55,7 +55,6 @@ MarkupSafe==3.0.3 mcp==1.21.1 mdurl==0.1.2 more-itertools==10.8.0 -neo4j==5.28.1 numpy==2.3.4 ollama==0.4.9 openai==1.109.1 @@ -71,7 +70,7 @@ pluggy==1.6.0 portalocker==2.8.0 prometheus_client==0.23.1 protobuf==6.33.1 -psycopg2-binary==2.9.9 +psycopg2-binary==2.9.11 py-key-value-aio==0.2.8 py-key-value-shared==0.2.8 pycparser==2.23 @@ -123,6 +122,6 @@ tzdata==2025.2 ujson==5.11.0 urllib3==2.5.0 uvicorn==0.38.0 -uvloop==0.22.1 +uvloop==0.22.1; sys_platform != 'win32' watchfiles==1.1.1 websockets==15.0.1 \ No newline at end of file From 5c7e40eafb6018b21729fc036cc7b6738cddf58e Mon Sep 17 00:00:00 2001 From: pursues <15180521816@163.com> Date: Tue, 30 Dec 2025 15:41:21 +0800 Subject: [PATCH 16/19] add neo4j --- docker/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/requirements.txt b/docker/requirements.txt index dd4b089b8..939b13678 100644 --- a/docker/requirements.txt +++ b/docker/requirements.txt @@ -55,6 +55,7 @@ MarkupSafe==3.0.3 mcp==1.21.1 mdurl==0.1.2 more-itertools==10.8.0 +neo4j==5.28.1 numpy==2.3.4 ollama==0.4.9 openai==1.109.1 From 0d278af12367d8f99c2b854957a60a0c199f1305 Mon Sep 17 00:00:00 2001 From: fridayL Date: Sun, 4 Jan 2026 16:25:39 +0800 Subject: [PATCH 17/19] fix: logs context and empty embedding --- .../memories/textual/tree_text_memory/retrieve/searcher.py | 2 ++ src/memos/multi_mem_cube/composite_cube.py | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/memos/memories/textual/tree_text_memory/retrieve/searcher.py b/src/memos/memories/textual/tree_text_memory/retrieve/searcher.py index dc47dd4d7..cad9ab64b 100644 --- a/src/memos/memories/textual/tree_text_memory/retrieve/searcher.py +++ b/src/memos/memories/textual/tree_text_memory/retrieve/searcher.py @@ -313,6 +313,8 @@ def _retrieve_simple( ) logger.info(f"[SIMPLESEARCH] Items count: {len(items)}") documents = [getattr(item, "memory", "") for item in items] + if not documents: + return [] documents_embeddings = self.embedder.embed(documents) similarity_matrix = cosine_similarity_matrix(documents_embeddings) selected_indices, _ = find_best_unrelated_subgroup(documents, similarity_matrix) diff --git a/src/memos/multi_mem_cube/composite_cube.py b/src/memos/multi_mem_cube/composite_cube.py index 420856407..c1017bfae 100644 --- a/src/memos/multi_mem_cube/composite_cube.py +++ b/src/memos/multi_mem_cube/composite_cube.py @@ -1,9 +1,10 @@ from __future__ import annotations -from concurrent.futures import ThreadPoolExecutor, as_completed +from concurrent.futures import as_completed from dataclasses import dataclass from typing import TYPE_CHECKING, Any +from memos.context.context import ContextThreadPoolExecutor from memos.multi_mem_cube.views import MemCubeView @@ -52,7 +53,7 @@ def _search_single_cube(view: SingleCubeView) -> dict[str, Any]: return view.search_memories(search_req) # parallel search for each cube - with ThreadPoolExecutor(max_workers=2) as executor: + with ContextThreadPoolExecutor(max_workers=2) as executor: future_to_view = { executor.submit(_search_single_cube, view): view for view in self.cube_views } From 4d38ad0ae43c9955cf2482a110bf988b31af5ba7 Mon Sep 17 00:00:00 2001 From: pursues <15180521816@163.com> Date: Mon, 5 Jan 2026 16:41:23 +0800 Subject: [PATCH 18/19] fix reranker --- docker/.env.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/.env.example b/docker/.env.example index 3397a8029..f1979fe4c 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -49,9 +49,9 @@ MOS_EMBEDDER_API_BASE=http://localhost:8000/v1 # required when universal_api # embedding model key MOS_EMBEDDER_API_KEY=EMPTY # required when universal_api OLLAMA_API_BASE=http://localhost:11434 # required when backend=ollama -# reanker config +# reranker config MOS_RERANKER_BACKEND=http_bge # http_bge | http_bge_strategy | cosine_local -# reanker url +# reranker url MOS_RERANKER_URL=http://localhost:8001 # required when backend=http_bge* # reranker model MOS_RERANKER_MODEL=bge-reranker-v2-m3 # siliconflow β†’ use BAAI/bge-reranker-v2-m3 From 67b9d6b8fbdec5ef79cf5c2307d4289a258a9c45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B8=AD=E9=98=B3=E9=98=B3?= Date: Wed, 7 Jan 2026 12:08:15 +0800 Subject: [PATCH 19/19] fix: conflict --- docker/.env.example | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docker/.env.example b/docker/.env.example index 9a2da3b5b..ee26c7bcd 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -77,12 +77,6 @@ MODEL=gpt-4o-mini EMBEDDING_MODEL=nomic-embed-text:latest -# External Services (for evaluation scripts) -ZEP_API_KEY=your_zep_api_key_here -MEM0_API_KEY=your_mem0_api_key_here -MODEL=gpt-4o-mini -EMBEDDING_MODEL=nomic-embed-text:latest - ## Internet search & preference memory # Enable web search ENABLE_INTERNET=false