agentfield 0.1.22rc2__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.
- agentfield/__init__.py +66 -0
- agentfield/agent.py +3569 -0
- agentfield/agent_ai.py +1125 -0
- agentfield/agent_cli.py +386 -0
- agentfield/agent_field_handler.py +494 -0
- agentfield/agent_mcp.py +534 -0
- agentfield/agent_registry.py +29 -0
- agentfield/agent_server.py +1185 -0
- agentfield/agent_utils.py +269 -0
- agentfield/agent_workflow.py +323 -0
- agentfield/async_config.py +278 -0
- agentfield/async_execution_manager.py +1227 -0
- agentfield/client.py +1447 -0
- agentfield/connection_manager.py +280 -0
- agentfield/decorators.py +527 -0
- agentfield/did_manager.py +337 -0
- agentfield/dynamic_skills.py +304 -0
- agentfield/execution_context.py +255 -0
- agentfield/execution_state.py +453 -0
- agentfield/http_connection_manager.py +429 -0
- agentfield/litellm_adapters.py +140 -0
- agentfield/logger.py +249 -0
- agentfield/mcp_client.py +204 -0
- agentfield/mcp_manager.py +340 -0
- agentfield/mcp_stdio_bridge.py +550 -0
- agentfield/memory.py +723 -0
- agentfield/memory_events.py +489 -0
- agentfield/multimodal.py +173 -0
- agentfield/multimodal_response.py +403 -0
- agentfield/pydantic_utils.py +227 -0
- agentfield/rate_limiter.py +280 -0
- agentfield/result_cache.py +441 -0
- agentfield/router.py +190 -0
- agentfield/status.py +70 -0
- agentfield/types.py +710 -0
- agentfield/utils.py +26 -0
- agentfield/vc_generator.py +464 -0
- agentfield/vision.py +198 -0
- agentfield-0.1.22rc2.dist-info/METADATA +102 -0
- agentfield-0.1.22rc2.dist-info/RECORD +42 -0
- agentfield-0.1.22rc2.dist-info/WHEEL +5 -0
- agentfield-0.1.22rc2.dist-info/top_level.txt +1 -0
agentfield/memory.py
ADDED
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cross-Agent Persistent Memory Client for AgentField SDK.
|
|
3
|
+
|
|
4
|
+
This module provides the memory interface that enables seamless, automatic memory
|
|
5
|
+
sharing and synchronization across distributed agents.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
import sys
|
|
11
|
+
from functools import wraps
|
|
12
|
+
from typing import Any, Callable, Dict, List, Optional, Sequence, Union
|
|
13
|
+
from .client import AgentFieldClient
|
|
14
|
+
from .execution_context import ExecutionContext
|
|
15
|
+
from .memory_events import MemoryEventClient, ScopedMemoryEventClient
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Python 3.8 compatibility: asyncio.to_thread was added in Python 3.9
|
|
19
|
+
if sys.version_info >= (3, 9):
|
|
20
|
+
from asyncio import to_thread as _to_thread
|
|
21
|
+
else:
|
|
22
|
+
async def _to_thread(func, *args, **kwargs):
|
|
23
|
+
"""Compatibility shim for asyncio.to_thread on Python 3.8."""
|
|
24
|
+
loop = asyncio.get_event_loop()
|
|
25
|
+
return await loop.run_in_executor(None, lambda: func(*args, **kwargs))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _vector_to_list(values: Union[Sequence[float], Any]) -> List[float]:
|
|
29
|
+
"""
|
|
30
|
+
Normalize numpy arrays, tuples, or other sequences to a plain float list.
|
|
31
|
+
"""
|
|
32
|
+
if hasattr(values, "tolist"):
|
|
33
|
+
values = values.tolist()
|
|
34
|
+
return [float(x) for x in values] # type: ignore[arg-type]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class MemoryClient:
|
|
38
|
+
"""
|
|
39
|
+
Core memory client that communicates with the AgentField server's memory API.
|
|
40
|
+
|
|
41
|
+
This client handles the low-level HTTP operations for memory management
|
|
42
|
+
and automatically includes execution context headers for proper scoping.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
agentfield_client: AgentFieldClient,
|
|
48
|
+
execution_context: ExecutionContext,
|
|
49
|
+
agent_node_id: Optional[str] = None,
|
|
50
|
+
):
|
|
51
|
+
self.agentfield_client = agentfield_client
|
|
52
|
+
self.execution_context = execution_context
|
|
53
|
+
self.agent_node_id = agent_node_id
|
|
54
|
+
|
|
55
|
+
def _build_headers(
|
|
56
|
+
self, scope: Optional[str] = None, scope_id: Optional[str] = None
|
|
57
|
+
) -> Dict[str, str]:
|
|
58
|
+
"""Merge execution context headers with explicit scope overrides."""
|
|
59
|
+
|
|
60
|
+
headers = self.execution_context.to_headers()
|
|
61
|
+
|
|
62
|
+
if (not headers.get("X-Agent-Node-ID")) and self.agent_node_id:
|
|
63
|
+
headers["X-Agent-Node-ID"] = self.agent_node_id
|
|
64
|
+
|
|
65
|
+
if scope_id is not None:
|
|
66
|
+
header_name = {
|
|
67
|
+
"workflow": "X-Workflow-ID",
|
|
68
|
+
"session": "X-Session-ID",
|
|
69
|
+
"actor": "X-Actor-ID",
|
|
70
|
+
}.get(scope or "")
|
|
71
|
+
|
|
72
|
+
if header_name:
|
|
73
|
+
headers[header_name] = scope_id
|
|
74
|
+
|
|
75
|
+
return headers
|
|
76
|
+
|
|
77
|
+
async def _async_request(self, method: str, url: str, **kwargs):
|
|
78
|
+
"""Internal helper to perform HTTP requests with graceful fallbacks."""
|
|
79
|
+
if hasattr(self.agentfield_client, "_async_request"):
|
|
80
|
+
return await self.agentfield_client._async_request(method, url, **kwargs)
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
import httpx
|
|
84
|
+
|
|
85
|
+
async with httpx.AsyncClient() as client:
|
|
86
|
+
return await client.request(method, url, **kwargs)
|
|
87
|
+
except ImportError:
|
|
88
|
+
import requests
|
|
89
|
+
|
|
90
|
+
return await _to_thread(requests.request, method, url, **kwargs)
|
|
91
|
+
|
|
92
|
+
async def set(
|
|
93
|
+
self, key: str, data: Any, scope: Optional[str] = None, scope_id: Optional[str] = None
|
|
94
|
+
) -> None:
|
|
95
|
+
"""
|
|
96
|
+
Set a memory value with automatic scoping.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
key: The memory key
|
|
100
|
+
data: The data to store (will be JSON serialized)
|
|
101
|
+
scope: Optional explicit scope override
|
|
102
|
+
"""
|
|
103
|
+
from agentfield.logger import log_debug
|
|
104
|
+
|
|
105
|
+
headers = self._build_headers(scope, scope_id)
|
|
106
|
+
|
|
107
|
+
payload = {"key": key, "data": data}
|
|
108
|
+
|
|
109
|
+
if scope:
|
|
110
|
+
payload["scope"] = scope
|
|
111
|
+
|
|
112
|
+
# Test JSON serialization before sending
|
|
113
|
+
try:
|
|
114
|
+
json.dumps(payload)
|
|
115
|
+
log_debug(f"Memory set operation for key: {key}")
|
|
116
|
+
except Exception as json_error:
|
|
117
|
+
log_debug(
|
|
118
|
+
f"JSON serialization failed for memory key {key}: {type(json_error).__name__}: {json_error}"
|
|
119
|
+
)
|
|
120
|
+
raise
|
|
121
|
+
|
|
122
|
+
# Use synchronous requests to avoid event loop conflicts with AgentField SDK
|
|
123
|
+
url = f"{self.agentfield_client.api_base}/memory/set"
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
if hasattr(self.agentfield_client, "_async_request"):
|
|
127
|
+
response = await self.agentfield_client._async_request(
|
|
128
|
+
"POST",
|
|
129
|
+
url,
|
|
130
|
+
json=payload,
|
|
131
|
+
headers=headers,
|
|
132
|
+
timeout=10.0,
|
|
133
|
+
)
|
|
134
|
+
else:
|
|
135
|
+
import requests
|
|
136
|
+
|
|
137
|
+
response = await _to_thread(
|
|
138
|
+
requests.post,
|
|
139
|
+
url,
|
|
140
|
+
json=payload,
|
|
141
|
+
headers=headers,
|
|
142
|
+
timeout=10.0,
|
|
143
|
+
)
|
|
144
|
+
response.raise_for_status()
|
|
145
|
+
log_debug(f"Memory set successful for key: {key}")
|
|
146
|
+
except Exception as e:
|
|
147
|
+
log_debug(f"Memory set failed for key {key}: {type(e).__name__}: {e}")
|
|
148
|
+
raise
|
|
149
|
+
|
|
150
|
+
async def set_vector(
|
|
151
|
+
self,
|
|
152
|
+
key: str,
|
|
153
|
+
embedding: Union[Sequence[float], Any],
|
|
154
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
155
|
+
scope: Optional[str] = None,
|
|
156
|
+
scope_id: Optional[str] = None,
|
|
157
|
+
) -> None:
|
|
158
|
+
"""
|
|
159
|
+
Store a vector embedding with optional metadata.
|
|
160
|
+
"""
|
|
161
|
+
headers = self._build_headers(scope, scope_id)
|
|
162
|
+
payload: Dict[str, Any] = {
|
|
163
|
+
"key": key,
|
|
164
|
+
"embedding": _vector_to_list(embedding),
|
|
165
|
+
}
|
|
166
|
+
if metadata:
|
|
167
|
+
payload["metadata"] = metadata
|
|
168
|
+
if scope:
|
|
169
|
+
payload["scope"] = scope
|
|
170
|
+
|
|
171
|
+
response = await self._async_request(
|
|
172
|
+
"POST",
|
|
173
|
+
f"{self.agentfield_client.api_base}/memory/vector/set",
|
|
174
|
+
json=payload,
|
|
175
|
+
headers=headers,
|
|
176
|
+
timeout=15.0,
|
|
177
|
+
)
|
|
178
|
+
response.raise_for_status()
|
|
179
|
+
|
|
180
|
+
async def get(
|
|
181
|
+
self,
|
|
182
|
+
key: str,
|
|
183
|
+
default: Any = None,
|
|
184
|
+
scope: Optional[str] = None,
|
|
185
|
+
scope_id: Optional[str] = None,
|
|
186
|
+
) -> Any:
|
|
187
|
+
"""
|
|
188
|
+
Get a memory value with hierarchical lookup.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
key: The memory key
|
|
192
|
+
default: Default value if key not found
|
|
193
|
+
scope: Optional explicit scope override
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
The stored value or default if not found
|
|
197
|
+
"""
|
|
198
|
+
headers = self._build_headers(scope, scope_id)
|
|
199
|
+
|
|
200
|
+
payload = {"key": key}
|
|
201
|
+
|
|
202
|
+
if scope:
|
|
203
|
+
payload["scope"] = scope
|
|
204
|
+
|
|
205
|
+
response = await self._async_request(
|
|
206
|
+
"POST",
|
|
207
|
+
f"{self.agentfield_client.api_base}/memory/get",
|
|
208
|
+
json=payload,
|
|
209
|
+
headers=headers,
|
|
210
|
+
timeout=10.0,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if response.status_code == 404:
|
|
214
|
+
return default
|
|
215
|
+
|
|
216
|
+
response.raise_for_status()
|
|
217
|
+
result = response.json()
|
|
218
|
+
|
|
219
|
+
# Extract the actual data from the memory response
|
|
220
|
+
if isinstance(result, dict) and "data" in result:
|
|
221
|
+
# The server returns JSON-encoded data, so we need to decode it
|
|
222
|
+
data = result["data"]
|
|
223
|
+
if isinstance(data, str):
|
|
224
|
+
try:
|
|
225
|
+
return json.loads(data)
|
|
226
|
+
except json.JSONDecodeError:
|
|
227
|
+
return data
|
|
228
|
+
return data
|
|
229
|
+
|
|
230
|
+
return result
|
|
231
|
+
|
|
232
|
+
async def exists(
|
|
233
|
+
self, key: str, scope: Optional[str] = None, scope_id: Optional[str] = None
|
|
234
|
+
) -> bool:
|
|
235
|
+
"""
|
|
236
|
+
Check if a memory key exists.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
key: The memory key
|
|
240
|
+
scope: Optional explicit scope override
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
True if key exists, False otherwise
|
|
244
|
+
"""
|
|
245
|
+
try:
|
|
246
|
+
await self.get(key, scope=scope, scope_id=scope_id)
|
|
247
|
+
return True
|
|
248
|
+
except Exception:
|
|
249
|
+
return False
|
|
250
|
+
|
|
251
|
+
async def delete(
|
|
252
|
+
self, key: str, scope: Optional[str] = None, scope_id: Optional[str] = None
|
|
253
|
+
) -> None:
|
|
254
|
+
"""
|
|
255
|
+
Delete a memory value.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
key: The memory key
|
|
259
|
+
scope: Optional explicit scope override
|
|
260
|
+
"""
|
|
261
|
+
headers = self._build_headers(scope, scope_id)
|
|
262
|
+
|
|
263
|
+
payload = {"key": key}
|
|
264
|
+
|
|
265
|
+
if scope:
|
|
266
|
+
payload["scope"] = scope
|
|
267
|
+
|
|
268
|
+
response = await self._async_request(
|
|
269
|
+
"POST",
|
|
270
|
+
f"{self.agentfield_client.api_base}/memory/delete",
|
|
271
|
+
json=payload,
|
|
272
|
+
headers=headers,
|
|
273
|
+
timeout=10.0,
|
|
274
|
+
)
|
|
275
|
+
response.raise_for_status()
|
|
276
|
+
|
|
277
|
+
async def delete_vector(
|
|
278
|
+
self, key: str, scope: Optional[str] = None, scope_id: Optional[str] = None
|
|
279
|
+
) -> None:
|
|
280
|
+
"""
|
|
281
|
+
Delete a stored vector embedding.
|
|
282
|
+
"""
|
|
283
|
+
headers = self._build_headers(scope, scope_id)
|
|
284
|
+
payload: Dict[str, Any] = {"key": key}
|
|
285
|
+
if scope:
|
|
286
|
+
payload["scope"] = scope
|
|
287
|
+
response = await self._async_request(
|
|
288
|
+
"POST",
|
|
289
|
+
f"{self.agentfield_client.api_base}/memory/vector/delete",
|
|
290
|
+
json=payload,
|
|
291
|
+
headers=headers,
|
|
292
|
+
timeout=10.0,
|
|
293
|
+
)
|
|
294
|
+
response.raise_for_status()
|
|
295
|
+
|
|
296
|
+
async def list_keys(
|
|
297
|
+
self, scope: str, scope_id: Optional[str] = None
|
|
298
|
+
) -> List[str]:
|
|
299
|
+
"""
|
|
300
|
+
List all keys in a specific scope.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
scope: The scope to list keys from
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
List of memory keys in the scope
|
|
307
|
+
"""
|
|
308
|
+
headers = self._build_headers(scope, scope_id)
|
|
309
|
+
|
|
310
|
+
response = await self._async_request(
|
|
311
|
+
"GET",
|
|
312
|
+
f"{self.agentfield_client.api_base}/memory/list",
|
|
313
|
+
params={"scope": scope},
|
|
314
|
+
headers=headers,
|
|
315
|
+
timeout=10.0,
|
|
316
|
+
)
|
|
317
|
+
response.raise_for_status()
|
|
318
|
+
result = response.json()
|
|
319
|
+
|
|
320
|
+
# Extract keys from the memory list response
|
|
321
|
+
if isinstance(result, list):
|
|
322
|
+
return [item.get("key", "") for item in result if "key" in item]
|
|
323
|
+
|
|
324
|
+
return []
|
|
325
|
+
|
|
326
|
+
async def similarity_search(
|
|
327
|
+
self,
|
|
328
|
+
query_embedding: Union[Sequence[float], Any],
|
|
329
|
+
top_k: int = 10,
|
|
330
|
+
scope: Optional[str] = None,
|
|
331
|
+
scope_id: Optional[str] = None,
|
|
332
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
333
|
+
) -> List[Dict[str, Any]]:
|
|
334
|
+
"""
|
|
335
|
+
Perform a similarity search against stored vectors.
|
|
336
|
+
"""
|
|
337
|
+
headers = self._build_headers(scope, scope_id)
|
|
338
|
+
payload: Dict[str, Any] = {
|
|
339
|
+
"query_embedding": _vector_to_list(query_embedding),
|
|
340
|
+
"top_k": top_k,
|
|
341
|
+
"filters": filters or {},
|
|
342
|
+
}
|
|
343
|
+
if scope:
|
|
344
|
+
payload["scope"] = scope
|
|
345
|
+
|
|
346
|
+
response = await self._async_request(
|
|
347
|
+
"POST",
|
|
348
|
+
f"{self.agentfield_client.api_base}/memory/vector/search",
|
|
349
|
+
json=payload,
|
|
350
|
+
headers=headers,
|
|
351
|
+
timeout=15.0,
|
|
352
|
+
)
|
|
353
|
+
response.raise_for_status()
|
|
354
|
+
return response.json()
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
class ScopedMemoryClient:
|
|
358
|
+
"""
|
|
359
|
+
Memory client that operates within a specific scope.
|
|
360
|
+
|
|
361
|
+
This provides a scoped view of memory operations, automatically
|
|
362
|
+
using the specified scope for all operations.
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
def __init__(
|
|
366
|
+
self,
|
|
367
|
+
memory_client: MemoryClient,
|
|
368
|
+
scope: str,
|
|
369
|
+
scope_id: str,
|
|
370
|
+
event_client: Optional[MemoryEventClient] = None,
|
|
371
|
+
):
|
|
372
|
+
self.memory_client = memory_client
|
|
373
|
+
self.scope = scope
|
|
374
|
+
self.scope_id = scope_id
|
|
375
|
+
self.events = (
|
|
376
|
+
ScopedMemoryEventClient(event_client, scope, scope_id)
|
|
377
|
+
if event_client
|
|
378
|
+
else None
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
async def set(self, key: str, data: Any) -> None:
|
|
382
|
+
"""Set a value in this specific scope."""
|
|
383
|
+
await self.memory_client.set(
|
|
384
|
+
key, data, scope=self.scope, scope_id=self.scope_id
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
async def get(self, key: str, default: Any = None) -> Any:
|
|
388
|
+
"""Get a value from this specific scope."""
|
|
389
|
+
return await self.memory_client.get(
|
|
390
|
+
key, default=default, scope=self.scope, scope_id=self.scope_id
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
async def exists(self, key: str) -> bool:
|
|
394
|
+
"""Check if a key exists in this specific scope."""
|
|
395
|
+
return await self.memory_client.exists(
|
|
396
|
+
key, scope=self.scope, scope_id=self.scope_id
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
async def delete(self, key: str) -> None:
|
|
400
|
+
"""Delete a value from this specific scope."""
|
|
401
|
+
await self.memory_client.delete(
|
|
402
|
+
key, scope=self.scope, scope_id=self.scope_id
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
async def list_keys(self) -> List[str]:
|
|
406
|
+
"""List all keys in this specific scope."""
|
|
407
|
+
return await self.memory_client.list_keys(self.scope, scope_id=self.scope_id)
|
|
408
|
+
|
|
409
|
+
async def set_vector(
|
|
410
|
+
self,
|
|
411
|
+
key: str,
|
|
412
|
+
embedding: Union[Sequence[float], Any],
|
|
413
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
414
|
+
) -> None:
|
|
415
|
+
"""Store a vector within this scope."""
|
|
416
|
+
await self.memory_client.set_vector(
|
|
417
|
+
key,
|
|
418
|
+
embedding,
|
|
419
|
+
metadata=metadata,
|
|
420
|
+
scope=self.scope,
|
|
421
|
+
scope_id=self.scope_id,
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
async def delete_vector(self, key: str) -> None:
|
|
425
|
+
"""Delete a vector within this scope."""
|
|
426
|
+
await self.memory_client.delete_vector(
|
|
427
|
+
key, scope=self.scope, scope_id=self.scope_id
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
async def similarity_search(
|
|
431
|
+
self,
|
|
432
|
+
query_embedding: Union[Sequence[float], Any],
|
|
433
|
+
top_k: int = 10,
|
|
434
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
435
|
+
) -> List[Dict[str, Any]]:
|
|
436
|
+
"""Search vectors within this scope."""
|
|
437
|
+
return await self.memory_client.similarity_search(
|
|
438
|
+
query_embedding,
|
|
439
|
+
top_k=top_k,
|
|
440
|
+
scope=self.scope,
|
|
441
|
+
scope_id=self.scope_id,
|
|
442
|
+
filters=filters,
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
def on_change(self, patterns: Union[str, List[str]]):
|
|
446
|
+
"""
|
|
447
|
+
Decorator for subscribing to memory change events in this scope.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
patterns: Pattern(s) to match against memory keys
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
Decorator function
|
|
454
|
+
"""
|
|
455
|
+
if self.events:
|
|
456
|
+
return self.events.on_change(patterns)
|
|
457
|
+
else:
|
|
458
|
+
# Return a no-op decorator if events are not available
|
|
459
|
+
def decorator(func):
|
|
460
|
+
return func
|
|
461
|
+
|
|
462
|
+
return decorator
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
class GlobalMemoryClient:
|
|
466
|
+
"""
|
|
467
|
+
Memory client for global scope operations.
|
|
468
|
+
|
|
469
|
+
This provides access to the global memory scope that is shared
|
|
470
|
+
across all agents and sessions.
|
|
471
|
+
"""
|
|
472
|
+
|
|
473
|
+
def __init__(
|
|
474
|
+
self,
|
|
475
|
+
memory_client: MemoryClient,
|
|
476
|
+
event_client: Optional[MemoryEventClient] = None,
|
|
477
|
+
):
|
|
478
|
+
self.memory_client = memory_client
|
|
479
|
+
self.event_client = event_client
|
|
480
|
+
|
|
481
|
+
async def set(self, key: str, data: Any) -> None:
|
|
482
|
+
"""Set a value in global scope."""
|
|
483
|
+
await self.memory_client.set(key, data, scope="global")
|
|
484
|
+
|
|
485
|
+
async def get(self, key: str, default: Any = None) -> Any:
|
|
486
|
+
"""Get a value from global scope."""
|
|
487
|
+
return await self.memory_client.get(key, default=default, scope="global")
|
|
488
|
+
|
|
489
|
+
async def exists(self, key: str) -> bool:
|
|
490
|
+
"""Check if a key exists in global scope."""
|
|
491
|
+
return await self.memory_client.exists(key, scope="global")
|
|
492
|
+
|
|
493
|
+
async def delete(self, key: str) -> None:
|
|
494
|
+
"""Delete a value from global scope."""
|
|
495
|
+
await self.memory_client.delete(key, scope="global")
|
|
496
|
+
|
|
497
|
+
async def list_keys(self) -> List[str]:
|
|
498
|
+
"""List all keys in global scope."""
|
|
499
|
+
return await self.memory_client.list_keys("global")
|
|
500
|
+
|
|
501
|
+
async def set_vector(
|
|
502
|
+
self,
|
|
503
|
+
key: str,
|
|
504
|
+
embedding: Union[Sequence[float], Any],
|
|
505
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
506
|
+
) -> None:
|
|
507
|
+
"""Store a vector in global scope."""
|
|
508
|
+
await self.memory_client.set_vector(
|
|
509
|
+
key, embedding, metadata=metadata, scope="global"
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
async def delete_vector(self, key: str) -> None:
|
|
513
|
+
"""Delete a vector in global scope."""
|
|
514
|
+
await self.memory_client.delete_vector(key, scope="global")
|
|
515
|
+
|
|
516
|
+
async def similarity_search(
|
|
517
|
+
self,
|
|
518
|
+
query_embedding: Union[Sequence[float], Any],
|
|
519
|
+
top_k: int = 10,
|
|
520
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
521
|
+
) -> List[Dict[str, Any]]:
|
|
522
|
+
"""Search vectors in global scope."""
|
|
523
|
+
return await self.memory_client.similarity_search(
|
|
524
|
+
query_embedding, top_k=top_k, scope="global", filters=filters
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
def on_change(self, patterns: Union[str, List[str]]) -> Callable:
|
|
528
|
+
"""
|
|
529
|
+
Decorator for subscribing to global-scope memory change events.
|
|
530
|
+
|
|
531
|
+
Args:
|
|
532
|
+
patterns: Pattern(s) to match against memory keys
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
Decorator function
|
|
536
|
+
"""
|
|
537
|
+
|
|
538
|
+
if not self.event_client:
|
|
539
|
+
# No event client available (e.g., during unit tests) — return no-op decorator
|
|
540
|
+
def decorator(func: Callable) -> Callable:
|
|
541
|
+
return func
|
|
542
|
+
|
|
543
|
+
return decorator
|
|
544
|
+
|
|
545
|
+
def decorator(func: Callable) -> Callable:
|
|
546
|
+
@wraps(func)
|
|
547
|
+
async def wrapper(event):
|
|
548
|
+
return await func(event)
|
|
549
|
+
|
|
550
|
+
self.event_client.subscribe(
|
|
551
|
+
patterns,
|
|
552
|
+
wrapper,
|
|
553
|
+
scope="global",
|
|
554
|
+
scope_id=None,
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
setattr(wrapper, "_memory_event_listener", True)
|
|
558
|
+
setattr(
|
|
559
|
+
wrapper,
|
|
560
|
+
"_memory_event_patterns",
|
|
561
|
+
patterns if isinstance(patterns, list) else [patterns],
|
|
562
|
+
)
|
|
563
|
+
setattr(wrapper, "_memory_event_scope", "global")
|
|
564
|
+
setattr(wrapper, "_memory_event_scope_id", None)
|
|
565
|
+
|
|
566
|
+
return wrapper
|
|
567
|
+
|
|
568
|
+
return decorator
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
class MemoryInterface:
|
|
572
|
+
"""
|
|
573
|
+
Developer-facing memory interface that provides the intuitive app.memory API.
|
|
574
|
+
|
|
575
|
+
This class provides the main interface that developers interact with,
|
|
576
|
+
offering automatic scoping, hierarchical lookup, and explicit scope access.
|
|
577
|
+
"""
|
|
578
|
+
|
|
579
|
+
def __init__(self, memory_client: MemoryClient, event_client: MemoryEventClient):
|
|
580
|
+
self.memory_client = memory_client
|
|
581
|
+
self.events = event_client
|
|
582
|
+
|
|
583
|
+
async def set(self, key: str, data: Any) -> None:
|
|
584
|
+
"""
|
|
585
|
+
Set a memory value with automatic scoping.
|
|
586
|
+
|
|
587
|
+
The value will be stored in the most specific available scope
|
|
588
|
+
based on the current execution context.
|
|
589
|
+
|
|
590
|
+
Args:
|
|
591
|
+
key: The memory key
|
|
592
|
+
data: The data to store
|
|
593
|
+
"""
|
|
594
|
+
await self.memory_client.set(key, data)
|
|
595
|
+
|
|
596
|
+
async def set_vector(
|
|
597
|
+
self,
|
|
598
|
+
key: str,
|
|
599
|
+
embedding: Union[Sequence[float], Any],
|
|
600
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
601
|
+
) -> None:
|
|
602
|
+
"""
|
|
603
|
+
Store a vector embedding with automatic scoping.
|
|
604
|
+
"""
|
|
605
|
+
await self.memory_client.set_vector(key, embedding, metadata=metadata)
|
|
606
|
+
|
|
607
|
+
async def get(self, key: str, default: Any = None) -> Any:
|
|
608
|
+
"""
|
|
609
|
+
Get a memory value with hierarchical lookup.
|
|
610
|
+
|
|
611
|
+
This will search through scopes in order: workflow -> session -> actor -> global
|
|
612
|
+
and return the first match found.
|
|
613
|
+
|
|
614
|
+
Args:
|
|
615
|
+
key: The memory key
|
|
616
|
+
default: Default value if key not found in any scope
|
|
617
|
+
|
|
618
|
+
Returns:
|
|
619
|
+
The stored value or default if not found
|
|
620
|
+
"""
|
|
621
|
+
return await self.memory_client.get(key, default=default)
|
|
622
|
+
|
|
623
|
+
async def exists(self, key: str) -> bool:
|
|
624
|
+
"""
|
|
625
|
+
Check if a memory key exists in any scope.
|
|
626
|
+
|
|
627
|
+
Args:
|
|
628
|
+
key: The memory key
|
|
629
|
+
|
|
630
|
+
Returns:
|
|
631
|
+
True if key exists in any scope, False otherwise
|
|
632
|
+
"""
|
|
633
|
+
return await self.memory_client.exists(key)
|
|
634
|
+
|
|
635
|
+
async def delete(self, key: str) -> None:
|
|
636
|
+
"""
|
|
637
|
+
Delete a memory value from the current scope.
|
|
638
|
+
|
|
639
|
+
Args:
|
|
640
|
+
key: The memory key
|
|
641
|
+
"""
|
|
642
|
+
await self.memory_client.delete(key)
|
|
643
|
+
|
|
644
|
+
async def delete_vector(self, key: str) -> None:
|
|
645
|
+
"""
|
|
646
|
+
Delete a vector embedding from the current scope.
|
|
647
|
+
"""
|
|
648
|
+
await self.memory_client.delete_vector(key)
|
|
649
|
+
|
|
650
|
+
async def similarity_search(
|
|
651
|
+
self,
|
|
652
|
+
query_embedding: Union[Sequence[float], Any],
|
|
653
|
+
top_k: int = 10,
|
|
654
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
655
|
+
) -> List[Dict[str, Any]]:
|
|
656
|
+
"""
|
|
657
|
+
Search stored vectors using similarity matching.
|
|
658
|
+
"""
|
|
659
|
+
return await self.memory_client.similarity_search(
|
|
660
|
+
query_embedding, top_k=top_k, filters=filters
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
def on_change(self, patterns: Union[str, List[str]]):
|
|
664
|
+
"""
|
|
665
|
+
Decorator for subscribing to memory change events.
|
|
666
|
+
|
|
667
|
+
Args:
|
|
668
|
+
patterns: Pattern(s) to match against memory keys
|
|
669
|
+
|
|
670
|
+
Returns:
|
|
671
|
+
Decorator function
|
|
672
|
+
"""
|
|
673
|
+
return self.events.on_change(patterns)
|
|
674
|
+
|
|
675
|
+
def session(self, session_id: str) -> ScopedMemoryClient:
|
|
676
|
+
"""
|
|
677
|
+
Get a memory client scoped to a specific session.
|
|
678
|
+
|
|
679
|
+
Args:
|
|
680
|
+
session_id: The session ID to scope to
|
|
681
|
+
|
|
682
|
+
Returns:
|
|
683
|
+
ScopedMemoryClient for the specified session
|
|
684
|
+
"""
|
|
685
|
+
return ScopedMemoryClient(
|
|
686
|
+
self.memory_client, "session", session_id, self.events
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
def actor(self, actor_id: str) -> ScopedMemoryClient:
|
|
690
|
+
"""
|
|
691
|
+
Get a memory client scoped to a specific actor.
|
|
692
|
+
|
|
693
|
+
Args:
|
|
694
|
+
actor_id: The actor ID to scope to
|
|
695
|
+
|
|
696
|
+
Returns:
|
|
697
|
+
ScopedMemoryClient for the specified actor
|
|
698
|
+
"""
|
|
699
|
+
return ScopedMemoryClient(self.memory_client, "actor", actor_id, self.events)
|
|
700
|
+
|
|
701
|
+
def workflow(self, workflow_id: str) -> ScopedMemoryClient:
|
|
702
|
+
"""
|
|
703
|
+
Get a memory client scoped to a specific workflow.
|
|
704
|
+
|
|
705
|
+
Args:
|
|
706
|
+
workflow_id: The workflow ID to scope to
|
|
707
|
+
|
|
708
|
+
Returns:
|
|
709
|
+
ScopedMemoryClient for the specified workflow
|
|
710
|
+
"""
|
|
711
|
+
return ScopedMemoryClient(
|
|
712
|
+
self.memory_client, "workflow", workflow_id, self.events
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
@property
|
|
716
|
+
def global_scope(self) -> GlobalMemoryClient:
|
|
717
|
+
"""
|
|
718
|
+
Get a memory client for global scope operations.
|
|
719
|
+
|
|
720
|
+
Returns:
|
|
721
|
+
GlobalMemoryClient for global scope access
|
|
722
|
+
"""
|
|
723
|
+
return GlobalMemoryClient(self.memory_client, self.events)
|