areev 0.0.0__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.
- areev/__init__.py +49 -0
- areev/client.py +219 -0
- areev/generated/__init__.py +1 -0
- areev/grpc.py +14 -0
- areev/http.py +121 -0
- areev/types.py +173 -0
- areev-0.0.0.dist-info/METADATA +124 -0
- areev-0.0.0.dist-info/RECORD +10 -0
- areev-0.0.0.dist-info/WHEEL +4 -0
- areev-0.0.0.dist-info/licenses/LICENSE +7 -0
areev/__init__.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Areev Python SDK — client library for the Areev knowledge database."""
|
|
2
|
+
|
|
3
|
+
from areev.client import Areev, AsyncAreev
|
|
4
|
+
from areev.types import (
|
|
5
|
+
AddOptions,
|
|
6
|
+
AddRequest,
|
|
7
|
+
AddResponse,
|
|
8
|
+
AreevConfig,
|
|
9
|
+
AreevError,
|
|
10
|
+
GetResponse,
|
|
11
|
+
GrainType,
|
|
12
|
+
HealthResponse,
|
|
13
|
+
RecallRequest,
|
|
14
|
+
RecallResponse,
|
|
15
|
+
RememberRequest,
|
|
16
|
+
RememberResponse,
|
|
17
|
+
SearchHit,
|
|
18
|
+
StatsResponse,
|
|
19
|
+
SupersedeRequest,
|
|
20
|
+
SupersedeResponse,
|
|
21
|
+
)
|
|
22
|
+
from areev.http import HttpClient
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
# High-level clients (recommended)
|
|
26
|
+
"Areev",
|
|
27
|
+
"AsyncAreev",
|
|
28
|
+
# Low-level async client
|
|
29
|
+
"HttpClient",
|
|
30
|
+
# Types
|
|
31
|
+
"AddOptions",
|
|
32
|
+
"AddRequest",
|
|
33
|
+
"AddResponse",
|
|
34
|
+
"AreevConfig",
|
|
35
|
+
"AreevError",
|
|
36
|
+
"GetResponse",
|
|
37
|
+
"GrainType",
|
|
38
|
+
"HealthResponse",
|
|
39
|
+
"RecallRequest",
|
|
40
|
+
"RecallResponse",
|
|
41
|
+
"RememberRequest",
|
|
42
|
+
"RememberResponse",
|
|
43
|
+
"SearchHit",
|
|
44
|
+
"StatsResponse",
|
|
45
|
+
"SupersedeRequest",
|
|
46
|
+
"SupersedeResponse",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
__version__ = "0.1.0"
|
areev/client.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""High-level Areev client with minimal boilerplate."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from areev.types import (
|
|
11
|
+
AddResponse,
|
|
12
|
+
AreevError,
|
|
13
|
+
GetResponse,
|
|
14
|
+
GrainType,
|
|
15
|
+
HealthResponse,
|
|
16
|
+
RecallResponse,
|
|
17
|
+
RememberResponse,
|
|
18
|
+
StatsResponse,
|
|
19
|
+
SupersedeResponse,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
_DEFAULT_URL = "https://app.areev.ai"
|
|
23
|
+
_DEFAULT_MEMORY = "default"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _resolve(
|
|
27
|
+
api_key: str | None,
|
|
28
|
+
url: str | None,
|
|
29
|
+
memory_id: str | None,
|
|
30
|
+
) -> tuple[str | None, str, str]:
|
|
31
|
+
return (
|
|
32
|
+
api_key or os.environ.get("AREEV_API_KEY"),
|
|
33
|
+
(url or os.environ.get("AREEV_URL", _DEFAULT_URL)).rstrip("/"),
|
|
34
|
+
memory_id or os.environ.get("AREEV_MEMORY_ID", _DEFAULT_MEMORY),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _check(resp: httpx.Response) -> Any:
|
|
39
|
+
if resp.status_code >= 400:
|
|
40
|
+
raise AreevError(resp.status_code, resp.text)
|
|
41
|
+
if resp.status_code == 204 or not resp.content:
|
|
42
|
+
return None
|
|
43
|
+
return resp.json()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _compact(d: dict) -> dict:
|
|
47
|
+
return {k: v for k, v in d.items() if v is not None}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Areev:
|
|
51
|
+
"""Synchronous Areev client.
|
|
52
|
+
|
|
53
|
+
Reads ``AREEV_API_KEY``, ``AREEV_URL``, and ``AREEV_MEMORY_ID`` from the
|
|
54
|
+
environment when no explicit values are provided.
|
|
55
|
+
|
|
56
|
+
Usage::
|
|
57
|
+
|
|
58
|
+
from areev import Areev
|
|
59
|
+
|
|
60
|
+
areev = Areev()
|
|
61
|
+
areev.remember("John likes coffee")
|
|
62
|
+
results = areev.recall("what does John like?")
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
*,
|
|
68
|
+
api_key: str | None = None,
|
|
69
|
+
url: str | None = None,
|
|
70
|
+
memory_id: str | None = None,
|
|
71
|
+
) -> None:
|
|
72
|
+
key, self._url, self._memory_id = _resolve(api_key, url, memory_id)
|
|
73
|
+
headers: dict[str, str] = {}
|
|
74
|
+
if key:
|
|
75
|
+
headers["X-API-Key"] = key
|
|
76
|
+
self._http = httpx.Client(headers=headers)
|
|
77
|
+
|
|
78
|
+
def _path(self, endpoint: str) -> str:
|
|
79
|
+
return f"{self._url}/api/memories/{self._memory_id}/{endpoint}"
|
|
80
|
+
|
|
81
|
+
# -- Primary methods -----------------------------------------------
|
|
82
|
+
|
|
83
|
+
def remember(self, text: str, *, sync: bool = True, **kwargs: Any) -> RememberResponse:
|
|
84
|
+
"""Store a natural-language memory. Returns extraction details."""
|
|
85
|
+
body = _compact({"text": text, "sync": sync, **kwargs})
|
|
86
|
+
return RememberResponse(**_check(self._http.post(self._path("remember"), json=body)))
|
|
87
|
+
|
|
88
|
+
def recall(self, query: str, *, limit: int = 10, **kwargs: Any) -> RecallResponse:
|
|
89
|
+
"""Search memories. Returns matching grains."""
|
|
90
|
+
params = _compact({"query": query, "limit": limit, **kwargs})
|
|
91
|
+
return RecallResponse(**_check(self._http.get(self._path("recall"), params=params)))
|
|
92
|
+
|
|
93
|
+
def forget(self, hash: str) -> None:
|
|
94
|
+
"""Delete a memory by hash."""
|
|
95
|
+
_check(self._http.delete(self._path(f"grains/{hash}")))
|
|
96
|
+
|
|
97
|
+
# -- Low-level methods ---------------------------------------------
|
|
98
|
+
|
|
99
|
+
def add(self, grain_type: str | GrainType, /, **fields: Any) -> str:
|
|
100
|
+
"""Add a typed grain. Returns the grain hash."""
|
|
101
|
+
grain = grain_type.value if isinstance(grain_type, GrainType) else grain_type
|
|
102
|
+
body = _compact({"grain_type": grain, **fields})
|
|
103
|
+
return AddResponse(**_check(self._http.post(self._path("add"), json=body))).hash
|
|
104
|
+
|
|
105
|
+
def get(self, hash: str) -> GetResponse:
|
|
106
|
+
"""Get a grain by hash."""
|
|
107
|
+
return GetResponse(**_check(self._http.get(self._path(f"grains/{hash}"))))
|
|
108
|
+
|
|
109
|
+
def supersede(self, old_hash: str, grain_type: str | GrainType, /, **fields: Any) -> str:
|
|
110
|
+
"""Update a grain. Returns the new hash."""
|
|
111
|
+
grain = grain_type.value if isinstance(grain_type, GrainType) else grain_type
|
|
112
|
+
body = _compact({"old_hash": old_hash, "grain_type": grain, **fields})
|
|
113
|
+
return SupersedeResponse(**_check(self._http.post(self._path("supersede"), json=body))).new_hash
|
|
114
|
+
|
|
115
|
+
def health(self) -> HealthResponse:
|
|
116
|
+
"""Health check."""
|
|
117
|
+
return HealthResponse(**_check(self._http.get(f"{self._url}/health")))
|
|
118
|
+
|
|
119
|
+
def stats(self) -> StatsResponse:
|
|
120
|
+
"""Database statistics."""
|
|
121
|
+
return StatsResponse(**_check(self._http.get(self._path("stats"))))
|
|
122
|
+
|
|
123
|
+
def flush(self) -> None:
|
|
124
|
+
"""Flush write buffer."""
|
|
125
|
+
_check(self._http.post(self._path("flush")))
|
|
126
|
+
|
|
127
|
+
# -- Lifecycle -----------------------------------------------------
|
|
128
|
+
|
|
129
|
+
def close(self) -> None:
|
|
130
|
+
"""Close the HTTP connection."""
|
|
131
|
+
self._http.close()
|
|
132
|
+
|
|
133
|
+
def __enter__(self) -> Areev:
|
|
134
|
+
return self
|
|
135
|
+
|
|
136
|
+
def __exit__(self, *args: Any) -> None:
|
|
137
|
+
self.close()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class AsyncAreev:
|
|
141
|
+
"""Async Areev client.
|
|
142
|
+
|
|
143
|
+
Same API as :class:`Areev` but all methods are coroutines.
|
|
144
|
+
|
|
145
|
+
Usage::
|
|
146
|
+
|
|
147
|
+
from areev import AsyncAreev
|
|
148
|
+
|
|
149
|
+
async with AsyncAreev() as areev:
|
|
150
|
+
await areev.remember("John likes coffee")
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
def __init__(
|
|
154
|
+
self,
|
|
155
|
+
*,
|
|
156
|
+
api_key: str | None = None,
|
|
157
|
+
url: str | None = None,
|
|
158
|
+
memory_id: str | None = None,
|
|
159
|
+
) -> None:
|
|
160
|
+
key, self._url, self._memory_id = _resolve(api_key, url, memory_id)
|
|
161
|
+
headers: dict[str, str] = {}
|
|
162
|
+
if key:
|
|
163
|
+
headers["X-API-Key"] = key
|
|
164
|
+
self._http = httpx.AsyncClient(headers=headers)
|
|
165
|
+
|
|
166
|
+
def _path(self, endpoint: str) -> str:
|
|
167
|
+
return f"{self._url}/api/memories/{self._memory_id}/{endpoint}"
|
|
168
|
+
|
|
169
|
+
async def remember(self, text: str, *, sync: bool = True, **kwargs: Any) -> RememberResponse:
|
|
170
|
+
"""Store a natural-language memory. Returns extraction details."""
|
|
171
|
+
body = _compact({"text": text, "sync": sync, **kwargs})
|
|
172
|
+
return RememberResponse(**_check(await self._http.post(self._path("remember"), json=body)))
|
|
173
|
+
|
|
174
|
+
async def recall(self, query: str, *, limit: int = 10, **kwargs: Any) -> RecallResponse:
|
|
175
|
+
"""Search memories. Returns matching grains."""
|
|
176
|
+
params = _compact({"query": query, "limit": limit, **kwargs})
|
|
177
|
+
return RecallResponse(**_check(await self._http.get(self._path("recall"), params=params)))
|
|
178
|
+
|
|
179
|
+
async def forget(self, hash: str) -> None:
|
|
180
|
+
"""Delete a memory by hash."""
|
|
181
|
+
_check(await self._http.delete(self._path(f"grains/{hash}")))
|
|
182
|
+
|
|
183
|
+
async def add(self, grain_type: str | GrainType, /, **fields: Any) -> str:
|
|
184
|
+
"""Add a typed grain. Returns the grain hash."""
|
|
185
|
+
grain = grain_type.value if isinstance(grain_type, GrainType) else grain_type
|
|
186
|
+
body = _compact({"grain_type": grain, **fields})
|
|
187
|
+
return AddResponse(**_check(await self._http.post(self._path("add"), json=body))).hash
|
|
188
|
+
|
|
189
|
+
async def get(self, hash: str) -> GetResponse:
|
|
190
|
+
"""Get a grain by hash."""
|
|
191
|
+
return GetResponse(**_check(await self._http.get(self._path(f"grains/{hash}"))))
|
|
192
|
+
|
|
193
|
+
async def supersede(self, old_hash: str, grain_type: str | GrainType, /, **fields: Any) -> str:
|
|
194
|
+
"""Update a grain. Returns the new hash."""
|
|
195
|
+
grain = grain_type.value if isinstance(grain_type, GrainType) else grain_type
|
|
196
|
+
body = _compact({"old_hash": old_hash, "grain_type": grain, **fields})
|
|
197
|
+
return SupersedeResponse(**_check(await self._http.post(self._path("supersede"), json=body))).new_hash
|
|
198
|
+
|
|
199
|
+
async def health(self) -> HealthResponse:
|
|
200
|
+
"""Health check."""
|
|
201
|
+
return HealthResponse(**_check(await self._http.get(f"{self._url}/health")))
|
|
202
|
+
|
|
203
|
+
async def stats(self) -> StatsResponse:
|
|
204
|
+
"""Database statistics."""
|
|
205
|
+
return StatsResponse(**_check(await self._http.get(self._path("stats"))))
|
|
206
|
+
|
|
207
|
+
async def flush(self) -> None:
|
|
208
|
+
"""Flush write buffer."""
|
|
209
|
+
_check(await self._http.post(self._path("flush")))
|
|
210
|
+
|
|
211
|
+
async def close(self) -> None:
|
|
212
|
+
"""Close the HTTP connection."""
|
|
213
|
+
await self._http.aclose()
|
|
214
|
+
|
|
215
|
+
async def __aenter__(self) -> AsyncAreev:
|
|
216
|
+
return self
|
|
217
|
+
|
|
218
|
+
async def __aexit__(self, *args: Any) -> None:
|
|
219
|
+
await self.close()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Auto-generated code from Areev specs. Do not edit manually."""
|
areev/grpc.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""gRPC transport for the Areev SDK (requires 'grpc' extra)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
import grpc
|
|
7
|
+
from areev.generated import areev_pb2, areev_pb2_grpc
|
|
8
|
+
except ImportError as e:
|
|
9
|
+
raise ImportError(
|
|
10
|
+
"gRPC transport requires the 'grpc' extra: pip install areev[grpc]"
|
|
11
|
+
) from e
|
|
12
|
+
|
|
13
|
+
# gRPC client implementation will be generated from proto stubs.
|
|
14
|
+
# This module wraps the generated stubs with the same interface as HttpClient.
|
areev/http.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""HTTP transport for the Areev SDK using httpx."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from areev.types import (
|
|
10
|
+
AddRequest,
|
|
11
|
+
AddResponse,
|
|
12
|
+
AreevConfig,
|
|
13
|
+
AreevError,
|
|
14
|
+
GetResponse,
|
|
15
|
+
HealthResponse,
|
|
16
|
+
RecallRequest,
|
|
17
|
+
RecallResponse,
|
|
18
|
+
RememberRequest,
|
|
19
|
+
RememberResponse,
|
|
20
|
+
StatsResponse,
|
|
21
|
+
SupersedeRequest,
|
|
22
|
+
SupersedeResponse,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class HttpClient:
|
|
27
|
+
"""Async HTTP client for Areev.
|
|
28
|
+
|
|
29
|
+
Usage::
|
|
30
|
+
|
|
31
|
+
client = HttpClient(AreevConfig(url="http://localhost:4009", memory_id="default"))
|
|
32
|
+
resp = await client.add(AddRequest(grain_type="belief", fields={"subject": "john"}))
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, config: AreevConfig) -> None:
|
|
36
|
+
self._base = config.url.rstrip("/")
|
|
37
|
+
self._memory_id = config.memory_id
|
|
38
|
+
headers: dict[str, str] = {}
|
|
39
|
+
if config.api_key:
|
|
40
|
+
headers["X-API-Key"] = config.api_key
|
|
41
|
+
self._client = httpx.AsyncClient(headers=headers)
|
|
42
|
+
|
|
43
|
+
def _url(self, path: str) -> str:
|
|
44
|
+
return f"{self._base}/api/memories/{self._memory_id}/{path}"
|
|
45
|
+
|
|
46
|
+
async def _handle(self, resp: httpx.Response) -> Any:
|
|
47
|
+
if resp.status_code >= 400:
|
|
48
|
+
raise AreevError(resp.status_code, resp.text)
|
|
49
|
+
if resp.status_code == 204 or not resp.content:
|
|
50
|
+
return None
|
|
51
|
+
return resp.json()
|
|
52
|
+
|
|
53
|
+
async def add(self, req: AddRequest) -> AddResponse:
|
|
54
|
+
"""Add a grain to memory."""
|
|
55
|
+
resp = await self._client.post(
|
|
56
|
+
self._url("add"),
|
|
57
|
+
json=req.model_dump(exclude_none=True),
|
|
58
|
+
)
|
|
59
|
+
return AddResponse(**(await self._handle(resp)))
|
|
60
|
+
|
|
61
|
+
async def remember(self, req: RememberRequest) -> RememberResponse:
|
|
62
|
+
"""Ingest natural language text as memory.
|
|
63
|
+
|
|
64
|
+
Accepts free-form text, stores it as an Observation grain, and
|
|
65
|
+
extracts structured Belief grains (sync or async depending on
|
|
66
|
+
``req.sync``).
|
|
67
|
+
"""
|
|
68
|
+
resp = await self._client.post(
|
|
69
|
+
self._url("remember"),
|
|
70
|
+
json=req.model_dump(exclude_none=True),
|
|
71
|
+
)
|
|
72
|
+
return RememberResponse(**(await self._handle(resp)))
|
|
73
|
+
|
|
74
|
+
async def recall(self, req: RecallRequest) -> RecallResponse:
|
|
75
|
+
"""Query (recall) grains from memory."""
|
|
76
|
+
params = req.model_dump(exclude_none=True)
|
|
77
|
+
resp = await self._client.get(self._url("recall"), params=params)
|
|
78
|
+
return RecallResponse(**(await self._handle(resp)))
|
|
79
|
+
|
|
80
|
+
async def get(self, hash: str) -> GetResponse:
|
|
81
|
+
"""Get a grain by its hash."""
|
|
82
|
+
resp = await self._client.get(self._url(f"grains/{hash}"))
|
|
83
|
+
return GetResponse(**(await self._handle(resp)))
|
|
84
|
+
|
|
85
|
+
async def forget(self, hash: str) -> None:
|
|
86
|
+
"""Forget (delete) a grain."""
|
|
87
|
+
resp = await self._client.delete(self._url(f"grains/{hash}"))
|
|
88
|
+
await self._handle(resp)
|
|
89
|
+
|
|
90
|
+
async def supersede(self, req: SupersedeRequest) -> SupersedeResponse:
|
|
91
|
+
"""Supersede (update) a grain."""
|
|
92
|
+
resp = await self._client.post(
|
|
93
|
+
self._url("supersede"),
|
|
94
|
+
json=req.model_dump(exclude_none=True),
|
|
95
|
+
)
|
|
96
|
+
return SupersedeResponse(**(await self._handle(resp)))
|
|
97
|
+
|
|
98
|
+
async def health(self) -> HealthResponse:
|
|
99
|
+
"""Get health status."""
|
|
100
|
+
resp = await self._client.get(f"{self._base}/health")
|
|
101
|
+
return HealthResponse(**(await self._handle(resp)))
|
|
102
|
+
|
|
103
|
+
async def stats(self) -> StatsResponse:
|
|
104
|
+
"""Get database statistics."""
|
|
105
|
+
resp = await self._client.get(self._url("stats"))
|
|
106
|
+
return StatsResponse(**(await self._handle(resp)))
|
|
107
|
+
|
|
108
|
+
async def flush(self) -> None:
|
|
109
|
+
"""Flush write buffer."""
|
|
110
|
+
resp = await self._client.post(self._url("flush"))
|
|
111
|
+
await self._handle(resp)
|
|
112
|
+
|
|
113
|
+
async def close(self) -> None:
|
|
114
|
+
"""Close the HTTP client."""
|
|
115
|
+
await self._client.aclose()
|
|
116
|
+
|
|
117
|
+
async def __aenter__(self) -> HttpClient:
|
|
118
|
+
return self
|
|
119
|
+
|
|
120
|
+
async def __aexit__(self, *args: Any) -> None:
|
|
121
|
+
await self.close()
|
areev/types.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Core types for the Areev SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GrainType(str, Enum):
|
|
12
|
+
"""Grain types supported by Areev."""
|
|
13
|
+
|
|
14
|
+
BELIEF = "belief"
|
|
15
|
+
EVENT = "event"
|
|
16
|
+
STATE = "state"
|
|
17
|
+
WORKFLOW = "workflow"
|
|
18
|
+
ACTION = "action"
|
|
19
|
+
OBSERVATION = "observation"
|
|
20
|
+
GOAL = "goal"
|
|
21
|
+
REASONING = "reasoning"
|
|
22
|
+
CONSENSUS = "consensus"
|
|
23
|
+
CONSENT = "consent"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AddOptions(BaseModel):
|
|
27
|
+
"""Options for the add operation (import intelligence)."""
|
|
28
|
+
|
|
29
|
+
extract_event_date: bool | None = None
|
|
30
|
+
auto_relate: bool | None = None
|
|
31
|
+
extract_memories: bool | None = None
|
|
32
|
+
sync: bool | None = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AddRequest(BaseModel):
|
|
36
|
+
"""Request to add a grain."""
|
|
37
|
+
|
|
38
|
+
grain_type: GrainType
|
|
39
|
+
fields: dict[str, Any]
|
|
40
|
+
options: AddOptions | None = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class AddResponse(BaseModel):
|
|
44
|
+
"""Response from adding a grain."""
|
|
45
|
+
|
|
46
|
+
hash: str
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class RecallRequest(BaseModel):
|
|
50
|
+
"""Request to recall (query) grains."""
|
|
51
|
+
|
|
52
|
+
query: str | None = None
|
|
53
|
+
subject: str | None = None
|
|
54
|
+
relation: str | None = None
|
|
55
|
+
object: str | None = None
|
|
56
|
+
namespace: str | None = None
|
|
57
|
+
user_id: str | None = None
|
|
58
|
+
grain_type: str | None = None
|
|
59
|
+
limit: int | None = None
|
|
60
|
+
temporal_expr: str | None = None
|
|
61
|
+
tags: list[str] | None = None
|
|
62
|
+
deduplicate: bool | None = None
|
|
63
|
+
rerank: bool | None = None
|
|
64
|
+
query_expansion: bool | None = None
|
|
65
|
+
explanation: bool | None = None
|
|
66
|
+
min_score: float | None = None
|
|
67
|
+
diversity: float | None = None
|
|
68
|
+
recency_weight: float | None = None
|
|
69
|
+
entity: str | None = None
|
|
70
|
+
multi_hop: int | None = None
|
|
71
|
+
|
|
72
|
+
model_config = {"extra": "allow"}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class SearchHit(BaseModel):
|
|
76
|
+
"""A single search result."""
|
|
77
|
+
|
|
78
|
+
hash: str
|
|
79
|
+
grain_type: str
|
|
80
|
+
score: float
|
|
81
|
+
fields: dict[str, Any]
|
|
82
|
+
source_namespace: str | None = None
|
|
83
|
+
explanation: str | None = None
|
|
84
|
+
relative_time: str | None = None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class RecallResponse(BaseModel):
|
|
88
|
+
"""Response from a recall query."""
|
|
89
|
+
|
|
90
|
+
count: int
|
|
91
|
+
results: list[SearchHit]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class SupersedeRequest(BaseModel):
|
|
95
|
+
"""Request to supersede a grain."""
|
|
96
|
+
|
|
97
|
+
old_hash: str
|
|
98
|
+
grain_type: GrainType
|
|
99
|
+
fields: dict[str, Any]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class SupersedeResponse(BaseModel):
|
|
103
|
+
"""Response from superseding a grain."""
|
|
104
|
+
|
|
105
|
+
new_hash: str
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class RememberRequest(BaseModel):
|
|
109
|
+
"""Request to ingest natural language text as memory."""
|
|
110
|
+
|
|
111
|
+
text: str
|
|
112
|
+
sync: bool = False
|
|
113
|
+
keep_source: bool = False
|
|
114
|
+
namespace: str | None = None
|
|
115
|
+
user_id: str | None = None
|
|
116
|
+
tags: list[str] | None = None
|
|
117
|
+
source_type: str | None = None
|
|
118
|
+
confidence: float | None = None
|
|
119
|
+
extract_event_date: bool | None = None
|
|
120
|
+
auto_relate: bool | None = None
|
|
121
|
+
created_at: int | None = None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class RememberResponse(BaseModel):
|
|
125
|
+
"""Response from the remember endpoint."""
|
|
126
|
+
|
|
127
|
+
source_hash: str
|
|
128
|
+
mode: str
|
|
129
|
+
extracted_count: int
|
|
130
|
+
source_forgotten: bool
|
|
131
|
+
extracted_hashes: list[str] | None = None
|
|
132
|
+
marker_status: str | None = None
|
|
133
|
+
warnings: list[str] | None = None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class GetResponse(BaseModel):
|
|
137
|
+
"""Grain detail response."""
|
|
138
|
+
|
|
139
|
+
hash: str
|
|
140
|
+
grain_type: str
|
|
141
|
+
fields: dict[str, Any]
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class HealthResponse(BaseModel):
|
|
145
|
+
"""Health check response."""
|
|
146
|
+
|
|
147
|
+
status: str
|
|
148
|
+
version: str
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class StatsResponse(BaseModel):
|
|
152
|
+
"""Database statistics."""
|
|
153
|
+
|
|
154
|
+
total_grains: int
|
|
155
|
+
disk_space_bytes: int
|
|
156
|
+
store_size: str
|
|
157
|
+
type_counts: dict[str, int] = Field(default_factory=dict)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class AreevConfig(BaseModel):
|
|
161
|
+
"""Client configuration."""
|
|
162
|
+
|
|
163
|
+
url: str
|
|
164
|
+
memory_id: str
|
|
165
|
+
api_key: str | None = None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class AreevError(Exception):
|
|
169
|
+
"""Error raised by the Areev client."""
|
|
170
|
+
|
|
171
|
+
def __init__(self, status: int, message: str) -> None:
|
|
172
|
+
self.status = status
|
|
173
|
+
super().__init__(f"Areev API error ({status}): {message}")
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: areev
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Python SDK for the Areev knowledge database — HTTP, gRPC, and MCP transports
|
|
5
|
+
Project-URL: Repository, https://github.com/AreevAI/areev-sdk-python
|
|
6
|
+
Project-URL: Documentation, https://areev.ai/docs
|
|
7
|
+
License-Expression: BUSL-1.1
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: httpx>=0.27
|
|
11
|
+
Requires-Dist: pydantic>=2.0
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: datamodel-code-generator>=0.26; extra == 'dev'
|
|
14
|
+
Requires-Dist: grpcio-tools>=1.60; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
16
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
17
|
+
Provides-Extra: grpc
|
|
18
|
+
Requires-Dist: grpcio>=1.60; extra == 'grpc'
|
|
19
|
+
Requires-Dist: protobuf>=5.0; extra == 'grpc'
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Areev Python SDK
|
|
23
|
+
|
|
24
|
+
Python client library for the [Areev](https://areev.ai) knowledge database.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install areev
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
For gRPC transport:
|
|
33
|
+
```bash
|
|
34
|
+
pip install areev[grpc]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from areev import Areev
|
|
41
|
+
|
|
42
|
+
areev = Areev() # reads AREEV_API_KEY, AREEV_URL from env
|
|
43
|
+
areev.remember("John likes coffee")
|
|
44
|
+
|
|
45
|
+
results = areev.recall("what does John like?")
|
|
46
|
+
for hit in results.results:
|
|
47
|
+
print(f" {hit.grain_type}: {hit.fields}")
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Configuration
|
|
51
|
+
|
|
52
|
+
The client reads from environment variables by default:
|
|
53
|
+
|
|
54
|
+
| Variable | Default | Description |
|
|
55
|
+
|----------|---------|-------------|
|
|
56
|
+
| `AREEV_API_KEY` | — | API key (sent as `X-API-Key` header) |
|
|
57
|
+
| `AREEV_URL` | `https://app.areev.ai` | Server endpoint |
|
|
58
|
+
| `AREEV_MEMORY_ID` | `default` | Memory database ID |
|
|
59
|
+
|
|
60
|
+
Or pass them explicitly:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
areev = Areev(api_key="ar_...", url="https://dub.areev.ai", memory_id="my-memory")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Async
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from areev import AsyncAreev
|
|
70
|
+
|
|
71
|
+
async with AsyncAreev() as areev:
|
|
72
|
+
await areev.remember("John likes coffee")
|
|
73
|
+
results = await areev.recall("what does John like?")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## API
|
|
77
|
+
|
|
78
|
+
| Method | Description |
|
|
79
|
+
|--------|-------------|
|
|
80
|
+
| `remember(text)` | Store natural-language memory (LLM extracts structure) |
|
|
81
|
+
| `recall(query)` | Search memories |
|
|
82
|
+
| `forget(hash)` | Delete a memory by hash |
|
|
83
|
+
| `add(grain_type, **fields)` | Add a typed grain (low-level) |
|
|
84
|
+
| `get(hash)` | Get a grain by hash |
|
|
85
|
+
| `supersede(old_hash, grain_type, **fields)` | Update a grain |
|
|
86
|
+
| `health()` | Health check |
|
|
87
|
+
| `stats()` | Database statistics |
|
|
88
|
+
| `flush()` | Flush write buffer |
|
|
89
|
+
|
|
90
|
+
### Low-Level Client
|
|
91
|
+
|
|
92
|
+
For advanced use cases (custom request objects, full async control):
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from areev import HttpClient, AreevConfig, AddRequest, GrainType
|
|
96
|
+
|
|
97
|
+
config = AreevConfig(url="http://localhost:4009", memory_id="default", api_key="your-key")
|
|
98
|
+
async with HttpClient(config) as client:
|
|
99
|
+
resp = await client.add(AddRequest(
|
|
100
|
+
grain_type=GrainType.BELIEF,
|
|
101
|
+
fields={"subject": "john", "relation": "likes", "object": "coffee"},
|
|
102
|
+
))
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Transports
|
|
106
|
+
|
|
107
|
+
| Transport | Extra | Status |
|
|
108
|
+
|-----------|-------|--------|
|
|
109
|
+
| HTTP/REST | _(default)_ | Available |
|
|
110
|
+
| gRPC | `areev[grpc]` | Available |
|
|
111
|
+
| MCP | — | Planned |
|
|
112
|
+
|
|
113
|
+
## Code Generation
|
|
114
|
+
|
|
115
|
+
Generate gRPC stubs and Pydantic models from the Areev spec:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
pip install areev[dev]
|
|
119
|
+
./scripts/generate.sh
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
BUSL-1.1
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
areev/__init__.py,sha256=1IuGtBdXYmz3tCsynmRAGDfPBDWhA51sHVWpUfp9C2M,971
|
|
2
|
+
areev/client.py,sha256=AtValmqMsAAyp4yGhAqxIZAqiTnQCVCVDIaoWpSUtYQ,7768
|
|
3
|
+
areev/grpc.py,sha256=8xhqniba-njixAgPy0N14YbGNSxqUlOsPePSrRIFcJ8,462
|
|
4
|
+
areev/http.py,sha256=p1aMbHKtVKorufGkOWiWwh3XL0UV6Qw1U14Vem9Qyes,3976
|
|
5
|
+
areev/types.py,sha256=iE-d7cYrTxK17uh37whGhUwE7J9DY5zX1-mlRwUDpsg,3862
|
|
6
|
+
areev/generated/__init__.py,sha256=nvnu8PEosfcYv7xT2VzI_xlgqeRc1rnXAoIdK6zLQxQ,66
|
|
7
|
+
areev-0.0.0.dist-info/METADATA,sha256=3RJYJ_hWCRCGFZJXHK8Gib9okmabKJnR1l-fAAPD6A4,3184
|
|
8
|
+
areev-0.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
9
|
+
areev-0.0.0.dist-info/licenses/LICENSE,sha256=pAmHakG0ptbZ0By03_Nxza9WpIpfOOYxJnC_rTB2i7E,250
|
|
10
|
+
areev-0.0.0.dist-info/RECORD,,
|