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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hebbrix
3
- Version: 2.0.2
3
+ Version: 2.1.0
4
4
  Summary: Advanced Memory API for AI Agents with Reinforcement Learning - 3-line integration
5
5
  Home-page: https://github.com/hebbrix/hebbrix-python
6
6
  Author: Hebbrix Team
@@ -41,7 +41,7 @@ Features:
41
41
  - ✅ Type hints throughout
42
42
  """
43
43
 
44
- __version__ = "2.0.2"
44
+ __version__ = "2.1.0"
45
45
  __author__ = "Hebbrix Team"
46
46
  __license__ = "MIT"
47
47
 
@@ -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(self, content: str, collection_id: str, importance: float = 0.5) -> Dict[str, Any]:
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(self, query: str, collection_id: Optional[str] = None, limit: int = 10) -> List[Dict[str, Any]]:
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
- from hebbrix.resources import (
10
- AuthResource,
11
- CollectionsResource,
12
- MemoriesResource,
13
- SearchResource,
14
- RLResource,
15
- ProceduralResource,
16
- TemporalResource,
17
- WorkingMemoryResource,
18
- ConsolidationResource,
19
- MemoryToolsResource,
20
- WorldModelResource,
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 new memory.
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 score (0-1)
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 list(
252
+ async def list_page(
231
253
  self,
232
254
  collection_id: Optional[str] = None,
233
- skip: int = 0,
234
- limit: int = 100,
235
- ) -> List[Dict[str, Any]]:
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
- skip: Number of items to skip
242
- limit: Maximum number of items
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
- List of memories
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 = {"skip": skip, "limit": limit}
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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hebbrix
3
- Version: 2.0.2
3
+ Version: 2.1.0
4
4
  Summary: Advanced Memory API for AI Agents with Reinforcement Learning - 3-line integration
5
5
  Home-page: https://github.com/hebbrix/hebbrix-python
6
6
  Author: Hebbrix Team
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hebbrix"
7
- version = "2.0.2"
7
+ version = "2.1.0"
8
8
  description = "Advanced Memory API for AI Agents with Reinforcement Learning - 3-line integration"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -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.0.0",
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