MemoryOS 0.2.1__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of MemoryOS might be problematic. Click here for more details.
- {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/METADATA +7 -1
- {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/RECORD +87 -64
- memos/__init__.py +1 -1
- memos/api/config.py +158 -69
- memos/api/context/context.py +147 -0
- memos/api/context/dependencies.py +101 -0
- memos/api/product_models.py +5 -1
- memos/api/routers/product_router.py +54 -26
- memos/configs/graph_db.py +49 -1
- memos/configs/internet_retriever.py +19 -0
- memos/configs/mem_os.py +5 -0
- memos/configs/mem_reader.py +9 -0
- memos/configs/mem_scheduler.py +54 -18
- memos/configs/mem_user.py +58 -0
- memos/graph_dbs/base.py +38 -3
- memos/graph_dbs/factory.py +2 -0
- memos/graph_dbs/nebular.py +1612 -0
- memos/graph_dbs/neo4j.py +18 -9
- memos/log.py +6 -1
- memos/mem_cube/utils.py +13 -6
- memos/mem_os/core.py +157 -37
- memos/mem_os/main.py +2 -2
- memos/mem_os/product.py +252 -201
- memos/mem_os/utils/default_config.py +1 -1
- memos/mem_os/utils/format_utils.py +281 -70
- memos/mem_os/utils/reference_utils.py +133 -0
- memos/mem_reader/simple_struct.py +13 -5
- memos/mem_scheduler/base_scheduler.py +239 -266
- memos/mem_scheduler/{modules → general_modules}/base.py +4 -5
- memos/mem_scheduler/{modules → general_modules}/dispatcher.py +57 -21
- memos/mem_scheduler/general_modules/misc.py +104 -0
- memos/mem_scheduler/{modules → general_modules}/rabbitmq_service.py +12 -10
- memos/mem_scheduler/{modules → general_modules}/redis_service.py +1 -1
- memos/mem_scheduler/general_modules/retriever.py +199 -0
- memos/mem_scheduler/general_modules/scheduler_logger.py +261 -0
- memos/mem_scheduler/general_scheduler.py +243 -80
- memos/mem_scheduler/monitors/__init__.py +0 -0
- memos/mem_scheduler/monitors/dispatcher_monitor.py +305 -0
- memos/mem_scheduler/{modules/monitor.py → monitors/general_monitor.py} +106 -57
- memos/mem_scheduler/mos_for_test_scheduler.py +23 -20
- memos/mem_scheduler/schemas/__init__.py +0 -0
- memos/mem_scheduler/schemas/general_schemas.py +44 -0
- memos/mem_scheduler/schemas/message_schemas.py +149 -0
- memos/mem_scheduler/schemas/monitor_schemas.py +337 -0
- memos/mem_scheduler/utils/__init__.py +0 -0
- memos/mem_scheduler/utils/filter_utils.py +176 -0
- memos/mem_scheduler/utils/misc_utils.py +102 -0
- memos/mem_user/factory.py +94 -0
- memos/mem_user/mysql_persistent_user_manager.py +271 -0
- memos/mem_user/mysql_user_manager.py +500 -0
- memos/mem_user/persistent_factory.py +96 -0
- memos/mem_user/user_manager.py +4 -4
- memos/memories/activation/item.py +5 -1
- memos/memories/activation/kv.py +20 -8
- memos/memories/textual/base.py +2 -2
- memos/memories/textual/general.py +36 -92
- memos/memories/textual/item.py +5 -33
- memos/memories/textual/tree.py +13 -7
- memos/memories/textual/tree_text_memory/organize/{conflict.py → handler.py} +34 -50
- memos/memories/textual/tree_text_memory/organize/manager.py +8 -96
- memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +49 -43
- memos/memories/textual/tree_text_memory/organize/reorganizer.py +107 -142
- memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +229 -0
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +6 -3
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +11 -0
- memos/memories/textual/tree_text_memory/retrieve/recall.py +15 -8
- memos/memories/textual/tree_text_memory/retrieve/reranker.py +1 -1
- memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +2 -0
- memos/memories/textual/tree_text_memory/retrieve/searcher.py +191 -116
- memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +47 -15
- memos/memories/textual/tree_text_memory/retrieve/utils.py +11 -7
- memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +62 -58
- memos/memos_tools/dinding_report_bot.py +422 -0
- memos/memos_tools/lockfree_dict.py +120 -0
- memos/memos_tools/notification_service.py +44 -0
- memos/memos_tools/notification_utils.py +96 -0
- memos/memos_tools/thread_safe_dict.py +288 -0
- memos/settings.py +3 -1
- memos/templates/mem_reader_prompts.py +4 -1
- memos/templates/mem_scheduler_prompts.py +62 -15
- memos/templates/mos_prompts.py +116 -0
- memos/templates/tree_reorganize_prompts.py +24 -17
- memos/utils.py +19 -0
- memos/mem_scheduler/modules/misc.py +0 -39
- memos/mem_scheduler/modules/retriever.py +0 -268
- memos/mem_scheduler/modules/schemas.py +0 -328
- memos/mem_scheduler/utils.py +0 -75
- memos/memories/textual/tree_text_memory/organize/redundancy.py +0 -193
- {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/LICENSE +0 -0
- {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/WHEEL +0 -0
- {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/entry_points.txt +0 -0
- /memos/mem_scheduler/{modules → general_modules}/__init__.py +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lock-free dictionary implementation using copy-on-write strategy.
|
|
3
|
+
This provides better performance but uses more memory.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import threading
|
|
7
|
+
|
|
8
|
+
from collections.abc import ItemsView, Iterator, KeysView, ValuesView
|
|
9
|
+
from typing import Generic, TypeVar
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
K = TypeVar("K")
|
|
13
|
+
V = TypeVar("V")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CopyOnWriteDict(Generic[K, V]):
|
|
17
|
+
"""
|
|
18
|
+
A lock-free dictionary using copy-on-write strategy.
|
|
19
|
+
|
|
20
|
+
Reads are completely lock-free and very fast.
|
|
21
|
+
Writes create a new copy of the dictionary.
|
|
22
|
+
Uses more memory but provides excellent read performance.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, initial_dict: dict[K, V] | None = None):
|
|
26
|
+
"""Initialize with optional initial dictionary."""
|
|
27
|
+
self._dict = initial_dict.copy() if initial_dict else {}
|
|
28
|
+
self._write_lock = threading.Lock() # Only for writes
|
|
29
|
+
|
|
30
|
+
def __getitem__(self, key: K) -> V:
|
|
31
|
+
"""Get item by key - completely lock-free."""
|
|
32
|
+
return self._dict[key]
|
|
33
|
+
|
|
34
|
+
def __setitem__(self, key: K, value: V) -> None:
|
|
35
|
+
"""Set item by key - uses copy-on-write."""
|
|
36
|
+
with self._write_lock:
|
|
37
|
+
# Create a new dictionary with the update
|
|
38
|
+
new_dict = self._dict.copy()
|
|
39
|
+
new_dict[key] = value
|
|
40
|
+
# Atomic replacement
|
|
41
|
+
self._dict = new_dict
|
|
42
|
+
|
|
43
|
+
def __delitem__(self, key: K) -> None:
|
|
44
|
+
"""Delete item by key - uses copy-on-write."""
|
|
45
|
+
with self._write_lock:
|
|
46
|
+
new_dict = self._dict.copy()
|
|
47
|
+
del new_dict[key]
|
|
48
|
+
self._dict = new_dict
|
|
49
|
+
|
|
50
|
+
def __contains__(self, key: K) -> bool:
|
|
51
|
+
"""Check if key exists - completely lock-free."""
|
|
52
|
+
return key in self._dict
|
|
53
|
+
|
|
54
|
+
def __len__(self) -> int:
|
|
55
|
+
"""Get length - completely lock-free."""
|
|
56
|
+
return len(self._dict)
|
|
57
|
+
|
|
58
|
+
def __bool__(self) -> bool:
|
|
59
|
+
"""Check if not empty - completely lock-free."""
|
|
60
|
+
return bool(self._dict)
|
|
61
|
+
|
|
62
|
+
def __iter__(self) -> Iterator[K]:
|
|
63
|
+
"""Iterate over keys - completely lock-free."""
|
|
64
|
+
return iter(self._dict.keys())
|
|
65
|
+
|
|
66
|
+
def get(self, key: K, default: V | None = None) -> V:
|
|
67
|
+
"""Get with default - completely lock-free."""
|
|
68
|
+
return self._dict.get(key, default)
|
|
69
|
+
|
|
70
|
+
def keys(self) -> KeysView[K]:
|
|
71
|
+
"""Get keys - completely lock-free."""
|
|
72
|
+
return self._dict.keys()
|
|
73
|
+
|
|
74
|
+
def values(self) -> ValuesView[V]:
|
|
75
|
+
"""Get values - completely lock-free."""
|
|
76
|
+
return self._dict.values()
|
|
77
|
+
|
|
78
|
+
def items(self) -> ItemsView[K, V]:
|
|
79
|
+
"""Get items - completely lock-free."""
|
|
80
|
+
return self._dict.items()
|
|
81
|
+
|
|
82
|
+
def copy(self) -> dict[K, V]:
|
|
83
|
+
"""Create a copy - completely lock-free."""
|
|
84
|
+
return self._dict.copy()
|
|
85
|
+
|
|
86
|
+
def update(self, *args, **kwargs) -> None:
|
|
87
|
+
"""Update dictionary - uses copy-on-write."""
|
|
88
|
+
with self._write_lock:
|
|
89
|
+
new_dict = self._dict.copy()
|
|
90
|
+
new_dict.update(*args, **kwargs)
|
|
91
|
+
self._dict = new_dict
|
|
92
|
+
|
|
93
|
+
def clear(self) -> None:
|
|
94
|
+
"""Clear all items."""
|
|
95
|
+
with self._write_lock:
|
|
96
|
+
self._dict = {}
|
|
97
|
+
|
|
98
|
+
def pop(self, key: K, *args) -> V:
|
|
99
|
+
"""Pop item by key."""
|
|
100
|
+
with self._write_lock:
|
|
101
|
+
new_dict = self._dict.copy()
|
|
102
|
+
result = new_dict.pop(key, *args)
|
|
103
|
+
self._dict = new_dict
|
|
104
|
+
return result
|
|
105
|
+
|
|
106
|
+
def setdefault(self, key: K, default: V | None = None) -> V:
|
|
107
|
+
"""Set default value for key if not exists."""
|
|
108
|
+
# Fast path for existing keys
|
|
109
|
+
if key in self._dict:
|
|
110
|
+
return self._dict[key]
|
|
111
|
+
|
|
112
|
+
with self._write_lock:
|
|
113
|
+
# Double-check after acquiring lock
|
|
114
|
+
if key in self._dict:
|
|
115
|
+
return self._dict[key]
|
|
116
|
+
|
|
117
|
+
new_dict = self._dict.copy()
|
|
118
|
+
result = new_dict.setdefault(key, default)
|
|
119
|
+
self._dict = new_dict
|
|
120
|
+
return result
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Simple online_bot integration utility.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_online_bot_function() -> Callable | None:
|
|
14
|
+
"""
|
|
15
|
+
Get online_bot function if available, otherwise return None.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
online_bot function if available, None otherwise
|
|
19
|
+
"""
|
|
20
|
+
try:
|
|
21
|
+
from memos.memos_tools.dinding_report_bot import online_bot
|
|
22
|
+
|
|
23
|
+
logger.info("online_bot function loaded successfully")
|
|
24
|
+
return online_bot
|
|
25
|
+
except ImportError as e:
|
|
26
|
+
logger.warning(f"Failed to import online_bot: {e}, returning None")
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_error_bot_function() -> Callable | None:
|
|
31
|
+
"""
|
|
32
|
+
Get error_bot function if available, otherwise return None.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
error_bot function if available, None otherwise
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
from memos.memos_tools.dinding_report_bot import error_bot
|
|
39
|
+
|
|
40
|
+
logger.info("error_bot function loaded successfully")
|
|
41
|
+
return error_bot
|
|
42
|
+
except ImportError as e:
|
|
43
|
+
logger.warning(f"Failed to import error_bot: {e}, returning None")
|
|
44
|
+
return None
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Notification utilities for MemOS product.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def send_online_bot_notification(
|
|
15
|
+
online_bot: Callable | None,
|
|
16
|
+
header_name: str,
|
|
17
|
+
sub_title_name: str,
|
|
18
|
+
title_color: str,
|
|
19
|
+
other_data1: dict[str, Any],
|
|
20
|
+
other_data2: dict[str, Any],
|
|
21
|
+
emoji: dict[str, str],
|
|
22
|
+
) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Send notification via online_bot if available.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
online_bot: The online_bot function or None
|
|
28
|
+
header_name: Header name for the report
|
|
29
|
+
sub_title_name: Subtitle for the report
|
|
30
|
+
title_color: Title color
|
|
31
|
+
other_data1: First data dict
|
|
32
|
+
other_data2: Second data dict
|
|
33
|
+
emoji: Emoji configuration dict
|
|
34
|
+
"""
|
|
35
|
+
if online_bot is None:
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
online_bot(
|
|
40
|
+
header_name=header_name,
|
|
41
|
+
sub_title_name=sub_title_name,
|
|
42
|
+
title_color=title_color,
|
|
43
|
+
other_data1=other_data1,
|
|
44
|
+
other_data2=other_data2,
|
|
45
|
+
emoji=emoji,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
logger.info(f"Online bot notification sent successfully: {header_name}")
|
|
49
|
+
|
|
50
|
+
except Exception as e:
|
|
51
|
+
logger.warning(f"Failed to send online bot notification: {e}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def send_error_bot_notification(
|
|
55
|
+
error_bot: Callable | None,
|
|
56
|
+
err: str,
|
|
57
|
+
title: str = "MemOS Error",
|
|
58
|
+
level: str = "P2",
|
|
59
|
+
user_ids: list | None = None,
|
|
60
|
+
) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Send error alert if error_bot is available.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
error_bot: The error_bot function or None
|
|
66
|
+
err: Error message
|
|
67
|
+
title: Alert title
|
|
68
|
+
level: Alert level (P0, P1, P2)
|
|
69
|
+
user_ids: List of user IDs to notify
|
|
70
|
+
"""
|
|
71
|
+
if error_bot is None:
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
error_bot(
|
|
76
|
+
err=err,
|
|
77
|
+
title=title,
|
|
78
|
+
level=level,
|
|
79
|
+
user_ids=user_ids or [],
|
|
80
|
+
)
|
|
81
|
+
logger.info(f"Error alert sent successfully: {title}")
|
|
82
|
+
except Exception as e:
|
|
83
|
+
logger.warning(f"Failed to send error alert: {e}")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# Keep backward compatibility
|
|
87
|
+
def send_error_alert(
|
|
88
|
+
error_bot: Callable | None,
|
|
89
|
+
error_message: str,
|
|
90
|
+
title: str = "MemOS Error",
|
|
91
|
+
level: str = "P2",
|
|
92
|
+
) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Send error alert if error_bot is available (backward compatibility).
|
|
95
|
+
"""
|
|
96
|
+
send_error_bot_notification(error_bot, error_message, title, level)
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Thread-safe dictionary wrapper for concurrent access with optimized read-write locks.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import threading
|
|
6
|
+
|
|
7
|
+
from collections.abc import ItemsView, Iterator, KeysView, ValuesView
|
|
8
|
+
from typing import Generic, TypeVar
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
K = TypeVar("K")
|
|
12
|
+
V = TypeVar("V")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ReadWriteLock:
|
|
16
|
+
"""A simple read-write lock implementation. use for product-server scenario"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self._read_ready = threading.Condition(threading.RLock())
|
|
20
|
+
self._readers = 0
|
|
21
|
+
|
|
22
|
+
def acquire_read(self):
|
|
23
|
+
"""Acquire a read lock. Multiple readers can hold the lock simultaneously."""
|
|
24
|
+
self._read_ready.acquire()
|
|
25
|
+
try:
|
|
26
|
+
self._readers += 1
|
|
27
|
+
finally:
|
|
28
|
+
self._read_ready.release()
|
|
29
|
+
|
|
30
|
+
def release_read(self):
|
|
31
|
+
"""Release a read lock."""
|
|
32
|
+
self._read_ready.acquire()
|
|
33
|
+
try:
|
|
34
|
+
self._readers -= 1
|
|
35
|
+
if self._readers == 0:
|
|
36
|
+
self._read_ready.notify_all()
|
|
37
|
+
finally:
|
|
38
|
+
self._read_ready.release()
|
|
39
|
+
|
|
40
|
+
def acquire_write(self):
|
|
41
|
+
"""Acquire a write lock. Only one writer can hold the lock."""
|
|
42
|
+
self._read_ready.acquire()
|
|
43
|
+
while self._readers > 0:
|
|
44
|
+
self._read_ready.wait()
|
|
45
|
+
|
|
46
|
+
def release_write(self):
|
|
47
|
+
"""Release a write lock."""
|
|
48
|
+
self._read_ready.release()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ThreadSafeDict(Generic[K, V]):
|
|
52
|
+
"""
|
|
53
|
+
A thread-safe dictionary wrapper with optimized read-write locks.
|
|
54
|
+
|
|
55
|
+
This class allows multiple concurrent readers while ensuring exclusive access for writers.
|
|
56
|
+
Read operations (get, contains, iteration) can happen concurrently.
|
|
57
|
+
Write operations (set, delete, update) are exclusive.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, initial_dict: dict[K, V] | None = None):
|
|
61
|
+
"""
|
|
62
|
+
Initialize the thread-safe dictionary.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
initial_dict: Optional initial dictionary to copy from
|
|
66
|
+
"""
|
|
67
|
+
self._dict: dict[K, V] = initial_dict.copy() if initial_dict else {}
|
|
68
|
+
self._lock = ReadWriteLock()
|
|
69
|
+
|
|
70
|
+
def __getitem__(self, key: K) -> V:
|
|
71
|
+
"""Get item by key."""
|
|
72
|
+
self._lock.acquire_read()
|
|
73
|
+
try:
|
|
74
|
+
return self._dict[key]
|
|
75
|
+
finally:
|
|
76
|
+
self._lock.release_read()
|
|
77
|
+
|
|
78
|
+
def __setitem__(self, key: K, value: V) -> None:
|
|
79
|
+
"""Set item by key."""
|
|
80
|
+
self._lock.acquire_write()
|
|
81
|
+
try:
|
|
82
|
+
self._dict[key] = value
|
|
83
|
+
finally:
|
|
84
|
+
self._lock.release_write()
|
|
85
|
+
|
|
86
|
+
def __delitem__(self, key: K) -> None:
|
|
87
|
+
"""Delete item by key."""
|
|
88
|
+
self._lock.acquire_write()
|
|
89
|
+
try:
|
|
90
|
+
del self._dict[key]
|
|
91
|
+
finally:
|
|
92
|
+
self._lock.release_write()
|
|
93
|
+
|
|
94
|
+
def __contains__(self, key: K) -> bool:
|
|
95
|
+
"""Check if key exists in dictionary."""
|
|
96
|
+
self._lock.acquire_read()
|
|
97
|
+
try:
|
|
98
|
+
return key in self._dict
|
|
99
|
+
finally:
|
|
100
|
+
self._lock.release_read()
|
|
101
|
+
|
|
102
|
+
def __len__(self) -> int:
|
|
103
|
+
"""Get length of dictionary."""
|
|
104
|
+
self._lock.acquire_read()
|
|
105
|
+
try:
|
|
106
|
+
return len(self._dict)
|
|
107
|
+
finally:
|
|
108
|
+
self._lock.release_read()
|
|
109
|
+
|
|
110
|
+
def __bool__(self) -> bool:
|
|
111
|
+
"""Check if dictionary is not empty."""
|
|
112
|
+
self._lock.acquire_read()
|
|
113
|
+
try:
|
|
114
|
+
return bool(self._dict)
|
|
115
|
+
finally:
|
|
116
|
+
self._lock.release_read()
|
|
117
|
+
|
|
118
|
+
def __iter__(self) -> Iterator[K]:
|
|
119
|
+
"""Iterate over keys. Returns a snapshot to avoid iteration issues."""
|
|
120
|
+
self._lock.acquire_read()
|
|
121
|
+
try:
|
|
122
|
+
# Return a snapshot of keys to avoid iteration issues
|
|
123
|
+
return iter(list(self._dict.keys()))
|
|
124
|
+
finally:
|
|
125
|
+
self._lock.release_read()
|
|
126
|
+
|
|
127
|
+
def get(self, key: K, default: V | None = None) -> V:
|
|
128
|
+
"""Get item by key with optional default."""
|
|
129
|
+
self._lock.acquire_read()
|
|
130
|
+
try:
|
|
131
|
+
return self._dict.get(key, default)
|
|
132
|
+
finally:
|
|
133
|
+
self._lock.release_read()
|
|
134
|
+
|
|
135
|
+
def pop(self, key: K, *args) -> V:
|
|
136
|
+
"""Pop item by key."""
|
|
137
|
+
self._lock.acquire_write()
|
|
138
|
+
try:
|
|
139
|
+
return self._dict.pop(key, *args)
|
|
140
|
+
finally:
|
|
141
|
+
self._lock.release_write()
|
|
142
|
+
|
|
143
|
+
def update(self, *args, **kwargs) -> None:
|
|
144
|
+
"""Update dictionary."""
|
|
145
|
+
self._lock.acquire_write()
|
|
146
|
+
try:
|
|
147
|
+
self._dict.update(*args, **kwargs)
|
|
148
|
+
finally:
|
|
149
|
+
self._lock.release_write()
|
|
150
|
+
|
|
151
|
+
def clear(self) -> None:
|
|
152
|
+
"""Clear all items."""
|
|
153
|
+
self._lock.acquire_write()
|
|
154
|
+
try:
|
|
155
|
+
self._dict.clear()
|
|
156
|
+
finally:
|
|
157
|
+
self._lock.release_write()
|
|
158
|
+
|
|
159
|
+
def keys(self) -> KeysView[K]:
|
|
160
|
+
"""Get dictionary keys view (snapshot)."""
|
|
161
|
+
self._lock.acquire_read()
|
|
162
|
+
try:
|
|
163
|
+
return list(self._dict.keys())
|
|
164
|
+
finally:
|
|
165
|
+
self._lock.release_read()
|
|
166
|
+
|
|
167
|
+
def values(self) -> ValuesView[V]:
|
|
168
|
+
"""Get dictionary values view (snapshot)."""
|
|
169
|
+
self._lock.acquire_read()
|
|
170
|
+
try:
|
|
171
|
+
return list(self._dict.values())
|
|
172
|
+
finally:
|
|
173
|
+
self._lock.release_read()
|
|
174
|
+
|
|
175
|
+
def items(self) -> ItemsView[K, V]:
|
|
176
|
+
"""Get dictionary items view (snapshot)."""
|
|
177
|
+
self._lock.acquire_read()
|
|
178
|
+
try:
|
|
179
|
+
return list(self._dict.items())
|
|
180
|
+
finally:
|
|
181
|
+
self._lock.release_read()
|
|
182
|
+
|
|
183
|
+
def copy(self) -> dict[K, V]:
|
|
184
|
+
"""Create a copy of the dictionary."""
|
|
185
|
+
self._lock.acquire_read()
|
|
186
|
+
try:
|
|
187
|
+
return self._dict.copy()
|
|
188
|
+
finally:
|
|
189
|
+
self._lock.release_read()
|
|
190
|
+
|
|
191
|
+
def setdefault(self, key: K, default: V | None = None) -> V:
|
|
192
|
+
"""Set default value for key if not exists."""
|
|
193
|
+
self._lock.acquire_write()
|
|
194
|
+
try:
|
|
195
|
+
return self._dict.setdefault(key, default)
|
|
196
|
+
finally:
|
|
197
|
+
self._lock.release_write()
|
|
198
|
+
|
|
199
|
+
def __repr__(self) -> str:
|
|
200
|
+
"""String representation."""
|
|
201
|
+
self._lock.acquire_read()
|
|
202
|
+
try:
|
|
203
|
+
return f"ThreadSafeDict({self._dict})"
|
|
204
|
+
finally:
|
|
205
|
+
self._lock.release_read()
|
|
206
|
+
|
|
207
|
+
def __str__(self) -> str:
|
|
208
|
+
"""String representation."""
|
|
209
|
+
self._lock.acquire_read()
|
|
210
|
+
try:
|
|
211
|
+
return str(self._dict)
|
|
212
|
+
finally:
|
|
213
|
+
self._lock.release_read()
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class SimpleThreadSafeDict(Generic[K, V]):
|
|
217
|
+
"""
|
|
218
|
+
Simple thread-safe dictionary with exclusive locks for all operations.
|
|
219
|
+
Use this if you prefer simplicity over performance.
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
def __init__(self, initial_dict: dict[K, V] | None = None):
|
|
223
|
+
self._dict: dict[K, V] = initial_dict.copy() if initial_dict else {}
|
|
224
|
+
self._lock = threading.RLock()
|
|
225
|
+
|
|
226
|
+
def __getitem__(self, key: K) -> V:
|
|
227
|
+
with self._lock:
|
|
228
|
+
return self._dict[key]
|
|
229
|
+
|
|
230
|
+
def __setitem__(self, key: K, value: V) -> None:
|
|
231
|
+
with self._lock:
|
|
232
|
+
self._dict[key] = value
|
|
233
|
+
|
|
234
|
+
def __delitem__(self, key: K) -> None:
|
|
235
|
+
with self._lock:
|
|
236
|
+
del self._dict[key]
|
|
237
|
+
|
|
238
|
+
def __contains__(self, key: K) -> bool:
|
|
239
|
+
with self._lock:
|
|
240
|
+
return key in self._dict
|
|
241
|
+
|
|
242
|
+
def __len__(self) -> int:
|
|
243
|
+
with self._lock:
|
|
244
|
+
return len(self._dict)
|
|
245
|
+
|
|
246
|
+
def __bool__(self) -> bool:
|
|
247
|
+
with self._lock:
|
|
248
|
+
return bool(self._dict)
|
|
249
|
+
|
|
250
|
+
def __iter__(self) -> Iterator[K]:
|
|
251
|
+
with self._lock:
|
|
252
|
+
return iter(list(self._dict.keys()))
|
|
253
|
+
|
|
254
|
+
def get(self, key: K, default: V | None = None) -> V:
|
|
255
|
+
with self._lock:
|
|
256
|
+
return self._dict.get(key, default)
|
|
257
|
+
|
|
258
|
+
def pop(self, key: K, *args) -> V:
|
|
259
|
+
with self._lock:
|
|
260
|
+
return self._dict.pop(key, *args)
|
|
261
|
+
|
|
262
|
+
def update(self, *args, **kwargs) -> None:
|
|
263
|
+
with self._lock:
|
|
264
|
+
self._dict.update(*args, **kwargs)
|
|
265
|
+
|
|
266
|
+
def clear(self) -> None:
|
|
267
|
+
with self._lock:
|
|
268
|
+
self._dict.clear()
|
|
269
|
+
|
|
270
|
+
def keys(self):
|
|
271
|
+
with self._lock:
|
|
272
|
+
return list(self._dict.keys())
|
|
273
|
+
|
|
274
|
+
def values(self):
|
|
275
|
+
with self._lock:
|
|
276
|
+
return list(self._dict.values())
|
|
277
|
+
|
|
278
|
+
def items(self):
|
|
279
|
+
with self._lock:
|
|
280
|
+
return list(self._dict.items())
|
|
281
|
+
|
|
282
|
+
def copy(self) -> dict[K, V]:
|
|
283
|
+
with self._lock:
|
|
284
|
+
return self._dict.copy()
|
|
285
|
+
|
|
286
|
+
def setdefault(self, key: K, default: V | None = None) -> V:
|
|
287
|
+
with self._lock:
|
|
288
|
+
return self._dict.setdefault(key, default)
|
memos/settings.py
CHANGED
|
@@ -2,6 +2,8 @@ SIMPLE_STRUCT_MEM_READER_PROMPT = """You are a memory extraction expert.
|
|
|
2
2
|
Your task is to extract memories from the perspective of user, based on a conversation between user and assistant. This means identifying what user would plausibly remember — including their own experiences, thoughts, plans, or relevant statements and actions made by others (such as assistant) that impacted or were acknowledged by user.
|
|
3
3
|
Please perform:
|
|
4
4
|
1. Identify information that reflects user's experiences, beliefs, concerns, decisions, plans, or reactions — including meaningful input from assistant that user acknowledged or responded to.
|
|
5
|
+
If the message is from the user, extract user-relevant memories; if it is from the assistant, only extract factual memories that the user acknowledged or responded to.
|
|
6
|
+
|
|
5
7
|
2. Resolve all time, person, and event references clearly:
|
|
6
8
|
- Convert relative time expressions (e.g., “yesterday,” “next Friday”) into absolute dates using the message timestamp if possible.
|
|
7
9
|
- Clearly distinguish between event time and message time.
|
|
@@ -16,6 +18,7 @@ For example, write "The user felt exhausted..." instead of "I felt exhausted..."
|
|
|
16
18
|
- Include all key experiences, thoughts, emotional responses, and plans — even if they seem minor.
|
|
17
19
|
- Prioritize completeness and fidelity over conciseness.
|
|
18
20
|
- Do not generalize or skip details that could be personally meaningful to user.
|
|
21
|
+
5. Please avoid any content that violates national laws and regulations or involves politically sensitive information in the memories you extract.
|
|
19
22
|
|
|
20
23
|
Return a single valid JSON object with the following structure:
|
|
21
24
|
|
|
@@ -150,7 +153,7 @@ Output:
|
|
|
150
153
|
"summary": "Tom is currently focused on managing a new project with a tight schedule. After a team meeting on June 25, 2025, he realized the original deadline of December 15 might not be feasible due to backend delays. Concerned about insufficient testing time, he welcomed Jerry’s suggestion of proposing an extension. Tom plans to raise the idea of shifting the deadline to January 5, 2026 in the next morning’s meeting. His actions reflect both stress about timelines and a proactive, team-oriented problem-solving approach."
|
|
151
154
|
}
|
|
152
155
|
|
|
153
|
-
Another Example in Chinese (注意:
|
|
156
|
+
Another Example in Chinese (注意: 当user的语言为中文时,你就需要也输出中文):
|
|
154
157
|
{
|
|
155
158
|
"memory list": [
|
|
156
159
|
{
|