axon-protocol 0.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.
- axon_protocol-0.1.0/LICENSE +21 -0
- axon_protocol-0.1.0/PKG-INFO +158 -0
- axon_protocol-0.1.0/README.md +127 -0
- axon_protocol-0.1.0/axon/__init__.py +51 -0
- axon_protocol-0.1.0/axon/_base.py +142 -0
- axon_protocol-0.1.0/axon/client.py +132 -0
- axon_protocol-0.1.0/axon/coordination.py +161 -0
- axon_protocol-0.1.0/axon/events.py +116 -0
- axon_protocol-0.1.0/axon/exceptions.py +51 -0
- axon_protocol-0.1.0/axon/integrations/__init__.py +15 -0
- axon_protocol-0.1.0/axon/integrations/crewai.py +232 -0
- axon_protocol-0.1.0/axon/integrations/langchain.py +233 -0
- axon_protocol-0.1.0/axon/memory.py +150 -0
- axon_protocol-0.1.0/axon/messages.py +84 -0
- axon_protocol-0.1.0/axon/receipts.py +193 -0
- axon_protocol-0.1.0/axon/types.py +97 -0
- axon_protocol-0.1.0/axon_protocol.egg-info/PKG-INFO +158 -0
- axon_protocol-0.1.0/axon_protocol.egg-info/SOURCES.txt +27 -0
- axon_protocol-0.1.0/axon_protocol.egg-info/dependency_links.txt +1 -0
- axon_protocol-0.1.0/axon_protocol.egg-info/requires.txt +7 -0
- axon_protocol-0.1.0/axon_protocol.egg-info/top_level.txt +1 -0
- axon_protocol-0.1.0/pyproject.toml +50 -0
- axon_protocol-0.1.0/setup.cfg +4 -0
- axon_protocol-0.1.0/tests/test_coordination.py +151 -0
- axon_protocol-0.1.0/tests/test_crewai.py +68 -0
- axon_protocol-0.1.0/tests/test_langchain.py +68 -0
- axon_protocol-0.1.0/tests/test_memory.py +145 -0
- axon_protocol-0.1.0/tests/test_messages.py +66 -0
- axon_protocol-0.1.0/tests/test_receipts.py +184 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sarthak Dhatrak
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: axon-protocol
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for Axon Protocol — persistent memory, coordination, and reasoning receipts for AI agents
|
|
5
|
+
Author-email: Sarthak Dhatrak <sarthakmdhtr@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/axon-protocol/axon-sdk-python
|
|
8
|
+
Project-URL: Documentation, https://docs.axon.so
|
|
9
|
+
Project-URL: Repository, https://github.com/axon-protocol/axon-sdk-python
|
|
10
|
+
Project-URL: Issues, https://github.com/axon-protocol/axon-sdk-python/issues
|
|
11
|
+
Keywords: ai,agents,memory,coordination,llm,multiagent,axon
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: httpx>=0.27.0
|
|
25
|
+
Requires-Dist: websockets>=12.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
|
|
29
|
+
Requires-Dist: respx>=0.21.0; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# Axon Protocol — Python SDK (`axon-protocol`)
|
|
33
|
+
|
|
34
|
+
Official Python client library for integration with the Axon core server. Handles semantic memory, coordination locks, and tamper-proof reasoning receipts for multi-agent AI environments.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
Install the library locally:
|
|
41
|
+
```bash
|
|
42
|
+
pip install axon-protocol
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Quickstart
|
|
48
|
+
|
|
49
|
+
### Async Usage
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import asyncio
|
|
53
|
+
from axon import AxonClient
|
|
54
|
+
|
|
55
|
+
async def main():
|
|
56
|
+
async with AxonClient(api_key="axon-...", project_id="my-project") as axon:
|
|
57
|
+
# Store memory
|
|
58
|
+
await axon.memory.store("User prefers dark mode")
|
|
59
|
+
|
|
60
|
+
# Search memory
|
|
61
|
+
results = await axon.memory.search("ui preferences")
|
|
62
|
+
print(results.results[0].content)
|
|
63
|
+
|
|
64
|
+
# Acquire lock
|
|
65
|
+
async with axon.lock("resource_1"):
|
|
66
|
+
print("Lock held!")
|
|
67
|
+
|
|
68
|
+
asyncio.run(main())
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Sync Usage
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from axon import AxonSyncClient
|
|
75
|
+
|
|
76
|
+
with AxonSyncClient(api_key="axon-...", project_id="my-project") as axon:
|
|
77
|
+
# Store memory
|
|
78
|
+
axon.memory.store("User prefers dark mode")
|
|
79
|
+
|
|
80
|
+
# Search memory
|
|
81
|
+
results = axon.memory.search("ui preferences")
|
|
82
|
+
print(results.results[0].content)
|
|
83
|
+
|
|
84
|
+
# Acquire lock
|
|
85
|
+
with axon.lock("resource_1"):
|
|
86
|
+
print("Lock held!")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Features
|
|
92
|
+
|
|
93
|
+
### A. Persistent Memory (Semantic Vector Search)
|
|
94
|
+
|
|
95
|
+
Store memories and query them with semantic similarity matching:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
# Async
|
|
99
|
+
memory_id = await axon.memory.store(
|
|
100
|
+
content="The API service key is stored in the vault.",
|
|
101
|
+
tags={"category": "vault"},
|
|
102
|
+
scope="project",
|
|
103
|
+
ttl=3600 # auto-expire in 1 hour
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
results = await axon.memory.search(
|
|
107
|
+
query="Where is the API key?",
|
|
108
|
+
limit=5,
|
|
109
|
+
min_similarity=0.6
|
|
110
|
+
)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### B. Resource Locking (Mutex)
|
|
114
|
+
|
|
115
|
+
Prevent race conditions in multi-agent environments. Use the lock context manager to automatically release locks:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
# Sync Lock Context Manager
|
|
119
|
+
with axon.lock("resource_lock", timeout=60) as lock:
|
|
120
|
+
# Exclusive access section
|
|
121
|
+
print(f"Lock acquired, expires at: {lock.expires_at}")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### C. Reasoning Steps Recording
|
|
125
|
+
|
|
126
|
+
Generate cryptographically chained, signed receipts validating agent reasoning processes:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
# Async receipt creation
|
|
130
|
+
receipt = await axon.receipts.create(
|
|
131
|
+
input="Fix the login bug in auth.py",
|
|
132
|
+
steps=[
|
|
133
|
+
ReasoningStep(thought="Read the login function"),
|
|
134
|
+
ReasoningStep(
|
|
135
|
+
thought="Found missing token validation",
|
|
136
|
+
tool_called="read_file",
|
|
137
|
+
result="Null check missing at line 47"
|
|
138
|
+
)
|
|
139
|
+
],
|
|
140
|
+
output="Bug fixed"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Automated decoration
|
|
144
|
+
@axon.receipts.track(input_param="task")
|
|
145
|
+
async def agent_function(task: str, steps_logger=None) -> str:
|
|
146
|
+
steps_logger.add(thought="Analyzing the task")
|
|
147
|
+
return "Done"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### D. Real-Time Events Stream
|
|
151
|
+
|
|
152
|
+
Listen to event streams (like memory additions or lock releases) happening across your project:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
# Async stream
|
|
156
|
+
async for event in axon.events.listen():
|
|
157
|
+
print(f"Event Captured: {event['event_type']}")
|
|
158
|
+
```
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Axon Protocol — Python SDK (`axon-protocol`)
|
|
2
|
+
|
|
3
|
+
Official Python client library for integration with the Axon core server. Handles semantic memory, coordination locks, and tamper-proof reasoning receipts for multi-agent AI environments.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Install the library locally:
|
|
10
|
+
```bash
|
|
11
|
+
pip install axon-protocol
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Quickstart
|
|
17
|
+
|
|
18
|
+
### Async Usage
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
import asyncio
|
|
22
|
+
from axon import AxonClient
|
|
23
|
+
|
|
24
|
+
async def main():
|
|
25
|
+
async with AxonClient(api_key="axon-...", project_id="my-project") as axon:
|
|
26
|
+
# Store memory
|
|
27
|
+
await axon.memory.store("User prefers dark mode")
|
|
28
|
+
|
|
29
|
+
# Search memory
|
|
30
|
+
results = await axon.memory.search("ui preferences")
|
|
31
|
+
print(results.results[0].content)
|
|
32
|
+
|
|
33
|
+
# Acquire lock
|
|
34
|
+
async with axon.lock("resource_1"):
|
|
35
|
+
print("Lock held!")
|
|
36
|
+
|
|
37
|
+
asyncio.run(main())
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Sync Usage
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from axon import AxonSyncClient
|
|
44
|
+
|
|
45
|
+
with AxonSyncClient(api_key="axon-...", project_id="my-project") as axon:
|
|
46
|
+
# Store memory
|
|
47
|
+
axon.memory.store("User prefers dark mode")
|
|
48
|
+
|
|
49
|
+
# Search memory
|
|
50
|
+
results = axon.memory.search("ui preferences")
|
|
51
|
+
print(results.results[0].content)
|
|
52
|
+
|
|
53
|
+
# Acquire lock
|
|
54
|
+
with axon.lock("resource_1"):
|
|
55
|
+
print("Lock held!")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Features
|
|
61
|
+
|
|
62
|
+
### A. Persistent Memory (Semantic Vector Search)
|
|
63
|
+
|
|
64
|
+
Store memories and query them with semantic similarity matching:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
# Async
|
|
68
|
+
memory_id = await axon.memory.store(
|
|
69
|
+
content="The API service key is stored in the vault.",
|
|
70
|
+
tags={"category": "vault"},
|
|
71
|
+
scope="project",
|
|
72
|
+
ttl=3600 # auto-expire in 1 hour
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
results = await axon.memory.search(
|
|
76
|
+
query="Where is the API key?",
|
|
77
|
+
limit=5,
|
|
78
|
+
min_similarity=0.6
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### B. Resource Locking (Mutex)
|
|
83
|
+
|
|
84
|
+
Prevent race conditions in multi-agent environments. Use the lock context manager to automatically release locks:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
# Sync Lock Context Manager
|
|
88
|
+
with axon.lock("resource_lock", timeout=60) as lock:
|
|
89
|
+
# Exclusive access section
|
|
90
|
+
print(f"Lock acquired, expires at: {lock.expires_at}")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### C. Reasoning Steps Recording
|
|
94
|
+
|
|
95
|
+
Generate cryptographically chained, signed receipts validating agent reasoning processes:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
# Async receipt creation
|
|
99
|
+
receipt = await axon.receipts.create(
|
|
100
|
+
input="Fix the login bug in auth.py",
|
|
101
|
+
steps=[
|
|
102
|
+
ReasoningStep(thought="Read the login function"),
|
|
103
|
+
ReasoningStep(
|
|
104
|
+
thought="Found missing token validation",
|
|
105
|
+
tool_called="read_file",
|
|
106
|
+
result="Null check missing at line 47"
|
|
107
|
+
)
|
|
108
|
+
],
|
|
109
|
+
output="Bug fixed"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Automated decoration
|
|
113
|
+
@axon.receipts.track(input_param="task")
|
|
114
|
+
async def agent_function(task: str, steps_logger=None) -> str:
|
|
115
|
+
steps_logger.add(thought="Analyzing the task")
|
|
116
|
+
return "Done"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### D. Real-Time Events Stream
|
|
120
|
+
|
|
121
|
+
Listen to event streams (like memory additions or lock releases) happening across your project:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
# Async stream
|
|
125
|
+
async for event in axon.events.listen():
|
|
126
|
+
print(f"Event Captured: {event['event_type']}")
|
|
127
|
+
```
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from axon.client import AxonClient, AxonSyncClient
|
|
2
|
+
from axon.messages import MessagesClient, SyncMessagesClient
|
|
3
|
+
from axon.types import (
|
|
4
|
+
MemoryResult,
|
|
5
|
+
MemorySearchResponse,
|
|
6
|
+
StoredMemory,
|
|
7
|
+
LockInfo,
|
|
8
|
+
LockStatus,
|
|
9
|
+
ReasoningStep,
|
|
10
|
+
StepsLogger,
|
|
11
|
+
ReceiptInfo,
|
|
12
|
+
ReceiptVerifyResult,
|
|
13
|
+
)
|
|
14
|
+
from axon.exceptions import (
|
|
15
|
+
AxonError,
|
|
16
|
+
AuthError,
|
|
17
|
+
LockConflictError,
|
|
18
|
+
NotFoundError,
|
|
19
|
+
AxonPermissionError,
|
|
20
|
+
RateLimitError,
|
|
21
|
+
ServerError,
|
|
22
|
+
AxonConnectionError,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__version__ = "0.1.0"
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"AxonClient",
|
|
29
|
+
"AxonSyncClient",
|
|
30
|
+
"MessagesClient",
|
|
31
|
+
"SyncMessagesClient",
|
|
32
|
+
# Types
|
|
33
|
+
"MemoryResult",
|
|
34
|
+
"MemorySearchResponse",
|
|
35
|
+
"StoredMemory",
|
|
36
|
+
"LockInfo",
|
|
37
|
+
"LockStatus",
|
|
38
|
+
"ReasoningStep",
|
|
39
|
+
"StepsLogger",
|
|
40
|
+
"ReceiptInfo",
|
|
41
|
+
"ReceiptVerifyResult",
|
|
42
|
+
# Exceptions
|
|
43
|
+
"AxonError",
|
|
44
|
+
"AuthError",
|
|
45
|
+
"LockConflictError",
|
|
46
|
+
"NotFoundError",
|
|
47
|
+
"AxonPermissionError",
|
|
48
|
+
"RateLimitError",
|
|
49
|
+
"ServerError",
|
|
50
|
+
"AxonConnectionError",
|
|
51
|
+
]
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import time
|
|
3
|
+
import asyncio
|
|
4
|
+
from axon.exceptions import (
|
|
5
|
+
AuthError,
|
|
6
|
+
NotFoundError,
|
|
7
|
+
AxonPermissionError,
|
|
8
|
+
LockConflictError,
|
|
9
|
+
RateLimitError,
|
|
10
|
+
ServerError,
|
|
11
|
+
AxonConnectionError,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class _BaseClient:
|
|
16
|
+
def __init__(self, http: httpx.AsyncClient, base_url: str):
|
|
17
|
+
self._http = http
|
|
18
|
+
self._base_url = base_url.rstrip("/")
|
|
19
|
+
|
|
20
|
+
async def _request(self, method: str, path: str, **kwargs) -> dict:
|
|
21
|
+
url = f"{self._base_url}{path}"
|
|
22
|
+
retries = 3
|
|
23
|
+
backoff = 0.5
|
|
24
|
+
|
|
25
|
+
for attempt in range(retries):
|
|
26
|
+
try:
|
|
27
|
+
response = await self._http.request(method, url, **kwargs)
|
|
28
|
+
if response.status_code >= 500 and attempt < retries - 1:
|
|
29
|
+
await asyncio.sleep(backoff * (2 ** attempt))
|
|
30
|
+
continue
|
|
31
|
+
break
|
|
32
|
+
except httpx.ConnectError:
|
|
33
|
+
if attempt == retries - 1:
|
|
34
|
+
raise AxonConnectionError(
|
|
35
|
+
f"Cannot connect to Axon server at {self._base_url}. "
|
|
36
|
+
f"Make sure the server is running."
|
|
37
|
+
)
|
|
38
|
+
await asyncio.sleep(backoff * (2 ** attempt))
|
|
39
|
+
except httpx.TimeoutException:
|
|
40
|
+
if attempt == retries - 1:
|
|
41
|
+
raise AxonConnectionError(
|
|
42
|
+
f"Request to {url} timed out. Server may be overloaded."
|
|
43
|
+
)
|
|
44
|
+
await asyncio.sleep(backoff * (2 ** attempt))
|
|
45
|
+
|
|
46
|
+
# Success
|
|
47
|
+
if response.status_code == 200:
|
|
48
|
+
return response.json()
|
|
49
|
+
|
|
50
|
+
# Parse error detail from response body
|
|
51
|
+
try:
|
|
52
|
+
detail = response.json().get("detail", response.text)
|
|
53
|
+
except Exception:
|
|
54
|
+
detail = response.text
|
|
55
|
+
|
|
56
|
+
# Map status codes to specific exceptions
|
|
57
|
+
if response.status_code == 401:
|
|
58
|
+
raise AuthError(f"Authentication failed: {detail}", 401)
|
|
59
|
+
elif response.status_code == 403:
|
|
60
|
+
raise AxonPermissionError(f"Permission denied: {detail}", 403)
|
|
61
|
+
elif response.status_code == 404:
|
|
62
|
+
raise NotFoundError(f"Not found: {detail}", 404)
|
|
63
|
+
elif response.status_code == 409:
|
|
64
|
+
raise LockConflictError("", detail=detail)
|
|
65
|
+
elif response.status_code == 429:
|
|
66
|
+
retry_after = int(response.headers.get("Retry-After", 60))
|
|
67
|
+
raise RateLimitError(retry_after)
|
|
68
|
+
elif response.status_code >= 500:
|
|
69
|
+
raise ServerError(
|
|
70
|
+
f"Server error ({response.status_code}): {detail}",
|
|
71
|
+
response.status_code,
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
raise ServerError(
|
|
75
|
+
f"Unexpected status {response.status_code}: {detail}",
|
|
76
|
+
response.status_code,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class _BaseSyncClient:
|
|
81
|
+
def __init__(self, http: httpx.Client, base_url: str):
|
|
82
|
+
self._http = http
|
|
83
|
+
self._base_url = base_url.rstrip("/")
|
|
84
|
+
|
|
85
|
+
def _request(self, method: str, path: str, **kwargs) -> dict:
|
|
86
|
+
url = f"{self._base_url}{path}"
|
|
87
|
+
retries = 3
|
|
88
|
+
backoff = 0.5
|
|
89
|
+
|
|
90
|
+
for attempt in range(retries):
|
|
91
|
+
try:
|
|
92
|
+
response = self._http.request(method, url, **kwargs)
|
|
93
|
+
if response.status_code >= 500 and attempt < retries - 1:
|
|
94
|
+
time.sleep(backoff * (2 ** attempt))
|
|
95
|
+
continue
|
|
96
|
+
break
|
|
97
|
+
except httpx.ConnectError:
|
|
98
|
+
if attempt == retries - 1:
|
|
99
|
+
raise AxonConnectionError(
|
|
100
|
+
f"Cannot connect to Axon server at {self._base_url}. "
|
|
101
|
+
f"Make sure the server is running."
|
|
102
|
+
)
|
|
103
|
+
time.sleep(backoff * (2 ** attempt))
|
|
104
|
+
except httpx.TimeoutException:
|
|
105
|
+
if attempt == retries - 1:
|
|
106
|
+
raise AxonConnectionError(
|
|
107
|
+
f"Request to {url} timed out. Server may be overloaded."
|
|
108
|
+
)
|
|
109
|
+
time.sleep(backoff * (2 ** attempt))
|
|
110
|
+
|
|
111
|
+
# Success
|
|
112
|
+
if response.status_code == 200:
|
|
113
|
+
return response.json()
|
|
114
|
+
|
|
115
|
+
# Parse error detail from response body
|
|
116
|
+
try:
|
|
117
|
+
detail = response.json().get("detail", response.text)
|
|
118
|
+
except Exception:
|
|
119
|
+
detail = response.text
|
|
120
|
+
|
|
121
|
+
# Map status codes to specific exceptions
|
|
122
|
+
if response.status_code == 401:
|
|
123
|
+
raise AuthError(f"Authentication failed: {detail}", 401)
|
|
124
|
+
elif response.status_code == 403:
|
|
125
|
+
raise AxonPermissionError(f"Permission denied: {detail}", 403)
|
|
126
|
+
elif response.status_code == 404:
|
|
127
|
+
raise NotFoundError(f"Not found: {detail}", 404)
|
|
128
|
+
elif response.status_code == 409:
|
|
129
|
+
raise LockConflictError("", detail=detail)
|
|
130
|
+
elif response.status_code == 429:
|
|
131
|
+
retry_after = int(response.headers.get("Retry-After", 60))
|
|
132
|
+
raise RateLimitError(retry_after)
|
|
133
|
+
elif response.status_code >= 500:
|
|
134
|
+
raise ServerError(
|
|
135
|
+
f"Server error ({response.status_code}): {detail}",
|
|
136
|
+
response.status_code,
|
|
137
|
+
)
|
|
138
|
+
else:
|
|
139
|
+
raise ServerError(
|
|
140
|
+
f"Unexpected status {response.status_code}: {detail}",
|
|
141
|
+
response.status_code,
|
|
142
|
+
)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
from axon.memory import MemoryClient, SyncMemoryClient
|
|
3
|
+
from axon.coordination import CoordinationClient, SyncCoordinationClient
|
|
4
|
+
from axon.receipts import ReceiptsClient, SyncReceiptsClient
|
|
5
|
+
from axon.events import EventsClient, SyncEventsClient
|
|
6
|
+
from axon.messages import MessagesClient, SyncMessagesClient
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AxonClient:
|
|
10
|
+
"""
|
|
11
|
+
Async entry point for the Axon Protocol Python SDK.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
api_key: str,
|
|
17
|
+
project_id: str,
|
|
18
|
+
agent_token: str = None,
|
|
19
|
+
agent_id: str = None,
|
|
20
|
+
base_url: str = "http://localhost:8000",
|
|
21
|
+
timeout: float = 30.0,
|
|
22
|
+
):
|
|
23
|
+
self.api_key = api_key
|
|
24
|
+
self.project_id = project_id
|
|
25
|
+
self.agent_token = agent_token
|
|
26
|
+
self.agent_id = agent_id
|
|
27
|
+
self.base_url = base_url.rstrip("/")
|
|
28
|
+
|
|
29
|
+
headers = {}
|
|
30
|
+
if api_key:
|
|
31
|
+
headers["X-API-Key"] = api_key
|
|
32
|
+
if agent_token:
|
|
33
|
+
headers["Authorization"] = f"Bearer {agent_token}"
|
|
34
|
+
|
|
35
|
+
self._http = httpx.AsyncClient(
|
|
36
|
+
headers=headers,
|
|
37
|
+
timeout=httpx.Timeout(timeout),
|
|
38
|
+
limits=httpx.Limits(
|
|
39
|
+
max_connections=20,
|
|
40
|
+
max_keepalive_connections=10,
|
|
41
|
+
keepalive_expiry=30,
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
self.memory = MemoryClient(self._http, self.base_url)
|
|
46
|
+
self.lock = CoordinationClient(self._http, self.base_url)
|
|
47
|
+
self.receipts = ReceiptsClient(self._http, self.base_url)
|
|
48
|
+
self.events = EventsClient(self.base_url, api_key, project_id)
|
|
49
|
+
self.messages = MessagesClient(self._http, self.base_url)
|
|
50
|
+
|
|
51
|
+
async def ping(self) -> bool:
|
|
52
|
+
"""
|
|
53
|
+
Check if the Axon server is reachable (async).
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
response = await self._http.get(f"{self.base_url}/v1/health")
|
|
57
|
+
return response.status_code == 200
|
|
58
|
+
except Exception:
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
async def close(self):
|
|
62
|
+
"""Close the underlying HTTP connection pool."""
|
|
63
|
+
await self._http.aclose()
|
|
64
|
+
|
|
65
|
+
async def __aenter__(self):
|
|
66
|
+
return self
|
|
67
|
+
|
|
68
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
69
|
+
await self.close()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class AxonSyncClient:
|
|
73
|
+
"""
|
|
74
|
+
Sync entry point for the Axon Protocol Python SDK.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
api_key: str,
|
|
80
|
+
project_id: str,
|
|
81
|
+
agent_token: str = None,
|
|
82
|
+
agent_id: str = None,
|
|
83
|
+
base_url: str = "http://localhost:8000",
|
|
84
|
+
timeout: float = 30.0,
|
|
85
|
+
):
|
|
86
|
+
self.api_key = api_key
|
|
87
|
+
self.project_id = project_id
|
|
88
|
+
self.agent_token = agent_token
|
|
89
|
+
self.agent_id = agent_id
|
|
90
|
+
self.base_url = base_url.rstrip("/")
|
|
91
|
+
|
|
92
|
+
headers = {}
|
|
93
|
+
if api_key:
|
|
94
|
+
headers["X-API-Key"] = api_key
|
|
95
|
+
if agent_token:
|
|
96
|
+
headers["Authorization"] = f"Bearer {agent_token}"
|
|
97
|
+
|
|
98
|
+
self._http = httpx.Client(
|
|
99
|
+
headers=headers,
|
|
100
|
+
timeout=httpx.Timeout(timeout),
|
|
101
|
+
limits=httpx.Limits(
|
|
102
|
+
max_connections=20,
|
|
103
|
+
max_keepalive_connections=10,
|
|
104
|
+
keepalive_expiry=30,
|
|
105
|
+
),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
self.memory = SyncMemoryClient(self._http, self.base_url)
|
|
109
|
+
self.lock = SyncCoordinationClient(self._http, self.base_url)
|
|
110
|
+
self.receipts = SyncReceiptsClient(self._http, self.base_url)
|
|
111
|
+
self.events = SyncEventsClient(self.base_url, api_key, project_id)
|
|
112
|
+
self.messages = SyncMessagesClient(self._http, self.base_url)
|
|
113
|
+
|
|
114
|
+
def ping(self) -> bool:
|
|
115
|
+
"""
|
|
116
|
+
Check if the Axon server is reachable (sync).
|
|
117
|
+
"""
|
|
118
|
+
try:
|
|
119
|
+
response = self._http.get(f"{self.base_url}/v1/health")
|
|
120
|
+
return response.status_code == 200
|
|
121
|
+
except Exception:
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
def close(self):
|
|
125
|
+
"""Close the underlying HTTP connection pool."""
|
|
126
|
+
self._http.close()
|
|
127
|
+
|
|
128
|
+
def __enter__(self):
|
|
129
|
+
return self
|
|
130
|
+
|
|
131
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
132
|
+
self.close()
|