hebbrix 2.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.
- hebbrix/__init__.py +69 -0
- hebbrix/chat.py +349 -0
- hebbrix/client.py +226 -0
- hebbrix/exceptions.py +48 -0
- hebbrix/resources.py +978 -0
- hebbrix-2.0.0.dist-info/METADATA +125 -0
- hebbrix-2.0.0.dist-info/RECORD +9 -0
- hebbrix-2.0.0.dist-info/WHEEL +5 -0
- hebbrix-2.0.0.dist-info/top_level.txt +1 -0
hebbrix/__init__.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hebbrix Python SDK - Advanced Memory API for AI Agents
|
|
3
|
+
|
|
4
|
+
A modern, async-first Python SDK for the Hebbrix Memory API.
|
|
5
|
+
The only memory API with Reinforcement Learning for AI agents.
|
|
6
|
+
|
|
7
|
+
Quick Start:
|
|
8
|
+
|
|
9
|
+
from hebbrix import MemoryClient, MemoryChat
|
|
10
|
+
|
|
11
|
+
# Option 1: Simple 3-line chat integration
|
|
12
|
+
chat = MemoryChat(api_key="hbx_...")
|
|
13
|
+
response = chat.send("Remember I love Python!", "user_123")
|
|
14
|
+
response = chat.send("What language do I like?", "user_123")
|
|
15
|
+
|
|
16
|
+
# Option 2: Full async API client
|
|
17
|
+
async with MemoryClient(api_key="hbx_...") as client:
|
|
18
|
+
# Create collection
|
|
19
|
+
collection = await client.collections.create(name="My Agent")
|
|
20
|
+
|
|
21
|
+
# Store memory
|
|
22
|
+
memory = await client.memories.create(
|
|
23
|
+
collection_id=collection["id"],
|
|
24
|
+
content="Important information"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Search with hybrid vector + BM25 + graph
|
|
28
|
+
results = await client.search(query="What was important?", limit=5)
|
|
29
|
+
|
|
30
|
+
# AI-powered reasoning over memories
|
|
31
|
+
answer = await client.reason(query="Explain what I learned")
|
|
32
|
+
|
|
33
|
+
Features:
|
|
34
|
+
- ✅ Reinforcement Learning for memory optimization
|
|
35
|
+
- ✅ Temporal Knowledge Graphs with bi-temporal model
|
|
36
|
+
- ✅ Procedural Memory (skills and learned behaviors)
|
|
37
|
+
- ✅ Working Memory (short-term context buffer)
|
|
38
|
+
- ✅ Memory Consolidation (automatic compression)
|
|
39
|
+
- ✅ 6-layer Hybrid Search (Vector + BM25 + KG + Decay + AI + RL)
|
|
40
|
+
- ✅ Complete async/await support
|
|
41
|
+
- ✅ Type hints throughout
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
__version__ = "2.0.0"
|
|
45
|
+
__author__ = "Hebbrix Team"
|
|
46
|
+
__license__ = "MIT"
|
|
47
|
+
|
|
48
|
+
from hebbrix.client import MemoryClient
|
|
49
|
+
from hebbrix.chat import MemoryChat
|
|
50
|
+
from hebbrix.exceptions import (
|
|
51
|
+
HebbrixError,
|
|
52
|
+
AuthenticationError,
|
|
53
|
+
ValidationError,
|
|
54
|
+
NotFoundError,
|
|
55
|
+
RateLimitError,
|
|
56
|
+
ServerError,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
__all__ = [
|
|
61
|
+
"MemoryClient",
|
|
62
|
+
"MemoryChat",
|
|
63
|
+
"HebbrixError",
|
|
64
|
+
"AuthenticationError",
|
|
65
|
+
"ValidationError",
|
|
66
|
+
"NotFoundError",
|
|
67
|
+
"RateLimitError",
|
|
68
|
+
"ServerError",
|
|
69
|
+
]
|
hebbrix/chat.py
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hebbrix Chat Wrapper - 3-Line Integration
|
|
3
|
+
|
|
4
|
+
The easiest way to add memory-powered conversations to your application.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
from hebbrix import MemoryChat
|
|
8
|
+
|
|
9
|
+
chat = MemoryChat(api_key="mem_sk_...")
|
|
10
|
+
response = chat.send("What's my favorite food?", session_id="user_123")
|
|
11
|
+
"""
|
|
12
|
+
from typing import Optional, Dict, Any, List
|
|
13
|
+
import requests
|
|
14
|
+
|
|
15
|
+
from hebbrix.client import MemoryClient
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MemoryChat:
|
|
19
|
+
"""
|
|
20
|
+
Simple chat interface with automatic memory integration.
|
|
21
|
+
|
|
22
|
+
This is the easiest way to use Hebbrix - just 3 lines of code!
|
|
23
|
+
|
|
24
|
+
Features:
|
|
25
|
+
- Automatic memory retrieval
|
|
26
|
+
- User profile injection
|
|
27
|
+
- Session management
|
|
28
|
+
- RL-powered personalization
|
|
29
|
+
- OpenAI-compatible responses
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
```python
|
|
33
|
+
from hebbrix import MemoryChat
|
|
34
|
+
|
|
35
|
+
# Line 1: Initialize
|
|
36
|
+
chat = MemoryChat(api_key="mem_sk_...")
|
|
37
|
+
|
|
38
|
+
# Line 2: Send message
|
|
39
|
+
response = chat.send("Hi, remember that I love pizza?", "user_123")
|
|
40
|
+
|
|
41
|
+
# Line 3: Get response with memory
|
|
42
|
+
response = chat.send("What's my favorite food?", "user_123")
|
|
43
|
+
print(response) # "Your favorite food is pizza!"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
api_key: Hebbrix API key
|
|
48
|
+
base_url: API base URL (default: https://api.hebbrix.com)
|
|
49
|
+
model: LLM model to use (default: gpt-4o-mini)
|
|
50
|
+
auto_memory: Enable automatic memory retrieval (default: True)
|
|
51
|
+
auto_profile: Enable user profile injection (default: True)
|
|
52
|
+
personalization: Enable RL-powered personalization (default: True)
|
|
53
|
+
learning: Enable RL trajectory logging (default: True)
|
|
54
|
+
prompt_strategy: Prompt template strategy (default: "default")
|
|
55
|
+
memory_limit: Max memories to retrieve per message (default: 10)
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
api_key: str,
|
|
61
|
+
base_url: str = "http://localhost:8000",
|
|
62
|
+
model: str = "gpt-4o-mini",
|
|
63
|
+
auto_memory: bool = True,
|
|
64
|
+
auto_profile: bool = True,
|
|
65
|
+
personalization: bool = True,
|
|
66
|
+
learning: bool = True,
|
|
67
|
+
prompt_strategy: str = "default",
|
|
68
|
+
memory_limit: int = 10,
|
|
69
|
+
):
|
|
70
|
+
self.api_key = api_key
|
|
71
|
+
self.base_url = base_url.rstrip("/")
|
|
72
|
+
self.model = model
|
|
73
|
+
self.prompt_strategy = prompt_strategy
|
|
74
|
+
self.memory_limit = memory_limit
|
|
75
|
+
|
|
76
|
+
# Feature configuration
|
|
77
|
+
self.features = {
|
|
78
|
+
"memory": auto_memory,
|
|
79
|
+
"user_profile": auto_profile,
|
|
80
|
+
"personalization": personalization,
|
|
81
|
+
"learning": learning,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# Also create full client for advanced operations
|
|
85
|
+
self.client = MemoryClient(api_key=api_key, base_url=base_url)
|
|
86
|
+
|
|
87
|
+
def send(
|
|
88
|
+
self,
|
|
89
|
+
message: str,
|
|
90
|
+
session_id: str,
|
|
91
|
+
temperature: float = 0.7,
|
|
92
|
+
max_tokens: Optional[int] = None,
|
|
93
|
+
) -> str:
|
|
94
|
+
"""
|
|
95
|
+
Send a message and get a response with automatic memory integration.
|
|
96
|
+
|
|
97
|
+
This is the main method - just call it and Hebbrix handles everything!
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
message: Your message
|
|
101
|
+
session_id: Session identifier (user ID or conversation ID)
|
|
102
|
+
temperature: Response randomness (0-2, default: 0.7)
|
|
103
|
+
max_tokens: Maximum response length (default: None)
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Assistant's response as a string
|
|
107
|
+
|
|
108
|
+
Example:
|
|
109
|
+
response = chat.send("What did I tell you earlier?", "user_123")
|
|
110
|
+
"""
|
|
111
|
+
url = f"{self.base_url}/v1/chat/completions"
|
|
112
|
+
|
|
113
|
+
payload = {
|
|
114
|
+
"model": self.model,
|
|
115
|
+
"messages": [{"role": "user", "content": message}],
|
|
116
|
+
"session_id": session_id,
|
|
117
|
+
"features": self.features,
|
|
118
|
+
"prompt_strategy": self.prompt_strategy,
|
|
119
|
+
"memory_limit": self.memory_limit,
|
|
120
|
+
"temperature": temperature,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if max_tokens:
|
|
124
|
+
payload["max_tokens"] = max_tokens
|
|
125
|
+
|
|
126
|
+
headers = {
|
|
127
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
128
|
+
"Content-Type": "application/json",
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
response = requests.post(url, json=payload, headers=headers)
|
|
132
|
+
response.raise_for_status()
|
|
133
|
+
|
|
134
|
+
data = response.json()
|
|
135
|
+
return data["choices"][0]["message"]["content"]
|
|
136
|
+
|
|
137
|
+
def send_with_context(
|
|
138
|
+
self,
|
|
139
|
+
message: str,
|
|
140
|
+
session_id: str,
|
|
141
|
+
temperature: float = 0.7,
|
|
142
|
+
max_tokens: Optional[int] = None,
|
|
143
|
+
) -> Dict[str, Any]:
|
|
144
|
+
"""
|
|
145
|
+
Send a message and get full response with memory context.
|
|
146
|
+
|
|
147
|
+
Use this when you want to see which memories were used.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
message: Your message
|
|
151
|
+
session_id: Session identifier
|
|
152
|
+
temperature: Response randomness (0-2, default: 0.7)
|
|
153
|
+
max_tokens: Maximum response length (default: None)
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Full response dict with memory context
|
|
157
|
+
|
|
158
|
+
Example:
|
|
159
|
+
result = chat.send_with_context("What's my name?", "user_123")
|
|
160
|
+
print(result["message"]) # Assistant's response
|
|
161
|
+
print(result["memory_context"]) # Which memories were used
|
|
162
|
+
"""
|
|
163
|
+
url = f"{self.base_url}/v1/chat/completions"
|
|
164
|
+
|
|
165
|
+
payload = {
|
|
166
|
+
"model": self.model,
|
|
167
|
+
"messages": [{"role": "user", "content": message}],
|
|
168
|
+
"session_id": session_id,
|
|
169
|
+
"features": self.features,
|
|
170
|
+
"prompt_strategy": self.prompt_strategy,
|
|
171
|
+
"memory_limit": self.memory_limit,
|
|
172
|
+
"temperature": temperature,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if max_tokens:
|
|
176
|
+
payload["max_tokens"] = max_tokens
|
|
177
|
+
|
|
178
|
+
headers = {
|
|
179
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
180
|
+
"Content-Type": "application/json",
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
response = requests.post(url, json=payload, headers=headers)
|
|
184
|
+
response.raise_for_status()
|
|
185
|
+
|
|
186
|
+
data = response.json()
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
"message": data["choices"][0]["message"]["content"],
|
|
190
|
+
"memory_context": data["memory_context"],
|
|
191
|
+
"usage": data["usage"],
|
|
192
|
+
"model": data["model"],
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
def configure_features(
|
|
196
|
+
self,
|
|
197
|
+
auto_memory: Optional[bool] = None,
|
|
198
|
+
auto_profile: Optional[bool] = None,
|
|
199
|
+
personalization: Optional[bool] = None,
|
|
200
|
+
learning: Optional[bool] = None,
|
|
201
|
+
) -> None:
|
|
202
|
+
"""
|
|
203
|
+
Configure features on the fly.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
auto_memory: Enable/disable automatic memory retrieval
|
|
207
|
+
auto_profile: Enable/disable user profile injection
|
|
208
|
+
personalization: Enable/disable RL personalization
|
|
209
|
+
learning: Enable/disable RL learning
|
|
210
|
+
"""
|
|
211
|
+
if auto_memory is not None:
|
|
212
|
+
self.features["memory"] = auto_memory
|
|
213
|
+
if auto_profile is not None:
|
|
214
|
+
self.features["user_profile"] = auto_profile
|
|
215
|
+
if personalization is not None:
|
|
216
|
+
self.features["personalization"] = personalization
|
|
217
|
+
if learning is not None:
|
|
218
|
+
self.features["learning"] = learning
|
|
219
|
+
|
|
220
|
+
def set_model(self, model: str) -> None:
|
|
221
|
+
"""
|
|
222
|
+
Change the LLM model.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
model: Model name (e.g., "gpt-4o-mini", "claude-3-sonnet")
|
|
226
|
+
"""
|
|
227
|
+
self.model = model
|
|
228
|
+
|
|
229
|
+
def set_strategy(self, strategy: str) -> None:
|
|
230
|
+
"""
|
|
231
|
+
Change the prompt strategy.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
strategy: Strategy name ("default", "concise", "detailed")
|
|
235
|
+
"""
|
|
236
|
+
self.prompt_strategy = strategy
|
|
237
|
+
|
|
238
|
+
def list_sessions(self, active_only: bool = True) -> List[Dict[str, Any]]:
|
|
239
|
+
"""
|
|
240
|
+
List all chat sessions.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
active_only: Only return active sessions
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
List of session dicts
|
|
247
|
+
"""
|
|
248
|
+
url = f"{self.base_url}/v1/chat/sessions"
|
|
249
|
+
params = {"active_only": active_only}
|
|
250
|
+
headers = {"Authorization": f"Bearer {self.api_key}"}
|
|
251
|
+
|
|
252
|
+
response = requests.get(url, params=params, headers=headers)
|
|
253
|
+
response.raise_for_status()
|
|
254
|
+
|
|
255
|
+
return response.json()["sessions"]
|
|
256
|
+
|
|
257
|
+
def get_session_info(self, session_id: str) -> Dict[str, Any]:
|
|
258
|
+
"""
|
|
259
|
+
Get information about a specific session.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
session_id: Session identifier
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Session info and statistics
|
|
266
|
+
"""
|
|
267
|
+
url = f"{self.base_url}/v1/chat/sessions/{session_id}"
|
|
268
|
+
headers = {"Authorization": f"Bearer {self.api_key}"}
|
|
269
|
+
|
|
270
|
+
response = requests.get(url, headers=headers)
|
|
271
|
+
response.raise_for_status()
|
|
272
|
+
|
|
273
|
+
return response.json()
|
|
274
|
+
|
|
275
|
+
def delete_session(self, session_id: str) -> None:
|
|
276
|
+
"""
|
|
277
|
+
Delete a chat session.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
session_id: Session identifier
|
|
281
|
+
"""
|
|
282
|
+
url = f"{self.base_url}/v1/chat/sessions/{session_id}"
|
|
283
|
+
headers = {"Authorization": f"Bearer {self.api_key}"}
|
|
284
|
+
|
|
285
|
+
response = requests.delete(url, headers=headers)
|
|
286
|
+
response.raise_for_status()
|
|
287
|
+
|
|
288
|
+
# Convenience methods for memory operations
|
|
289
|
+
def add_memory(self, content: str, collection_id: str, importance: float = 0.5) -> Dict[str, Any]:
|
|
290
|
+
"""
|
|
291
|
+
Add a memory manually.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
content: Memory content
|
|
295
|
+
collection_id: Collection ID
|
|
296
|
+
importance: Memory importance (0-1)
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
Created memory dict
|
|
300
|
+
"""
|
|
301
|
+
return self.client.memories.create(
|
|
302
|
+
content=content,
|
|
303
|
+
collection_id=collection_id,
|
|
304
|
+
importance=importance,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
def search_memories(self, query: str, collection_id: Optional[str] = None, limit: int = 10) -> List[Dict[str, Any]]:
|
|
308
|
+
"""
|
|
309
|
+
Search memories manually.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
query: Search query
|
|
313
|
+
collection_id: Optional collection filter
|
|
314
|
+
limit: Number of results
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
List of memory dicts
|
|
318
|
+
"""
|
|
319
|
+
return self.client.memories.search(
|
|
320
|
+
query=query,
|
|
321
|
+
collection_id=collection_id,
|
|
322
|
+
limit=limit,
|
|
323
|
+
)["results"]
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
# Convenience function for even simpler usage
|
|
327
|
+
def quick_chat(api_key: str, message: str, session_id: str) -> str:
|
|
328
|
+
"""
|
|
329
|
+
The absolute simplest way to use Hebbrix - single function call.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
api_key: Hebbrix API key
|
|
333
|
+
message: Your message
|
|
334
|
+
session_id: Session identifier
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
Assistant's response
|
|
338
|
+
|
|
339
|
+
Example:
|
|
340
|
+
from hebbrix.chat import quick_chat
|
|
341
|
+
|
|
342
|
+
response = quick_chat(
|
|
343
|
+
api_key="mem_sk_...",
|
|
344
|
+
message="What's my favorite color?",
|
|
345
|
+
session_id="user_123"
|
|
346
|
+
)
|
|
347
|
+
"""
|
|
348
|
+
chat = MemoryChat(api_key=api_key)
|
|
349
|
+
return chat.send(message, session_id)
|
hebbrix/client.py
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hebbrix Client
|
|
3
|
+
|
|
4
|
+
Main client class for interacting with the Hebbrix api.
|
|
5
|
+
"""
|
|
6
|
+
from typing import Optional, Dict, Any, List
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
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
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MemoryClient:
|
|
33
|
+
"""
|
|
34
|
+
Hebbrix api client.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
api_key: API key for authentication
|
|
38
|
+
base_url: Base URL of the API (default: https://api.hebbrix.com)
|
|
39
|
+
timeout: Request timeout in seconds (default: 30)
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
>>> client = MemoryClient(api_key="mem_sk_...")
|
|
43
|
+
>>> memory = await client.memories.create(
|
|
44
|
+
... collection_id="col_123",
|
|
45
|
+
... content="Important note"
|
|
46
|
+
... )
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
api_key: Optional[str] = None,
|
|
52
|
+
base_url: str = "https://memory-api.livelystone-78e9a45c.centralus.azurecontainerapps.io",
|
|
53
|
+
timeout: float = 120.0,
|
|
54
|
+
):
|
|
55
|
+
self.api_key = api_key
|
|
56
|
+
self.base_url = base_url.rstrip("/")
|
|
57
|
+
self.timeout = timeout
|
|
58
|
+
|
|
59
|
+
# HTTP client
|
|
60
|
+
self._client = httpx.AsyncClient(
|
|
61
|
+
base_url=self.base_url,
|
|
62
|
+
timeout=timeout,
|
|
63
|
+
headers=self._get_headers(),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Initialize resources
|
|
67
|
+
self.auth = AuthResource(self)
|
|
68
|
+
self.collections = CollectionsResource(self)
|
|
69
|
+
self.memories = MemoriesResource(self)
|
|
70
|
+
self.search_resource = SearchResource(self)
|
|
71
|
+
self.rl = RLResource(self)
|
|
72
|
+
self.procedural = ProceduralResource(self)
|
|
73
|
+
self.temporal = TemporalResource(self)
|
|
74
|
+
self.working_memory = WorkingMemoryResource(self)
|
|
75
|
+
self.consolidation = ConsolidationResource(self)
|
|
76
|
+
self.memory_tools = MemoryToolsResource(self)
|
|
77
|
+
self.world_model = WorldModelResource(self)
|
|
78
|
+
|
|
79
|
+
def _get_headers(self) -> Dict[str, str]:
|
|
80
|
+
"""Get request headers."""
|
|
81
|
+
headers = {
|
|
82
|
+
"Content-Type": "application/json",
|
|
83
|
+
"User-Agent": "hebbrix-python/2.0.0",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if self.api_key:
|
|
87
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
88
|
+
|
|
89
|
+
return headers
|
|
90
|
+
|
|
91
|
+
def _handle_error(self, response: httpx.Response) -> None:
|
|
92
|
+
"""Handle error responses."""
|
|
93
|
+
status_code = response.status_code
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
error_data = response.json()
|
|
97
|
+
message = error_data.get("error", {}).get("message", response.text)
|
|
98
|
+
except Exception:
|
|
99
|
+
message = response.text
|
|
100
|
+
|
|
101
|
+
if status_code == 401:
|
|
102
|
+
raise AuthenticationError(message)
|
|
103
|
+
elif status_code == 404:
|
|
104
|
+
raise NotFoundError(message)
|
|
105
|
+
elif status_code == 422:
|
|
106
|
+
errors = error_data.get("error", {}).get("details", [])
|
|
107
|
+
raise ValidationError(message, errors=errors)
|
|
108
|
+
elif status_code == 429:
|
|
109
|
+
raise RateLimitError(message)
|
|
110
|
+
elif status_code >= 500:
|
|
111
|
+
raise ServerError(message)
|
|
112
|
+
else:
|
|
113
|
+
raise HebbrixError(message, status_code=status_code)
|
|
114
|
+
|
|
115
|
+
async def request(
|
|
116
|
+
self,
|
|
117
|
+
method: str,
|
|
118
|
+
path: str,
|
|
119
|
+
**kwargs,
|
|
120
|
+
) -> Dict[str, Any]:
|
|
121
|
+
"""
|
|
122
|
+
Make an HTTP request.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
method: HTTP method (GET, POST, etc.)
|
|
126
|
+
path: API path
|
|
127
|
+
**kwargs: Additional arguments for httpx
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Response data as dictionary
|
|
131
|
+
|
|
132
|
+
Raises:
|
|
133
|
+
HebbrixError: On API errors
|
|
134
|
+
"""
|
|
135
|
+
url = f"{self.base_url}{path}"
|
|
136
|
+
|
|
137
|
+
response = await self._client.request(method, url, **kwargs)
|
|
138
|
+
|
|
139
|
+
if response.status_code >= 400:
|
|
140
|
+
self._handle_error(response)
|
|
141
|
+
|
|
142
|
+
return response.json() if response.text else {}
|
|
143
|
+
|
|
144
|
+
async def get(self, path: str, **kwargs) -> Dict[str, Any]:
|
|
145
|
+
"""Make a GET request."""
|
|
146
|
+
return await self.request("GET", path, **kwargs)
|
|
147
|
+
|
|
148
|
+
async def post(self, path: str, **kwargs) -> Dict[str, Any]:
|
|
149
|
+
"""Make a POST request."""
|
|
150
|
+
return await self.request("POST", path, **kwargs)
|
|
151
|
+
|
|
152
|
+
async def patch(self, path: str, **kwargs) -> Dict[str, Any]:
|
|
153
|
+
"""Make a PATCH request."""
|
|
154
|
+
return await self.request("PATCH", path, **kwargs)
|
|
155
|
+
|
|
156
|
+
async def delete(self, path: str, **kwargs) -> Dict[str, Any]:
|
|
157
|
+
"""Make a DELETE request."""
|
|
158
|
+
return await self.request("DELETE", path, **kwargs)
|
|
159
|
+
|
|
160
|
+
# Convenience methods
|
|
161
|
+
async def search(
|
|
162
|
+
self,
|
|
163
|
+
query: str,
|
|
164
|
+
collection_id: Optional[str] = None,
|
|
165
|
+
limit: int = 10,
|
|
166
|
+
search_type: str = "hybrid",
|
|
167
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
168
|
+
) -> List[Dict[str, Any]]:
|
|
169
|
+
"""
|
|
170
|
+
Search memories.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
query: Search query
|
|
174
|
+
collection_id: Optional collection filter
|
|
175
|
+
limit: Number of results
|
|
176
|
+
search_type: Type of search (hybrid, vector, bm25, graph)
|
|
177
|
+
filters: Additional filters
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
List of search results
|
|
181
|
+
"""
|
|
182
|
+
return await self.search_resource.search(
|
|
183
|
+
query=query,
|
|
184
|
+
collection_id=collection_id,
|
|
185
|
+
limit=limit,
|
|
186
|
+
search_type=search_type,
|
|
187
|
+
filters=filters or {},
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
async def reason(
|
|
191
|
+
self,
|
|
192
|
+
query: str,
|
|
193
|
+
collection_id: Optional[str] = None,
|
|
194
|
+
provider: Optional[str] = None,
|
|
195
|
+
include_steps: bool = False,
|
|
196
|
+
) -> Dict[str, Any]:
|
|
197
|
+
"""
|
|
198
|
+
Perform reasoning over memories.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
query: Question or query
|
|
202
|
+
collection_id: Optional collection filter
|
|
203
|
+
provider: LLM provider (gemini, openai, anthropic)
|
|
204
|
+
include_steps: Include reasoning steps
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Reasoning result with answer and sources
|
|
208
|
+
"""
|
|
209
|
+
return await self.search_resource.reason(
|
|
210
|
+
query=query,
|
|
211
|
+
collection_id=collection_id,
|
|
212
|
+
provider=provider,
|
|
213
|
+
include_steps=include_steps,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
async def close(self):
|
|
217
|
+
"""Close the HTTP client."""
|
|
218
|
+
await self._client.aclose()
|
|
219
|
+
|
|
220
|
+
async def __aenter__(self):
|
|
221
|
+
"""Async context manager entry."""
|
|
222
|
+
return self
|
|
223
|
+
|
|
224
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
225
|
+
"""Async context manager exit."""
|
|
226
|
+
await self.close()
|
hebbrix/exceptions.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hebbrix SDK Exceptions
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class HebbrixError(Exception):
|
|
7
|
+
"""Base exception for Hebbrix SDK."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message: str, status_code: int = None):
|
|
10
|
+
self.message = message
|
|
11
|
+
self.status_code = status_code
|
|
12
|
+
super().__init__(self.message)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AuthenticationError(HebbrixError):
|
|
16
|
+
"""Raised when authentication fails."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, message: str = "Authentication failed"):
|
|
19
|
+
super().__init__(message, status_code=401)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ValidationError(HebbrixError):
|
|
23
|
+
"""Raised when request validation fails."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, message: str, errors: list = None):
|
|
26
|
+
self.errors = errors or []
|
|
27
|
+
super().__init__(message, status_code=422)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class NotFoundError(HebbrixError):
|
|
31
|
+
"""Raised when a resource is not found."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, message: str = "Resource not found"):
|
|
34
|
+
super().__init__(message, status_code=404)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class RateLimitError(HebbrixError):
|
|
38
|
+
"""Raised when rate limit is exceeded."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, message: str = "Rate limit exceeded"):
|
|
41
|
+
super().__init__(message, status_code=429)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ServerError(HebbrixError):
|
|
45
|
+
"""Raised when server returns 5xx error."""
|
|
46
|
+
|
|
47
|
+
def __init__(self, message: str = "Internal server error"):
|
|
48
|
+
super().__init__(message, status_code=500)
|