memwal 0.2.2.dev6__py3-none-any.whl → 0.2.3.dev0__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.
- memwal/__init__.py +3 -1
- memwal/client.py +70 -4
- memwal/middleware.py +9 -3
- memwal/mock.py +14 -1
- memwal/types.py +3 -0
- {memwal-0.2.2.dev6.dist-info → memwal-0.2.3.dev0.dist-info}/METADATA +1 -1
- memwal-0.2.3.dev0.dist-info/RECORD +10 -0
- memwal-0.2.2.dev6.dist-info/RECORD +0 -10
- {memwal-0.2.2.dev6.dist-info → memwal-0.2.3.dev0.dist-info}/WHEEL +0 -0
memwal/__init__.py
CHANGED
|
@@ -29,6 +29,7 @@ from .client import (
|
|
|
29
29
|
MemWalError,
|
|
30
30
|
MemWalJsonBodyError,
|
|
31
31
|
MemWalRateLimited,
|
|
32
|
+
MemWalRecallAllDropped,
|
|
32
33
|
MemWalRememberJobFailed,
|
|
33
34
|
MemWalRememberJobNotFound,
|
|
34
35
|
MemWalRememberJobTimeout,
|
|
@@ -87,6 +88,7 @@ __all__ = [
|
|
|
87
88
|
"MemWalClockDriftError",
|
|
88
89
|
"MemWalJsonBodyError",
|
|
89
90
|
"MemWalRateLimited",
|
|
91
|
+
"MemWalRecallAllDropped",
|
|
90
92
|
"MemWalRememberJobFailed",
|
|
91
93
|
"MemWalRememberJobNotFound",
|
|
92
94
|
"MemWalRememberJobTimeout",
|
|
@@ -132,4 +134,4 @@ __all__ = [
|
|
|
132
134
|
"RecallManualResult",
|
|
133
135
|
]
|
|
134
136
|
|
|
135
|
-
__version__ = "0.2.
|
|
137
|
+
__version__ = "0.2.3.dev0"
|
memwal/client.py
CHANGED
|
@@ -453,6 +453,15 @@ def _check_limit(limit: Any) -> None:
|
|
|
453
453
|
raise ValueError(f"limit must be a positive integer, got {limit!r}")
|
|
454
454
|
|
|
455
455
|
|
|
456
|
+
def _check_on_dropped(on_dropped: Any) -> str:
|
|
457
|
+
"""Validate the recall all-dropped handling policy before signing."""
|
|
458
|
+
if on_dropped not in ("throw", "ignore"):
|
|
459
|
+
raise ValueError(
|
|
460
|
+
f"on_dropped must be 'throw' or 'ignore', got {on_dropped!r}"
|
|
461
|
+
)
|
|
462
|
+
return str(on_dropped)
|
|
463
|
+
|
|
464
|
+
|
|
456
465
|
def _check_vector(vector: Any) -> None:
|
|
457
466
|
"""Reject a vector that is not a non-empty list of finite numbers (#1218).
|
|
458
467
|
|
|
@@ -1175,6 +1184,7 @@ class MemWal:
|
|
|
1175
1184
|
limit: int = 10,
|
|
1176
1185
|
namespace: Optional[str] = None,
|
|
1177
1186
|
max_distance: Optional[float] = None,
|
|
1187
|
+
on_dropped: str = "ignore",
|
|
1178
1188
|
) -> RecallResult:
|
|
1179
1189
|
"""Recall memories similar to a query.
|
|
1180
1190
|
|
|
@@ -1200,6 +1210,11 @@ class MemWal:
|
|
|
1200
1210
|
max_distance: Optional client-side relevance threshold. Memories
|
|
1201
1211
|
with ``distance >= max_distance`` are dropped. Ignored when
|
|
1202
1212
|
``query`` is a :class:`RecallParams`.
|
|
1213
|
+
on_dropped: Policy when every matched memory failed download or
|
|
1214
|
+
decrypt (``"ignore"`` by default returns the degraded
|
|
1215
|
+
:class:`RecallResult` with ``degraded=True`` and
|
|
1216
|
+
``dropped_count``; ``"throw"`` raises
|
|
1217
|
+
:class:`MemWalRecallAllDropped`).
|
|
1203
1218
|
|
|
1204
1219
|
Returns:
|
|
1205
1220
|
:class:`RecallResult` with decrypted text results.
|
|
@@ -1214,15 +1229,18 @@ class MemWal:
|
|
|
1214
1229
|
max_distance = params.max_distance
|
|
1215
1230
|
scoring_weights = params.scoring_weights
|
|
1216
1231
|
sort = params.sort
|
|
1232
|
+
on_dropped = params.on_dropped
|
|
1217
1233
|
else:
|
|
1218
1234
|
query_text = query
|
|
1219
1235
|
_reject_blank_text(query_text, "query cannot be empty")
|
|
1220
1236
|
_check_limit(limit)
|
|
1221
1237
|
validate_max_distance(max_distance)
|
|
1238
|
+
policy = _check_on_dropped(on_dropped)
|
|
1239
|
+
resolved_namespace = self._resolve_namespace(namespace)
|
|
1222
1240
|
body = {
|
|
1223
1241
|
"query": query_text,
|
|
1224
1242
|
"limit": limit,
|
|
1225
|
-
"namespace":
|
|
1243
|
+
"namespace": resolved_namespace,
|
|
1226
1244
|
}
|
|
1227
1245
|
if scoring_weights is not None:
|
|
1228
1246
|
body["scoring_weights"] = scoring_weights.to_wire()
|
|
@@ -1241,16 +1259,34 @@ class MemWal:
|
|
|
1241
1259
|
]
|
|
1242
1260
|
raw_dropped = data.get("dropped_count") or 0
|
|
1243
1261
|
try:
|
|
1244
|
-
dropped = int(raw_dropped)
|
|
1262
|
+
dropped = max(0, int(raw_dropped))
|
|
1245
1263
|
except (TypeError, ValueError):
|
|
1246
1264
|
dropped = 0
|
|
1247
|
-
|
|
1265
|
+
degraded = bool(data.get("degraded")) or dropped > 0
|
|
1266
|
+
raw_total = data.get("total", len(memories))
|
|
1267
|
+
try:
|
|
1268
|
+
total_count = int(raw_total)
|
|
1269
|
+
except (TypeError, ValueError):
|
|
1270
|
+
total_count = len(memories)
|
|
1271
|
+
|
|
1272
|
+
if len(memories) == 0 and degraded:
|
|
1273
|
+
effective_dropped = dropped if dropped > 0 else max(1, total_count)
|
|
1274
|
+
effective_total = max(total_count, effective_dropped)
|
|
1275
|
+
if policy == "throw":
|
|
1276
|
+
raise MemWalRecallAllDropped(
|
|
1277
|
+
dropped_count=effective_dropped,
|
|
1278
|
+
total=effective_total,
|
|
1279
|
+
namespace=resolved_namespace,
|
|
1280
|
+
)
|
|
1281
|
+
dropped = effective_dropped
|
|
1282
|
+
|
|
1248
1283
|
if max_distance is not None:
|
|
1249
1284
|
memories = [m for m in memories if m.distance < max_distance]
|
|
1250
1285
|
return RecallResult(
|
|
1251
1286
|
results=memories,
|
|
1252
1287
|
total=total_count,
|
|
1253
1288
|
dropped_count=dropped,
|
|
1289
|
+
degraded=degraded,
|
|
1254
1290
|
)
|
|
1255
1291
|
|
|
1256
1292
|
async def forget(self, namespace: Optional[str] = None) -> ForgetResult:
|
|
@@ -2311,6 +2347,27 @@ class MemWalRememberJobTimeout(MemWalError):
|
|
|
2311
2347
|
self.last_refusal = last_refusal
|
|
2312
2348
|
|
|
2313
2349
|
|
|
2350
|
+
class MemWalRecallAllDropped(MemWalError):
|
|
2351
|
+
"""Raised by :meth:`MemWal.recall` when ``on_dropped="throw"`` is set and
|
|
2352
|
+
every matched memory failed to download or decrypt (``results == []`` and
|
|
2353
|
+
``dropped_count > 0``).
|
|
2354
|
+
|
|
2355
|
+
By default (``on_dropped="ignore"``), :meth:`MemWal.recall` returns the
|
|
2356
|
+
degraded :class:`RecallResult` without raising.
|
|
2357
|
+
"""
|
|
2358
|
+
|
|
2359
|
+
def __init__(self, dropped_count: int, total: int, namespace: str) -> None:
|
|
2360
|
+
noun = "match" if total == 1 else "matches"
|
|
2361
|
+
super().__init__(
|
|
2362
|
+
f'MemWal recall matched {total} {noun} in namespace "{namespace}", '
|
|
2363
|
+
f"but all {dropped_count} failed to download or decrypt"
|
|
2364
|
+
)
|
|
2365
|
+
self.code = "RECALL_ALL_DROPPED"
|
|
2366
|
+
self.dropped_count = dropped_count
|
|
2367
|
+
self.total = total
|
|
2368
|
+
self.namespace = namespace
|
|
2369
|
+
|
|
2370
|
+
|
|
2314
2371
|
async def _discard_http_client(memwal: MemWal) -> None:
|
|
2315
2372
|
"""Close the cached httpx client, and drop it either way.
|
|
2316
2373
|
|
|
@@ -2502,10 +2559,19 @@ class MemWalSync:
|
|
|
2502
2559
|
limit: int = 10,
|
|
2503
2560
|
namespace: Optional[str] = None,
|
|
2504
2561
|
max_distance: Optional[float] = None,
|
|
2562
|
+
on_dropped: str = "ignore",
|
|
2505
2563
|
) -> RecallResult:
|
|
2506
2564
|
"""Synchronous version of :meth:`MemWal.recall` (accepts
|
|
2507
2565
|
:class:`RecallParams` for the recommended object-style call)."""
|
|
2508
|
-
return self._run(
|
|
2566
|
+
return self._run(
|
|
2567
|
+
self._inner.recall(
|
|
2568
|
+
query,
|
|
2569
|
+
limit,
|
|
2570
|
+
namespace,
|
|
2571
|
+
max_distance,
|
|
2572
|
+
on_dropped=on_dropped,
|
|
2573
|
+
)
|
|
2574
|
+
)
|
|
2509
2575
|
|
|
2510
2576
|
def forget(self, namespace: Optional[str] = None) -> ForgetResult:
|
|
2511
2577
|
"""Synchronous version of :meth:`MemWal.forget`."""
|
memwal/middleware.py
CHANGED
|
@@ -659,7 +659,9 @@ def with_memwal_langchain(
|
|
|
659
659
|
return messages
|
|
660
660
|
|
|
661
661
|
try:
|
|
662
|
-
recall_result = await memwal.recall(
|
|
662
|
+
recall_result = await memwal.recall(
|
|
663
|
+
user_text, max_memories, namespace, on_dropped="throw"
|
|
664
|
+
)
|
|
663
665
|
relevant = [
|
|
664
666
|
m for m in recall_result.results
|
|
665
667
|
if (1 - m.distance) >= min_relevance
|
|
@@ -837,7 +839,9 @@ def _wrap_async_openai(
|
|
|
837
839
|
user_text = _find_last_user_message(messages)
|
|
838
840
|
if user_text:
|
|
839
841
|
try:
|
|
840
|
-
recall_result = await memwal.recall(
|
|
842
|
+
recall_result = await memwal.recall(
|
|
843
|
+
user_text, max_memories, namespace, on_dropped="throw"
|
|
844
|
+
)
|
|
841
845
|
relevant = [
|
|
842
846
|
m for m in recall_result.results
|
|
843
847
|
if (1 - m.distance) >= min_relevance
|
|
@@ -915,7 +919,9 @@ def _wrap_sync_openai(
|
|
|
915
919
|
if user_text:
|
|
916
920
|
try:
|
|
917
921
|
recall_result = _run_memwal(
|
|
918
|
-
lambda: memwal.recall(
|
|
922
|
+
lambda: memwal.recall(
|
|
923
|
+
user_text, max_memories, namespace, on_dropped="throw"
|
|
924
|
+
)
|
|
919
925
|
)
|
|
920
926
|
relevant = [
|
|
921
927
|
m for m in recall_result.results
|
memwal/mock.py
CHANGED
|
@@ -267,6 +267,7 @@ class MemWalMock:
|
|
|
267
267
|
limit: int = 10,
|
|
268
268
|
namespace: Optional[str] = None,
|
|
269
269
|
max_distance: Optional[float] = None,
|
|
270
|
+
on_dropped: str = "ignore",
|
|
270
271
|
) -> RecallResult:
|
|
271
272
|
"""Token-overlap recall over the in-memory store.
|
|
272
273
|
|
|
@@ -281,6 +282,7 @@ class MemWalMock:
|
|
|
281
282
|
namespace = query.namespace
|
|
282
283
|
max_distance = query.max_distance
|
|
283
284
|
sort = query.sort
|
|
285
|
+
on_dropped = query.on_dropped
|
|
284
286
|
query_text = query.query
|
|
285
287
|
else:
|
|
286
288
|
sort = None
|
|
@@ -288,6 +290,10 @@ class MemWalMock:
|
|
|
288
290
|
_validate_text(query_text, "query")
|
|
289
291
|
_check_limit(limit)
|
|
290
292
|
validate_max_distance(max_distance)
|
|
293
|
+
if on_dropped not in ("throw", "ignore"):
|
|
294
|
+
raise ValueError(
|
|
295
|
+
f"on_dropped must be 'throw' or 'ignore', got {on_dropped!r}"
|
|
296
|
+
)
|
|
291
297
|
if sort not in (None, "relevance", "recent"):
|
|
292
298
|
raise ValueError("sort must be 'relevance' or 'recent'")
|
|
293
299
|
resolved_namespace = self._namespace if namespace is None else namespace
|
|
@@ -670,9 +676,16 @@ class MemWalMockSync:
|
|
|
670
676
|
limit: int = 10,
|
|
671
677
|
namespace: Optional[str] = None,
|
|
672
678
|
max_distance: Optional[float] = None,
|
|
679
|
+
on_dropped: str = "ignore",
|
|
673
680
|
) -> RecallResult:
|
|
674
681
|
return self._run(
|
|
675
|
-
self._inner.recall(
|
|
682
|
+
self._inner.recall(
|
|
683
|
+
query,
|
|
684
|
+
limit,
|
|
685
|
+
namespace,
|
|
686
|
+
max_distance,
|
|
687
|
+
on_dropped=on_dropped,
|
|
688
|
+
)
|
|
676
689
|
)
|
|
677
690
|
|
|
678
691
|
def analyze(
|
memwal/types.py
CHANGED
|
@@ -121,6 +121,7 @@ class RecallParams:
|
|
|
121
121
|
max_distance: Optional[float] = None
|
|
122
122
|
scoring_weights: Optional["ScoringWeights"] = None
|
|
123
123
|
sort: Optional[str] = None
|
|
124
|
+
on_dropped: str = "ignore"
|
|
124
125
|
|
|
125
126
|
|
|
126
127
|
@dataclass
|
|
@@ -132,6 +133,8 @@ class RecallResult:
|
|
|
132
133
|
# Matches omitted because download or decrypt failed. 0 also means the
|
|
133
134
|
# relayer omitted the field (older servers).
|
|
134
135
|
dropped_count: int = 0
|
|
136
|
+
# True whenever one or more matched blobs were dropped (dropped_count > 0).
|
|
137
|
+
degraded: bool = False
|
|
135
138
|
|
|
136
139
|
|
|
137
140
|
@dataclass
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: memwal
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3.dev0
|
|
4
4
|
Summary: Python SDK for Walrus Memory — Privacy-first AI memory with Ed25519 signing
|
|
5
5
|
Project-URL: Homepage, https://memory.walrus.xyz
|
|
6
6
|
Project-URL: Documentation, https://memory.walrus.xyz
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
memwal/__init__.py,sha256=qms4CbnYe-3LFoMW15Af_aByF0pWXs0yp0vsz2cV8Ts,3290
|
|
2
|
+
memwal/client.py,sha256=R6Btr_iUaChkoTHo0zJaqQiPM-Anz__5MRBfRS_ABpQ,104586
|
|
3
|
+
memwal/compatibility.py,sha256=R3Gt1gVRjYSLNa78leNrTpGMZI1UATYo7N8Ie_44CGI,3054
|
|
4
|
+
memwal/middleware.py,sha256=n_kbXDO4kLqtR8ctPInAzdsorl7jTGg9rnOwhRA4aH4,38603
|
|
5
|
+
memwal/mock.py,sha256=0YBmxeixQ6lTjtiYRRTev3ZEw-x5-2lyWCeI6LYK6dM,25282
|
|
6
|
+
memwal/types.py,sha256=03e91D9rFKQesa7q7oYRIBZJ_Ru96yxGzhLaOkY742Q,14282
|
|
7
|
+
memwal/utils.py,sha256=RYlELBpKUHN9RuvWQu0cSGMoyXpAdTWlNKawfDpDbQ0,13361
|
|
8
|
+
memwal-0.2.3.dev0.dist-info/METADATA,sha256=PKU67DhcdIRBfVqGyMg149fJIfcG_cGyzeiJw2o_To8,12151
|
|
9
|
+
memwal-0.2.3.dev0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
10
|
+
memwal-0.2.3.dev0.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
memwal/__init__.py,sha256=dSoujKelLWb8QaOZtr9vDVQSpxy6OZ8vXRY4z7gK1Rc,3232
|
|
2
|
-
memwal/client.py,sha256=eCp7wnvWryITN_Z3fw_NwBV1u1_mlU_mVrdBhLUoltM,102076
|
|
3
|
-
memwal/compatibility.py,sha256=R3Gt1gVRjYSLNa78leNrTpGMZI1UATYo7N8Ie_44CGI,3054
|
|
4
|
-
memwal/middleware.py,sha256=z2xdbvaobBg4byJXnrX7amWdVYE4jfeO4t31DoDBv_o,38413
|
|
5
|
-
memwal/mock.py,sha256=oG0znsO-gekoCkQ0mNgT1kjQwP1ku8TjQEkaNkbRDlQ,24878
|
|
6
|
-
memwal/types.py,sha256=jekUFnpGhVezU_VavY4DEzty6EWb0GxAwspmszk5Cdc,14144
|
|
7
|
-
memwal/utils.py,sha256=RYlELBpKUHN9RuvWQu0cSGMoyXpAdTWlNKawfDpDbQ0,13361
|
|
8
|
-
memwal-0.2.2.dev6.dist-info/METADATA,sha256=lqyLcVerCRkvXJXvEYG24Zh7ZiOE0ugIbV9rTXingHM,12151
|
|
9
|
-
memwal-0.2.2.dev6.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
10
|
-
memwal-0.2.2.dev6.dist-info/RECORD,,
|
|
File without changes
|