supermemory-agent-framework 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.
- supermemory_agent_framework/__init__.py +60 -0
- supermemory_agent_framework/connection.py +63 -0
- supermemory_agent_framework/context_provider.py +330 -0
- supermemory_agent_framework/exceptions.py +66 -0
- supermemory_agent_framework/middleware.py +421 -0
- supermemory_agent_framework/tools.py +176 -0
- supermemory_agent_framework/utils.py +152 -0
- supermemory_agent_framework-1.0.0.dist-info/METADATA +383 -0
- supermemory_agent_framework-1.0.0.dist-info/RECORD +11 -0
- supermemory_agent_framework-1.0.0.dist-info/WHEEL +4 -0
- supermemory_agent_framework-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
"""Supermemory middleware for Microsoft Agent Framework.
|
|
2
|
+
|
|
3
|
+
Provides ChatMiddleware that automatically injects relevant memories into
|
|
4
|
+
the system prompt before LLM calls, and optionally saves conversations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Any, Awaitable, Callable, Literal, Optional
|
|
10
|
+
|
|
11
|
+
import supermemory
|
|
12
|
+
from agent_framework import ChatMiddleware, Message
|
|
13
|
+
|
|
14
|
+
from .connection import AgentSupermemory
|
|
15
|
+
from .exceptions import (
|
|
16
|
+
SupermemoryMemoryOperationError,
|
|
17
|
+
SupermemoryNetworkError,
|
|
18
|
+
)
|
|
19
|
+
from .utils import (
|
|
20
|
+
Logger,
|
|
21
|
+
convert_profile_to_markdown,
|
|
22
|
+
create_logger,
|
|
23
|
+
deduplicate_memories,
|
|
24
|
+
wrap_memory_injection,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class SupermemoryMiddlewareOptions:
|
|
30
|
+
"""Configuration options for Supermemory middleware."""
|
|
31
|
+
|
|
32
|
+
verbose: bool = False
|
|
33
|
+
mode: Literal["profile", "query", "full"] = "profile"
|
|
34
|
+
add_memory: Literal["always", "never"] = "never"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _get_last_user_message(messages: Any) -> str:
|
|
38
|
+
"""Extract the last user message from the messages sequence."""
|
|
39
|
+
if not messages:
|
|
40
|
+
return ""
|
|
41
|
+
|
|
42
|
+
for msg in reversed(list(messages)):
|
|
43
|
+
role = None
|
|
44
|
+
content = None
|
|
45
|
+
|
|
46
|
+
if hasattr(msg, "role"):
|
|
47
|
+
role = msg.role
|
|
48
|
+
elif isinstance(msg, dict):
|
|
49
|
+
role = msg.get("role")
|
|
50
|
+
|
|
51
|
+
if role == "user":
|
|
52
|
+
if hasattr(msg, "text"):
|
|
53
|
+
content = msg.text
|
|
54
|
+
elif hasattr(msg, "content"):
|
|
55
|
+
content = msg.content
|
|
56
|
+
elif isinstance(msg, dict):
|
|
57
|
+
content = msg.get("content", "") or msg.get("text", "")
|
|
58
|
+
|
|
59
|
+
if isinstance(content, str):
|
|
60
|
+
return content
|
|
61
|
+
if isinstance(content, list):
|
|
62
|
+
text_parts = []
|
|
63
|
+
for part in content:
|
|
64
|
+
if isinstance(part, dict) and part.get("type") == "text":
|
|
65
|
+
text_parts.append(part.get("text", ""))
|
|
66
|
+
elif isinstance(part, str):
|
|
67
|
+
text_parts.append(part)
|
|
68
|
+
return " ".join(text_parts)
|
|
69
|
+
return ""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _get_conversation_content(messages: Any) -> str:
|
|
73
|
+
"""Convert messages into a formatted conversation string."""
|
|
74
|
+
conversation_parts = []
|
|
75
|
+
|
|
76
|
+
for msg in messages:
|
|
77
|
+
role = None
|
|
78
|
+
content = None
|
|
79
|
+
|
|
80
|
+
if hasattr(msg, "role"):
|
|
81
|
+
role = msg.role
|
|
82
|
+
elif isinstance(msg, dict):
|
|
83
|
+
role = msg.get("role")
|
|
84
|
+
|
|
85
|
+
if hasattr(msg, "text"):
|
|
86
|
+
content = msg.text
|
|
87
|
+
elif hasattr(msg, "content"):
|
|
88
|
+
content = msg.content
|
|
89
|
+
elif isinstance(msg, dict):
|
|
90
|
+
content = msg.get("content", "") or msg.get("text", "")
|
|
91
|
+
|
|
92
|
+
if role and content:
|
|
93
|
+
role_display = {
|
|
94
|
+
"user": "User",
|
|
95
|
+
"assistant": "Assistant",
|
|
96
|
+
"system": "System",
|
|
97
|
+
}.get(role, role.capitalize() if isinstance(role, str) else str(role))
|
|
98
|
+
|
|
99
|
+
if isinstance(content, str):
|
|
100
|
+
content_text = content
|
|
101
|
+
elif isinstance(content, list):
|
|
102
|
+
text_parts = []
|
|
103
|
+
for part in content:
|
|
104
|
+
if isinstance(part, dict) and part.get("type") == "text":
|
|
105
|
+
text_parts.append(part.get("text", ""))
|
|
106
|
+
elif isinstance(part, str):
|
|
107
|
+
text_parts.append(part)
|
|
108
|
+
content_text = " ".join(text_parts)
|
|
109
|
+
else:
|
|
110
|
+
content_text = str(content)
|
|
111
|
+
|
|
112
|
+
if content_text:
|
|
113
|
+
conversation_parts.append(f"{role_display}: {content_text}")
|
|
114
|
+
|
|
115
|
+
return "\n\n".join(conversation_parts)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
async def _build_memories_text(
|
|
119
|
+
container_tag: str,
|
|
120
|
+
logger: Logger,
|
|
121
|
+
mode: Literal["profile", "query", "full"],
|
|
122
|
+
client: supermemory.AsyncSupermemory,
|
|
123
|
+
query_text: str = "",
|
|
124
|
+
) -> str:
|
|
125
|
+
"""Build formatted memories text from Supermemory API."""
|
|
126
|
+
kwargs: dict[str, Any] = {"container_tag": container_tag}
|
|
127
|
+
if query_text:
|
|
128
|
+
kwargs["q"] = query_text
|
|
129
|
+
|
|
130
|
+
memories_response = await client.profile(**kwargs)
|
|
131
|
+
|
|
132
|
+
profile = memories_response.profile if memories_response.profile else None
|
|
133
|
+
static = list(profile.static) if profile and profile.static else []
|
|
134
|
+
dynamic = list(profile.dynamic) if profile and profile.dynamic else []
|
|
135
|
+
search_results_raw = (
|
|
136
|
+
list(memories_response.search_results.results)
|
|
137
|
+
if memories_response.search_results and memories_response.search_results.results
|
|
138
|
+
else []
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
logger.info(
|
|
142
|
+
"Memory search completed",
|
|
143
|
+
{
|
|
144
|
+
"container_tag": container_tag,
|
|
145
|
+
"memory_count_static": len(static),
|
|
146
|
+
"memory_count_dynamic": len(dynamic),
|
|
147
|
+
"query_text": (
|
|
148
|
+
query_text[:100] + ("..." if len(query_text) > 100 else "")
|
|
149
|
+
),
|
|
150
|
+
"mode": mode,
|
|
151
|
+
},
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
deduplicated = deduplicate_memories(
|
|
155
|
+
static=static,
|
|
156
|
+
dynamic=dynamic,
|
|
157
|
+
search_results=search_results_raw,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
profile_data = ""
|
|
161
|
+
if mode != "query":
|
|
162
|
+
profile_data = convert_profile_to_markdown(
|
|
163
|
+
{
|
|
164
|
+
"profile": {
|
|
165
|
+
"static": deduplicated.static,
|
|
166
|
+
"dynamic": deduplicated.dynamic,
|
|
167
|
+
},
|
|
168
|
+
"searchResults": {"results": []},
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
search_results_memories = ""
|
|
173
|
+
if mode != "profile" and deduplicated.search_results:
|
|
174
|
+
search_results_memories = (
|
|
175
|
+
"Search results for user's recent message: \n"
|
|
176
|
+
+ "\n".join(f"- {memory}" for memory in deduplicated.search_results)
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
return f"{profile_data}\n{search_results_memories}".strip()
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
async def _save_memory(
|
|
183
|
+
client: supermemory.AsyncSupermemory,
|
|
184
|
+
container_tag: str,
|
|
185
|
+
content: str,
|
|
186
|
+
custom_id: str,
|
|
187
|
+
logger: Logger,
|
|
188
|
+
) -> None:
|
|
189
|
+
"""Save a memory to Supermemory."""
|
|
190
|
+
try:
|
|
191
|
+
add_params: dict[str, Any] = {
|
|
192
|
+
"content": content,
|
|
193
|
+
"container_tag": container_tag,
|
|
194
|
+
"custom_id": custom_id,
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
response = await client.add(**add_params)
|
|
198
|
+
|
|
199
|
+
logger.info(
|
|
200
|
+
"Memory saved successfully",
|
|
201
|
+
{
|
|
202
|
+
"container_tag": container_tag,
|
|
203
|
+
"custom_id": custom_id,
|
|
204
|
+
"content_length": len(content),
|
|
205
|
+
"memory_id": getattr(response, "id", None),
|
|
206
|
+
},
|
|
207
|
+
)
|
|
208
|
+
except (OSError, ConnectionError) as network_error:
|
|
209
|
+
logger.error(
|
|
210
|
+
"Network error while saving memory", {"error": str(network_error)}
|
|
211
|
+
)
|
|
212
|
+
raise SupermemoryNetworkError(
|
|
213
|
+
"Failed to save memory due to network error", network_error
|
|
214
|
+
)
|
|
215
|
+
except Exception as error:
|
|
216
|
+
logger.error("Error saving memory", {"error": str(error)})
|
|
217
|
+
raise SupermemoryMemoryOperationError("Failed to save memory", error)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class SupermemoryChatMiddleware(ChatMiddleware):
|
|
221
|
+
"""Chat middleware that injects Supermemory memories into the system prompt.
|
|
222
|
+
|
|
223
|
+
This middleware intercepts chat requests before they reach the LLM,
|
|
224
|
+
fetches relevant memories from Supermemory, and injects them into
|
|
225
|
+
the system prompt. It can also save conversations as memories.
|
|
226
|
+
|
|
227
|
+
Example:
|
|
228
|
+
```python
|
|
229
|
+
from agent_framework.openai import OpenAIResponsesClient
|
|
230
|
+
from supermemory_agent_framework import (
|
|
231
|
+
AgentSupermemory,
|
|
232
|
+
SupermemoryChatMiddleware,
|
|
233
|
+
SupermemoryMiddlewareOptions,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
conn = AgentSupermemory(api_key="your-key", container_tag="user-123")
|
|
237
|
+
|
|
238
|
+
middleware = SupermemoryChatMiddleware(
|
|
239
|
+
conn,
|
|
240
|
+
options=SupermemoryMiddlewareOptions(
|
|
241
|
+
mode="full",
|
|
242
|
+
verbose=True,
|
|
243
|
+
add_memory="always",
|
|
244
|
+
),
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
agent = OpenAIResponsesClient().as_agent(
|
|
248
|
+
name="MemoryAgent",
|
|
249
|
+
instructions="You are a helpful assistant with memory.",
|
|
250
|
+
middleware=[middleware],
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
response = await agent.run("What's my favorite language?")
|
|
254
|
+
```
|
|
255
|
+
"""
|
|
256
|
+
|
|
257
|
+
def __init__(
|
|
258
|
+
self,
|
|
259
|
+
connection: AgentSupermemory,
|
|
260
|
+
options: Optional[SupermemoryMiddlewareOptions] = None,
|
|
261
|
+
) -> None:
|
|
262
|
+
self._connection = connection
|
|
263
|
+
self._container_tag = connection.container_tag
|
|
264
|
+
self._options = options or SupermemoryMiddlewareOptions()
|
|
265
|
+
self._logger = create_logger(self._options.verbose)
|
|
266
|
+
self._supermemory_client = connection.client
|
|
267
|
+
self._background_tasks: set[asyncio.Task[None]] = set()
|
|
268
|
+
|
|
269
|
+
async def process(
|
|
270
|
+
self,
|
|
271
|
+
context: Any,
|
|
272
|
+
call_next: Callable[[], Awaitable[None]],
|
|
273
|
+
) -> None:
|
|
274
|
+
"""Process the chat request by injecting memories and optionally saving conversations."""
|
|
275
|
+
messages = context.messages
|
|
276
|
+
|
|
277
|
+
# Save conversation memory in background if configured
|
|
278
|
+
if self._options.add_memory == "always":
|
|
279
|
+
user_message = _get_last_user_message(messages)
|
|
280
|
+
if user_message and user_message.strip():
|
|
281
|
+
content = _get_conversation_content(messages)
|
|
282
|
+
|
|
283
|
+
task = asyncio.create_task(
|
|
284
|
+
_save_memory(
|
|
285
|
+
self._supermemory_client,
|
|
286
|
+
self._container_tag,
|
|
287
|
+
content,
|
|
288
|
+
self._connection.custom_id,
|
|
289
|
+
self._logger,
|
|
290
|
+
)
|
|
291
|
+
)
|
|
292
|
+
self._background_tasks.add(task)
|
|
293
|
+
task.add_done_callback(self._background_tasks.discard)
|
|
294
|
+
|
|
295
|
+
def _handle_task_exception(task_obj: asyncio.Task[None]) -> None:
|
|
296
|
+
try:
|
|
297
|
+
exc = task_obj.exception()
|
|
298
|
+
if exc is not None:
|
|
299
|
+
self._logger.warn(
|
|
300
|
+
"Background memory storage failed",
|
|
301
|
+
{"error": str(exc), "type": type(exc).__name__},
|
|
302
|
+
)
|
|
303
|
+
except asyncio.CancelledError:
|
|
304
|
+
self._logger.debug("Memory storage task was cancelled")
|
|
305
|
+
|
|
306
|
+
task.add_done_callback(_handle_task_exception)
|
|
307
|
+
|
|
308
|
+
# Determine query text based on mode
|
|
309
|
+
query_text = ""
|
|
310
|
+
if self._options.mode != "profile":
|
|
311
|
+
user_message = _get_last_user_message(messages)
|
|
312
|
+
if not user_message:
|
|
313
|
+
self._logger.debug("No user message found, skipping memory search")
|
|
314
|
+
await call_next()
|
|
315
|
+
return
|
|
316
|
+
query_text = user_message
|
|
317
|
+
|
|
318
|
+
self._logger.info(
|
|
319
|
+
"Starting memory search",
|
|
320
|
+
{
|
|
321
|
+
"container_tag": self._container_tag,
|
|
322
|
+
"conversation_id": self._connection.conversation_id,
|
|
323
|
+
"mode": self._options.mode,
|
|
324
|
+
},
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Fetch and build memories text
|
|
328
|
+
try:
|
|
329
|
+
memories = await _build_memories_text(
|
|
330
|
+
self._container_tag,
|
|
331
|
+
self._logger,
|
|
332
|
+
self._options.mode,
|
|
333
|
+
self._supermemory_client,
|
|
334
|
+
query_text,
|
|
335
|
+
)
|
|
336
|
+
except Exception as e:
|
|
337
|
+
self._logger.error(
|
|
338
|
+
"Failed to fetch memories, proceeding without",
|
|
339
|
+
{"error": str(e)},
|
|
340
|
+
)
|
|
341
|
+
await call_next()
|
|
342
|
+
return
|
|
343
|
+
|
|
344
|
+
if memories:
|
|
345
|
+
# Prepend entity context if available
|
|
346
|
+
if self._connection.entity_context:
|
|
347
|
+
memories = f"{self._connection.entity_context}\n\n{memories}"
|
|
348
|
+
|
|
349
|
+
self._logger.debug(
|
|
350
|
+
"Memory content preview",
|
|
351
|
+
{"content": memories[:200], "full_length": len(memories)},
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
# Inject memories into messages
|
|
355
|
+
_inject_memories(context, memories)
|
|
356
|
+
|
|
357
|
+
await call_next()
|
|
358
|
+
|
|
359
|
+
async def wait_for_background_tasks(
|
|
360
|
+
self, timeout: Optional[float] = 10.0
|
|
361
|
+
) -> None:
|
|
362
|
+
"""Wait for all background memory storage tasks to complete."""
|
|
363
|
+
if not self._background_tasks:
|
|
364
|
+
return
|
|
365
|
+
|
|
366
|
+
self._logger.debug(
|
|
367
|
+
f"Waiting for {len(self._background_tasks)} background tasks"
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
try:
|
|
371
|
+
if timeout is not None:
|
|
372
|
+
await asyncio.wait_for(
|
|
373
|
+
asyncio.gather(*self._background_tasks, return_exceptions=True),
|
|
374
|
+
timeout=timeout,
|
|
375
|
+
)
|
|
376
|
+
else:
|
|
377
|
+
await asyncio.gather(*self._background_tasks, return_exceptions=True)
|
|
378
|
+
self._logger.debug("All background tasks completed")
|
|
379
|
+
except asyncio.TimeoutError:
|
|
380
|
+
self._logger.warn(
|
|
381
|
+
f"Background tasks did not complete within {timeout}s timeout"
|
|
382
|
+
)
|
|
383
|
+
for task in self._background_tasks:
|
|
384
|
+
if not task.done():
|
|
385
|
+
task.cancel()
|
|
386
|
+
raise
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _inject_memories(context: Any, memories: str) -> None:
|
|
390
|
+
"""Inject memories into the chat context messages.
|
|
391
|
+
|
|
392
|
+
Handles both object-based and dict-based message formats used by
|
|
393
|
+
different Agent Framework providers.
|
|
394
|
+
"""
|
|
395
|
+
messages = context.messages
|
|
396
|
+
memory_text = f"\n\n{wrap_memory_injection(memories)}"
|
|
397
|
+
|
|
398
|
+
# Try to find and augment existing system message
|
|
399
|
+
for i, msg in enumerate(messages):
|
|
400
|
+
role = None
|
|
401
|
+
if hasattr(msg, "role"):
|
|
402
|
+
role = msg.role
|
|
403
|
+
elif isinstance(msg, dict):
|
|
404
|
+
role = msg.get("role")
|
|
405
|
+
|
|
406
|
+
if role == "system":
|
|
407
|
+
if hasattr(msg, "text"):
|
|
408
|
+
msg.text = (msg.text or "") + memory_text
|
|
409
|
+
elif hasattr(msg, "content"):
|
|
410
|
+
msg.content = (msg.content or "") + memory_text
|
|
411
|
+
elif isinstance(msg, dict):
|
|
412
|
+
msg["content"] = (msg.get("content", "") or "") + memory_text
|
|
413
|
+
return
|
|
414
|
+
|
|
415
|
+
# No system message found - prepend one
|
|
416
|
+
try:
|
|
417
|
+
if isinstance(messages, list):
|
|
418
|
+
messages.insert(0, Message("system", [memories]))
|
|
419
|
+
except Exception:
|
|
420
|
+
# If messages is immutable, log a warning
|
|
421
|
+
pass
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Supermemory tools for Microsoft Agent Framework.
|
|
2
|
+
|
|
3
|
+
Provides FunctionTool-compatible tools that can be passed to Agent.run(tools=[...]).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
from typing import Annotated, Any, TypedDict
|
|
8
|
+
|
|
9
|
+
from agent_framework import FunctionTool, tool
|
|
10
|
+
|
|
11
|
+
from .connection import AgentSupermemory
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MemorySearchResult(TypedDict, total=False):
|
|
15
|
+
"""Result type for memory search operations."""
|
|
16
|
+
|
|
17
|
+
success: bool
|
|
18
|
+
results: list[Any] | None
|
|
19
|
+
count: int | None
|
|
20
|
+
error: str | None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MemoryAddResult(TypedDict, total=False):
|
|
24
|
+
"""Result type for memory add operations."""
|
|
25
|
+
|
|
26
|
+
success: bool
|
|
27
|
+
memory: Any | None
|
|
28
|
+
error: str | None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ProfileResult(TypedDict, total=False):
|
|
32
|
+
"""Result type for profile operations."""
|
|
33
|
+
|
|
34
|
+
success: bool
|
|
35
|
+
profile: dict[str, Any] | None
|
|
36
|
+
search_results: dict[str, Any] | None
|
|
37
|
+
error: str | None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SupermemoryTools:
|
|
41
|
+
"""Memory tools for Microsoft Agent Framework.
|
|
42
|
+
|
|
43
|
+
Creates FunctionTool instances that can be passed to Agent.run(tools=[...]).
|
|
44
|
+
|
|
45
|
+
Example:
|
|
46
|
+
```python
|
|
47
|
+
from supermemory_agent_framework import AgentSupermemory, SupermemoryTools
|
|
48
|
+
|
|
49
|
+
conn = AgentSupermemory(api_key="your-key", container_tag="user-123")
|
|
50
|
+
tools = SupermemoryTools(conn)
|
|
51
|
+
agent_tools = tools.get_tools()
|
|
52
|
+
|
|
53
|
+
response = await agent.run(
|
|
54
|
+
"What do you remember about me?",
|
|
55
|
+
tools=agent_tools,
|
|
56
|
+
)
|
|
57
|
+
```
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, connection: AgentSupermemory) -> None:
|
|
61
|
+
self._connection = connection
|
|
62
|
+
self._client = connection.client
|
|
63
|
+
|
|
64
|
+
async def search_memories(
|
|
65
|
+
self,
|
|
66
|
+
information_to_get: Annotated[
|
|
67
|
+
str, "Terms to search for in the user's memories"
|
|
68
|
+
],
|
|
69
|
+
include_full_docs: Annotated[
|
|
70
|
+
bool,
|
|
71
|
+
"Whether to include full document content. Defaults to true for better AI context.",
|
|
72
|
+
] = True,
|
|
73
|
+
limit: Annotated[int, "Maximum number of results to return"] = 10,
|
|
74
|
+
) -> str:
|
|
75
|
+
"""Search (recall) memories/details/information about the user or other facts or entities. Run when explicitly asked or when context about user's past choices would be helpful."""
|
|
76
|
+
try:
|
|
77
|
+
response = await self._client.search.execute(
|
|
78
|
+
q=information_to_get,
|
|
79
|
+
container_tags=[self._connection.container_tag],
|
|
80
|
+
limit=limit,
|
|
81
|
+
chunk_threshold=0.6,
|
|
82
|
+
include_full_docs=include_full_docs,
|
|
83
|
+
)
|
|
84
|
+
result: MemorySearchResult = {
|
|
85
|
+
"success": True,
|
|
86
|
+
"results": response.results,
|
|
87
|
+
"count": len(response.results) if response.results else 0,
|
|
88
|
+
}
|
|
89
|
+
return json.dumps(result, default=str)
|
|
90
|
+
except Exception as error:
|
|
91
|
+
result = {"success": False, "error": str(error)}
|
|
92
|
+
return json.dumps(result)
|
|
93
|
+
|
|
94
|
+
async def add_memory(
|
|
95
|
+
self,
|
|
96
|
+
memory: Annotated[
|
|
97
|
+
str,
|
|
98
|
+
"The text content of the memory to add. Should be a single sentence or short paragraph.",
|
|
99
|
+
],
|
|
100
|
+
) -> str:
|
|
101
|
+
"""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."""
|
|
102
|
+
try:
|
|
103
|
+
response = await self._client.add(
|
|
104
|
+
content=memory,
|
|
105
|
+
container_tag=self._connection.container_tag,
|
|
106
|
+
custom_id=self._connection.custom_id,
|
|
107
|
+
)
|
|
108
|
+
result: MemoryAddResult = {
|
|
109
|
+
"success": True,
|
|
110
|
+
"memory": response,
|
|
111
|
+
}
|
|
112
|
+
return json.dumps(result, default=str)
|
|
113
|
+
except Exception as error:
|
|
114
|
+
result = {"success": False, "error": str(error)}
|
|
115
|
+
return json.dumps(result)
|
|
116
|
+
|
|
117
|
+
async def get_profile(
|
|
118
|
+
self,
|
|
119
|
+
query: Annotated[
|
|
120
|
+
str,
|
|
121
|
+
"Optional search query to include relevant search results.",
|
|
122
|
+
] = "",
|
|
123
|
+
) -> str:
|
|
124
|
+
"""Get user profile containing static memories (permanent facts) and dynamic memories (recent context). Optionally include search results by providing a query."""
|
|
125
|
+
try:
|
|
126
|
+
kwargs: dict[str, Any] = {"container_tag": self._connection.container_tag}
|
|
127
|
+
if query:
|
|
128
|
+
kwargs["q"] = query
|
|
129
|
+
|
|
130
|
+
response = await self._client.profile(**kwargs)
|
|
131
|
+
result: dict[str, Any] = {
|
|
132
|
+
"success": True,
|
|
133
|
+
"profile": response.profile if hasattr(response, "profile") else None,
|
|
134
|
+
"search_results": (
|
|
135
|
+
response.search_results
|
|
136
|
+
if hasattr(response, "search_results")
|
|
137
|
+
else None
|
|
138
|
+
),
|
|
139
|
+
}
|
|
140
|
+
return json.dumps(result, default=str)
|
|
141
|
+
except Exception as error:
|
|
142
|
+
result = {"success": False, "error": str(error)}
|
|
143
|
+
return json.dumps(result)
|
|
144
|
+
|
|
145
|
+
def get_tools(self) -> list[FunctionTool]:
|
|
146
|
+
"""Get all Supermemory tools as FunctionTool instances.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
List of FunctionTool instances ready to pass to Agent.run(tools=...)
|
|
150
|
+
"""
|
|
151
|
+
return [
|
|
152
|
+
tool(
|
|
153
|
+
name="search_memories",
|
|
154
|
+
description=(
|
|
155
|
+
"Search (recall) memories/details/information about the user or other "
|
|
156
|
+
"facts or entities. Run when explicitly asked or when context about "
|
|
157
|
+
"user's past choices would be helpful."
|
|
158
|
+
),
|
|
159
|
+
)(self.search_memories),
|
|
160
|
+
tool(
|
|
161
|
+
name="add_memory",
|
|
162
|
+
description=(
|
|
163
|
+
"Add (remember) memories/details/information about the user or other "
|
|
164
|
+
"facts or entities. Run when explicitly asked or when the user mentions "
|
|
165
|
+
"any information generalizable beyond the context of the current conversation."
|
|
166
|
+
),
|
|
167
|
+
)(self.add_memory),
|
|
168
|
+
tool(
|
|
169
|
+
name="get_profile",
|
|
170
|
+
description=(
|
|
171
|
+
"Get user profile containing static memories (permanent facts) and "
|
|
172
|
+
"dynamic memories (recent context). Optionally include search results "
|
|
173
|
+
"by providing a query."
|
|
174
|
+
),
|
|
175
|
+
)(self.get_profile),
|
|
176
|
+
]
|