sannex-agent 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.
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: sannex-agent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python Telemetry & Monitoring Client for Sannex AI Operations
|
|
5
|
+
Project-URL: Homepage, https://sannex.ng
|
|
6
|
+
Project-URL: Repository, https://github.com/sannex/sannex-agent
|
|
7
|
+
Author-email: Sannex Tech LTD <info@sannex.ng>
|
|
8
|
+
License: MIT
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Requires-Dist: httpx>=0.24.0
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# sannex-agent (Python SDK)
|
|
14
|
+
|
|
15
|
+
Official, ultra-lightweight Python client for Sannex AI Operations, telemetry streaming, and dynamic remote configuration sync.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install sannex-agent
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from sannex_agent import SannexClient
|
|
27
|
+
|
|
28
|
+
# Initialize client
|
|
29
|
+
client = SannexClient(api_key="your_sannex_api_key")
|
|
30
|
+
|
|
31
|
+
# Track customer interaction / transaction asynchronously
|
|
32
|
+
client.track(
|
|
33
|
+
channel="whatsapp",
|
|
34
|
+
customer_id="+2348012345678",
|
|
35
|
+
event="payment_success",
|
|
36
|
+
amount=25000.0,
|
|
37
|
+
metadata={"order_id": "ORD-123456", "gateway": "paystack"}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Fetch dynamic system prompts and knowledge base configs
|
|
41
|
+
config = client.get_config()
|
|
42
|
+
print(config)
|
|
43
|
+
|
|
44
|
+
# Graceful shutdown
|
|
45
|
+
client.close()
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
MIT
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# sannex-agent (Python SDK)
|
|
2
|
+
|
|
3
|
+
Official, ultra-lightweight Python client for Sannex AI Operations, telemetry streaming, and dynamic remote configuration sync.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install sannex-agent
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from sannex_agent import SannexClient
|
|
15
|
+
|
|
16
|
+
# Initialize client
|
|
17
|
+
client = SannexClient(api_key="your_sannex_api_key")
|
|
18
|
+
|
|
19
|
+
# Track customer interaction / transaction asynchronously
|
|
20
|
+
client.track(
|
|
21
|
+
channel="whatsapp",
|
|
22
|
+
customer_id="+2348012345678",
|
|
23
|
+
event="payment_success",
|
|
24
|
+
amount=25000.0,
|
|
25
|
+
metadata={"order_id": "ORD-123456", "gateway": "paystack"}
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Fetch dynamic system prompts and knowledge base configs
|
|
29
|
+
config = client.get_config()
|
|
30
|
+
print(config)
|
|
31
|
+
|
|
32
|
+
# Graceful shutdown
|
|
33
|
+
client.close()
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## License
|
|
37
|
+
MIT
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sannex-agent"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python Telemetry & Monitoring Client for Sannex AI Operations"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Sannex Tech LTD", email = "info@sannex.ng" }
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"httpx>=0.24.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://sannex.ng"
|
|
21
|
+
Repository = "https://github.com/sannex/sannex-agent"
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import queue
|
|
2
|
+
import threading
|
|
3
|
+
import time
|
|
4
|
+
from typing import Optional, Dict, Any, List
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SannexClient:
|
|
9
|
+
"""Official Python Telemetry & Remote Config Client for Sannex AI Operations."""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
api_key: str,
|
|
14
|
+
host: str = "https://api.sannex.ng",
|
|
15
|
+
flush_interval: float = 1.0,
|
|
16
|
+
max_queue_size: int = 1000,
|
|
17
|
+
):
|
|
18
|
+
if not api_key:
|
|
19
|
+
raise ValueError("api_key must not be empty.")
|
|
20
|
+
self.api_key = api_key
|
|
21
|
+
self.host = host.rstrip("/")
|
|
22
|
+
self.endpoint = f"{self.host}/v1/events"
|
|
23
|
+
self.config_endpoint = f"{self.host}/v1/tenants/config"
|
|
24
|
+
self.flush_interval = flush_interval
|
|
25
|
+
self._queue: queue.Queue = queue.Queue(maxsize=max_queue_size)
|
|
26
|
+
self._running = True
|
|
27
|
+
self._client = httpx.Client(timeout=5.0)
|
|
28
|
+
|
|
29
|
+
self._worker_thread = threading.Thread(target=self._worker, daemon=True)
|
|
30
|
+
self._worker_thread.start()
|
|
31
|
+
|
|
32
|
+
def track(
|
|
33
|
+
self,
|
|
34
|
+
channel: str,
|
|
35
|
+
customer_id: str,
|
|
36
|
+
event: str,
|
|
37
|
+
status: str = "success",
|
|
38
|
+
amount: float = 0.0,
|
|
39
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Enqueue an event to be dispatched asynchronously without blocking the caller."""
|
|
42
|
+
payload = {
|
|
43
|
+
"channel": channel,
|
|
44
|
+
"customer_id": str(customer_id),
|
|
45
|
+
"event": event,
|
|
46
|
+
"status": status,
|
|
47
|
+
"amount": float(amount),
|
|
48
|
+
"metadata": metadata or {},
|
|
49
|
+
"timestamp": time.time(),
|
|
50
|
+
}
|
|
51
|
+
try:
|
|
52
|
+
self._queue.put_nowait(payload)
|
|
53
|
+
except queue.Full:
|
|
54
|
+
pass # Drop telemetry safely rather than degrading host performance
|
|
55
|
+
|
|
56
|
+
def get_config(self) -> Dict[str, Any]:
|
|
57
|
+
"""Fetches dynamic system prompts, LLM parameters, catalogs, and knowledge base documents."""
|
|
58
|
+
try:
|
|
59
|
+
res = self._client.get(
|
|
60
|
+
self.config_endpoint,
|
|
61
|
+
headers={"Authorization": f"Bearer {self.api_key}"},
|
|
62
|
+
)
|
|
63
|
+
if res.status_code == 200:
|
|
64
|
+
return res.json()
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
return {"status": "error", "config": {}, "knowledge_docs": [], "catalog_items": []}
|
|
68
|
+
|
|
69
|
+
def _worker(self) -> None:
|
|
70
|
+
while self._running:
|
|
71
|
+
events: List[Dict[str, Any]] = []
|
|
72
|
+
while not self._queue.empty() and len(events) < 50:
|
|
73
|
+
try:
|
|
74
|
+
events.append(self._queue.get_nowait())
|
|
75
|
+
except queue.Empty:
|
|
76
|
+
break
|
|
77
|
+
|
|
78
|
+
if events:
|
|
79
|
+
self._flush(events)
|
|
80
|
+
time.sleep(self.flush_interval)
|
|
81
|
+
|
|
82
|
+
def _flush(self, events: List[Dict[str, Any]]) -> None:
|
|
83
|
+
try:
|
|
84
|
+
self._client.post(
|
|
85
|
+
self.endpoint,
|
|
86
|
+
json={"batch": events},
|
|
87
|
+
headers={
|
|
88
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
89
|
+
"Content-Type": "application/json",
|
|
90
|
+
},
|
|
91
|
+
)
|
|
92
|
+
except Exception:
|
|
93
|
+
pass # Swallowed silently so it never breaks host server execution
|
|
94
|
+
|
|
95
|
+
def flush(self) -> None:
|
|
96
|
+
"""Manually flushes all currently queued events immediately."""
|
|
97
|
+
events: List[Dict[str, Any]] = []
|
|
98
|
+
while not self._queue.empty():
|
|
99
|
+
try:
|
|
100
|
+
events.append(self._queue.get_nowait())
|
|
101
|
+
except queue.Empty:
|
|
102
|
+
break
|
|
103
|
+
if events:
|
|
104
|
+
self._flush(events)
|
|
105
|
+
|
|
106
|
+
def close(self) -> None:
|
|
107
|
+
self._running = False
|
|
108
|
+
self.flush()
|
|
109
|
+
try:
|
|
110
|
+
self._worker_thread.join(timeout=2.0)
|
|
111
|
+
self._client.close()
|
|
112
|
+
except Exception:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
def __enter__(self):
|
|
116
|
+
return self
|
|
117
|
+
|
|
118
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
119
|
+
self.close()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Marker file for PEP 561
|