audrey-memory 0.23.1__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.
- audrey_memory/__init__.py +51 -0
- audrey_memory/_version.py +1 -0
- audrey_memory/client.py +372 -0
- audrey_memory/py.typed +1 -0
- audrey_memory/types.py +157 -0
- audrey_memory-0.23.1.dist-info/METADATA +104 -0
- audrey_memory-0.23.1.dist-info/RECORD +9 -0
- audrey_memory-0.23.1.dist-info/WHEEL +5 -0
- audrey_memory-0.23.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from ._version import __version__
|
|
2
|
+
from .client import AsyncAudrey, Audrey, AudreyAPIError
|
|
3
|
+
from .types import (
|
|
4
|
+
AckResponse,
|
|
5
|
+
Affect,
|
|
6
|
+
AnalyticsResponse,
|
|
7
|
+
ConsolidateRequest,
|
|
8
|
+
ContradictionStatus,
|
|
9
|
+
DreamRequest,
|
|
10
|
+
EncodeRequest,
|
|
11
|
+
EncodeResponse,
|
|
12
|
+
ForgetRequest,
|
|
13
|
+
ForgetResponse,
|
|
14
|
+
HealthResponse,
|
|
15
|
+
MarkUsedRequest,
|
|
16
|
+
MemorySnapshot,
|
|
17
|
+
OperationResult,
|
|
18
|
+
RecallError,
|
|
19
|
+
RecallRequest,
|
|
20
|
+
RecallResponse,
|
|
21
|
+
RecallResult,
|
|
22
|
+
RestoreResponse,
|
|
23
|
+
StatusResponse,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"__version__",
|
|
28
|
+
"AckResponse",
|
|
29
|
+
"Affect",
|
|
30
|
+
"AnalyticsResponse",
|
|
31
|
+
"AsyncAudrey",
|
|
32
|
+
"Audrey",
|
|
33
|
+
"AudreyAPIError",
|
|
34
|
+
"ConsolidateRequest",
|
|
35
|
+
"ContradictionStatus",
|
|
36
|
+
"DreamRequest",
|
|
37
|
+
"EncodeRequest",
|
|
38
|
+
"EncodeResponse",
|
|
39
|
+
"ForgetRequest",
|
|
40
|
+
"ForgetResponse",
|
|
41
|
+
"HealthResponse",
|
|
42
|
+
"MarkUsedRequest",
|
|
43
|
+
"MemorySnapshot",
|
|
44
|
+
"OperationResult",
|
|
45
|
+
"RecallError",
|
|
46
|
+
"RecallRequest",
|
|
47
|
+
"RecallResponse",
|
|
48
|
+
"RecallResult",
|
|
49
|
+
"RestoreResponse",
|
|
50
|
+
"StatusResponse",
|
|
51
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.23.1"
|
audrey_memory/client.py
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Mapping, TypeVar
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from ._version import __version__
|
|
9
|
+
from .types import (
|
|
10
|
+
AckResponse,
|
|
11
|
+
ConsolidateRequest,
|
|
12
|
+
DreamRequest,
|
|
13
|
+
EncodeRequest,
|
|
14
|
+
EncodeResponse,
|
|
15
|
+
ForgetRequest,
|
|
16
|
+
ForgetResponse,
|
|
17
|
+
HealthResponse,
|
|
18
|
+
MarkUsedRequest,
|
|
19
|
+
MemorySnapshot,
|
|
20
|
+
OperationResult,
|
|
21
|
+
RecallRequest,
|
|
22
|
+
RecallResponse,
|
|
23
|
+
RecallResult,
|
|
24
|
+
RestoreResponse,
|
|
25
|
+
StatusResponse,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
ModelT = TypeVar("ModelT", bound=BaseModel)
|
|
29
|
+
DEFAULT_TIMEOUT = 30.0
|
|
30
|
+
DEFAULT_BASE_URL = "http://127.0.0.1:7437"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AudreyAPIError(RuntimeError):
|
|
34
|
+
def __init__(self, status_code: int, message: str, response_body: Any = None) -> None:
|
|
35
|
+
super().__init__(message)
|
|
36
|
+
self.status_code = status_code
|
|
37
|
+
self.response_body = response_body
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _build_headers(api_key: str | None, agent: str | None) -> dict[str, str]:
|
|
41
|
+
headers = {
|
|
42
|
+
"Accept": "application/json",
|
|
43
|
+
"Content-Type": "application/json",
|
|
44
|
+
"User-Agent": f"audrey-memory-python/{__version__}",
|
|
45
|
+
}
|
|
46
|
+
if api_key:
|
|
47
|
+
headers["Authorization"] = f"Bearer {api_key}"
|
|
48
|
+
if agent:
|
|
49
|
+
headers["X-Audrey-Agent"] = agent
|
|
50
|
+
return headers
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _dump_payload(payload: BaseModel | Mapping[str, Any] | None) -> dict[str, Any] | None:
|
|
54
|
+
if payload is None:
|
|
55
|
+
return None
|
|
56
|
+
if isinstance(payload, BaseModel):
|
|
57
|
+
return payload.model_dump(exclude_none=True, mode="json")
|
|
58
|
+
return {key: value for key, value in dict(payload).items() if value is not None}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _error_message(response: httpx.Response, data: Any) -> str:
|
|
62
|
+
if isinstance(data, dict):
|
|
63
|
+
detail = data.get("error") or data.get("message")
|
|
64
|
+
if isinstance(detail, str) and detail.strip():
|
|
65
|
+
return detail
|
|
66
|
+
return f"Audrey API request failed with status {response.status_code}"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _decode_json(response: httpx.Response) -> Any:
|
|
70
|
+
try:
|
|
71
|
+
data = response.json()
|
|
72
|
+
except ValueError:
|
|
73
|
+
data = None
|
|
74
|
+
if response.is_error:
|
|
75
|
+
raise AudreyAPIError(response.status_code, _error_message(response, data), data)
|
|
76
|
+
return data
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _validate(model_type: type[ModelT], data: Any) -> ModelT:
|
|
80
|
+
return model_type.model_validate(data)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _build_model_payload(
|
|
84
|
+
payload: BaseModel | Mapping[str, Any] | str,
|
|
85
|
+
model_type: type[ModelT],
|
|
86
|
+
field_name: str,
|
|
87
|
+
extra: dict[str, Any],
|
|
88
|
+
) -> ModelT:
|
|
89
|
+
if isinstance(payload, model_type):
|
|
90
|
+
if extra:
|
|
91
|
+
raise TypeError(f"{model_type.__name__} payload cannot be combined with keyword overrides")
|
|
92
|
+
return payload
|
|
93
|
+
if isinstance(payload, Mapping):
|
|
94
|
+
if extra:
|
|
95
|
+
raise TypeError(f"Mapping payload cannot be combined with keyword overrides for {model_type.__name__}")
|
|
96
|
+
return model_type.model_validate(payload)
|
|
97
|
+
return model_type.model_validate({field_name: payload, **extra})
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _optional_model_payload(
|
|
101
|
+
payload: BaseModel | Mapping[str, Any] | None,
|
|
102
|
+
model_type: type[ModelT],
|
|
103
|
+
extra: dict[str, Any],
|
|
104
|
+
) -> ModelT | None:
|
|
105
|
+
if isinstance(payload, model_type):
|
|
106
|
+
if extra:
|
|
107
|
+
raise TypeError(f"{model_type.__name__} payload cannot be combined with keyword overrides")
|
|
108
|
+
return payload
|
|
109
|
+
if payload is None:
|
|
110
|
+
return model_type.model_validate(extra) if extra else None
|
|
111
|
+
if extra:
|
|
112
|
+
raise TypeError(f"Mapping payload cannot be combined with keyword overrides for {model_type.__name__}")
|
|
113
|
+
return model_type.model_validate(payload)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Audrey:
|
|
117
|
+
def __init__(
|
|
118
|
+
self,
|
|
119
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
120
|
+
*,
|
|
121
|
+
api_key: str | None = None,
|
|
122
|
+
agent: str | None = None,
|
|
123
|
+
timeout: float | httpx.Timeout = DEFAULT_TIMEOUT,
|
|
124
|
+
transport: httpx.BaseTransport | None = None,
|
|
125
|
+
) -> None:
|
|
126
|
+
self._client = httpx.Client(
|
|
127
|
+
base_url=base_url.rstrip("/"),
|
|
128
|
+
timeout=timeout,
|
|
129
|
+
transport=transport,
|
|
130
|
+
headers=_build_headers(api_key, agent),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def close(self) -> None:
|
|
134
|
+
self._client.close()
|
|
135
|
+
|
|
136
|
+
def __enter__(self) -> Audrey:
|
|
137
|
+
return self
|
|
138
|
+
|
|
139
|
+
def __exit__(self, exc_type: object, exc: object, traceback: object) -> None:
|
|
140
|
+
self.close()
|
|
141
|
+
|
|
142
|
+
def health(self) -> HealthResponse:
|
|
143
|
+
return _validate(HealthResponse, _decode_json(self._client.get("/health")))
|
|
144
|
+
|
|
145
|
+
def status(self) -> StatusResponse:
|
|
146
|
+
return _validate(StatusResponse, _decode_json(self._client.get("/v1/status")))
|
|
147
|
+
|
|
148
|
+
def impact(self, *, window_days: int = 7, limit: int = 5) -> dict[str, Any]:
|
|
149
|
+
"""Closed-loop visibility report: validations, decay, promotions over a window.
|
|
150
|
+
|
|
151
|
+
Mirrors `audrey impact` and `Audrey.impact()` on the TypeScript side.
|
|
152
|
+
"""
|
|
153
|
+
return _decode_json(
|
|
154
|
+
self._client.get(
|
|
155
|
+
"/v1/impact",
|
|
156
|
+
params={"windowDays": window_days, "limit": limit},
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def analytics(self) -> dict[str, Any]:
|
|
161
|
+
# analytics() is kept as an alias of impact() for callers that already
|
|
162
|
+
# adopted the older spelling. New code should call impact() directly.
|
|
163
|
+
return self.impact()
|
|
164
|
+
|
|
165
|
+
def encode(self, payload: EncodeRequest | Mapping[str, Any] | str, /, **kwargs: Any) -> str:
|
|
166
|
+
request = _build_model_payload(payload, EncodeRequest, "content", kwargs)
|
|
167
|
+
data = _decode_json(self._client.post("/v1/encode", json=_dump_payload(request)))
|
|
168
|
+
return _validate(EncodeResponse, data).id
|
|
169
|
+
|
|
170
|
+
def recall(self, payload: RecallRequest | Mapping[str, Any] | str, /, **kwargs: Any):
|
|
171
|
+
request = _build_model_payload(payload, RecallRequest, "query", kwargs)
|
|
172
|
+
data = _decode_json(self._client.post("/v1/recall", json=_dump_payload(request)))
|
|
173
|
+
if isinstance(data, dict) and isinstance(data.get("results"), list):
|
|
174
|
+
data = data["results"]
|
|
175
|
+
elif not isinstance(data, list):
|
|
176
|
+
raise TypeError(f"unexpected /v1/recall payload shape: {type(data).__name__}")
|
|
177
|
+
return [_validate(RecallResult, row) for row in data]
|
|
178
|
+
|
|
179
|
+
def recall_response(self, payload: RecallRequest | Mapping[str, Any] | str, /, **kwargs: Any) -> RecallResponse:
|
|
180
|
+
request = _build_model_payload(payload, RecallRequest, "query", kwargs)
|
|
181
|
+
data = _decode_json(self._client.post("/v1/recall", json=_dump_payload(request)))
|
|
182
|
+
if isinstance(data, list):
|
|
183
|
+
return RecallResponse(results=[_validate(RecallResult, row) for row in data])
|
|
184
|
+
if isinstance(data, dict):
|
|
185
|
+
return _validate(RecallResponse, data)
|
|
186
|
+
raise TypeError(f"unexpected /v1/recall payload shape: {type(data).__name__}")
|
|
187
|
+
|
|
188
|
+
def dream(self, payload: DreamRequest | Mapping[str, Any] | None = None, /, **kwargs: Any) -> OperationResult:
|
|
189
|
+
request = _optional_model_payload(payload, DreamRequest, kwargs)
|
|
190
|
+
data = _decode_json(self._client.post("/v1/dream", json=_dump_payload(request)))
|
|
191
|
+
return _validate(OperationResult, data)
|
|
192
|
+
|
|
193
|
+
def consolidate(
|
|
194
|
+
self,
|
|
195
|
+
payload: ConsolidateRequest | Mapping[str, Any] | None = None,
|
|
196
|
+
/,
|
|
197
|
+
**kwargs: Any,
|
|
198
|
+
) -> OperationResult:
|
|
199
|
+
request = _optional_model_payload(payload, ConsolidateRequest, kwargs)
|
|
200
|
+
data = _decode_json(self._client.post("/v1/consolidate", json=_dump_payload(request)))
|
|
201
|
+
return _validate(OperationResult, data)
|
|
202
|
+
|
|
203
|
+
def mark_used(self, memory_id: str) -> AckResponse:
|
|
204
|
+
request = MarkUsedRequest(id=memory_id)
|
|
205
|
+
data = _decode_json(self._client.post("/v1/mark-used", json=_dump_payload(request)))
|
|
206
|
+
return _validate(AckResponse, data)
|
|
207
|
+
|
|
208
|
+
def validate(self, memory_id: str, outcome: str = "used") -> dict[str, Any]:
|
|
209
|
+
"""Closed-loop feedback. outcome is one of {"used","helpful","wrong"}.
|
|
210
|
+
|
|
211
|
+
"helpful" reinforces salience and retrieval. "wrong" decreases
|
|
212
|
+
salience and bumps challenge_count for semantic memories. "used"
|
|
213
|
+
is a neutral signal that the memory was referenced.
|
|
214
|
+
"""
|
|
215
|
+
if outcome not in ("used", "helpful", "wrong"):
|
|
216
|
+
raise ValueError(f"outcome must be used|helpful|wrong, got {outcome!r}")
|
|
217
|
+
return _decode_json(self._client.post("/v1/validate", json={"id": memory_id, "outcome": outcome}))
|
|
218
|
+
|
|
219
|
+
def forget(
|
|
220
|
+
self,
|
|
221
|
+
*,
|
|
222
|
+
id: str | None = None,
|
|
223
|
+
query: str | None = None,
|
|
224
|
+
purge: bool | None = None,
|
|
225
|
+
min_similarity: float | None = None,
|
|
226
|
+
) -> ForgetResponse | None:
|
|
227
|
+
request = ForgetRequest(
|
|
228
|
+
id=id,
|
|
229
|
+
query=query,
|
|
230
|
+
purge=purge,
|
|
231
|
+
minSimilarity=min_similarity,
|
|
232
|
+
)
|
|
233
|
+
data = _decode_json(self._client.post("/v1/forget", json=_dump_payload(request)))
|
|
234
|
+
if data is None:
|
|
235
|
+
return None
|
|
236
|
+
return _validate(ForgetResponse, data)
|
|
237
|
+
|
|
238
|
+
def snapshot(self) -> MemorySnapshot:
|
|
239
|
+
# Server exposes snapshot as GET /v1/export.
|
|
240
|
+
data = _decode_json(self._client.get("/v1/export"))
|
|
241
|
+
return _validate(MemorySnapshot, data)
|
|
242
|
+
|
|
243
|
+
def restore(self, snapshot: MemorySnapshot | Mapping[str, Any]) -> RestoreResponse:
|
|
244
|
+
# Server exposes restore as POST /v1/import. The TS handler reads
|
|
245
|
+
# body.snapshot (not the body root), so wrap the payload accordingly.
|
|
246
|
+
request = snapshot if isinstance(snapshot, MemorySnapshot) else MemorySnapshot.model_validate(snapshot)
|
|
247
|
+
data = _decode_json(self._client.post("/v1/import", json={"snapshot": _dump_payload(request)}))
|
|
248
|
+
return _validate(RestoreResponse, data)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class AsyncAudrey:
|
|
252
|
+
def __init__(
|
|
253
|
+
self,
|
|
254
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
255
|
+
*,
|
|
256
|
+
api_key: str | None = None,
|
|
257
|
+
agent: str | None = None,
|
|
258
|
+
timeout: float | httpx.Timeout = DEFAULT_TIMEOUT,
|
|
259
|
+
transport: httpx.AsyncBaseTransport | None = None,
|
|
260
|
+
) -> None:
|
|
261
|
+
self._client = httpx.AsyncClient(
|
|
262
|
+
base_url=base_url.rstrip("/"),
|
|
263
|
+
timeout=timeout,
|
|
264
|
+
transport=transport,
|
|
265
|
+
headers=_build_headers(api_key, agent),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
async def aclose(self) -> None:
|
|
269
|
+
await self._client.aclose()
|
|
270
|
+
|
|
271
|
+
async def __aenter__(self) -> AsyncAudrey:
|
|
272
|
+
return self
|
|
273
|
+
|
|
274
|
+
async def __aexit__(self, exc_type: object, exc: object, traceback: object) -> None:
|
|
275
|
+
await self.aclose()
|
|
276
|
+
|
|
277
|
+
async def health(self) -> HealthResponse:
|
|
278
|
+
return _validate(HealthResponse, _decode_json(await self._client.get("/health")))
|
|
279
|
+
|
|
280
|
+
async def status(self) -> StatusResponse:
|
|
281
|
+
return _validate(StatusResponse, _decode_json(await self._client.get("/v1/status")))
|
|
282
|
+
|
|
283
|
+
async def impact(self, *, window_days: int = 7, limit: int = 5) -> dict[str, Any]:
|
|
284
|
+
"""Closed-loop visibility report — async counterpart of `Audrey.impact`."""
|
|
285
|
+
response = await self._client.get(
|
|
286
|
+
"/v1/impact",
|
|
287
|
+
params={"windowDays": window_days, "limit": limit},
|
|
288
|
+
)
|
|
289
|
+
return _decode_json(response)
|
|
290
|
+
|
|
291
|
+
async def analytics(self) -> dict[str, Any]:
|
|
292
|
+
return await self.impact()
|
|
293
|
+
|
|
294
|
+
async def encode(self, payload: EncodeRequest | Mapping[str, Any] | str, /, **kwargs: Any) -> str:
|
|
295
|
+
request = _build_model_payload(payload, EncodeRequest, "content", kwargs)
|
|
296
|
+
data = _decode_json(await self._client.post("/v1/encode", json=_dump_payload(request)))
|
|
297
|
+
return _validate(EncodeResponse, data).id
|
|
298
|
+
|
|
299
|
+
async def recall(self, payload: RecallRequest | Mapping[str, Any] | str, /, **kwargs: Any):
|
|
300
|
+
request = _build_model_payload(payload, RecallRequest, "query", kwargs)
|
|
301
|
+
data = _decode_json(await self._client.post("/v1/recall", json=_dump_payload(request)))
|
|
302
|
+
if isinstance(data, dict) and isinstance(data.get("results"), list):
|
|
303
|
+
data = data["results"]
|
|
304
|
+
elif not isinstance(data, list):
|
|
305
|
+
raise TypeError(f"unexpected /v1/recall payload shape: {type(data).__name__}")
|
|
306
|
+
return [_validate(RecallResult, row) for row in data]
|
|
307
|
+
|
|
308
|
+
async def recall_response(self, payload: RecallRequest | Mapping[str, Any] | str, /, **kwargs: Any) -> RecallResponse:
|
|
309
|
+
request = _build_model_payload(payload, RecallRequest, "query", kwargs)
|
|
310
|
+
data = _decode_json(await self._client.post("/v1/recall", json=_dump_payload(request)))
|
|
311
|
+
if isinstance(data, list):
|
|
312
|
+
return RecallResponse(results=[_validate(RecallResult, row) for row in data])
|
|
313
|
+
if isinstance(data, dict):
|
|
314
|
+
return _validate(RecallResponse, data)
|
|
315
|
+
raise TypeError(f"unexpected /v1/recall payload shape: {type(data).__name__}")
|
|
316
|
+
|
|
317
|
+
async def dream(self, payload: DreamRequest | Mapping[str, Any] | None = None, /, **kwargs: Any) -> OperationResult:
|
|
318
|
+
request = _optional_model_payload(payload, DreamRequest, kwargs)
|
|
319
|
+
data = _decode_json(await self._client.post("/v1/dream", json=_dump_payload(request)))
|
|
320
|
+
return _validate(OperationResult, data)
|
|
321
|
+
|
|
322
|
+
async def consolidate(
|
|
323
|
+
self,
|
|
324
|
+
payload: ConsolidateRequest | Mapping[str, Any] | None = None,
|
|
325
|
+
/,
|
|
326
|
+
**kwargs: Any,
|
|
327
|
+
) -> OperationResult:
|
|
328
|
+
request = _optional_model_payload(payload, ConsolidateRequest, kwargs)
|
|
329
|
+
data = _decode_json(await self._client.post("/v1/consolidate", json=_dump_payload(request)))
|
|
330
|
+
return _validate(OperationResult, data)
|
|
331
|
+
|
|
332
|
+
async def mark_used(self, memory_id: str) -> AckResponse:
|
|
333
|
+
request = MarkUsedRequest(id=memory_id)
|
|
334
|
+
data = _decode_json(await self._client.post("/v1/mark-used", json=_dump_payload(request)))
|
|
335
|
+
return _validate(AckResponse, data)
|
|
336
|
+
|
|
337
|
+
async def validate(self, memory_id: str, outcome: str = "used") -> dict[str, Any]:
|
|
338
|
+
"""Closed-loop feedback. See sync validate()."""
|
|
339
|
+
if outcome not in ("used", "helpful", "wrong"):
|
|
340
|
+
raise ValueError(f"outcome must be used|helpful|wrong, got {outcome!r}")
|
|
341
|
+
return _decode_json(await self._client.post("/v1/validate", json={"id": memory_id, "outcome": outcome}))
|
|
342
|
+
|
|
343
|
+
async def forget(
|
|
344
|
+
self,
|
|
345
|
+
*,
|
|
346
|
+
id: str | None = None,
|
|
347
|
+
query: str | None = None,
|
|
348
|
+
purge: bool | None = None,
|
|
349
|
+
min_similarity: float | None = None,
|
|
350
|
+
) -> ForgetResponse | None:
|
|
351
|
+
request = ForgetRequest(
|
|
352
|
+
id=id,
|
|
353
|
+
query=query,
|
|
354
|
+
purge=purge,
|
|
355
|
+
minSimilarity=min_similarity,
|
|
356
|
+
)
|
|
357
|
+
data = _decode_json(await self._client.post("/v1/forget", json=_dump_payload(request)))
|
|
358
|
+
if data is None:
|
|
359
|
+
return None
|
|
360
|
+
return _validate(ForgetResponse, data)
|
|
361
|
+
|
|
362
|
+
async def snapshot(self) -> MemorySnapshot:
|
|
363
|
+
# Server exposes snapshot as GET /v1/export.
|
|
364
|
+
data = _decode_json(await self._client.get("/v1/export"))
|
|
365
|
+
return _validate(MemorySnapshot, data)
|
|
366
|
+
|
|
367
|
+
async def restore(self, snapshot: MemorySnapshot | Mapping[str, Any]) -> RestoreResponse:
|
|
368
|
+
# Server exposes restore as POST /v1/import. The TS handler reads
|
|
369
|
+
# body.snapshot (not the body root), so wrap the payload accordingly.
|
|
370
|
+
request = snapshot if isinstance(snapshot, MemorySnapshot) else MemorySnapshot.model_validate(snapshot)
|
|
371
|
+
data = _decode_json(await self._client.post("/v1/import", json={"snapshot": _dump_payload(request)}))
|
|
372
|
+
return _validate(RestoreResponse, data)
|
audrey_memory/py.typed
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
audrey_memory/types.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AudreyModel(BaseModel):
|
|
9
|
+
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Affect(AudreyModel):
|
|
13
|
+
valence: float
|
|
14
|
+
arousal: float | None = None
|
|
15
|
+
label: str | None = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class HealthResponse(AudreyModel):
|
|
19
|
+
ok: bool
|
|
20
|
+
version: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ContradictionStatus(AudreyModel):
|
|
24
|
+
open: int = 0
|
|
25
|
+
resolved: int = 0
|
|
26
|
+
context_dependent: int = 0
|
|
27
|
+
reopened: int = 0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class StatusResponse(AudreyModel):
|
|
31
|
+
episodic: int | None = None
|
|
32
|
+
semantic: int | None = None
|
|
33
|
+
procedural: int | None = None
|
|
34
|
+
causalLinks: int | None = None
|
|
35
|
+
contradictions: ContradictionStatus | None = None
|
|
36
|
+
dormant: int | None = None
|
|
37
|
+
lastConsolidation: str | None = None
|
|
38
|
+
totalConsolidationRuns: int | None = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AnalyticsRow(AudreyModel):
|
|
42
|
+
id: str | None = None
|
|
43
|
+
content: str | None = None
|
|
44
|
+
agent: str | None = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class AnalyticsResponse(AudreyModel):
|
|
48
|
+
topEpisodes: list[AnalyticsRow] = Field(default_factory=list)
|
|
49
|
+
topSemantics: list[AnalyticsRow] = Field(default_factory=list)
|
|
50
|
+
recentRuns: list[AnalyticsRow] = Field(default_factory=list)
|
|
51
|
+
metrics: list[AnalyticsRow] = Field(default_factory=list)
|
|
52
|
+
agents: list[AnalyticsRow] = Field(default_factory=list)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class EncodeRequest(AudreyModel):
|
|
56
|
+
content: str
|
|
57
|
+
source: str
|
|
58
|
+
salience: float | None = Field(default=None, ge=0, le=1)
|
|
59
|
+
tags: list[str] | None = None
|
|
60
|
+
context: dict[str, Any] | None = None
|
|
61
|
+
affect: Affect | None = None
|
|
62
|
+
causal: dict[str, Any] | None = None
|
|
63
|
+
supersedes: str | None = None
|
|
64
|
+
private: bool | None = None
|
|
65
|
+
agent: str | None = None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class EncodeResponse(AudreyModel):
|
|
69
|
+
id: str
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class RecallRequest(AudreyModel):
|
|
73
|
+
query: str
|
|
74
|
+
limit: int | None = Field(default=None, ge=1, le=50)
|
|
75
|
+
context: dict[str, Any] | None = None
|
|
76
|
+
mood: dict[str, Any] | None = None
|
|
77
|
+
types: list[str] | None = None
|
|
78
|
+
scope: str | None = None
|
|
79
|
+
includePrivate: bool | None = None
|
|
80
|
+
agent: str | None = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class RecallResult(AudreyModel):
|
|
84
|
+
id: str
|
|
85
|
+
content: str
|
|
86
|
+
type: str | None = None
|
|
87
|
+
confidence: float | None = None
|
|
88
|
+
score: float | None = None
|
|
89
|
+
source: str | None = None
|
|
90
|
+
createdAt: str | None = None
|
|
91
|
+
agent: str | None = None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class RecallError(AudreyModel):
|
|
95
|
+
type: str | None = None
|
|
96
|
+
stage: str | None = None
|
|
97
|
+
message: str | None = None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class RecallResponse(AudreyModel):
|
|
101
|
+
results: list[RecallResult] = Field(default_factory=list)
|
|
102
|
+
partialFailure: bool = Field(default=False, alias="partial_failure")
|
|
103
|
+
errors: list[RecallError] = Field(default_factory=list)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class DreamRequest(AudreyModel):
|
|
107
|
+
dormantThreshold: float | None = Field(default=None, ge=0, le=1)
|
|
108
|
+
minClusterSize: int | None = Field(default=None, ge=1)
|
|
109
|
+
similarityThreshold: float | None = Field(default=None, ge=0, le=1)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class ConsolidateRequest(AudreyModel):
|
|
113
|
+
minClusterSize: int | None = Field(default=None, ge=1)
|
|
114
|
+
similarityThreshold: float | None = Field(default=None, ge=0, le=1)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class OperationResult(AudreyModel):
|
|
118
|
+
ok: bool | None = None
|
|
119
|
+
status: str | None = None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class MarkUsedRequest(AudreyModel):
|
|
123
|
+
id: str
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class AckResponse(AudreyModel):
|
|
127
|
+
ok: bool
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class ForgetRequest(AudreyModel):
|
|
131
|
+
id: str | None = None
|
|
132
|
+
query: str | None = None
|
|
133
|
+
purge: bool | None = None
|
|
134
|
+
minSimilarity: float | None = Field(default=None, ge=0, le=1)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class ForgetResponse(AudreyModel):
|
|
138
|
+
id: str | None = None
|
|
139
|
+
type: str | None = None
|
|
140
|
+
purged: bool | None = None
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class MemorySnapshot(AudreyModel):
|
|
144
|
+
version: str
|
|
145
|
+
exportedAt: str | None = None
|
|
146
|
+
episodes: list[dict[str, Any]] = Field(default_factory=list)
|
|
147
|
+
semantics: list[dict[str, Any]] = Field(default_factory=list)
|
|
148
|
+
procedures: list[dict[str, Any]] = Field(default_factory=list)
|
|
149
|
+
causalLinks: list[dict[str, Any]] = Field(default_factory=list)
|
|
150
|
+
contradictions: list[dict[str, Any]] = Field(default_factory=list)
|
|
151
|
+
consolidationRuns: list[dict[str, Any]] = Field(default_factory=list)
|
|
152
|
+
consolidationMetrics: list[dict[str, Any]] = Field(default_factory=list)
|
|
153
|
+
config: dict[str, Any] = Field(default_factory=dict)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class RestoreResponse(StatusResponse):
|
|
157
|
+
ok: bool
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: audrey-memory
|
|
3
|
+
Version: 0.23.1
|
|
4
|
+
Summary: Typed Python client for the Audrey LLM memory server
|
|
5
|
+
Author: evilander
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Evilander/Audrey
|
|
8
|
+
Project-URL: Repository, https://github.com/Evilander/Audrey
|
|
9
|
+
Project-URL: Issues, https://github.com/Evilander/Audrey/issues
|
|
10
|
+
Keywords: ai,agents,audrey,llm,memory,pydantic,python
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: httpx <1,>=0.27
|
|
23
|
+
Requires-Dist: pydantic <3,>=2.7
|
|
24
|
+
|
|
25
|
+
# Audrey Python SDK
|
|
26
|
+
|
|
27
|
+
Typed Python client for the Audrey REST API.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install audrey-memory
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
For local development from this repository:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
cd python
|
|
39
|
+
python -m pip install -e .
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
Start Audrey's REST API:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx audrey serve
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Then use the client:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from audrey_memory import Audrey
|
|
54
|
+
|
|
55
|
+
brain = Audrey(
|
|
56
|
+
base_url="http://127.0.0.1:7437",
|
|
57
|
+
api_key="secret",
|
|
58
|
+
agent="support-agent",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
memory_id = brain.encode(
|
|
62
|
+
"Stripe returns HTTP 429 above 100 req/s",
|
|
63
|
+
source="direct-observation",
|
|
64
|
+
tags=["stripe", "rate-limit"],
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
results = brain.recall("stripe rate limits", limit=5)
|
|
68
|
+
snapshot = brain.snapshot()
|
|
69
|
+
brain.close()
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Restore snapshots only into an empty Audrey store, such as a sidecar started with a fresh `AUDREY_DATA_DIR`:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
restore_target = Audrey(base_url="http://127.0.0.1:7437", api_key="secret")
|
|
76
|
+
restore_target.restore(snapshot)
|
|
77
|
+
restore_target.close()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Async usage:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
import asyncio
|
|
84
|
+
|
|
85
|
+
from audrey_memory import AsyncAudrey
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def main() -> None:
|
|
89
|
+
async with AsyncAudrey(base_url="http://127.0.0.1:7437") as brain:
|
|
90
|
+
await brain.health()
|
|
91
|
+
await brain.encode("Deploy failed due to OOM", source="direct-observation")
|
|
92
|
+
await brain.recall("deploy failure", limit=3)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
asyncio.run(main())
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Features
|
|
99
|
+
|
|
100
|
+
- Sync and async clients powered by `httpx`
|
|
101
|
+
- Pydantic request and response models
|
|
102
|
+
- Bearer auth via `AUDREY_API_KEY`
|
|
103
|
+
- Optional `X-Audrey-Agent` header on client requests
|
|
104
|
+
- Snapshot export and restore support
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
audrey_memory/__init__.py,sha256=knZBkpKxdcekXXfRtQBF4-wcCR_efeRhkW5qE76r1T4,1081
|
|
2
|
+
audrey_memory/_version.py,sha256=EmtVqV41YmZp6Mj5pkm4iGXDMznVEE4GQEUYGGidq30,23
|
|
3
|
+
audrey_memory/client.py,sha256=UuTj8Lh2-INRlmiUhW-qWlM0ACaw-dgbWMO36A4rcwM,15511
|
|
4
|
+
audrey_memory/py.typed,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
|
|
5
|
+
audrey_memory/types.py,sha256=IMIuqY9ZDaBn3-vmm6-WJeCmd_94j8W5vLx51fkX5Fo,4438
|
|
6
|
+
audrey_memory-0.23.1.dist-info/METADATA,sha256=jxUA6acll-Ft5D56MNQshJd6weoFxIePLbtOmgAenJs,2611
|
|
7
|
+
audrey_memory-0.23.1.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
|
|
8
|
+
audrey_memory-0.23.1.dist-info/top_level.txt,sha256=VcpfwHta6WEMWkft9GVKyvimIE7Sbi6HssR22ZrxsLI,14
|
|
9
|
+
audrey_memory-0.23.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
audrey_memory
|