supermemory-agent-framework 1.0.3__tar.gz → 2.0.0__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.
- {supermemory_agent_framework-1.0.3 → supermemory_agent_framework-2.0.0}/PKG-INFO +39 -16
- {supermemory_agent_framework-1.0.3 → supermemory_agent_framework-2.0.0}/README.md +37 -14
- {supermemory_agent_framework-1.0.3 → supermemory_agent_framework-2.0.0}/pyproject.toml +2 -2
- {supermemory_agent_framework-1.0.3 → supermemory_agent_framework-2.0.0}/src/supermemory_agent_framework/__init__.py +18 -23
- {supermemory_agent_framework-1.0.3 → supermemory_agent_framework-2.0.0}/src/supermemory_agent_framework/context_provider.py +16 -32
- {supermemory_agent_framework-1.0.3 → supermemory_agent_framework-2.0.0}/src/supermemory_agent_framework/middleware.py +20 -38
- {supermemory_agent_framework-1.0.3 → supermemory_agent_framework-2.0.0}/src/supermemory_agent_framework/tools.py +44 -24
- {supermemory_agent_framework-1.0.3 → supermemory_agent_framework-2.0.0}/src/supermemory_agent_framework/utils.py +29 -1
- {supermemory_agent_framework-1.0.3 → supermemory_agent_framework-2.0.0}/.gitignore +0 -0
- {supermemory_agent_framework-1.0.3 → supermemory_agent_framework-2.0.0}/LICENSE +0 -0
- {supermemory_agent_framework-1.0.3 → supermemory_agent_framework-2.0.0}/src/supermemory_agent_framework/connection.py +0 -0
- {supermemory_agent_framework-1.0.3 → supermemory_agent_framework-2.0.0}/src/supermemory_agent_framework/exceptions.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: supermemory-agent-framework
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Memory tools and middleware for Microsoft Agent Framework with supermemory
|
|
5
5
|
Project-URL: Homepage, https://supermemory.ai
|
|
6
6
|
Project-URL: Repository, https://github.com/supermemoryai/supermemory
|
|
@@ -20,7 +20,7 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
20
20
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
21
|
Requires-Python: >=3.10
|
|
22
22
|
Requires-Dist: agent-framework-core>=1.0.0rc3
|
|
23
|
-
Requires-Dist: supermemory<5
|
|
23
|
+
Requires-Dist: supermemory<6,>=5.0.0
|
|
24
24
|
Requires-Dist: typing-extensions>=4.0.0
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
|
|
@@ -32,16 +32,20 @@ This package provides both **automatic memory injection middleware** and **manua
|
|
|
32
32
|
|
|
33
33
|
## Installation
|
|
34
34
|
|
|
35
|
+
Adapter version `>=2.0.0,<3` supports the Supermemory Python SDK `>=5.0.0,<6`.
|
|
36
|
+
|
|
37
|
+
The OpenAI client is a separate Agent Framework package. Include `agent-framework-openai` when using the OpenAI examples below, which target its current `OpenAIChatClient` Responses API client. The adapter also supports older framework cores, but their OpenAI client names and model arguments can differ.
|
|
38
|
+
|
|
35
39
|
Install using uv (recommended):
|
|
36
40
|
|
|
37
41
|
```bash
|
|
38
|
-
uv add supermemory-agent-framework
|
|
42
|
+
uv add "supermemory-agent-framework>=2.0.0,<3" agent-framework-openai
|
|
39
43
|
```
|
|
40
44
|
|
|
41
45
|
Or with pip:
|
|
42
46
|
|
|
43
47
|
```bash
|
|
44
|
-
pip install supermemory-agent-framework
|
|
48
|
+
pip install "supermemory-agent-framework>=2.0.0,<3" agent-framework-openai
|
|
45
49
|
```
|
|
46
50
|
|
|
47
51
|
## Quick Start
|
|
@@ -52,7 +56,7 @@ The easiest way to add memory capabilities is using the `SupermemoryChatMiddlewa
|
|
|
52
56
|
|
|
53
57
|
```python
|
|
54
58
|
import asyncio
|
|
55
|
-
from agent_framework.openai import
|
|
59
|
+
from agent_framework.openai import OpenAIChatClient
|
|
56
60
|
from supermemory_agent_framework import (
|
|
57
61
|
AgentSupermemory,
|
|
58
62
|
SupermemoryChatMiddleware,
|
|
@@ -75,7 +79,7 @@ async def main():
|
|
|
75
79
|
)
|
|
76
80
|
|
|
77
81
|
# Create agent with middleware
|
|
78
|
-
agent =
|
|
82
|
+
agent = OpenAIChatClient(model="gpt-5").as_agent(
|
|
79
83
|
name="MemoryAgent",
|
|
80
84
|
instructions="You are a helpful assistant with memory.",
|
|
81
85
|
middleware=[middleware],
|
|
@@ -85,6 +89,7 @@ async def main():
|
|
|
85
89
|
response = await agent.run(
|
|
86
90
|
"What's my favorite programming language?"
|
|
87
91
|
)
|
|
92
|
+
await middleware.wait_for_background_tasks()
|
|
88
93
|
print(response.text)
|
|
89
94
|
|
|
90
95
|
asyncio.run(main())
|
|
@@ -97,7 +102,7 @@ The most idiomatic way to add memory in Agent Framework, using the same pattern
|
|
|
97
102
|
```python
|
|
98
103
|
import asyncio
|
|
99
104
|
from agent_framework import AgentSession
|
|
100
|
-
from agent_framework.openai import
|
|
105
|
+
from agent_framework.openai import OpenAIChatClient
|
|
101
106
|
from supermemory_agent_framework import AgentSupermemory, SupermemoryContextProvider
|
|
102
107
|
|
|
103
108
|
async def main():
|
|
@@ -113,7 +118,7 @@ async def main():
|
|
|
113
118
|
)
|
|
114
119
|
|
|
115
120
|
# Create agent with context provider
|
|
116
|
-
agent =
|
|
121
|
+
agent = OpenAIChatClient(model="gpt-5").as_agent(
|
|
117
122
|
name="MemoryAgent",
|
|
118
123
|
instructions="You are a helpful assistant with memory.",
|
|
119
124
|
context_providers=[provider],
|
|
@@ -136,7 +141,7 @@ For explicit tool-based memory access:
|
|
|
136
141
|
|
|
137
142
|
```python
|
|
138
143
|
import asyncio
|
|
139
|
-
from agent_framework.openai import
|
|
144
|
+
from agent_framework.openai import OpenAIChatClient
|
|
140
145
|
from supermemory_agent_framework import AgentSupermemory, SupermemoryTools
|
|
141
146
|
|
|
142
147
|
async def main():
|
|
@@ -147,7 +152,7 @@ async def main():
|
|
|
147
152
|
tools = SupermemoryTools(connection)
|
|
148
153
|
|
|
149
154
|
# Create agent
|
|
150
|
-
agent =
|
|
155
|
+
agent = OpenAIChatClient(model="gpt-5").as_agent(
|
|
151
156
|
name="MemoryAgent",
|
|
152
157
|
instructions="You are a helpful assistant with access to user memories.",
|
|
153
158
|
)
|
|
@@ -168,7 +173,7 @@ For maximum flexibility, use both middleware (automatic context injection) and t
|
|
|
168
173
|
|
|
169
174
|
```python
|
|
170
175
|
import asyncio
|
|
171
|
-
from agent_framework.openai import
|
|
176
|
+
from agent_framework.openai import OpenAIChatClient
|
|
172
177
|
from supermemory_agent_framework import (
|
|
173
178
|
AgentSupermemory,
|
|
174
179
|
SupermemoryChatMiddleware,
|
|
@@ -190,7 +195,7 @@ async def main():
|
|
|
190
195
|
|
|
191
196
|
tools = SupermemoryTools(connection)
|
|
192
197
|
|
|
193
|
-
agent =
|
|
198
|
+
agent = OpenAIChatClient(model="gpt-5").as_agent(
|
|
194
199
|
name="MemoryAgent",
|
|
195
200
|
instructions="You are a helpful assistant with memory.",
|
|
196
201
|
middleware=[middleware],
|
|
@@ -284,10 +289,23 @@ result = await tools.add_memory("User prefers dark mode")
|
|
|
284
289
|
result = await tools.get_profile()
|
|
285
290
|
```
|
|
286
291
|
|
|
287
|
-
`search_memories` uses
|
|
292
|
+
`search_memories` uses v5 hybrid search, so results can contain either a
|
|
288
293
|
structured memory or a source chunk. The old Python-only `include_full_docs`
|
|
289
|
-
argument
|
|
290
|
-
source documents
|
|
294
|
+
argument remains deprecated and ignored; this tool does not request full
|
|
295
|
+
source documents, and the argument is not exposed to the model.
|
|
296
|
+
|
|
297
|
+
### V5 compatibility
|
|
298
|
+
|
|
299
|
+
- Keep passing `container_tag` and `conversation_id`. The adapter passes the container tag as the v5 namespace and uses the unchanged `conversation_<conversation_id>` value as the document `id`. Choose a separate container tag for each tenant; the default `msft_agent_chat` is shared, not tenant-specific.
|
|
300
|
+
- All writes still use add/append, including tool writes and automatic conversation storage. Reusing a conversation ID adds or diffs new content into its document; it does not replace earlier turns. No document update or replacement operation is used.
|
|
301
|
+
- `entity_context` remains display context prepended to retrieved memories; this migration does not start sending it as ingestion `supporting_context`.
|
|
302
|
+
- Profile mode makes one profile request, query mode makes one search request, and full mode makes both when there is a user query. V5 profiles no longer accept a query. Profile-associated search keeps the legacy memory-only mode and `0.6` threshold rather than adopting v5's broader defaults; the explicit search tool keeps its hybrid mode and `0.6` threshold. Provider and middleware context still contains fact text rather than `{id, memory}` objects and deduplicates facts across profile/search results.
|
|
303
|
+
- Tool JSON envelopes remain unchanged: search returns `success`, `results`, and `count`; add returns `success` and `memory`; profile returns `success`, `profile`, and `search_results`. Profile static/dynamic/bucket values remain strings. Profile search results retain `results`, `timing`, and `total` (the number returned). Search results retain a top-level `updated_at` mapped from v5 `system.updated_at`, along with v5 fields. Legacy optional fields that v5 does not return, such as version numbers and file paths, remain present as `null`; their values cannot be reconstructed.
|
|
304
|
+
- The provider has no adapter-owned persisted state schema and leaves its scoped session state unchanged. Existing framework session exports remain loadable; keep using the same container tag and conversation ID when reconstructing the connection. The API client's credentials are not serialized into session state.
|
|
305
|
+
|
|
306
|
+
This maps requests but does not move server-side data. If existing v3/v4 data has not been migrated into the corresponding v5 namespace, follow the [v5 migration guide](https://supermemory.ai/docs/migration/api-v5) before relying on historical recall. The adapter does not delete or rewrite the old data.
|
|
307
|
+
|
|
308
|
+
Writes are accepted asynchronously; `queued` is not a guarantee that a later search already contains the new memory. The SDK's default processing mode is unchanged. Enabling both provider storage and middleware storage can submit overlapping conversation content, so use one automatic storage path unless that is intentional.
|
|
291
309
|
|
|
292
310
|
### SupermemoryChatMiddleware
|
|
293
311
|
|
|
@@ -333,6 +351,8 @@ except SupermemoryConfigurationError as e:
|
|
|
333
351
|
|
|
334
352
|
### Exception Types
|
|
335
353
|
|
|
354
|
+
Tools return failures as JSON with `success: false` and `error`. Provider retrieval/storage and middleware retrieval failures are logged and do not abort the agent run. Middleware background write failures are logged; `wait_for_background_tasks()` waits for those tasks but does not re-raise their operation errors (its own wait timeout still raises `asyncio.TimeoutError`). SDK connection and request timeout failures are classified separately for background writes.
|
|
355
|
+
|
|
336
356
|
- **`SupermemoryError`** - Base class for all Supermemory exceptions
|
|
337
357
|
- **`SupermemoryConfigurationError`** - Missing API keys, invalid configuration
|
|
338
358
|
- **`SupermemoryAPIError`** - API request failures (includes status codes)
|
|
@@ -349,7 +369,7 @@ except SupermemoryConfigurationError as e:
|
|
|
349
369
|
|
|
350
370
|
### Required
|
|
351
371
|
- `agent-framework-core>=1.0.0rc3` - Microsoft Agent Framework
|
|
352
|
-
- `supermemory>=
|
|
372
|
+
- `supermemory>=5.0.0,<6` - Namespace-first Supermemory v5 client
|
|
353
373
|
- `typing-extensions>=4.0.0` - Typing compatibility helpers
|
|
354
374
|
|
|
355
375
|
## Development
|
|
@@ -368,8 +388,11 @@ uv run mypy src/supermemory_agent_framework
|
|
|
368
388
|
# Formatting
|
|
369
389
|
uv run black src/ tests/
|
|
370
390
|
uv run isort src/ tests/
|
|
391
|
+
uv run flake8 src/ tests/ --ignore=E501,W503,E704
|
|
371
392
|
```
|
|
372
393
|
|
|
394
|
+
The HTTP-transport regression suite uses the actual Supermemory SDK and runs a real Agent Framework agent/tool loop without API credentials or a live model. It is verified against both `agent-framework-core==1.0.0rc3` and `1.21.0`.
|
|
395
|
+
|
|
373
396
|
## License
|
|
374
397
|
|
|
375
398
|
MIT License - see LICENSE file for details.
|
|
@@ -6,16 +6,20 @@ This package provides both **automatic memory injection middleware** and **manua
|
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
+
Adapter version `>=2.0.0,<3` supports the Supermemory Python SDK `>=5.0.0,<6`.
|
|
10
|
+
|
|
11
|
+
The OpenAI client is a separate Agent Framework package. Include `agent-framework-openai` when using the OpenAI examples below, which target its current `OpenAIChatClient` Responses API client. The adapter also supports older framework cores, but their OpenAI client names and model arguments can differ.
|
|
12
|
+
|
|
9
13
|
Install using uv (recommended):
|
|
10
14
|
|
|
11
15
|
```bash
|
|
12
|
-
uv add supermemory-agent-framework
|
|
16
|
+
uv add "supermemory-agent-framework>=2.0.0,<3" agent-framework-openai
|
|
13
17
|
```
|
|
14
18
|
|
|
15
19
|
Or with pip:
|
|
16
20
|
|
|
17
21
|
```bash
|
|
18
|
-
pip install supermemory-agent-framework
|
|
22
|
+
pip install "supermemory-agent-framework>=2.0.0,<3" agent-framework-openai
|
|
19
23
|
```
|
|
20
24
|
|
|
21
25
|
## Quick Start
|
|
@@ -26,7 +30,7 @@ The easiest way to add memory capabilities is using the `SupermemoryChatMiddlewa
|
|
|
26
30
|
|
|
27
31
|
```python
|
|
28
32
|
import asyncio
|
|
29
|
-
from agent_framework.openai import
|
|
33
|
+
from agent_framework.openai import OpenAIChatClient
|
|
30
34
|
from supermemory_agent_framework import (
|
|
31
35
|
AgentSupermemory,
|
|
32
36
|
SupermemoryChatMiddleware,
|
|
@@ -49,7 +53,7 @@ async def main():
|
|
|
49
53
|
)
|
|
50
54
|
|
|
51
55
|
# Create agent with middleware
|
|
52
|
-
agent =
|
|
56
|
+
agent = OpenAIChatClient(model="gpt-5").as_agent(
|
|
53
57
|
name="MemoryAgent",
|
|
54
58
|
instructions="You are a helpful assistant with memory.",
|
|
55
59
|
middleware=[middleware],
|
|
@@ -59,6 +63,7 @@ async def main():
|
|
|
59
63
|
response = await agent.run(
|
|
60
64
|
"What's my favorite programming language?"
|
|
61
65
|
)
|
|
66
|
+
await middleware.wait_for_background_tasks()
|
|
62
67
|
print(response.text)
|
|
63
68
|
|
|
64
69
|
asyncio.run(main())
|
|
@@ -71,7 +76,7 @@ The most idiomatic way to add memory in Agent Framework, using the same pattern
|
|
|
71
76
|
```python
|
|
72
77
|
import asyncio
|
|
73
78
|
from agent_framework import AgentSession
|
|
74
|
-
from agent_framework.openai import
|
|
79
|
+
from agent_framework.openai import OpenAIChatClient
|
|
75
80
|
from supermemory_agent_framework import AgentSupermemory, SupermemoryContextProvider
|
|
76
81
|
|
|
77
82
|
async def main():
|
|
@@ -87,7 +92,7 @@ async def main():
|
|
|
87
92
|
)
|
|
88
93
|
|
|
89
94
|
# Create agent with context provider
|
|
90
|
-
agent =
|
|
95
|
+
agent = OpenAIChatClient(model="gpt-5").as_agent(
|
|
91
96
|
name="MemoryAgent",
|
|
92
97
|
instructions="You are a helpful assistant with memory.",
|
|
93
98
|
context_providers=[provider],
|
|
@@ -110,7 +115,7 @@ For explicit tool-based memory access:
|
|
|
110
115
|
|
|
111
116
|
```python
|
|
112
117
|
import asyncio
|
|
113
|
-
from agent_framework.openai import
|
|
118
|
+
from agent_framework.openai import OpenAIChatClient
|
|
114
119
|
from supermemory_agent_framework import AgentSupermemory, SupermemoryTools
|
|
115
120
|
|
|
116
121
|
async def main():
|
|
@@ -121,7 +126,7 @@ async def main():
|
|
|
121
126
|
tools = SupermemoryTools(connection)
|
|
122
127
|
|
|
123
128
|
# Create agent
|
|
124
|
-
agent =
|
|
129
|
+
agent = OpenAIChatClient(model="gpt-5").as_agent(
|
|
125
130
|
name="MemoryAgent",
|
|
126
131
|
instructions="You are a helpful assistant with access to user memories.",
|
|
127
132
|
)
|
|
@@ -142,7 +147,7 @@ For maximum flexibility, use both middleware (automatic context injection) and t
|
|
|
142
147
|
|
|
143
148
|
```python
|
|
144
149
|
import asyncio
|
|
145
|
-
from agent_framework.openai import
|
|
150
|
+
from agent_framework.openai import OpenAIChatClient
|
|
146
151
|
from supermemory_agent_framework import (
|
|
147
152
|
AgentSupermemory,
|
|
148
153
|
SupermemoryChatMiddleware,
|
|
@@ -164,7 +169,7 @@ async def main():
|
|
|
164
169
|
|
|
165
170
|
tools = SupermemoryTools(connection)
|
|
166
171
|
|
|
167
|
-
agent =
|
|
172
|
+
agent = OpenAIChatClient(model="gpt-5").as_agent(
|
|
168
173
|
name="MemoryAgent",
|
|
169
174
|
instructions="You are a helpful assistant with memory.",
|
|
170
175
|
middleware=[middleware],
|
|
@@ -258,10 +263,23 @@ result = await tools.add_memory("User prefers dark mode")
|
|
|
258
263
|
result = await tools.get_profile()
|
|
259
264
|
```
|
|
260
265
|
|
|
261
|
-
`search_memories` uses
|
|
266
|
+
`search_memories` uses v5 hybrid search, so results can contain either a
|
|
262
267
|
structured memory or a source chunk. The old Python-only `include_full_docs`
|
|
263
|
-
argument
|
|
264
|
-
source documents
|
|
268
|
+
argument remains deprecated and ignored; this tool does not request full
|
|
269
|
+
source documents, and the argument is not exposed to the model.
|
|
270
|
+
|
|
271
|
+
### V5 compatibility
|
|
272
|
+
|
|
273
|
+
- Keep passing `container_tag` and `conversation_id`. The adapter passes the container tag as the v5 namespace and uses the unchanged `conversation_<conversation_id>` value as the document `id`. Choose a separate container tag for each tenant; the default `msft_agent_chat` is shared, not tenant-specific.
|
|
274
|
+
- All writes still use add/append, including tool writes and automatic conversation storage. Reusing a conversation ID adds or diffs new content into its document; it does not replace earlier turns. No document update or replacement operation is used.
|
|
275
|
+
- `entity_context` remains display context prepended to retrieved memories; this migration does not start sending it as ingestion `supporting_context`.
|
|
276
|
+
- Profile mode makes one profile request, query mode makes one search request, and full mode makes both when there is a user query. V5 profiles no longer accept a query. Profile-associated search keeps the legacy memory-only mode and `0.6` threshold rather than adopting v5's broader defaults; the explicit search tool keeps its hybrid mode and `0.6` threshold. Provider and middleware context still contains fact text rather than `{id, memory}` objects and deduplicates facts across profile/search results.
|
|
277
|
+
- Tool JSON envelopes remain unchanged: search returns `success`, `results`, and `count`; add returns `success` and `memory`; profile returns `success`, `profile`, and `search_results`. Profile static/dynamic/bucket values remain strings. Profile search results retain `results`, `timing`, and `total` (the number returned). Search results retain a top-level `updated_at` mapped from v5 `system.updated_at`, along with v5 fields. Legacy optional fields that v5 does not return, such as version numbers and file paths, remain present as `null`; their values cannot be reconstructed.
|
|
278
|
+
- The provider has no adapter-owned persisted state schema and leaves its scoped session state unchanged. Existing framework session exports remain loadable; keep using the same container tag and conversation ID when reconstructing the connection. The API client's credentials are not serialized into session state.
|
|
279
|
+
|
|
280
|
+
This maps requests but does not move server-side data. If existing v3/v4 data has not been migrated into the corresponding v5 namespace, follow the [v5 migration guide](https://supermemory.ai/docs/migration/api-v5) before relying on historical recall. The adapter does not delete or rewrite the old data.
|
|
281
|
+
|
|
282
|
+
Writes are accepted asynchronously; `queued` is not a guarantee that a later search already contains the new memory. The SDK's default processing mode is unchanged. Enabling both provider storage and middleware storage can submit overlapping conversation content, so use one automatic storage path unless that is intentional.
|
|
265
283
|
|
|
266
284
|
### SupermemoryChatMiddleware
|
|
267
285
|
|
|
@@ -307,6 +325,8 @@ except SupermemoryConfigurationError as e:
|
|
|
307
325
|
|
|
308
326
|
### Exception Types
|
|
309
327
|
|
|
328
|
+
Tools return failures as JSON with `success: false` and `error`. Provider retrieval/storage and middleware retrieval failures are logged and do not abort the agent run. Middleware background write failures are logged; `wait_for_background_tasks()` waits for those tasks but does not re-raise their operation errors (its own wait timeout still raises `asyncio.TimeoutError`). SDK connection and request timeout failures are classified separately for background writes.
|
|
329
|
+
|
|
310
330
|
- **`SupermemoryError`** - Base class for all Supermemory exceptions
|
|
311
331
|
- **`SupermemoryConfigurationError`** - Missing API keys, invalid configuration
|
|
312
332
|
- **`SupermemoryAPIError`** - API request failures (includes status codes)
|
|
@@ -323,7 +343,7 @@ except SupermemoryConfigurationError as e:
|
|
|
323
343
|
|
|
324
344
|
### Required
|
|
325
345
|
- `agent-framework-core>=1.0.0rc3` - Microsoft Agent Framework
|
|
326
|
-
- `supermemory>=
|
|
346
|
+
- `supermemory>=5.0.0,<6` - Namespace-first Supermemory v5 client
|
|
327
347
|
- `typing-extensions>=4.0.0` - Typing compatibility helpers
|
|
328
348
|
|
|
329
349
|
## Development
|
|
@@ -342,8 +362,11 @@ uv run mypy src/supermemory_agent_framework
|
|
|
342
362
|
# Formatting
|
|
343
363
|
uv run black src/ tests/
|
|
344
364
|
uv run isort src/ tests/
|
|
365
|
+
uv run flake8 src/ tests/ --ignore=E501,W503,E704
|
|
345
366
|
```
|
|
346
367
|
|
|
368
|
+
The HTTP-transport regression suite uses the actual Supermemory SDK and runs a real Agent Framework agent/tool loop without API credentials or a live model. It is verified against both `agent-framework-core==1.0.0rc3` and `1.21.0`.
|
|
369
|
+
|
|
347
370
|
## License
|
|
348
371
|
|
|
349
372
|
MIT License - see LICENSE file for details.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "supermemory-agent-framework"
|
|
7
|
-
version = "
|
|
7
|
+
version = "2.0.0"
|
|
8
8
|
description = "Memory tools and middleware for Microsoft Agent Framework with supermemory"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -25,7 +25,7 @@ classifiers = [
|
|
|
25
25
|
requires-python = ">=3.10"
|
|
26
26
|
dependencies = [
|
|
27
27
|
"agent-framework-core>=1.0.0rc3",
|
|
28
|
-
"supermemory>=
|
|
28
|
+
"supermemory>=5.0.0,<6",
|
|
29
29
|
"typing-extensions>=4.0.0",
|
|
30
30
|
]
|
|
31
31
|
|
|
@@ -3,38 +3,33 @@
|
|
|
3
3
|
from .connection import (
|
|
4
4
|
AgentSupermemory,
|
|
5
5
|
)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
from .context_provider import (
|
|
7
|
+
SupermemoryContextProvider,
|
|
8
|
+
)
|
|
9
|
+
from .exceptions import (
|
|
10
|
+
SupermemoryAPIError,
|
|
11
|
+
SupermemoryConfigurationError,
|
|
12
|
+
SupermemoryError,
|
|
13
|
+
SupermemoryMemoryOperationError,
|
|
14
|
+
SupermemoryNetworkError,
|
|
15
|
+
SupermemoryTimeoutError,
|
|
12
16
|
)
|
|
13
|
-
|
|
14
17
|
from .middleware import (
|
|
15
18
|
SupermemoryChatMiddleware,
|
|
16
19
|
SupermemoryMiddlewareOptions,
|
|
17
20
|
)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
from .tools import (
|
|
22
|
+
MemoryAddResult,
|
|
23
|
+
MemorySearchResult,
|
|
24
|
+
ProfileResult,
|
|
25
|
+
SupermemoryTools,
|
|
21
26
|
)
|
|
22
|
-
|
|
23
27
|
from .utils import (
|
|
28
|
+
DeduplicatedMemories,
|
|
24
29
|
Logger,
|
|
30
|
+
convert_profile_to_markdown,
|
|
25
31
|
create_logger,
|
|
26
32
|
deduplicate_memories,
|
|
27
|
-
DeduplicatedMemories,
|
|
28
|
-
convert_profile_to_markdown,
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
from .exceptions import (
|
|
32
|
-
SupermemoryError,
|
|
33
|
-
SupermemoryConfigurationError,
|
|
34
|
-
SupermemoryAPIError,
|
|
35
|
-
SupermemoryMemoryOperationError,
|
|
36
|
-
SupermemoryTimeoutError,
|
|
37
|
-
SupermemoryNetworkError,
|
|
38
33
|
)
|
|
39
34
|
|
|
40
35
|
__all__ = [
|
|
@@ -57,4 +52,4 @@ __all__ = [
|
|
|
57
52
|
"SupermemoryMemoryOperationError",
|
|
58
53
|
"SupermemoryTimeoutError",
|
|
59
54
|
"SupermemoryNetworkError",
|
|
60
|
-
]
|
|
55
|
+
]
|
|
@@ -14,13 +14,11 @@ from agent_framework import Message
|
|
|
14
14
|
try:
|
|
15
15
|
from agent_framework import BaseContextProvider # type: ignore[attr-defined]
|
|
16
16
|
except ImportError:
|
|
17
|
-
# Renamed in agent-framework-core 1.0.0 stable; the interface is
|
|
18
|
-
# unchanged (source_id __init__, before_run/after_run hooks with
|
|
19
|
-
# identical keyword-only signatures).
|
|
20
17
|
from agent_framework import ContextProvider as BaseContextProvider
|
|
21
18
|
|
|
22
19
|
from .connection import AgentSupermemory
|
|
23
20
|
from .utils import (
|
|
21
|
+
_fetch_profile_and_search,
|
|
24
22
|
convert_profile_to_markdown,
|
|
25
23
|
create_logger,
|
|
26
24
|
deduplicate_memories,
|
|
@@ -40,7 +38,7 @@ class SupermemoryContextProvider(BaseContextProvider):
|
|
|
40
38
|
Example:
|
|
41
39
|
```python
|
|
42
40
|
from agent_framework import Agent, AgentSession
|
|
43
|
-
from agent_framework.openai import
|
|
41
|
+
from agent_framework.openai import OpenAIChatClient
|
|
44
42
|
from supermemory_agent_framework import (
|
|
45
43
|
AgentSupermemory,
|
|
46
44
|
SupermemoryContextProvider,
|
|
@@ -54,7 +52,7 @@ class SupermemoryContextProvider(BaseContextProvider):
|
|
|
54
52
|
store_conversations=True,
|
|
55
53
|
)
|
|
56
54
|
|
|
57
|
-
agent =
|
|
55
|
+
agent = OpenAIChatClient(model="gpt-5").as_agent(
|
|
58
56
|
name="MemoryAgent",
|
|
59
57
|
instructions="You are a helpful assistant with memory.",
|
|
60
58
|
context_providers=[provider],
|
|
@@ -107,7 +105,6 @@ class SupermemoryContextProvider(BaseContextProvider):
|
|
|
107
105
|
state: dict[str, Any],
|
|
108
106
|
) -> None:
|
|
109
107
|
"""Search Supermemory for relevant memories and inject into context."""
|
|
110
|
-
# Extract query text from input messages
|
|
111
108
|
query_text = ""
|
|
112
109
|
if self._mode != "profile":
|
|
113
110
|
query_text = self._extract_query_from_context(context)
|
|
@@ -137,11 +134,9 @@ class SupermemoryContextProvider(BaseContextProvider):
|
|
|
137
134
|
self._logger.debug("No memories found")
|
|
138
135
|
return
|
|
139
136
|
|
|
140
|
-
# Prepend entity context if available
|
|
141
137
|
if self._connection.entity_context:
|
|
142
138
|
memories_text = f"{self._connection.entity_context}\n\n{memories_text}"
|
|
143
139
|
|
|
144
|
-
# Inject memories into the session context
|
|
145
140
|
full_text = wrap_memory_injection(memories_text, self._context_prompt)
|
|
146
141
|
|
|
147
142
|
self._logger.debug(
|
|
@@ -149,11 +144,9 @@ class SupermemoryContextProvider(BaseContextProvider):
|
|
|
149
144
|
{"length": len(memories_text)},
|
|
150
145
|
)
|
|
151
146
|
|
|
152
|
-
# Use extend_instructions to add memory context
|
|
153
147
|
if hasattr(context, "extend_instructions"):
|
|
154
148
|
context.extend_instructions(self.source_id, full_text)
|
|
155
149
|
elif hasattr(context, "extend_messages"):
|
|
156
|
-
# Fallback: add as a system message
|
|
157
150
|
context.extend_messages(
|
|
158
151
|
self.source_id,
|
|
159
152
|
[Message("system", [full_text])],
|
|
@@ -185,13 +178,11 @@ class SupermemoryContextProvider(BaseContextProvider):
|
|
|
185
178
|
},
|
|
186
179
|
)
|
|
187
180
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
await self._client.add(**add_params)
|
|
181
|
+
await self._client.add(
|
|
182
|
+
self._container_tag,
|
|
183
|
+
content=conversation_text,
|
|
184
|
+
id=self._connection.custom_id,
|
|
185
|
+
)
|
|
195
186
|
|
|
196
187
|
self._logger.info("Conversation stored successfully")
|
|
197
188
|
|
|
@@ -203,20 +194,16 @@ class SupermemoryContextProvider(BaseContextProvider):
|
|
|
203
194
|
|
|
204
195
|
async def _fetch_memories(self, query_text: str = "") -> str:
|
|
205
196
|
"""Fetch and format memories from Supermemory."""
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
profile = response.profile if response
|
|
197
|
+
response, search = await _fetch_profile_and_search(
|
|
198
|
+
self._client,
|
|
199
|
+
self._container_tag,
|
|
200
|
+
include_profile=self._mode != "query",
|
|
201
|
+
query=query_text if self._mode != "profile" else "",
|
|
202
|
+
)
|
|
203
|
+
profile = response.profile if response else None
|
|
213
204
|
static = list(profile.static) if profile and profile.static else []
|
|
214
205
|
dynamic = list(profile.dynamic) if profile and profile.dynamic else []
|
|
215
|
-
search_results_raw = (
|
|
216
|
-
list(response.search_results.results)
|
|
217
|
-
if response.search_results and response.search_results.results
|
|
218
|
-
else []
|
|
219
|
-
)
|
|
206
|
+
search_results_raw = list(search.results) if search else []
|
|
220
207
|
|
|
221
208
|
deduplicated = deduplicate_memories(
|
|
222
209
|
static=static if self._mode != "query" else [],
|
|
@@ -224,7 +211,6 @@ class SupermemoryContextProvider(BaseContextProvider):
|
|
|
224
211
|
search_results=search_results_raw,
|
|
225
212
|
)
|
|
226
213
|
|
|
227
|
-
# Build formatted text based on mode
|
|
228
214
|
profile_text = ""
|
|
229
215
|
if self._mode != "query":
|
|
230
216
|
profile_text = convert_profile_to_markdown(
|
|
@@ -290,13 +276,11 @@ class SupermemoryContextProvider(BaseContextProvider):
|
|
|
290
276
|
"""Extract conversation text from context for storage."""
|
|
291
277
|
messages: list[Any] = []
|
|
292
278
|
|
|
293
|
-
# Gather input messages
|
|
294
279
|
if hasattr(context, "input_messages"):
|
|
295
280
|
messages.extend(context.input_messages or [])
|
|
296
281
|
elif hasattr(context, "messages"):
|
|
297
282
|
messages.extend(context.messages or [])
|
|
298
283
|
|
|
299
|
-
# Gather response messages
|
|
300
284
|
if hasattr(context, "response") and context.response:
|
|
301
285
|
resp = context.response
|
|
302
286
|
if hasattr(resp, "text") and resp.text:
|
|
@@ -15,9 +15,11 @@ from .connection import AgentSupermemory
|
|
|
15
15
|
from .exceptions import (
|
|
16
16
|
SupermemoryMemoryOperationError,
|
|
17
17
|
SupermemoryNetworkError,
|
|
18
|
+
SupermemoryTimeoutError,
|
|
18
19
|
)
|
|
19
20
|
from .utils import (
|
|
20
21
|
Logger,
|
|
22
|
+
_fetch_profile_and_search,
|
|
21
23
|
convert_profile_to_markdown,
|
|
22
24
|
create_logger,
|
|
23
25
|
deduplicate_memories,
|
|
@@ -125,20 +127,16 @@ async def _build_memories_text(
|
|
|
125
127
|
query_text: str = "",
|
|
126
128
|
) -> str:
|
|
127
129
|
"""Build formatted memories text from Supermemory API."""
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
profile = memories_response.profile if memories_response
|
|
130
|
+
memories_response, search = await _fetch_profile_and_search(
|
|
131
|
+
client,
|
|
132
|
+
container_tag,
|
|
133
|
+
include_profile=mode != "query",
|
|
134
|
+
query=query_text if mode != "profile" else "",
|
|
135
|
+
)
|
|
136
|
+
profile = memories_response.profile if memories_response else None
|
|
135
137
|
static = list(profile.static) if profile and profile.static else []
|
|
136
138
|
dynamic = list(profile.dynamic) if profile and profile.dynamic else []
|
|
137
|
-
search_results_raw = (
|
|
138
|
-
list(memories_response.search_results.results)
|
|
139
|
-
if memories_response.search_results and memories_response.search_results.results
|
|
140
|
-
else []
|
|
141
|
-
)
|
|
139
|
+
search_results_raw = list(search.results) if search else []
|
|
142
140
|
|
|
143
141
|
logger.info(
|
|
144
142
|
"Memory search completed",
|
|
@@ -146,9 +144,7 @@ async def _build_memories_text(
|
|
|
146
144
|
"container_tag": container_tag,
|
|
147
145
|
"memory_count_static": len(static),
|
|
148
146
|
"memory_count_dynamic": len(dynamic),
|
|
149
|
-
"query_text": (
|
|
150
|
-
query_text[:100] + ("..." if len(query_text) > 100 else "")
|
|
151
|
-
),
|
|
147
|
+
"query_text": (query_text[:100] + ("..." if len(query_text) > 100 else "")),
|
|
152
148
|
"mode": mode,
|
|
153
149
|
},
|
|
154
150
|
)
|
|
@@ -190,13 +186,7 @@ async def _save_memory(
|
|
|
190
186
|
) -> None:
|
|
191
187
|
"""Save a memory to Supermemory."""
|
|
192
188
|
try:
|
|
193
|
-
|
|
194
|
-
"content": content,
|
|
195
|
-
"container_tag": container_tag,
|
|
196
|
-
"custom_id": custom_id,
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
response = await client.add(**add_params)
|
|
189
|
+
response = await client.add(container_tag, content=content, id=custom_id)
|
|
200
190
|
|
|
201
191
|
logger.info(
|
|
202
192
|
"Memory saved successfully",
|
|
@@ -207,10 +197,11 @@ async def _save_memory(
|
|
|
207
197
|
"memory_id": getattr(response, "id", None),
|
|
208
198
|
},
|
|
209
199
|
)
|
|
210
|
-
except
|
|
211
|
-
logger.error(
|
|
212
|
-
|
|
213
|
-
|
|
200
|
+
except supermemory.APITimeoutError as timeout_error:
|
|
201
|
+
logger.error("Timeout while saving memory", {"error": str(timeout_error)})
|
|
202
|
+
raise SupermemoryTimeoutError("Timed out saving memory", timeout_error)
|
|
203
|
+
except (supermemory.APIConnectionError, OSError, ConnectionError) as network_error:
|
|
204
|
+
logger.error("Network error while saving memory", {"error": str(network_error)})
|
|
214
205
|
raise SupermemoryNetworkError(
|
|
215
206
|
"Failed to save memory due to network error", network_error
|
|
216
207
|
)
|
|
@@ -228,7 +219,7 @@ class SupermemoryChatMiddleware(ChatMiddleware):
|
|
|
228
219
|
|
|
229
220
|
Example:
|
|
230
221
|
```python
|
|
231
|
-
from agent_framework.openai import
|
|
222
|
+
from agent_framework.openai import OpenAIChatClient
|
|
232
223
|
from supermemory_agent_framework import (
|
|
233
224
|
AgentSupermemory,
|
|
234
225
|
SupermemoryChatMiddleware,
|
|
@@ -246,7 +237,7 @@ class SupermemoryChatMiddleware(ChatMiddleware):
|
|
|
246
237
|
),
|
|
247
238
|
)
|
|
248
239
|
|
|
249
|
-
agent =
|
|
240
|
+
agent = OpenAIChatClient(model="gpt-5").as_agent(
|
|
250
241
|
name="MemoryAgent",
|
|
251
242
|
instructions="You are a helpful assistant with memory.",
|
|
252
243
|
middleware=[middleware],
|
|
@@ -274,12 +265,9 @@ class SupermemoryChatMiddleware(ChatMiddleware):
|
|
|
274
265
|
call_next: Callable[[], Awaitable[None]],
|
|
275
266
|
) -> None:
|
|
276
267
|
"""Process the chat request by injecting memories and optionally saving conversations."""
|
|
277
|
-
# Remove stale SDK-owned context before every lifecycle path. A failed,
|
|
278
|
-
# empty, or skipped lookup must never leak memories from a prior run.
|
|
279
268
|
_inject_memories(context, "")
|
|
280
269
|
messages = context.messages
|
|
281
270
|
|
|
282
|
-
# Save conversation memory in background if configured
|
|
283
271
|
if self._options.add_memory == "always":
|
|
284
272
|
user_message = _get_last_user_message(messages)
|
|
285
273
|
if user_message and user_message.strip():
|
|
@@ -310,7 +298,6 @@ class SupermemoryChatMiddleware(ChatMiddleware):
|
|
|
310
298
|
|
|
311
299
|
task.add_done_callback(_handle_task_exception)
|
|
312
300
|
|
|
313
|
-
# Determine query text based on mode
|
|
314
301
|
query_text = ""
|
|
315
302
|
if self._options.mode != "profile":
|
|
316
303
|
user_message = _get_last_user_message(messages)
|
|
@@ -329,7 +316,6 @@ class SupermemoryChatMiddleware(ChatMiddleware):
|
|
|
329
316
|
},
|
|
330
317
|
)
|
|
331
318
|
|
|
332
|
-
# Fetch and build memories text
|
|
333
319
|
try:
|
|
334
320
|
memories = await _build_memories_text(
|
|
335
321
|
self._container_tag,
|
|
@@ -347,7 +333,6 @@ class SupermemoryChatMiddleware(ChatMiddleware):
|
|
|
347
333
|
return
|
|
348
334
|
|
|
349
335
|
if memories:
|
|
350
|
-
# Prepend entity context if available
|
|
351
336
|
if self._connection.entity_context:
|
|
352
337
|
memories = f"{self._connection.entity_context}\n\n{memories}"
|
|
353
338
|
|
|
@@ -356,14 +341,11 @@ class SupermemoryChatMiddleware(ChatMiddleware):
|
|
|
356
341
|
{"content": memories[:200], "full_length": len(memories)},
|
|
357
342
|
)
|
|
358
343
|
|
|
359
|
-
# Inject memories into messages
|
|
360
344
|
_inject_memories(context, memories)
|
|
361
345
|
|
|
362
346
|
await call_next()
|
|
363
347
|
|
|
364
|
-
async def wait_for_background_tasks(
|
|
365
|
-
self, timeout: Optional[float] = 10.0
|
|
366
|
-
) -> None:
|
|
348
|
+
async def wait_for_background_tasks(self, timeout: Optional[float] = 10.0) -> None:
|
|
367
349
|
"""Wait for all background memory storage tasks to complete."""
|
|
368
350
|
if not self._background_tasks:
|
|
369
351
|
return
|
|
@@ -8,8 +8,10 @@ import warnings
|
|
|
8
8
|
from typing import Annotated, Any, Optional, TypedDict
|
|
9
9
|
|
|
10
10
|
from agent_framework import FunctionTool, tool
|
|
11
|
+
from supermemory.types import SearchResponse
|
|
11
12
|
|
|
12
13
|
from .connection import AgentSupermemory
|
|
14
|
+
from .utils import _fetch_profile_and_search
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
class MemorySearchResult(TypedDict, total=False):
|
|
@@ -50,13 +52,30 @@ def _to_jsonable(value: Any) -> Any:
|
|
|
50
52
|
try:
|
|
51
53
|
return _to_jsonable(model_dump(mode="json"))
|
|
52
54
|
except TypeError:
|
|
53
|
-
# Compatibility with pydantic-like models whose model_dump does not
|
|
54
|
-
# accept Pydantic v2's ``mode`` argument.
|
|
55
55
|
return _to_jsonable(model_dump())
|
|
56
56
|
|
|
57
57
|
return value
|
|
58
58
|
|
|
59
59
|
|
|
60
|
+
def _serialize_search_results(response: SearchResponse) -> dict[str, Any]:
|
|
61
|
+
"""Keep the tool's legacy result fields while retaining v5 metadata."""
|
|
62
|
+
results = [
|
|
63
|
+
{
|
|
64
|
+
"chunks": None,
|
|
65
|
+
"context": None,
|
|
66
|
+
"documents": None,
|
|
67
|
+
"filepath": None,
|
|
68
|
+
"is_aggregated": None,
|
|
69
|
+
"root_memory_id": None,
|
|
70
|
+
"version": None,
|
|
71
|
+
**_to_jsonable(item),
|
|
72
|
+
"updated_at": item.system.updated_at,
|
|
73
|
+
}
|
|
74
|
+
for item in response.results
|
|
75
|
+
]
|
|
76
|
+
return {"results": results, "timing": response.search_time, "total": len(results)}
|
|
77
|
+
|
|
78
|
+
|
|
60
79
|
class SupermemoryTools:
|
|
61
80
|
"""Memory tools for Microsoft Agent Framework.
|
|
62
81
|
|
|
@@ -92,28 +111,28 @@ class SupermemoryTools:
|
|
|
92
111
|
"""Search stored memories and source chunks.
|
|
93
112
|
|
|
94
113
|
``include_full_docs`` remains a deprecated Python-only argument for
|
|
95
|
-
source compatibility.
|
|
114
|
+
source compatibility. This tool does not return full source documents.
|
|
96
115
|
"""
|
|
97
116
|
if include_full_docs is not None:
|
|
98
117
|
warnings.warn(
|
|
99
|
-
"include_full_docs is deprecated and ignored
|
|
100
|
-
"does not
|
|
118
|
+
"include_full_docs is deprecated and ignored; search_memories "
|
|
119
|
+
"does not request full source documents",
|
|
101
120
|
DeprecationWarning,
|
|
102
121
|
stacklevel=2,
|
|
103
122
|
)
|
|
104
123
|
|
|
105
124
|
try:
|
|
106
|
-
response = await self._client.search
|
|
107
|
-
|
|
108
|
-
|
|
125
|
+
response = await self._client.search(
|
|
126
|
+
self._connection.container_tag,
|
|
127
|
+
query=information_to_get,
|
|
109
128
|
limit=limit,
|
|
110
129
|
threshold=0.6,
|
|
111
130
|
search_mode="hybrid",
|
|
112
131
|
)
|
|
113
|
-
results = response
|
|
132
|
+
results = _serialize_search_results(response)["results"]
|
|
114
133
|
result: MemorySearchResult = {
|
|
115
134
|
"success": True,
|
|
116
|
-
"results":
|
|
135
|
+
"results": results,
|
|
117
136
|
"count": len(results),
|
|
118
137
|
}
|
|
119
138
|
return json.dumps(result, default=str)
|
|
@@ -131,9 +150,9 @@ class SupermemoryTools:
|
|
|
131
150
|
"""Add (remember) memories/details/information about the user or other facts or entities. Run when explicitly asked or when the user mentions any information generalizable beyond the context of the current conversation."""
|
|
132
151
|
try:
|
|
133
152
|
response = await self._client.add(
|
|
153
|
+
self._connection.container_tag,
|
|
134
154
|
content=memory,
|
|
135
|
-
|
|
136
|
-
custom_id=self._connection.custom_id,
|
|
155
|
+
id=self._connection.custom_id,
|
|
137
156
|
)
|
|
138
157
|
result: MemoryAddResult = {
|
|
139
158
|
"success": True,
|
|
@@ -153,23 +172,24 @@ class SupermemoryTools:
|
|
|
153
172
|
) -> str:
|
|
154
173
|
"""Get user profile containing static memories (permanent facts) and dynamic memories (recent context). Optionally include search results by providing a query."""
|
|
155
174
|
try:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
response = await self._client.profile(**kwargs)
|
|
175
|
+
response, search = await _fetch_profile_and_search(
|
|
176
|
+
self._client, self._connection.container_tag, query=query
|
|
177
|
+
)
|
|
161
178
|
result: dict[str, Any] = {
|
|
162
179
|
"success": True,
|
|
163
180
|
"profile": (
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
181
|
+
{
|
|
182
|
+
"static": [fact.memory for fact in response.profile.static],
|
|
183
|
+
"dynamic": [fact.memory for fact in response.profile.dynamic],
|
|
184
|
+
"buckets": {
|
|
185
|
+
name: [fact.memory for fact in facts]
|
|
186
|
+
for name, facts in response.profile.buckets.items()
|
|
187
|
+
},
|
|
188
|
+
}
|
|
189
|
+
if response
|
|
171
190
|
else None
|
|
172
191
|
),
|
|
192
|
+
"search_results": _serialize_search_results(search) if search else None,
|
|
173
193
|
}
|
|
174
194
|
return json.dumps(result, default=str)
|
|
175
195
|
except Exception as error:
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"""Utility functions for Supermemory Agent Framework integration."""
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import json
|
|
4
5
|
import re
|
|
5
6
|
from typing import Any, Optional, Protocol
|
|
6
7
|
|
|
8
|
+
import supermemory
|
|
9
|
+
from supermemory.types import ProfileResponse, SearchResponse
|
|
10
|
+
|
|
7
11
|
DEFAULT_CONTEXT_PROMPT = "The following are retrieved memories about the user."
|
|
8
12
|
MEMORY_CONTEXT_PATTERN = re.compile(
|
|
9
13
|
r'(?:\r?\n)?<supermemory context="user-memories" readonly>.*?</supermemory>',
|
|
@@ -15,6 +19,31 @@ SUPERMEMORY_TAG_PATTERN = re.compile(
|
|
|
15
19
|
)
|
|
16
20
|
|
|
17
21
|
|
|
22
|
+
async def _fetch_profile_and_search(
|
|
23
|
+
client: supermemory.AsyncSupermemory,
|
|
24
|
+
container_tag: str,
|
|
25
|
+
*,
|
|
26
|
+
include_profile: bool = True,
|
|
27
|
+
query: str = "",
|
|
28
|
+
) -> tuple[Optional[ProfileResponse], Optional[SearchResponse]]:
|
|
29
|
+
"""Fetch the independent v5 profile and query resources in one namespace."""
|
|
30
|
+
if include_profile and query:
|
|
31
|
+
profile, search = await asyncio.gather(
|
|
32
|
+
client.profile(container_tag),
|
|
33
|
+
client.search(
|
|
34
|
+
container_tag, query=query, threshold=0.6, search_mode="memories"
|
|
35
|
+
),
|
|
36
|
+
)
|
|
37
|
+
return profile, search
|
|
38
|
+
if include_profile:
|
|
39
|
+
return await client.profile(container_tag), None
|
|
40
|
+
if query:
|
|
41
|
+
return None, await client.search(
|
|
42
|
+
container_tag, query=query, threshold=0.6, search_mode="memories"
|
|
43
|
+
)
|
|
44
|
+
return None, None
|
|
45
|
+
|
|
46
|
+
|
|
18
47
|
def _escape_supermemory_tags(content: str) -> str:
|
|
19
48
|
"""Escape nested Supermemory tags supplied as untrusted memory data."""
|
|
20
49
|
|
|
@@ -134,7 +163,6 @@ def deduplicate_memories(
|
|
|
134
163
|
if isinstance(value, str) and value.strip():
|
|
135
164
|
return value.strip()
|
|
136
165
|
return None
|
|
137
|
-
# Stainless SDK returns pydantic models (attribute access, snake_case).
|
|
138
166
|
for field in ("memory", "chunk", "content"):
|
|
139
167
|
value = getattr(item, field, None)
|
|
140
168
|
if isinstance(value, str) and value.strip():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|