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.
@@ -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"
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (70.2.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ audrey_memory