agent-framework-mem0 1.0.0b260127__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Microsoft Corporation.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-framework-mem0
3
+ Version: 1.0.0b260127
4
+ Summary: Mem0 integration for Microsoft Agent Framework.
5
+ Author-email: Microsoft <af-support@microsoft.com>
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Typing :: Typed
18
+ License-File: LICENSE
19
+ Requires-Dist: agent-framework-core
20
+ Requires-Dist: mem0ai>=1.0.0
21
+ Project-URL: homepage, https://aka.ms/agent-framework
22
+ Project-URL: issues, https://github.com/microsoft/agent-framework/issues
23
+ Project-URL: release_notes, https://github.com/microsoft/agent-framework/releases?q=tag%3Apython-1&expanded=true
24
+ Project-URL: source, https://github.com/microsoft/agent-framework/tree/main/python
25
+
26
+ # Get Started with Microsoft Agent Framework Mem0
27
+
28
+ Please install this package via pip:
29
+
30
+ ```bash
31
+ pip install agent-framework-mem0 --pre
32
+ ```
33
+
34
+ ## Memory Context Provider
35
+
36
+ The Mem0 context provider enables persistent memory capabilities for your agents, allowing them to remember user preferences and conversation context across different sessions and threads.
37
+
38
+ ### Basic Usage Example
39
+
40
+ See the [Mem0 basic example](https://github.com/microsoft/agent-framework/tree/main/python/samples/getting_started/context_providers/mem0/mem0_basic.py) which demonstrates:
41
+
42
+ - Setting up an agent with Mem0 context provider
43
+ - Teaching the agent user preferences
44
+ - Retrieving information using remembered context across new threads
45
+ - Persistent memory
46
+
@@ -0,0 +1,20 @@
1
+ # Get Started with Microsoft Agent Framework Mem0
2
+
3
+ Please install this package via pip:
4
+
5
+ ```bash
6
+ pip install agent-framework-mem0 --pre
7
+ ```
8
+
9
+ ## Memory Context Provider
10
+
11
+ The Mem0 context provider enables persistent memory capabilities for your agents, allowing them to remember user preferences and conversation context across different sessions and threads.
12
+
13
+ ### Basic Usage Example
14
+
15
+ See the [Mem0 basic example](https://github.com/microsoft/agent-framework/tree/main/python/samples/getting_started/context_providers/mem0/mem0_basic.py) which demonstrates:
16
+
17
+ - Setting up an agent with Mem0 context provider
18
+ - Teaching the agent user preferences
19
+ - Retrieving information using remembered context across new threads
20
+ - Persistent memory
@@ -0,0 +1,15 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ import importlib.metadata
4
+
5
+ from ._provider import Mem0Provider
6
+
7
+ try:
8
+ __version__ = importlib.metadata.version(__name__)
9
+ except importlib.metadata.PackageNotFoundError:
10
+ __version__ = "0.0.0" # Fallback for development mode
11
+
12
+ __all__ = [
13
+ "Mem0Provider",
14
+ "__version__",
15
+ ]
@@ -0,0 +1,229 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ import sys
4
+ from collections.abc import MutableSequence, Sequence
5
+ from contextlib import AbstractAsyncContextManager
6
+ from typing import Any, TypedDict
7
+
8
+ from agent_framework import ChatMessage, Context, ContextProvider
9
+ from agent_framework.exceptions import ServiceInitializationError
10
+ from mem0 import AsyncMemory, AsyncMemoryClient
11
+
12
+ if sys.version_info >= (3, 12):
13
+ from typing import override # type: ignore # pragma: no cover
14
+ else:
15
+ from typing_extensions import override # type: ignore[import] # pragma: no cover
16
+
17
+ if sys.version_info >= (3, 11):
18
+ from typing import NotRequired, Self # pragma: no cover
19
+ else:
20
+ from typing_extensions import NotRequired, Self # pragma: no cover
21
+
22
+
23
+ # Type aliases for Mem0 search response formats (v1.1 and v2; v1 is deprecated, but matches the type definition for v2)
24
+ class MemorySearchResponse_v1_1(TypedDict):
25
+ results: list[dict[str, Any]]
26
+ relations: NotRequired[list[dict[str, Any]]]
27
+
28
+
29
+ MemorySearchResponse_v2 = list[dict[str, Any]]
30
+
31
+
32
+ class Mem0Provider(ContextProvider):
33
+ """Mem0 Context Provider."""
34
+
35
+ def __init__(
36
+ self,
37
+ mem0_client: AsyncMemory | AsyncMemoryClient | None = None,
38
+ api_key: str | None = None,
39
+ application_id: str | None = None,
40
+ agent_id: str | None = None,
41
+ thread_id: str | None = None,
42
+ user_id: str | None = None,
43
+ scope_to_per_operation_thread_id: bool = False,
44
+ context_prompt: str = ContextProvider.DEFAULT_CONTEXT_PROMPT,
45
+ ) -> None:
46
+ """Initializes a new instance of the Mem0Provider class.
47
+
48
+ Args:
49
+ mem0_client: A pre-created Mem0 MemoryClient or None to create a default client.
50
+ api_key: The API key for authenticating with the Mem0 API. If not
51
+ provided, it will attempt to use the MEM0_API_KEY environment variable.
52
+ application_id: The application ID for scoping memories or None.
53
+ agent_id: The agent ID for scoping memories or None.
54
+ thread_id: The thread ID for scoping memories or None.
55
+ user_id: The user ID for scoping memories or None.
56
+ scope_to_per_operation_thread_id: Whether to scope memories to per-operation thread ID.
57
+ context_prompt: The prompt to prepend to retrieved memories.
58
+ """
59
+ should_close_client = False
60
+ if mem0_client is None:
61
+ mem0_client = AsyncMemoryClient(api_key=api_key)
62
+ should_close_client = True
63
+
64
+ self.api_key = api_key
65
+ self.application_id = application_id
66
+ self.agent_id = agent_id
67
+ self.thread_id = thread_id
68
+ self.user_id = user_id
69
+ self.scope_to_per_operation_thread_id = scope_to_per_operation_thread_id
70
+ self.context_prompt = context_prompt
71
+ self.mem0_client = mem0_client
72
+ self._per_operation_thread_id: str | None = None
73
+ self._should_close_client = should_close_client
74
+
75
+ async def __aenter__(self) -> "Self":
76
+ """Async context manager entry."""
77
+ if self.mem0_client and isinstance(self.mem0_client, AbstractAsyncContextManager):
78
+ await self.mem0_client.__aenter__()
79
+ return self
80
+
81
+ async def __aexit__(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any) -> None:
82
+ """Async context manager exit."""
83
+ if self._should_close_client and self.mem0_client and isinstance(self.mem0_client, AbstractAsyncContextManager):
84
+ await self.mem0_client.__aexit__(exc_type, exc_val, exc_tb)
85
+
86
+ async def thread_created(self, thread_id: str | None = None) -> None:
87
+ """Called when a new thread is created.
88
+
89
+ Args:
90
+ thread_id: The ID of the thread or None.
91
+ """
92
+ self._validate_per_operation_thread_id(thread_id)
93
+ self._per_operation_thread_id = self._per_operation_thread_id or thread_id
94
+
95
+ @override
96
+ async def invoked(
97
+ self,
98
+ request_messages: ChatMessage | Sequence[ChatMessage],
99
+ response_messages: ChatMessage | Sequence[ChatMessage] | None = None,
100
+ invoke_exception: Exception | None = None,
101
+ **kwargs: Any,
102
+ ) -> None:
103
+ self._validate_filters()
104
+
105
+ request_messages_list = (
106
+ [request_messages] if isinstance(request_messages, ChatMessage) else list(request_messages)
107
+ )
108
+ response_messages_list = (
109
+ [response_messages]
110
+ if isinstance(response_messages, ChatMessage)
111
+ else list(response_messages)
112
+ if response_messages
113
+ else []
114
+ )
115
+ messages_list = [*request_messages_list, *response_messages_list]
116
+
117
+ messages: list[dict[str, str]] = [
118
+ {"role": message.role.value, "content": message.text}
119
+ for message in messages_list
120
+ if message.role.value in {"user", "assistant", "system"} and message.text and message.text.strip()
121
+ ]
122
+
123
+ if messages:
124
+ await self.mem0_client.add( # type: ignore[misc]
125
+ messages=messages,
126
+ user_id=self.user_id,
127
+ agent_id=self.agent_id,
128
+ run_id=self._per_operation_thread_id if self.scope_to_per_operation_thread_id else self.thread_id,
129
+ metadata={"application_id": self.application_id},
130
+ )
131
+
132
+ @override
133
+ async def invoking(self, messages: ChatMessage | MutableSequence[ChatMessage], **kwargs: Any) -> Context:
134
+ """Called before invoking the AI model to provide context.
135
+
136
+ Args:
137
+ messages: List of new messages in the thread.
138
+
139
+ Keyword Args:
140
+ **kwargs: not used at present.
141
+
142
+ Returns:
143
+ Context: Context object containing instructions with memories.
144
+ """
145
+ self._validate_filters()
146
+ messages_list = [messages] if isinstance(messages, ChatMessage) else list(messages)
147
+ input_text = "\n".join(msg.text for msg in messages_list if msg and msg.text and msg.text.strip())
148
+
149
+ # Validate input text is not empty before searching (possible for function approval responses)
150
+ if not input_text.strip():
151
+ return Context(messages=None)
152
+
153
+ # Build filters from init parameters
154
+ filters = self._build_filters()
155
+
156
+ search_response: MemorySearchResponse_v1_1 | MemorySearchResponse_v2 = await self.mem0_client.search( # type: ignore[misc]
157
+ query=input_text,
158
+ filters=filters,
159
+ )
160
+
161
+ # Depending on the API version, the response schema varies slightly
162
+ if isinstance(search_response, list):
163
+ memories = search_response
164
+ elif isinstance(search_response, dict) and "results" in search_response:
165
+ memories = search_response["results"]
166
+ else:
167
+ # Fallback for unexpected schema - return response as text as-is
168
+ memories = [search_response]
169
+
170
+ line_separated_memories = "\n".join(memory.get("memory", "") for memory in memories)
171
+
172
+ return Context(
173
+ messages=[ChatMessage(role="user", text=f"{self.context_prompt}\n{line_separated_memories}")]
174
+ if line_separated_memories
175
+ else None
176
+ )
177
+
178
+ def _validate_filters(self) -> None:
179
+ """Validates that at least one filter is provided.
180
+
181
+ Raises:
182
+ ServiceInitializationError: If no filters are provided.
183
+ """
184
+ if not self.agent_id and not self.user_id and not self.application_id and not self.thread_id:
185
+ raise ServiceInitializationError(
186
+ "At least one of the filters: agent_id, user_id, application_id, or thread_id is required."
187
+ )
188
+
189
+ def _build_filters(self) -> dict[str, Any]:
190
+ """Build search filters from initialization parameters.
191
+
192
+ Returns:
193
+ Filter dictionary for mem0 v2 search API containing initialization parameters.
194
+ In the v2 API, filters holds the user_id, agent_id, run_id (thread_id), and app_id
195
+ (application_id) which are required for scoping memory search operations.
196
+ """
197
+ filters: dict[str, Any] = {}
198
+
199
+ if self.user_id:
200
+ filters["user_id"] = self.user_id
201
+ if self.agent_id:
202
+ filters["agent_id"] = self.agent_id
203
+ if self.scope_to_per_operation_thread_id and self._per_operation_thread_id:
204
+ filters["run_id"] = self._per_operation_thread_id
205
+ elif self.thread_id:
206
+ filters["run_id"] = self.thread_id
207
+ if self.application_id:
208
+ filters["app_id"] = self.application_id
209
+
210
+ return filters
211
+
212
+ def _validate_per_operation_thread_id(self, thread_id: str | None) -> None:
213
+ """Validates that a new thread ID doesn't conflict with an existing one when scoped.
214
+
215
+ Args:
216
+ thread_id: The new thread ID or None.
217
+
218
+ Raises:
219
+ ValueError: If a new thread ID is provided when one already exists.
220
+ """
221
+ if (
222
+ self.scope_to_per_operation_thread_id
223
+ and thread_id
224
+ and self._per_operation_thread_id
225
+ and thread_id != self._per_operation_thread_id
226
+ ):
227
+ raise ValueError(
228
+ "Mem0Provider can only be used with one thread at a time when scope_to_per_operation_thread_id is True."
229
+ )
@@ -0,0 +1,89 @@
1
+ [project]
2
+ name = "agent-framework-mem0"
3
+ description = "Mem0 integration for Microsoft Agent Framework."
4
+ authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ version = "1.0.0b260127"
8
+ license-files = ["LICENSE"]
9
+ urls.homepage = "https://aka.ms/agent-framework"
10
+ urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
11
+ urls.release_notes = "https://github.com/microsoft/agent-framework/releases?q=tag%3Apython-1&expanded=true"
12
+ urls.issues = "https://github.com/microsoft/agent-framework/issues"
13
+ classifiers = [
14
+ "License :: OSI Approved :: MIT License",
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Programming Language :: Python :: 3.14",
23
+ "Typing :: Typed",
24
+ ]
25
+ dependencies = [
26
+ "agent-framework-core",
27
+ "mem0ai>=1.0.0",
28
+ ]
29
+
30
+ [tool.uv]
31
+ prerelease = "if-necessary-or-explicit"
32
+ environments = [
33
+ "sys_platform == 'darwin'",
34
+ "sys_platform == 'linux'",
35
+ "sys_platform == 'win32'"
36
+ ]
37
+
38
+ [tool.uv-dynamic-versioning]
39
+ fallback-version = "0.0.0"
40
+ [tool.pytest.ini_options]
41
+ testpaths = 'tests'
42
+ addopts = "-ra -q -r fEX"
43
+ asyncio_mode = "auto"
44
+ asyncio_default_fixture_loop_scope = "function"
45
+ filterwarnings = [
46
+ "ignore:Support for class-based `config` is deprecated:DeprecationWarning:pydantic.*"
47
+ ]
48
+ timeout = 120
49
+
50
+ [tool.ruff]
51
+ extend = "../../pyproject.toml"
52
+
53
+ [tool.coverage.run]
54
+ omit = [
55
+ "**/__init__.py"
56
+ ]
57
+
58
+ [tool.pyright]
59
+ extends = "../../pyproject.toml"
60
+
61
+
62
+ [tool.mypy]
63
+ plugins = ['pydantic.mypy']
64
+ strict = true
65
+ python_version = "3.10"
66
+ ignore_missing_imports = true
67
+ disallow_untyped_defs = true
68
+ no_implicit_optional = true
69
+ check_untyped_defs = true
70
+ warn_return_any = true
71
+ show_error_codes = true
72
+ warn_unused_ignores = false
73
+ disallow_incomplete_defs = true
74
+ disallow_untyped_decorators = true
75
+
76
+ [tool.bandit]
77
+ targets = ["agent_framework_mem0"]
78
+ exclude_dirs = ["tests"]
79
+
80
+ [tool.poe]
81
+ executor.type = "uv"
82
+ include = "../../shared_tasks.toml"
83
+ [tool.poe.tasks]
84
+ mypy = "mypy --config-file $POE_ROOT/pyproject.toml agent_framework_mem0"
85
+ test = "pytest --cov=agent_framework_mem0 --cov-report=term-missing:skip-covered tests"
86
+
87
+ [build-system]
88
+ requires = ["flit-core >= 3.11,<4.0"]
89
+ build-backend = "flit_core.buildapi"