Skip to content

Commit 8b259c3

Browse files
authored
feat(mem0): support mem0 as LTM (#197)
* support mem0 as LTM * reset test_long_term_memory.py
1 parent 53084fe commit 8b259c3

File tree

4 files changed

+156
-7
lines changed

4 files changed

+156
-7
lines changed

config.yaml.full

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ database:
119119
endpoint: tos-cn-beijing.volces.com # default Volcengine TOS endpoint
120120
region: cn-beijing # default Volcengine TOS region
121121
bucket:
122+
mem0:
123+
base_url:
124+
api_key:
125+
122126

123127

124128
# [optional] for prompt optimization in cli/app

veadk/configs/database_configs.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ class RedisConfig(BaseSettings):
8383
"""STS token for Redis auth, not supported yet."""
8484

8585

86+
class Mem0Config(BaseSettings):
87+
model_config = SettingsConfigDict(env_prefix="DATABASE_MEM0_")
88+
89+
api_key: str = ""
90+
"""Mem0 API key"""
91+
92+
base_url: str = "" # "https://api.mem0.ai/v1"
93+
94+
8695
class VikingKnowledgebaseConfig(BaseSettings):
8796
model_config = SettingsConfigDict(env_prefix="DATABASE_VIKING_")
8897

veadk/memory/long_term_memory.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ def _get_backend_cls(backend: str) -> type[BaseLongTermMemoryBackend]:
6262
)
6363

6464
return RedisLTMBackend
65+
case "mem0":
66+
from veadk.memory.long_term_memory_backends.mem0_backend import (
67+
Mem0LTMBackend,
68+
)
69+
70+
return Mem0LTMBackend
6571

6672
raise ValueError(f"Unsupported long term memory backend: {backend}")
6773

@@ -72,7 +78,7 @@ def build_long_term_memory_index(app_name: str, user_id: str):
7278

7379
class LongTermMemory(BaseMemoryService, BaseModel):
7480
backend: Union[
75-
Literal["local", "opensearch", "redis", "viking", "viking_mem"],
81+
Literal["local", "opensearch", "redis", "viking", "viking_mem", "mem0"],
7682
BaseLongTermMemoryBackend,
7783
] = "opensearch"
7884
"""Long term memory backend type"""
@@ -153,12 +159,6 @@ async def add_session_to_memory(
153159
app_name = session.app_name
154160
user_id = session.user_id
155161

156-
if self._index != build_long_term_memory_index(app_name, user_id):
157-
logger.warning(
158-
f"The `app_name` or `user_id` is different from the initialized one, skip add session to memory. Initialized index: {self._index}, current built index: {build_long_term_memory_index(app_name, user_id)}"
159-
)
160-
return
161-
162162
if not self._backend and isinstance(self.backend, str):
163163
self._index = build_long_term_memory_index(app_name, user_id)
164164
self._backend = _get_backend_cls(self.backend)(
@@ -168,6 +168,13 @@ async def add_session_to_memory(
168168
f"Initialize long term memory backend now, index is {self._index}"
169169
)
170170

171+
if not self._index and self._index != build_long_term_memory_index(
172+
app_name, user_id
173+
):
174+
logger.warning(
175+
f"The `app_name` or `user_id` is different from the initialized one, skip add session to memory. Initialized index: {self._index}, current built index: {build_long_term_memory_index(app_name, user_id)}"
176+
)
177+
return
171178
event_strings = self._filter_and_convert_events(session.events)
172179

173180
logger.info(
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from typing import Any
16+
from typing_extensions import override
17+
from pydantic import Field
18+
19+
from veadk.configs.database_configs import Mem0Config
20+
21+
22+
from veadk.memory.long_term_memory_backends.base_backend import (
23+
BaseLongTermMemoryBackend,
24+
)
25+
from veadk.utils.logger import get_logger
26+
27+
logger = get_logger(__name__)
28+
29+
try:
30+
from mem0 import MemoryClient
31+
32+
except ImportError:
33+
logger.error(
34+
"Failed to import mem0 or dotenv. Please install them with 'pip install mem0 '"
35+
)
36+
raise ImportError("Required packages not installed: mem0")
37+
38+
39+
class Mem0LTMBackend(BaseLongTermMemoryBackend):
40+
"""Mem0 long term memory backend implementation"""
41+
42+
mem0_config: Mem0Config = Field(default_factory=Mem0Config)
43+
44+
def model_post_init(self, __context: Any) -> None:
45+
"""Initialize Mem0 client"""
46+
47+
try:
48+
self._mem0_client = MemoryClient(
49+
# base_url=self.mem0_config.base_url, # mem0 endpoint
50+
api_key=self.mem0_config.api_key, # mem0 API key
51+
)
52+
logger.info(f"Initialized Mem0 client for index: {self.index}")
53+
except Exception as e:
54+
logger.error(f"Failed to initialize Mem0 client: {str(e)}")
55+
raise
56+
57+
def precheck_index_naming(self):
58+
"""Check if the index name is valid
59+
For Mem0, there are no specific naming constraints
60+
"""
61+
pass
62+
63+
@override
64+
def save_memory(self, event_strings: list[str], **kwargs) -> bool:
65+
"""Save memory to Mem0
66+
67+
Args:
68+
event_strings: List of event strings to save
69+
**kwargs: Additional parameters, including 'user_id' for Mem0
70+
71+
Returns:
72+
bool: True if saved successfully, False otherwise
73+
"""
74+
user_id = kwargs.get("user_id", "default_user")
75+
76+
try:
77+
logger.info(
78+
f"Saving {len(event_strings)} events to Mem0 for user: {user_id}"
79+
)
80+
81+
for event_string in event_strings:
82+
# Save event string to Mem0
83+
result = self._mem0_client.add(
84+
[{"role": "user", "content": event_string}],
85+
user_id=user_id,
86+
output_format="v1.1",
87+
)
88+
logger.debug(f"Saved memory result: {result}")
89+
90+
logger.info(f"Successfully saved {len(event_strings)} events to Mem0")
91+
return True
92+
except Exception as e:
93+
logger.error(f"Failed to save memory to Mem0: {str(e)}")
94+
return False
95+
96+
@override
97+
def search_memory(self, query: str, top_k: int, **kwargs) -> list[str]:
98+
"""Search memory from Mem0
99+
100+
Args:
101+
query: Search query
102+
top_k: Number of results to return
103+
**kwargs: Additional parameters, including 'user_id' for Mem0
104+
105+
Returns:
106+
list[str]: List of memory strings
107+
"""
108+
user_id = kwargs.get("user_id", "default_user")
109+
110+
try:
111+
logger.info(
112+
f"Searching Mem0 for query: {query}, user: {user_id}, top_k: {top_k}"
113+
)
114+
115+
memories = self._mem0_client.search(
116+
query, user_id=user_id, output_format="v1.1", top_k=top_k
117+
)
118+
119+
memory_list = []
120+
if memories.get("results", []):
121+
for mem in memories["results"]:
122+
if "memory" in mem:
123+
memory_list.append(mem["memory"])
124+
125+
logger.info(f"Found {len(memory_list)} memories matching query: {query}")
126+
return memory_list
127+
except Exception as e:
128+
logger.error(f"Failed to search memory from Mem0: {str(e)}")
129+
return []

0 commit comments

Comments
 (0)