hebbrix 2.0.2__tar.gz → 2.1.0__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.
- {hebbrix-2.0.2 → hebbrix-2.1.0}/PKG-INFO +1 -1
- {hebbrix-2.0.2 → hebbrix-2.1.0}/hebbrix/__init__.py +1 -1
- {hebbrix-2.0.2 → hebbrix-2.1.0}/hebbrix/chat.py +17 -5
- {hebbrix-2.0.2 → hebbrix-2.1.0}/hebbrix/client.py +18 -23
- {hebbrix-2.0.2 → hebbrix-2.1.0}/hebbrix/resources.py +100 -13
- {hebbrix-2.0.2 → hebbrix-2.1.0}/hebbrix.egg-info/PKG-INFO +1 -1
- {hebbrix-2.0.2 → hebbrix-2.1.0}/pyproject.toml +1 -1
- {hebbrix-2.0.2 → hebbrix-2.1.0}/setup.py +1 -1
- {hebbrix-2.0.2 → hebbrix-2.1.0}/MANIFEST.in +0 -0
- {hebbrix-2.0.2 → hebbrix-2.1.0}/README.md +0 -0
- {hebbrix-2.0.2 → hebbrix-2.1.0}/hebbrix/exceptions.py +0 -0
- {hebbrix-2.0.2 → hebbrix-2.1.0}/hebbrix.egg-info/SOURCES.txt +0 -0
- {hebbrix-2.0.2 → hebbrix-2.1.0}/hebbrix.egg-info/dependency_links.txt +0 -0
- {hebbrix-2.0.2 → hebbrix-2.1.0}/hebbrix.egg-info/requires.txt +0 -0
- {hebbrix-2.0.2 → hebbrix-2.1.0}/hebbrix.egg-info/top_level.txt +0 -0
- {hebbrix-2.0.2 → hebbrix-2.1.0}/setup.cfg +0 -0
|
@@ -9,9 +9,10 @@ Example:
|
|
|
9
9
|
chat = MemoryChat(api_key="mem_sk_...")
|
|
10
10
|
response = chat.send("What's my favorite food?", session_id="user_123")
|
|
11
11
|
"""
|
|
12
|
-
from typing import Optional, Dict, Any, List
|
|
13
|
-
import requests
|
|
14
12
|
|
|
13
|
+
from typing import Any, Dict, List, Optional
|
|
14
|
+
|
|
15
|
+
import requests
|
|
15
16
|
from hebbrix.client import MemoryClient
|
|
16
17
|
|
|
17
18
|
|
|
@@ -53,6 +54,7 @@ class MemoryChat:
|
|
|
53
54
|
learning: Enable RL trajectory logging (default: True)
|
|
54
55
|
prompt_strategy: Prompt template strategy (default: "default")
|
|
55
56
|
memory_limit: Max memories to retrieve per message (default: 10)
|
|
57
|
+
source: Collection source ("app" or "api") for data isolation. Sent as X-Hebbrix-Source header.
|
|
56
58
|
"""
|
|
57
59
|
|
|
58
60
|
def __init__(
|
|
@@ -66,12 +68,14 @@ class MemoryChat:
|
|
|
66
68
|
learning: bool = True,
|
|
67
69
|
prompt_strategy: str = "default",
|
|
68
70
|
memory_limit: int = 10,
|
|
71
|
+
source: Optional[str] = None,
|
|
69
72
|
):
|
|
70
73
|
self.api_key = api_key
|
|
71
74
|
self.base_url = base_url.rstrip("/")
|
|
72
75
|
self.model = model
|
|
73
76
|
self.prompt_strategy = prompt_strategy
|
|
74
77
|
self.memory_limit = memory_limit
|
|
78
|
+
self.source = source
|
|
75
79
|
|
|
76
80
|
# Feature configuration
|
|
77
81
|
self.features = {
|
|
@@ -82,7 +86,7 @@ class MemoryChat:
|
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
# Also create full client for advanced operations
|
|
85
|
-
self.client = MemoryClient(api_key=api_key, base_url=base_url)
|
|
89
|
+
self.client = MemoryClient(api_key=api_key, base_url=base_url, source=source)
|
|
86
90
|
|
|
87
91
|
def send(
|
|
88
92
|
self,
|
|
@@ -127,6 +131,8 @@ class MemoryChat:
|
|
|
127
131
|
"Authorization": f"Bearer {self.api_key}",
|
|
128
132
|
"Content-Type": "application/json",
|
|
129
133
|
}
|
|
134
|
+
if self.source:
|
|
135
|
+
headers["X-Hebbrix-Source"] = self.source
|
|
130
136
|
|
|
131
137
|
response = requests.post(url, json=payload, headers=headers)
|
|
132
138
|
response.raise_for_status()
|
|
@@ -179,6 +185,8 @@ class MemoryChat:
|
|
|
179
185
|
"Authorization": f"Bearer {self.api_key}",
|
|
180
186
|
"Content-Type": "application/json",
|
|
181
187
|
}
|
|
188
|
+
if self.source:
|
|
189
|
+
headers["X-Hebbrix-Source"] = self.source
|
|
182
190
|
|
|
183
191
|
response = requests.post(url, json=payload, headers=headers)
|
|
184
192
|
response.raise_for_status()
|
|
@@ -288,7 +296,9 @@ class MemoryChat:
|
|
|
288
296
|
# Convenience methods for memory operations
|
|
289
297
|
# These use synchronous requests.post() (same as send/send_with_context)
|
|
290
298
|
# instead of the async MemoryClient to avoid coroutine issues.
|
|
291
|
-
def add_memory(
|
|
299
|
+
def add_memory(
|
|
300
|
+
self, content: str, collection_id: str, importance: float = 0.5
|
|
301
|
+
) -> Dict[str, Any]:
|
|
292
302
|
"""
|
|
293
303
|
Add a memory manually.
|
|
294
304
|
|
|
@@ -315,7 +325,9 @@ class MemoryChat:
|
|
|
315
325
|
response.raise_for_status()
|
|
316
326
|
return response.json()
|
|
317
327
|
|
|
318
|
-
def search_memories(
|
|
328
|
+
def search_memories(
|
|
329
|
+
self, query: str, collection_id: Optional[str] = None, limit: int = 10
|
|
330
|
+
) -> List[Dict[str, Any]]:
|
|
319
331
|
"""
|
|
320
332
|
Search memories manually.
|
|
321
333
|
|
|
@@ -3,30 +3,19 @@ Hebbrix Client
|
|
|
3
3
|
|
|
4
4
|
Main client class for interacting with the Hebbrix api.
|
|
5
5
|
"""
|
|
6
|
-
from typing import Optional, Dict, Any, List
|
|
7
|
-
import httpx
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
)
|
|
22
|
-
from hebbrix.exceptions import (
|
|
23
|
-
HebbrixError,
|
|
24
|
-
AuthenticationError,
|
|
25
|
-
ValidationError,
|
|
26
|
-
NotFoundError,
|
|
27
|
-
RateLimitError,
|
|
28
|
-
ServerError,
|
|
29
|
-
)
|
|
7
|
+
import os
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
from hebbrix.exceptions import (AuthenticationError, HebbrixError,
|
|
12
|
+
NotFoundError, RateLimitError, ServerError,
|
|
13
|
+
ValidationError)
|
|
14
|
+
from hebbrix.resources import (AuthResource, CollectionsResource,
|
|
15
|
+
ConsolidationResource, MemoriesResource,
|
|
16
|
+
MemoryToolsResource, ProceduralResource,
|
|
17
|
+
RLResource, SearchResource, TemporalResource,
|
|
18
|
+
WorkingMemoryResource, WorldModelResource)
|
|
30
19
|
|
|
31
20
|
|
|
32
21
|
class MemoryClient:
|
|
@@ -37,6 +26,7 @@ class MemoryClient:
|
|
|
37
26
|
api_key: API key for authentication
|
|
38
27
|
base_url: Base URL of the API (default: https://api.hebbrix.com)
|
|
39
28
|
timeout: Request timeout in seconds (default: 30)
|
|
29
|
+
source: Collection source ("app" or "api") for data isolation. Falls back to HEBBRIX_SOURCE env var.
|
|
40
30
|
|
|
41
31
|
Example:
|
|
42
32
|
>>> client = MemoryClient(api_key="mem_sk_...")
|
|
@@ -51,10 +41,12 @@ class MemoryClient:
|
|
|
51
41
|
api_key: Optional[str] = None,
|
|
52
42
|
base_url: str = "https://api.hebbrix.com",
|
|
53
43
|
timeout: float = 120.0,
|
|
44
|
+
source: Optional[str] = None,
|
|
54
45
|
):
|
|
55
46
|
self.api_key = api_key
|
|
56
47
|
self.base_url = base_url.rstrip("/")
|
|
57
48
|
self.timeout = timeout
|
|
49
|
+
self.source = source or os.getenv("HEBBRIX_SOURCE")
|
|
58
50
|
|
|
59
51
|
# HTTP client
|
|
60
52
|
self._client = httpx.AsyncClient(
|
|
@@ -86,6 +78,9 @@ class MemoryClient:
|
|
|
86
78
|
if self.api_key:
|
|
87
79
|
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
88
80
|
|
|
81
|
+
if self.source:
|
|
82
|
+
headers["X-Hebbrix-Source"] = self.source
|
|
83
|
+
|
|
89
84
|
return headers
|
|
90
85
|
|
|
91
86
|
def _handle_error(self, response: httpx.Response) -> None:
|
|
@@ -3,7 +3,7 @@ API Resource classes
|
|
|
3
3
|
|
|
4
4
|
Each resource class wraps a specific set of API endpoints.
|
|
5
5
|
"""
|
|
6
|
-
from typing import Optional, Dict, Any, List, TYPE_CHECKING
|
|
6
|
+
from typing import Optional, Dict, Any, List, AsyncIterator, TYPE_CHECKING
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from hebbrix.client import MemoryClient
|
|
@@ -200,20 +200,41 @@ class MemoriesResource(BaseResource):
|
|
|
200
200
|
source_type: str = "text",
|
|
201
201
|
source_reference: Optional[str] = None,
|
|
202
202
|
metadata: Optional[Dict[str, Any]] = None,
|
|
203
|
+
infer: bool = False,
|
|
203
204
|
) -> Dict[str, Any]:
|
|
204
205
|
"""
|
|
205
|
-
Create a
|
|
206
|
+
Create a memory.
|
|
207
|
+
|
|
208
|
+
By default (``infer=False``) the content is stored verbatim as a single
|
|
209
|
+
memory and the response's ``results[0].id`` is that memory's real,
|
|
210
|
+
immediately-resolvable id — so ``create()`` round-trips with ``get()``.
|
|
211
|
+
|
|
212
|
+
Behavior change (SDK 2.1.0): previously ``create()`` implicitly used the
|
|
213
|
+
server's LLM extract-and-resolve path (``infer=true``), which rewrote the
|
|
214
|
+
content, could emit zero or several fact rows, and — when the server ran
|
|
215
|
+
extraction asynchronously — returned a transient job id that ``get()``
|
|
216
|
+
could not resolve. That transient id is why a create/get round-trip
|
|
217
|
+
failed. Storing verbatim by default makes the returned id stable.
|
|
218
|
+
|
|
219
|
+
Pass ``infer=True`` to opt into fact extraction (``POST /v1/memories``
|
|
220
|
+
with server-side inference). In that mode ``results`` may contain several
|
|
221
|
+
extracted-fact ids, or the response may be ``{"job_id": ...}`` if the
|
|
222
|
+
server extracts asynchronously — poll that job rather than treating it as
|
|
223
|
+
a memory id.
|
|
206
224
|
|
|
207
225
|
Args:
|
|
208
226
|
collection_id: Collection ID
|
|
209
227
|
content: Memory content
|
|
210
|
-
importance: Importance
|
|
228
|
+
importance: Importance hint (0-1)
|
|
211
229
|
source_type: Type of source
|
|
212
230
|
source_reference: Reference to source
|
|
213
231
|
metadata: Optional metadata
|
|
232
|
+
infer: When True, extract facts via the LLM pipeline instead of
|
|
233
|
+
storing ``content`` verbatim. Default False (direct store).
|
|
214
234
|
|
|
215
235
|
Returns:
|
|
216
|
-
Created memory
|
|
236
|
+
Created memory (MemoryAddResponse). With ``infer=False`` (default),
|
|
237
|
+
``results[0].id`` is the real, get-able memory id.
|
|
217
238
|
"""
|
|
218
239
|
return await self.client.post(
|
|
219
240
|
"/v1/memories",
|
|
@@ -224,32 +245,98 @@ class MemoriesResource(BaseResource):
|
|
|
224
245
|
"source_type": source_type,
|
|
225
246
|
"source_reference": source_reference,
|
|
226
247
|
"metadata": metadata or {},
|
|
248
|
+
"infer": infer,
|
|
227
249
|
},
|
|
228
250
|
)
|
|
229
251
|
|
|
230
|
-
async def
|
|
252
|
+
async def list_page(
|
|
231
253
|
self,
|
|
232
254
|
collection_id: Optional[str] = None,
|
|
233
|
-
|
|
234
|
-
limit: int =
|
|
235
|
-
) ->
|
|
255
|
+
cursor: Optional[str] = None,
|
|
256
|
+
limit: int = 50,
|
|
257
|
+
) -> Dict[str, Any]:
|
|
236
258
|
"""
|
|
237
|
-
List memories.
|
|
259
|
+
List memories with full pagination metadata.
|
|
260
|
+
|
|
261
|
+
Returns the raw cursor-paginated response from the backend so callers
|
|
262
|
+
can iterate pages explicitly.
|
|
238
263
|
|
|
239
264
|
Args:
|
|
240
265
|
collection_id: Optional collection filter
|
|
241
|
-
|
|
242
|
-
limit:
|
|
266
|
+
cursor: Opaque cursor from a previous page (None for the first page)
|
|
267
|
+
limit: Page size (backend max: 100)
|
|
243
268
|
|
|
244
269
|
Returns:
|
|
245
|
-
|
|
270
|
+
Dict with keys:
|
|
271
|
+
- items: List[Dict[str, Any]] — memories in this page
|
|
272
|
+
- next_cursor: Optional[str] — pass to the next call to continue
|
|
273
|
+
- has_more: bool — True if more pages exist
|
|
274
|
+
- total_count: int — total number of memories matching the filter
|
|
246
275
|
"""
|
|
247
|
-
params = {"
|
|
276
|
+
params: Dict[str, Any] = {"limit": limit}
|
|
248
277
|
if collection_id:
|
|
249
278
|
params["collection_id"] = collection_id
|
|
279
|
+
if cursor:
|
|
280
|
+
params["cursor"] = cursor
|
|
250
281
|
|
|
251
282
|
return await self.client.get("/v1/memories", params=params)
|
|
252
283
|
|
|
284
|
+
async def list(
|
|
285
|
+
self,
|
|
286
|
+
collection_id: Optional[str] = None,
|
|
287
|
+
cursor: Optional[str] = None,
|
|
288
|
+
limit: int = 50,
|
|
289
|
+
) -> List[Dict[str, Any]]:
|
|
290
|
+
"""
|
|
291
|
+
List memories (single page, items only).
|
|
292
|
+
|
|
293
|
+
Back-compat convenience that drops pagination metadata. For
|
|
294
|
+
page-at-a-time access use `list_page`; for iteration across all
|
|
295
|
+
pages use `iter_all`.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
collection_id: Optional collection filter
|
|
299
|
+
cursor: Opaque cursor from a previous page
|
|
300
|
+
limit: Page size (backend max: 100)
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
List of memory dicts for the requested page
|
|
304
|
+
"""
|
|
305
|
+
page = await self.list_page(
|
|
306
|
+
collection_id=collection_id, cursor=cursor, limit=limit
|
|
307
|
+
)
|
|
308
|
+
return page.get("items", [])
|
|
309
|
+
|
|
310
|
+
async def iter_all(
|
|
311
|
+
self,
|
|
312
|
+
collection_id: Optional[str] = None,
|
|
313
|
+
limit: int = 50,
|
|
314
|
+
) -> AsyncIterator[Dict[str, Any]]:
|
|
315
|
+
"""
|
|
316
|
+
Async-iterate every memory matching the filter, following cursors.
|
|
317
|
+
|
|
318
|
+
Example:
|
|
319
|
+
async for memory in client.memories.iter_all(collection_id="col_x"):
|
|
320
|
+
print(memory["id"])
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
collection_id: Optional collection filter
|
|
324
|
+
limit: Page size used under the hood (backend max: 100)
|
|
325
|
+
|
|
326
|
+
Yields:
|
|
327
|
+
Memory dicts, one per item, across all pages
|
|
328
|
+
"""
|
|
329
|
+
cursor: Optional[str] = None
|
|
330
|
+
while True:
|
|
331
|
+
page = await self.list_page(
|
|
332
|
+
collection_id=collection_id, cursor=cursor, limit=limit
|
|
333
|
+
)
|
|
334
|
+
for item in page.get("items", []):
|
|
335
|
+
yield item
|
|
336
|
+
if not page.get("has_more") or not page.get("next_cursor"):
|
|
337
|
+
break
|
|
338
|
+
cursor = page["next_cursor"]
|
|
339
|
+
|
|
253
340
|
async def get(self, memory_id: str) -> Dict[str, Any]:
|
|
254
341
|
"""
|
|
255
342
|
Get a specific memory.
|
|
@@ -9,7 +9,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
|
9
9
|
|
|
10
10
|
setup(
|
|
11
11
|
name="hebbrix",
|
|
12
|
-
version="2.
|
|
12
|
+
version="2.1.0",
|
|
13
13
|
author="Hebbrix Team",
|
|
14
14
|
author_email="support@hebbrix.com",
|
|
15
15
|
description="Advanced Memory API for AI Agents with Reinforcement Learning - 3-line integration",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|