memwal 0.1.2.dev3__tar.gz → 0.1.3.dev0__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.
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/CHANGELOG.md +11 -0
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/PKG-INFO +6 -6
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/README.md +5 -5
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/examples/async_remember_demo.py +2 -1
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/examples/interactive_demo.py +3 -3
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/memwal/__init__.py +4 -2
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/memwal/client.py +70 -21
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/memwal/types.py +18 -0
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/pyproject.toml +1 -1
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/tests/test_client.py +58 -1
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/.gitignore +0 -0
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/examples/.env.example +0 -0
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/examples/.gitignore +0 -0
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/examples/verify_credentials.py +0 -0
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/memwal/compatibility.py +0 -0
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/memwal/middleware.py +0 -0
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/memwal/utils.py +0 -0
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/run_tests.py +0 -0
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/tests/__init__.py +0 -0
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/tests/test_env_presets.py +0 -0
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/tests/test_integration.py +0 -0
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/tests/test_middleware.py +0 -0
- {memwal-0.1.2.dev3 → memwal-0.1.3.dev0}/tests/test_signing.py +0 -0
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# memwal
|
|
2
2
|
|
|
3
|
+
## 0.1.3
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Added `RecallParams` for object-style `recall(...)` calls.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Changed the default `restore()` limit from `50` to `10` to match the relayer and TypeScript SDK.
|
|
12
|
+
- Documented `restore()` response fields, default limit, pagination behavior, and performance expectations.
|
|
13
|
+
|
|
3
14
|
## 0.1.2
|
|
4
15
|
|
|
5
16
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memwal
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3.dev0
|
|
4
4
|
Summary: Python SDK for Walrus Memory — Privacy-first AI memory with Ed25519 signing
|
|
5
5
|
Project-URL: Homepage, https://memwal.ai
|
|
6
6
|
Project-URL: Documentation, https://docs.memwal.ai
|
|
@@ -75,7 +75,7 @@ must stay server-side.
|
|
|
75
75
|
```python
|
|
76
76
|
import asyncio
|
|
77
77
|
import os
|
|
78
|
-
from memwal import MemWal
|
|
78
|
+
from memwal import MemWal, RecallParams
|
|
79
79
|
|
|
80
80
|
async def main():
|
|
81
81
|
memwal = MemWal.create(
|
|
@@ -89,7 +89,7 @@ async def main():
|
|
|
89
89
|
print(result.blob_id)
|
|
90
90
|
|
|
91
91
|
# Recall memories
|
|
92
|
-
matches = await memwal.recall("food allergies", limit=10, max_distance=0.7)
|
|
92
|
+
matches = await memwal.recall(RecallParams(query="food allergies", limit=10, max_distance=0.7))
|
|
93
93
|
for memory in matches.results:
|
|
94
94
|
print(f"{memory.text} (relevance: {1 - memory.distance:.2f})")
|
|
95
95
|
|
|
@@ -107,7 +107,7 @@ asyncio.run(main())
|
|
|
107
107
|
|
|
108
108
|
```python
|
|
109
109
|
import os
|
|
110
|
-
from memwal import MemWalSync
|
|
110
|
+
from memwal import MemWalSync, RecallParams
|
|
111
111
|
|
|
112
112
|
client = MemWalSync.create(
|
|
113
113
|
key=os.environ["MEMWAL_PRIVATE_KEY"],
|
|
@@ -116,7 +116,7 @@ client = MemWalSync.create(
|
|
|
116
116
|
)
|
|
117
117
|
|
|
118
118
|
result = client.remember("I'm allergic to peanuts")
|
|
119
|
-
matches = client.recall("food allergies")
|
|
119
|
+
matches = client.recall(RecallParams(query="food allergies"))
|
|
120
120
|
client.close()
|
|
121
121
|
```
|
|
122
122
|
|
|
@@ -216,7 +216,7 @@ Create a new async client.
|
|
|
216
216
|
| Method | Description |
|
|
217
217
|
|--------|-------------|
|
|
218
218
|
| `await remember(text, namespace?)` | Store a memory |
|
|
219
|
-
| `await recall(query, limit?, namespace?, max_distance?)` | Search memories, optionally filtering by distance |
|
|
219
|
+
| `await recall(RecallParams(query, limit?, namespace?, max_distance?))` | Search memories, optionally filtering by distance |
|
|
220
220
|
| `await analyze(text, namespace?)` | Extract and store facts |
|
|
221
221
|
| `await ask(question, limit?, namespace?)` | Ask a question answered using memories |
|
|
222
222
|
| `await restore(namespace, limit?)` | Restore a namespace |
|
|
@@ -36,7 +36,7 @@ must stay server-side.
|
|
|
36
36
|
```python
|
|
37
37
|
import asyncio
|
|
38
38
|
import os
|
|
39
|
-
from memwal import MemWal
|
|
39
|
+
from memwal import MemWal, RecallParams
|
|
40
40
|
|
|
41
41
|
async def main():
|
|
42
42
|
memwal = MemWal.create(
|
|
@@ -50,7 +50,7 @@ async def main():
|
|
|
50
50
|
print(result.blob_id)
|
|
51
51
|
|
|
52
52
|
# Recall memories
|
|
53
|
-
matches = await memwal.recall("food allergies", limit=10, max_distance=0.7)
|
|
53
|
+
matches = await memwal.recall(RecallParams(query="food allergies", limit=10, max_distance=0.7))
|
|
54
54
|
for memory in matches.results:
|
|
55
55
|
print(f"{memory.text} (relevance: {1 - memory.distance:.2f})")
|
|
56
56
|
|
|
@@ -68,7 +68,7 @@ asyncio.run(main())
|
|
|
68
68
|
|
|
69
69
|
```python
|
|
70
70
|
import os
|
|
71
|
-
from memwal import MemWalSync
|
|
71
|
+
from memwal import MemWalSync, RecallParams
|
|
72
72
|
|
|
73
73
|
client = MemWalSync.create(
|
|
74
74
|
key=os.environ["MEMWAL_PRIVATE_KEY"],
|
|
@@ -77,7 +77,7 @@ client = MemWalSync.create(
|
|
|
77
77
|
)
|
|
78
78
|
|
|
79
79
|
result = client.remember("I'm allergic to peanuts")
|
|
80
|
-
matches = client.recall("food allergies")
|
|
80
|
+
matches = client.recall(RecallParams(query="food allergies"))
|
|
81
81
|
client.close()
|
|
82
82
|
```
|
|
83
83
|
|
|
@@ -177,7 +177,7 @@ Create a new async client.
|
|
|
177
177
|
| Method | Description |
|
|
178
178
|
|--------|-------------|
|
|
179
179
|
| `await remember(text, namespace?)` | Store a memory |
|
|
180
|
-
| `await recall(query, limit?, namespace?, max_distance?)` | Search memories, optionally filtering by distance |
|
|
180
|
+
| `await recall(RecallParams(query, limit?, namespace?, max_distance?))` | Search memories, optionally filtering by distance |
|
|
181
181
|
| `await analyze(text, namespace?)` | Extract and store facts |
|
|
182
182
|
| `await ask(question, limit?, namespace?)` | Ask a question answered using memories |
|
|
183
183
|
| `await restore(namespace, limit?)` | Restore a namespace |
|
|
@@ -51,6 +51,7 @@ sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
51
51
|
from memwal import ( # noqa: E402
|
|
52
52
|
MemWal,
|
|
53
53
|
MemWalRememberJobFailed,
|
|
54
|
+
RecallParams,
|
|
54
55
|
RememberBulkItem,
|
|
55
56
|
RememberBulkOptions,
|
|
56
57
|
)
|
|
@@ -119,7 +120,7 @@ async def main() -> None:
|
|
|
119
120
|
|
|
120
121
|
# 5. recall — confirm the memories are searchable
|
|
121
122
|
_section("5. recall('coffee')")
|
|
122
|
-
rc = await memwal.recall("coffee", limit=3)
|
|
123
|
+
rc = await memwal.recall(RecallParams(query="coffee", limit=3))
|
|
123
124
|
print(f" found {len(rc.results)} / total={rc.total}")
|
|
124
125
|
for m in rc.results[:3]:
|
|
125
126
|
print(f" [{m.distance:.3f}] {m.text[:80]}")
|
|
@@ -39,7 +39,7 @@ def _load_env() -> None:
|
|
|
39
39
|
_load_env()
|
|
40
40
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
41
41
|
|
|
42
|
-
from memwal import MemWal, RememberBulkItem, RememberBulkOptions # noqa: E402
|
|
42
|
+
from memwal import MemWal, RecallParams, RememberBulkItem, RememberBulkOptions # noqa: E402
|
|
43
43
|
|
|
44
44
|
# Path to the running server's log so we can show what happens on the
|
|
45
45
|
# server side as each SDK call is made.
|
|
@@ -164,7 +164,7 @@ async def main() -> None:
|
|
|
164
164
|
print(f" → query='{query}'")
|
|
165
165
|
off = _log_offset()
|
|
166
166
|
t = _now_ms()
|
|
167
|
-
rc = await memwal.recall(query, limit=3)
|
|
167
|
+
rc = await memwal.recall(RecallParams(query=query, limit=3))
|
|
168
168
|
rec_ms = _now_ms() - t
|
|
169
169
|
print(f" ← {rec_ms} ms {len(rc.results)} results")
|
|
170
170
|
for m in rc.results:
|
|
@@ -199,7 +199,7 @@ async def main() -> None:
|
|
|
199
199
|
# ───────────────────────────────────────────────────────────
|
|
200
200
|
_hr("STEP 6 POST /api/recall query='japan'")
|
|
201
201
|
off = _log_offset()
|
|
202
|
-
rc2 = await memwal.recall("japan", limit=2)
|
|
202
|
+
rc2 = await memwal.recall(RecallParams(query="japan", limit=2))
|
|
203
203
|
print(f" ← {len(rc2.results)} results")
|
|
204
204
|
for m in rc2.results:
|
|
205
205
|
print(f" [{m.distance:.3f}] {m.text}")
|
|
@@ -14,7 +14,7 @@ Quick start::
|
|
|
14
14
|
|
|
15
15
|
# Async
|
|
16
16
|
result = await memwal.remember("I love coffee")
|
|
17
|
-
matches = await memwal.recall("beverage preferences")
|
|
17
|
+
matches = await memwal.recall(RecallParams(query="beverage preferences"))
|
|
18
18
|
|
|
19
19
|
# Sync wrapper
|
|
20
20
|
from memwal import MemWalSync
|
|
@@ -46,6 +46,7 @@ from .types import (
|
|
|
46
46
|
RecallManualOptions,
|
|
47
47
|
RecallManualResult,
|
|
48
48
|
RecallMemory,
|
|
49
|
+
RecallParams,
|
|
49
50
|
RecallResult,
|
|
50
51
|
RememberAcceptedResult,
|
|
51
52
|
RememberBulkAcceptedResult,
|
|
@@ -98,6 +99,7 @@ __all__ = [
|
|
|
98
99
|
"RememberBulkStatusResult",
|
|
99
100
|
"RememberBulkItemResult",
|
|
100
101
|
"RememberBulkResult",
|
|
102
|
+
"RecallParams",
|
|
101
103
|
"RecallResult",
|
|
102
104
|
"RecallMemory",
|
|
103
105
|
"EmbedResult",
|
|
@@ -114,4 +116,4 @@ __all__ = [
|
|
|
114
116
|
"RecallManualResult",
|
|
115
117
|
]
|
|
116
118
|
|
|
117
|
-
__version__ = "0.1.
|
|
119
|
+
__version__ = "0.1.3.dev0"
|
|
@@ -20,7 +20,7 @@ Example::
|
|
|
20
20
|
|
|
21
21
|
# Async usage
|
|
22
22
|
result = await memwal.remember("I'm allergic to peanuts")
|
|
23
|
-
matches = await memwal.recall("food allergies")
|
|
23
|
+
matches = await memwal.recall(RecallParams(query="food allergies"))
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
from __future__ import annotations
|
|
@@ -49,6 +49,7 @@ from .types import (
|
|
|
49
49
|
RecallManualOptions,
|
|
50
50
|
RecallManualResult,
|
|
51
51
|
RecallMemory,
|
|
52
|
+
RecallParams,
|
|
52
53
|
RecallResult,
|
|
53
54
|
RememberAcceptedResult,
|
|
54
55
|
RememberBulkAcceptedResult,
|
|
@@ -473,7 +474,7 @@ class MemWal:
|
|
|
473
474
|
|
|
474
475
|
async def recall(
|
|
475
476
|
self,
|
|
476
|
-
query: str,
|
|
477
|
+
query: "str | RecallParams",
|
|
477
478
|
limit: int = 10,
|
|
478
479
|
namespace: Optional[str] = None,
|
|
479
480
|
max_distance: Optional[float] = None,
|
|
@@ -482,18 +483,40 @@ class MemWal:
|
|
|
482
483
|
|
|
483
484
|
Server handles: verify -> embed query -> search -> Walrus download -> decrypt.
|
|
484
485
|
|
|
486
|
+
**Preferred call style** is to pass a :class:`RecallParams`
|
|
487
|
+
object so the call site reads self-describingly:
|
|
488
|
+
|
|
489
|
+
await client.recall(RecallParams(
|
|
490
|
+
query="food allergies", limit=5, namespace="profile"))
|
|
491
|
+
|
|
492
|
+
The legacy positional call ``client.recall(query, limit, namespace)``
|
|
493
|
+
is still supported but is easy to mis-read as
|
|
494
|
+
``recall(query, namespace)``. Prefer kwargs at minimum.
|
|
495
|
+
|
|
485
496
|
Args:
|
|
486
|
-
query: Search query
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
497
|
+
query: Search query, or a :class:`RecallParams` carrying query +
|
|
498
|
+
limit + namespace + max_distance in one object.
|
|
499
|
+
limit: Max number of results (default: 10). Ignored when ``query``
|
|
500
|
+
is a :class:`RecallParams`.
|
|
501
|
+
namespace: Override the default namespace. Ignored when ``query``
|
|
502
|
+
is a :class:`RecallParams`.
|
|
503
|
+
max_distance: Optional client-side relevance threshold. Memories
|
|
504
|
+
with ``distance >= max_distance`` are dropped. Ignored when
|
|
505
|
+
``query`` is a :class:`RecallParams`.
|
|
491
506
|
|
|
492
507
|
Returns:
|
|
493
508
|
:class:`RecallResult` with decrypted text results.
|
|
494
509
|
"""
|
|
510
|
+
if isinstance(query, RecallParams):
|
|
511
|
+
params = query
|
|
512
|
+
query_text = params.query
|
|
513
|
+
limit = params.limit
|
|
514
|
+
namespace = params.namespace
|
|
515
|
+
max_distance = params.max_distance
|
|
516
|
+
else:
|
|
517
|
+
query_text = query
|
|
495
518
|
data = await self._signed_request("POST", "/api/recall", {
|
|
496
|
-
"query":
|
|
519
|
+
"query": query_text,
|
|
497
520
|
"limit": limit,
|
|
498
521
|
"namespace": namespace or self._namespace,
|
|
499
522
|
})
|
|
@@ -630,18 +653,42 @@ class MemWal:
|
|
|
630
653
|
memories=memories,
|
|
631
654
|
)
|
|
632
655
|
|
|
633
|
-
async def restore(self, namespace: str, limit: int =
|
|
634
|
-
"""
|
|
656
|
+
async def restore(self, namespace: str, limit: int = 10) -> RestoreResult:
|
|
657
|
+
"""Rebuild missing local index entries for ``namespace`` from Walrus.
|
|
658
|
+
|
|
659
|
+
The relayer queries Walrus for blobs the caller owns in ``namespace``,
|
|
660
|
+
ignores blobs already indexed locally, downloads the missing ones,
|
|
661
|
+
SEAL-decrypts them with the delegate key, re-embeds the plaintext, and
|
|
662
|
+
inserts a fresh vector row per blob.
|
|
663
|
+
|
|
664
|
+
Response semantics:
|
|
635
665
|
|
|
636
|
-
|
|
637
|
-
|
|
666
|
+
* ``restored`` — blobs that completed the full
|
|
667
|
+
download → decrypt → embed → DB insert pipeline this call.
|
|
668
|
+
* ``skipped`` — on-chain blobs already present in the local index
|
|
669
|
+
(no work needed). Decrypt / embed failures are dropped silently and
|
|
670
|
+
do **not** count as either restored or skipped.
|
|
671
|
+
* ``total`` — count of on-chain blobs the relayer saw for
|
|
672
|
+
``(owner, namespace)`` before the limit was applied.
|
|
638
673
|
|
|
639
674
|
Args:
|
|
640
|
-
namespace: Namespace to restore.
|
|
641
|
-
|
|
675
|
+
namespace: Namespace to restore. Exact match — no prefix or
|
|
676
|
+
hierarchy semantics (see SKILL.md "Namespace Semantics").
|
|
677
|
+
limit: Max blobs the relayer will *inspect* this call (default:
|
|
678
|
+
10, matches the server-side default and the TypeScript SDK).
|
|
679
|
+
The relayer fetches blobs newest-first and caps the work at
|
|
680
|
+
this number; it does **not** cap ``restored`` separately.
|
|
642
681
|
|
|
643
682
|
Returns:
|
|
644
|
-
:class:`RestoreResult
|
|
683
|
+
:class:`RestoreResult`.
|
|
684
|
+
|
|
685
|
+
Notes:
|
|
686
|
+
* **No pagination cursor** — restore is single-shot. To rebuild a
|
|
687
|
+
large namespace, call repeatedly with a growing ``limit`` or
|
|
688
|
+
prune the local index first.
|
|
689
|
+
* **Performance** scales linearly in ``limit``: up to 10 Walrus
|
|
690
|
+
downloads in parallel, then 3 SEAL decrypts in parallel, then
|
|
691
|
+
embeddings. Expect seconds-per-blob on cold caches.
|
|
645
692
|
"""
|
|
646
693
|
data = await self._signed_request("POST", "/api/restore", {
|
|
647
694
|
"namespace": namespace,
|
|
@@ -1053,11 +1100,11 @@ class MemWalSync:
|
|
|
1053
1100
|
|
|
1054
1101
|
Example::
|
|
1055
1102
|
|
|
1056
|
-
from memwal import MemWalSync
|
|
1103
|
+
from memwal import MemWalSync, RecallParams
|
|
1057
1104
|
|
|
1058
1105
|
client = MemWalSync.create(key="...", account_id="0x...")
|
|
1059
1106
|
result = client.remember("I love coffee")
|
|
1060
|
-
matches = client.recall("coffee preferences")
|
|
1107
|
+
matches = client.recall(RecallParams(query="coffee preferences"))
|
|
1061
1108
|
"""
|
|
1062
1109
|
|
|
1063
1110
|
def __init__(self, inner: MemWal) -> None:
|
|
@@ -1189,12 +1236,13 @@ class MemWalSync:
|
|
|
1189
1236
|
|
|
1190
1237
|
def recall(
|
|
1191
1238
|
self,
|
|
1192
|
-
query: str,
|
|
1239
|
+
query: "str | RecallParams",
|
|
1193
1240
|
limit: int = 10,
|
|
1194
1241
|
namespace: Optional[str] = None,
|
|
1195
1242
|
max_distance: Optional[float] = None,
|
|
1196
1243
|
) -> RecallResult:
|
|
1197
|
-
"""Synchronous version of :meth:`MemWal.recall
|
|
1244
|
+
"""Synchronous version of :meth:`MemWal.recall` (accepts
|
|
1245
|
+
:class:`RecallParams` for the recommended object-style call)."""
|
|
1198
1246
|
return self._run(self._inner.recall(query, limit, namespace, max_distance))
|
|
1199
1247
|
|
|
1200
1248
|
def analyze(self, text: str, namespace: Optional[str] = None) -> AnalyzeResult:
|
|
@@ -1218,8 +1266,9 @@ class MemWalSync:
|
|
|
1218
1266
|
"""Synchronous version of :meth:`MemWal.ask`."""
|
|
1219
1267
|
return self._run(self._inner.ask(question, limit, namespace))
|
|
1220
1268
|
|
|
1221
|
-
def restore(self, namespace: str, limit: int =
|
|
1222
|
-
"""Synchronous version of :meth:`MemWal.restore`.
|
|
1269
|
+
def restore(self, namespace: str, limit: int = 10) -> RestoreResult:
|
|
1270
|
+
"""Synchronous version of :meth:`MemWal.restore`. Default limit is 10
|
|
1271
|
+
(matches server + TypeScript SDK)."""
|
|
1223
1272
|
return self._run(self._inner.restore(namespace, limit))
|
|
1224
1273
|
|
|
1225
1274
|
def health(self) -> HealthResult:
|
|
@@ -92,6 +92,24 @@ class RecallMemory:
|
|
|
92
92
|
distance: float
|
|
93
93
|
|
|
94
94
|
|
|
95
|
+
@dataclass
|
|
96
|
+
class RecallParams:
|
|
97
|
+
"""Object-style input for :meth:`MemWal.recall`.
|
|
98
|
+
|
|
99
|
+
Preferred over positional args because positional ``recall(query, limit,
|
|
100
|
+
namespace)`` is easy to mis-read as ``recall(query, namespace)`` at call
|
|
101
|
+
sites. Construct this dataclass and pass it as the sole argument:
|
|
102
|
+
|
|
103
|
+
client.recall(RecallParams(query="food allergies", limit=5,
|
|
104
|
+
namespace="profile"))
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
query: str
|
|
108
|
+
limit: int = 10
|
|
109
|
+
namespace: Optional[str] = None
|
|
110
|
+
max_distance: Optional[float] = None
|
|
111
|
+
|
|
112
|
+
|
|
95
113
|
@dataclass
|
|
96
114
|
class RecallResult:
|
|
97
115
|
"""Result from recall()."""
|
|
@@ -17,7 +17,12 @@ import pytest
|
|
|
17
17
|
import respx
|
|
18
18
|
|
|
19
19
|
from memwal.client import MemWal, MemWalCompatibilityError, MemWalError
|
|
20
|
-
from memwal.types import
|
|
20
|
+
from memwal.types import (
|
|
21
|
+
RecallManualOptions,
|
|
22
|
+
RecallParams,
|
|
23
|
+
RememberManualOptions,
|
|
24
|
+
ScoringWeights,
|
|
25
|
+
)
|
|
21
26
|
from memwal.utils import build_signature_message, bytes_to_hex, sha256_hex
|
|
22
27
|
|
|
23
28
|
# ============================================================
|
|
@@ -270,6 +275,58 @@ class TestRecall:
|
|
|
270
275
|
assert result.results[0].distance == 0.1
|
|
271
276
|
assert result.results[1].blob_id == "b2"
|
|
272
277
|
|
|
278
|
+
@respx.mock
|
|
279
|
+
async def test_accepts_recall_params_object(
|
|
280
|
+
self, memwal_client: MemWal
|
|
281
|
+
) -> None:
|
|
282
|
+
"""recall(RecallParams(...)) sends the same request body as
|
|
283
|
+
the positional form, with query/limit/namespace pulled from the
|
|
284
|
+
dataclass instead of positional args."""
|
|
285
|
+
mock_seal_session_prereqs()
|
|
286
|
+
route = respx.post(f"{_TEST_SERVER}/api/recall").mock(
|
|
287
|
+
return_value=httpx.Response(
|
|
288
|
+
200,
|
|
289
|
+
json={"results": [], "total": 0},
|
|
290
|
+
)
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
await memwal_client.recall(
|
|
294
|
+
RecallParams(query="coffee", limit=7, namespace="profile"),
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
assert route.called
|
|
298
|
+
body = json.loads(route.calls[0].request.content)
|
|
299
|
+
assert body["query"] == "coffee"
|
|
300
|
+
assert body["limit"] == 7
|
|
301
|
+
assert body["namespace"] == "profile"
|
|
302
|
+
|
|
303
|
+
@respx.mock
|
|
304
|
+
async def test_recall_params_object_applies_max_distance_filter(
|
|
305
|
+
self, memwal_client: MemWal
|
|
306
|
+
) -> None:
|
|
307
|
+
"""max_distance on the RecallParams dataclass drops weak matches
|
|
308
|
+
client-side, matching the positional max_distance kwarg behavior."""
|
|
309
|
+
mock_seal_session_prereqs()
|
|
310
|
+
respx.post(f"{_TEST_SERVER}/api/recall").mock(
|
|
311
|
+
return_value=httpx.Response(
|
|
312
|
+
200,
|
|
313
|
+
json={
|
|
314
|
+
"results": [
|
|
315
|
+
{"blob_id": "b1", "text": "tight", "distance": 0.1},
|
|
316
|
+
{"blob_id": "b2", "text": "noisy", "distance": 0.9},
|
|
317
|
+
],
|
|
318
|
+
"total": 2,
|
|
319
|
+
},
|
|
320
|
+
)
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
result = await memwal_client.recall(
|
|
324
|
+
RecallParams(query="x", limit=10, max_distance=0.5),
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
assert len(result.results) == 1
|
|
328
|
+
assert result.results[0].blob_id == "b1"
|
|
329
|
+
|
|
273
330
|
@respx.mock
|
|
274
331
|
async def test_sends_correct_headers(self, memwal_client: MemWal) -> None:
|
|
275
332
|
"""recall() should include all required auth headers."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|