aeonic-agentguard-sdk-python 0.1.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.
- aeonic/__init__.py +5 -0
- aeonic/adapters/__init__.py +1 -0
- aeonic/adapters/django.py +275 -0
- aeonic/adapters/fastapi.py +337 -0
- aeonic/core/__init__.py +4 -0
- aeonic/core/agent_registry.py +209 -0
- aeonic/core/agent_status_cache.py +28 -0
- aeonic/core/agentguard/__init__.py +8 -0
- aeonic/core/agentguard/registration.py +93 -0
- aeonic/core/agentguard/sse_client.py +210 -0
- aeonic/core/assessment/__init__.py +5 -0
- aeonic/core/assessment/executor.py +207 -0
- aeonic/core/buffer.py +27 -0
- aeonic/core/init.py +218 -0
- aeonic/core/introspection.py +468 -0
- aeonic/core/sender.py +234 -0
- aeonic/core/types.py +51 -0
- aeonic/utils/__init__.py +1 -0
- aeonic/utils/safe_serialize.py +22 -0
- aeonic_agentguard_sdk_python-0.1.0.dist-info/METADATA +770 -0
- aeonic_agentguard_sdk_python-0.1.0.dist-info/RECORD +24 -0
- aeonic_agentguard_sdk_python-0.1.0.dist-info/WHEEL +5 -0
- aeonic_agentguard_sdk_python-0.1.0.dist-info/licenses/LICENSE +21 -0
- aeonic_agentguard_sdk_python-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,770 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aeonic-agentguard-sdk-python
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for Aeonic — runtime monitoring and governance for AI agents
|
|
5
|
+
Author-email: Aeonic <support@aeonic.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, http://45.79.111.106:3290/doc/sdk/python
|
|
8
|
+
Project-URL: Documentation, http://45.79.111.106:3290/doc/sdk/python
|
|
9
|
+
Project-URL: Repository, https://github.com/aeonic/aeonic-sdk-python
|
|
10
|
+
Project-URL: Issues, https://github.com/aeonic/aeonic-sdk-python/issues
|
|
11
|
+
Keywords: ai,agent,agentic-ai,sdk,monitoring,governance,llm,fastapi,django,middleware
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: requests>=2.25.0
|
|
23
|
+
Provides-Extra: fastapi
|
|
24
|
+
Requires-Dist: fastapi>=0.68.0; extra == "fastapi"
|
|
25
|
+
Provides-Extra: django
|
|
26
|
+
Requires-Dist: django>=3.2.0; extra == "django"
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
29
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
30
|
+
Requires-Dist: types-requests>=2.31.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: python-dotenv>=1.0.0; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# Aeonic Python SDK
|
|
35
|
+
|
|
36
|
+
Aeonic Python SDK enables **runtime monitoring, drift detection, and governance**
|
|
37
|
+
for AI agents running inside Python applications.
|
|
38
|
+
|
|
39
|
+
It works by **observing agent-powered routes at runtime**, capturing
|
|
40
|
+
request/response samples, and securely sending them to the Aeonic platform
|
|
41
|
+
for analysis.
|
|
42
|
+
|
|
43
|
+
The SDK is:
|
|
44
|
+
- Framework-aware
|
|
45
|
+
- Declarative
|
|
46
|
+
- Non-intrusive
|
|
47
|
+
- Production-safe
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## ✨ Supported Frameworks
|
|
52
|
+
|
|
53
|
+
- **FastAPI**
|
|
54
|
+
- **Django**
|
|
55
|
+
|
|
56
|
+
> Only routes explicitly marked as **agent routes** are tracked.
|
|
57
|
+
> All other routes are completely ignored by the SDK.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 📦 Installation
|
|
62
|
+
|
|
63
|
+
Install the base SDK (core only — `requests` is included):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install aeonic-sdk-python
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Install with your framework (recommended):
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# FastAPI
|
|
73
|
+
pip install aeonic-sdk-python[fastapi]
|
|
74
|
+
|
|
75
|
+
# Django
|
|
76
|
+
pip install aeonic-sdk-python[django]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> **Note:** The PyPI package name is `aeonic-sdk-python`, but you import it as `aeonic`.
|
|
80
|
+
|
|
81
|
+
Check the installed version:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import aeonic
|
|
85
|
+
print(aeonic.__version__)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 📋 Requirements
|
|
91
|
+
|
|
92
|
+
| Requirement | Details |
|
|
93
|
+
|---|---|
|
|
94
|
+
| Python | `>=3.9` |
|
|
95
|
+
| Core dependency | `requests>=2.25.0` (installed automatically) |
|
|
96
|
+
| FastAPI (optional) | `fastapi>=0.68.0` — install via `[fastapi]` extra |
|
|
97
|
+
| Django (optional) | `django>=3.2.0` — install via `[django]` extra |
|
|
98
|
+
|
|
99
|
+
### Environment variables (optional)
|
|
100
|
+
|
|
101
|
+
| Variable | Purpose |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `AEONIC_ENDPOINT` | Aeonic platform endpoint URL (provided in your Aeonic account or onboarding) |
|
|
104
|
+
| `AEONIC_DEBUG` | Set to any value to enable debug logging |
|
|
105
|
+
| `ORIGIN` / `BASE_URL` | Your application's public URL, sent with sample payloads |
|
|
106
|
+
| `PORT` / `HOST` | Used to auto-detect your app origin when `ORIGIN` is not set |
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 🚀 Quick Start (FastAPI)
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from fastapi import Depends, FastAPI
|
|
114
|
+
from aeonic import init
|
|
115
|
+
from aeonic.adapters.fastapi import agent_middleware, with_agent
|
|
116
|
+
|
|
117
|
+
app = FastAPI()
|
|
118
|
+
app.add_middleware(agent_middleware)
|
|
119
|
+
|
|
120
|
+
init({
|
|
121
|
+
"api_key": "your_api_key",
|
|
122
|
+
"app": app,
|
|
123
|
+
"service_name": "my-service",
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
@app.post(
|
|
127
|
+
"/agent/chat",
|
|
128
|
+
dependencies=[Depends(with_agent({"name": "ChatAgent", "type": "support", "model": ["gpt-4o"]}))],
|
|
129
|
+
)
|
|
130
|
+
async def chat_agent(payload: dict):
|
|
131
|
+
return {"reply": "Hello!"}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Run with:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
uvicorn main:app --reload
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 🔑 SDK Initialization (Required)
|
|
143
|
+
|
|
144
|
+
Initialize the SDK **once at application startup**.
|
|
145
|
+
|
|
146
|
+
### Configuration Options
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
init({
|
|
150
|
+
"api_key": str, # REQUIRED
|
|
151
|
+
"app": Any | None, # Optional: FastAPI/Django app for route introspection
|
|
152
|
+
"service_name": str | None, # Optional (recommended)
|
|
153
|
+
"introspection_delay_ms": int, # Optional: Delay before route scan (default: 2000ms)
|
|
154
|
+
"capture_payloads": {
|
|
155
|
+
"max_samples": int # Default: 10
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Example (Basic)
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from aeonic import init
|
|
164
|
+
|
|
165
|
+
init({
|
|
166
|
+
"api_key": "ag_test_123",
|
|
167
|
+
"service_name": "payments-api",
|
|
168
|
+
"capture_payloads": {
|
|
169
|
+
"max_samples": 10
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Example (With Route Introspection) ⭐ NEW
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from aeonic import init
|
|
178
|
+
from fastapi import FastAPI
|
|
179
|
+
|
|
180
|
+
app = FastAPI()
|
|
181
|
+
|
|
182
|
+
# Pass app instance for automatic route discovery
|
|
183
|
+
init({
|
|
184
|
+
"api_key": "ag_test_123",
|
|
185
|
+
"app": app, # ← Enables automatic agent discovery at startup
|
|
186
|
+
"service_name": "payments-api",
|
|
187
|
+
"introspection_delay_ms": 2000, # Wait 2s for routes to be defined
|
|
188
|
+
"capture_payloads": {
|
|
189
|
+
"max_samples": 10
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### What this does
|
|
195
|
+
|
|
196
|
+
* Authenticates your service with Aeonic
|
|
197
|
+
* Automatically resolves tenant identity via `api_key`
|
|
198
|
+
* Sets payload sampling limits per agent route
|
|
199
|
+
* **NEW:** Discovers and registers all agent routes at startup (if `app` is provided)
|
|
200
|
+
* **NEW:** Emits complete agent inventory to the Aeonic platform before serving traffic
|
|
201
|
+
|
|
202
|
+
> **📖 Route introspection:** Pass your `app` instance to `init()` so the SDK can discover agent routes automatically at startup.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
# 🚀 FastAPI Integration
|
|
207
|
+
|
|
208
|
+
Aeonic uses **two components** in FastAPI:
|
|
209
|
+
|
|
210
|
+
1. A **global middleware** (registered once)
|
|
211
|
+
2. A **route-level dependency** to mark agent routes
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## 1️⃣ Register Global Middleware
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from fastapi import FastAPI
|
|
219
|
+
from aeonic.adapters.fastapi import agent_middleware
|
|
220
|
+
|
|
221
|
+
app = FastAPI()
|
|
222
|
+
|
|
223
|
+
# 🔴 MUST be registered once
|
|
224
|
+
app.add_middleware(agent_middleware)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
This middleware:
|
|
228
|
+
|
|
229
|
+
* Hooks into the response lifecycle
|
|
230
|
+
* Does **nothing** unless the route is marked as an agent route
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 2️⃣ Mark Agent Routes (Required)
|
|
235
|
+
|
|
236
|
+
Only routes using `with_agent()` are tracked.
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
from fastapi import Depends
|
|
240
|
+
from aeonic.adapters.fastapi import with_agent
|
|
241
|
+
|
|
242
|
+
@app.post(
|
|
243
|
+
"/agent/finance",
|
|
244
|
+
dependencies=[
|
|
245
|
+
Depends(
|
|
246
|
+
with_agent({
|
|
247
|
+
"name": "RefundRiskAgent",
|
|
248
|
+
"type": "finance",
|
|
249
|
+
"model": ["gpt-4o"]
|
|
250
|
+
})
|
|
251
|
+
)
|
|
252
|
+
]
|
|
253
|
+
)
|
|
254
|
+
async def finance_agent(payload: dict):
|
|
255
|
+
return {
|
|
256
|
+
"approved": True,
|
|
257
|
+
"amount": payload["amount"]
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### What happens internally
|
|
262
|
+
|
|
263
|
+
* Request + response payloads are captured
|
|
264
|
+
* Samples are buffered per agent route
|
|
265
|
+
* Once `max_samples` is reached, data is sent to the Aeonic platform
|
|
266
|
+
* Agent health status is evaluated (healthy / warning / drift)
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## ✅ FastAPI Summary
|
|
271
|
+
|
|
272
|
+
| Feature | Behavior |
|
|
273
|
+
| -------------------------- | -------- |
|
|
274
|
+
| Only marked routes tracked | ✅ |
|
|
275
|
+
| Async safe | ✅ |
|
|
276
|
+
| Middleware-based | ✅ |
|
|
277
|
+
| No contextvars hacks | ✅ |
|
|
278
|
+
| Production ready | ✅ |
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
# 🏛️ Django Integration
|
|
283
|
+
|
|
284
|
+
Django uses:
|
|
285
|
+
|
|
286
|
+
* A **global middleware**
|
|
287
|
+
* A **route decorator** to mark agent routes
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## 1️⃣ Register Middleware
|
|
292
|
+
|
|
293
|
+
### `settings.py`
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
MIDDLEWARE = [
|
|
297
|
+
...
|
|
298
|
+
"aeonic.adapters.django.agent_middleware",
|
|
299
|
+
]
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
> Ensure `init()` is called during startup (e.g., in `settings.py` or `wsgi.py`).
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## 2️⃣ Mark Agent Routes with Decorator
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
from django.http import JsonResponse
|
|
310
|
+
from aeonic.adapters.django import with_agent
|
|
311
|
+
|
|
312
|
+
@with_agent({
|
|
313
|
+
"name": "RefundRiskAgent",
|
|
314
|
+
"type": "finance",
|
|
315
|
+
"model": ["gpt-4o"]
|
|
316
|
+
})
|
|
317
|
+
def finance_agent(request):
|
|
318
|
+
return JsonResponse({
|
|
319
|
+
"approved": True,
|
|
320
|
+
"amount": 500
|
|
321
|
+
})
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## ✅ Django Summary
|
|
327
|
+
|
|
328
|
+
| Feature | Behavior |
|
|
329
|
+
| ------------------------- | -------- |
|
|
330
|
+
| Declarative agent marking | ✅ |
|
|
331
|
+
| Middleware-based capture | ✅ |
|
|
332
|
+
| Sync-safe | ✅ |
|
|
333
|
+
| No framework coupling | ✅ |
|
|
334
|
+
| Production ready | ✅ |
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
# 🧠 Agent Concepts
|
|
339
|
+
|
|
340
|
+
### Agent Identity
|
|
341
|
+
|
|
342
|
+
Each agent route is identified by:
|
|
343
|
+
|
|
344
|
+
* `name` – logical agent name
|
|
345
|
+
* `type` – business domain (finance, healthcare, etc.)
|
|
346
|
+
* `model` – AI models used
|
|
347
|
+
|
|
348
|
+
### Route → Agent Mapping
|
|
349
|
+
|
|
350
|
+
Aeonic correlates:
|
|
351
|
+
|
|
352
|
+
```
|
|
353
|
+
HTTP Route + Method + Agent Metadata
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
This enables:
|
|
357
|
+
|
|
358
|
+
* Drift detection
|
|
359
|
+
* Agent behavior consistency checks
|
|
360
|
+
* Health classification per route
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
# 🔐 Security & Privacy
|
|
365
|
+
|
|
366
|
+
* Payloads are **sampled**, not streamed
|
|
367
|
+
* Only up to `max_samples` are collected per flush
|
|
368
|
+
* SDK never blocks or alters application behavior
|
|
369
|
+
* Non-agent routes are never inspected
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
# 📊 Agent Status Lifecycle
|
|
374
|
+
|
|
375
|
+
The Aeonic platform classifies agents as:
|
|
376
|
+
|
|
377
|
+
* **Healthy**
|
|
378
|
+
* **Warning**
|
|
379
|
+
* **Drifting**
|
|
380
|
+
* **Failed**
|
|
381
|
+
|
|
382
|
+
Status updates are visible in the Aeonic dashboard.
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
# ⚠️ Important Rules
|
|
387
|
+
|
|
388
|
+
* ❌ Do NOT wrap business logic in SDK functions
|
|
389
|
+
* ❌ Do NOT rely on thread-local or contextvar hacks
|
|
390
|
+
* ✅ Always mark agent routes explicitly
|
|
391
|
+
* ✅ Initialize SDK before app starts
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
# 🧩 Advanced Use Cases
|
|
396
|
+
|
|
397
|
+
* Background workers
|
|
398
|
+
* Batch inference jobs
|
|
399
|
+
* Non-HTTP agents
|
|
400
|
+
|
|
401
|
+
(Contact the Aeonic team for advanced SDK APIs.)
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
# 🔧 Troubleshooting
|
|
406
|
+
|
|
407
|
+
### `RuntimeError: Aeonic SDK not initialized`
|
|
408
|
+
|
|
409
|
+
Call `init({...})` once at application startup before any SDK features are used.
|
|
410
|
+
|
|
411
|
+
### Samples are not being captured
|
|
412
|
+
|
|
413
|
+
1. Ensure the route is marked with `with_agent()` (FastAPI dependency or Django decorator).
|
|
414
|
+
2. Ensure global middleware is registered (`agent_middleware`).
|
|
415
|
+
3. Confirm the agent is not `blocked` or `quarantined` (check Aeonic dashboard).
|
|
416
|
+
4. Set `AEONIC_DEBUG=1` to see SDK debug output.
|
|
417
|
+
|
|
418
|
+
### `ModuleNotFoundError: No module named 'fastapi'` or `'django'`
|
|
419
|
+
|
|
420
|
+
Install the matching framework extra:
|
|
421
|
+
|
|
422
|
+
```bash
|
|
423
|
+
pip install aeonic-sdk-python[fastapi]
|
|
424
|
+
# or
|
|
425
|
+
pip install aeonic-sdk-python[django]
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Agent inventory not emitted at startup
|
|
429
|
+
|
|
430
|
+
- For **FastAPI**, pass `"app": app` to `init()`.
|
|
431
|
+
- For **Django**, ensure `init()` runs during startup and routes are registered before introspection completes.
|
|
432
|
+
- Increase `introspection_delay_ms` if routes are registered late (e.g. `3000` or `5000`).
|
|
433
|
+
|
|
434
|
+
### Platform connection issues
|
|
435
|
+
|
|
436
|
+
Set your Aeonic endpoint URL (provided during onboarding):
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
export AEONIC_ENDPOINT=https://your-aeonic-endpoint
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
On Windows PowerShell:
|
|
443
|
+
|
|
444
|
+
```powershell
|
|
445
|
+
$env:AEONIC_ENDPOINT="https://your-aeonic-endpoint"
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
# 📄 License
|
|
451
|
+
|
|
452
|
+
MIT © Aeonic
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## 📚 SDK Function Reference
|
|
457
|
+
|
|
458
|
+
This section documents the public SDK functions and middleware for integrating Aeonic into your application.
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
### `aeonic.init(config: dict)`
|
|
463
|
+
|
|
464
|
+
**Purpose**
|
|
465
|
+
|
|
466
|
+
Initializes the Aeonic SDK once at application startup, validates and normalizes configuration, kicks off background route introspection, and (optionally) registers the process as an AgentGuard instance.
|
|
467
|
+
|
|
468
|
+
**Arguments**
|
|
469
|
+
|
|
470
|
+
- **`config`** (`dict`) – configuration object with the following keys:
|
|
471
|
+
- **`api_key`** (`str`, required): Your Aeonic API key. If missing, `init` raises `ValueError`.
|
|
472
|
+
- **`app`** (`FastAPI | Django | None`, optional): Application instance used for automatic route introspection and agent discovery at startup. Required for FastAPI introspection; optional for Django (Django can be introspected without `app`).
|
|
473
|
+
- **`service_name`** (`str | None`, optional): Logical name for the service (e.g. `"payments-api"`). Propagated to all emitted events and inventories.
|
|
474
|
+
- **`capture_payloads`** (`dict`, optional): Controls how request/response payload samples are collected.
|
|
475
|
+
- **`max_samples`** (`int`, optional): Number of samples to buffer per agent route before flushing. Defaults to `10` when omitted.
|
|
476
|
+
- **`introspection_delay_ms`** (`int`, optional): Delay (in milliseconds) before route introspection runs. Defaults to `2000` ms, giving the application time to register all routes.
|
|
477
|
+
- **`agentguard_enabled`** (`bool`, optional): When `True` (default), the SDK registers itself as an AgentGuard instance in a background thread. When `False`, this registration step is skipped, but sample collection still works.
|
|
478
|
+
|
|
479
|
+
**Behavior**
|
|
480
|
+
|
|
481
|
+
- Stores a normalized configuration internally containing:
|
|
482
|
+
- `api_key`, `service_name`, `max_samples`, `app`, `introspection_delay_ms`, `agentguard_enabled`.
|
|
483
|
+
- Logs a startup message (`"[Aeonic] SDK initialized."`) using the `aeonic` logger.
|
|
484
|
+
- If `agentguard_enabled` is `True`, registers your service with Aeonic in a **background daemon thread**:
|
|
485
|
+
- Failures are logged as warnings but never raised; the SDK continues collecting samples even if registration fails.
|
|
486
|
+
- Schedules **deferred route introspection** in another background daemon thread:
|
|
487
|
+
- Waits `introspection_delay_ms` before running.
|
|
488
|
+
- For FastAPI: requires `config["app"]` and calls `introspect_routes(app)`.
|
|
489
|
+
- For Django: if `app` is `None` but Django is installed, calls `introspect_routes(None)`, which uses Django’s URL resolver.
|
|
490
|
+
- On success, emits an **agent inventory** event with all registered agents (name, type, model, route, method, framework, timestamps).
|
|
491
|
+
- On any error, logs the traceback and falls back to emitting inventory with whatever agents were registered manually.
|
|
492
|
+
- If introspection is skipped (no `app` and Django not installed), calls a fallback that attempts to emit inventory once agents exist.
|
|
493
|
+
- Never blocks the main thread; all network calls and introspection work are done in background daemon threads.
|
|
494
|
+
|
|
495
|
+
**Typical usage**
|
|
496
|
+
|
|
497
|
+
```python
|
|
498
|
+
from aeonic import init
|
|
499
|
+
from fastapi import FastAPI
|
|
500
|
+
|
|
501
|
+
app = FastAPI()
|
|
502
|
+
|
|
503
|
+
init({
|
|
504
|
+
"api_key": "ag_test_123",
|
|
505
|
+
"app": app,
|
|
506
|
+
"service_name": "payments-api",
|
|
507
|
+
"introspection_delay_ms": 2000,
|
|
508
|
+
"capture_payloads": {
|
|
509
|
+
"max_samples": 10,
|
|
510
|
+
},
|
|
511
|
+
# Optional – disable AgentGuard registration if needed:
|
|
512
|
+
# "agentguard_enabled": False,
|
|
513
|
+
})
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
**Accessing effective config (advanced)**
|
|
517
|
+
|
|
518
|
+
For debugging or advanced inspection, you can read the effective configuration:
|
|
519
|
+
|
|
520
|
+
```python
|
|
521
|
+
from aeonic.core import get_config
|
|
522
|
+
|
|
523
|
+
cfg = get_config() # raises RuntimeError if init() was not called
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
528
|
+
### `aeonic.adapters.fastapi.agent_middleware`
|
|
529
|
+
|
|
530
|
+
**Purpose**
|
|
531
|
+
|
|
532
|
+
FastAPI-compatible middleware (`BaseHTTPMiddleware`) that observes responses and captures samples **only for requests that have been marked as agent routes** via `with_agent`.
|
|
533
|
+
|
|
534
|
+
**How to register**
|
|
535
|
+
|
|
536
|
+
```python
|
|
537
|
+
from fastapi import FastAPI
|
|
538
|
+
from aeonic.adapters.fastapi import agent_middleware
|
|
539
|
+
|
|
540
|
+
app = FastAPI()
|
|
541
|
+
|
|
542
|
+
app.add_middleware(agent_middleware)
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
**Request/response handling**
|
|
546
|
+
|
|
547
|
+
- For each request:
|
|
548
|
+
- Calls the next handler (`call_next(request)`) first to let your business logic run.
|
|
549
|
+
- Reads `request.state.aeonic`:
|
|
550
|
+
- If missing or `is_agent` is falsy, returns the response immediately (non-agent route).
|
|
551
|
+
- Skips **assessment traffic**:
|
|
552
|
+
- If header `x-aeonic-assessment: true` is present, no samples are collected.
|
|
553
|
+
- When the `AEONIC_DEBUG` environment variable is set, the middleware logs a debug message and includes optional `x-aeonic-job-id` for traceability.
|
|
554
|
+
- Skips **blocked/quarantined agents**:
|
|
555
|
+
- Uses `should_collect_samples(agent_key)` from `aeonic.core.agent_status_cache`.
|
|
556
|
+
- `agent_key` is `agent["name"]` when available, otherwise `"<METHOD>:<PATH>"`.
|
|
557
|
+
- When `AEONIC_DEBUG` is set and an agent is blocked, a message is printed for easier debugging.
|
|
558
|
+
|
|
559
|
+
**Payload capture**
|
|
560
|
+
|
|
561
|
+
- **Request payload**:
|
|
562
|
+
- Read from `request.state.aeonic["req_payload"]`, which is set by the `with_agent` dependency (to avoid re-consuming the body).
|
|
563
|
+
- Serialized via `safe_serialize` to guarantee JSON-safe structures.
|
|
564
|
+
- **Response payload**:
|
|
565
|
+
- Attempts to read `response.body` directly when available (typical for JSON responses).
|
|
566
|
+
- If only a `body_iterator` exists (streaming responses), it:
|
|
567
|
+
- Iterates over the body iterator, collects all chunks, and rebuilds a new `Response` with the same status, headers, and media type.
|
|
568
|
+
- Checks `Content-Type` header:
|
|
569
|
+
- If it contains `"json"` and the body is non-empty, attempts `json.loads()` and then `safe_serialize` the result.
|
|
570
|
+
- Any parsing failures are silently ignored (SDK never breaks your response).
|
|
571
|
+
|
|
572
|
+
**Sample creation and buffering**
|
|
573
|
+
|
|
574
|
+
- Builds a sample dictionary:
|
|
575
|
+
- `{"req": req_payload, "res": res_payload, "timestamp": int(time()), "status_code": status_code}`.
|
|
576
|
+
- Reads SDK config via `get_config()`:
|
|
577
|
+
- Uses `config["max_samples"]`, `config["api_key"]`, and `config["service_name"]`.
|
|
578
|
+
- If the status code is **2xx**:
|
|
579
|
+
- Resolves or creates a per-agent `SampleBuffer` in an in-memory `_BUFFERS` map, keyed by `agent_key`.
|
|
580
|
+
- Caps the total number of in-memory buffers at `100` agents; when exceeded, the **oldest key** is evicted.
|
|
581
|
+
- Calls `buffer.add(sample)`:
|
|
582
|
+
- When the buffer is full (`max_samples`), `buffer.flush()` is called and the payload is enqueued via `enqueue(...)` for asynchronous delivery to Aeonic.
|
|
583
|
+
- If the status code is **non-2xx**:
|
|
584
|
+
- Immediately calls `emit_error_sample(...)` with a single-sample payload.
|
|
585
|
+
|
|
586
|
+
**Key properties**
|
|
587
|
+
|
|
588
|
+
- Only applies to routes where `with_agent` has attached metadata.
|
|
589
|
+
- Never throws or mutates your response in a way that changes semantics; at worst it falls back to no-op.
|
|
590
|
+
- Designed to be safe in high-throughput, async production environments.
|
|
591
|
+
|
|
592
|
+
---
|
|
593
|
+
|
|
594
|
+
### `aeonic.adapters.fastapi.with_agent(agent: AgentContext)`
|
|
595
|
+
|
|
596
|
+
**Purpose**
|
|
597
|
+
|
|
598
|
+
FastAPI dependency factory that **marks an endpoint as an agent route** and attaches agent metadata and request payloads to the `request.state.aeonic` object.
|
|
599
|
+
|
|
600
|
+
**Agent context (`AgentContext`)**
|
|
601
|
+
|
|
602
|
+
`AgentContext` is a `dict`-like structure with keys such as:
|
|
603
|
+
|
|
604
|
+
- **`name`** (`str`, recommended): Logical agent name, used as the primary key for statistics and status.
|
|
605
|
+
- **`type`** (`str | None`): Business/domain category (e.g. `"finance"`, `"support"`, `"claims"`).
|
|
606
|
+
- **`model`** (`str | list[str] | None`): Model identifier(s) powering the agent (e.g. `"gpt-4o"`).
|
|
607
|
+
- Additional custom keys are supported and propagated as metadata.
|
|
608
|
+
|
|
609
|
+
**Declaration-time behavior**
|
|
610
|
+
|
|
611
|
+
- When you call `with_agent(agent)` at import time:
|
|
612
|
+
- Immediately calls `_register_agent_declaration(agent, framework="fastapi")`, which in turn:
|
|
613
|
+
- Resolves the current Aeonic config (if initialized) to obtain `service_name`.
|
|
614
|
+
- Registers the agent in the global agent registry with:
|
|
615
|
+
- `name`, `type`, `model`, `framework="fastapi"`, and `service_name`.
|
|
616
|
+
- `route_path=None` and `http_method=None` initially (filled in later by route introspection).
|
|
617
|
+
- Stores a copy of the agent metadata (with `"source": "manual"`) in an internal map keyed by the dependency function’s ID.
|
|
618
|
+
- Also attaches metadata to the dependency function as `__aeonic_metadata__` for robust detection, even when FastAPI wraps or decorates dependencies.
|
|
619
|
+
|
|
620
|
+
**Request-time behavior**
|
|
621
|
+
|
|
622
|
+
- `with_agent(agent)` returns an async dependency callable compatible with `Depends`:
|
|
623
|
+
|
|
624
|
+
```python
|
|
625
|
+
from fastapi import Depends
|
|
626
|
+
from aeonic.adapters.fastapi import with_agent
|
|
627
|
+
|
|
628
|
+
@app.post(
|
|
629
|
+
"/agent/finance",
|
|
630
|
+
dependencies=[Depends(with_agent({"name": "RefundRiskAgent", "type": "finance", "model": ["gpt-4o"]}))],
|
|
631
|
+
)
|
|
632
|
+
async def finance_agent(payload: dict):
|
|
633
|
+
...
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
- On each request, the dependency:
|
|
637
|
+
- Attempts to parse `await request.json()`:
|
|
638
|
+
- If parsing succeeds, `body` is serialized with `safe_serialize`.
|
|
639
|
+
- On any error (non-JSON, invalid body, etc.), `body` is set to `None`.
|
|
640
|
+
- Writes to `request.state.aeonic`:
|
|
641
|
+
- `{"is_agent": True, "agent": {**agent, "source": "manual"}, "req_payload": body}`.
|
|
642
|
+
- This `request.state.aeonic` object is later consumed by `agent_middleware` to decide whether and how to capture samples.
|
|
643
|
+
|
|
644
|
+
**Introspection helper: `get_agent_metadata(func)` (advanced)**
|
|
645
|
+
|
|
646
|
+
- `aeonic.adapters.fastapi.get_agent_metadata(func)`:
|
|
647
|
+
- Attempts to recover an `AgentContext` from:
|
|
648
|
+
- Direct ID lookup in the metadata map.
|
|
649
|
+
- `__aeonic_metadata__` attribute on the function or wrapped function.
|
|
650
|
+
- Closure variables containing the agent context.
|
|
651
|
+
- Underlying `.call` attribute (for FastAPI dependency objects).
|
|
652
|
+
- Used internally by the Aeonic introspection system to match FastAPI routes and dependencies to their agents.
|
|
653
|
+
- Typical SDK consumers do **not** need to call this directly; it is useful only for advanced tooling or framework integrations built on top of Aeonic.
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
657
|
+
### `aeonic.adapters.django.agent_middleware`
|
|
658
|
+
|
|
659
|
+
**Purpose**
|
|
660
|
+
|
|
661
|
+
Global Django middleware class that observes responses and captures samples **only for views that have been marked as agent routes** via the `with_agent` decorator.
|
|
662
|
+
|
|
663
|
+
**How to register**
|
|
664
|
+
|
|
665
|
+
```python
|
|
666
|
+
# settings.py
|
|
667
|
+
|
|
668
|
+
MIDDLEWARE = [
|
|
669
|
+
...
|
|
670
|
+
"aeonic.adapters.django.agent_middleware",
|
|
671
|
+
]
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
**Request/response handling**
|
|
675
|
+
|
|
676
|
+
- Constructed once per process with `get_response`.
|
|
677
|
+
- For each request:
|
|
678
|
+
- Calls `response = get_response(request)` to run your Django view and middleware stack.
|
|
679
|
+
- Reads `request.aeonic`:
|
|
680
|
+
- If missing or `is_agent` is falsy, returns the response immediately (non-agent route).
|
|
681
|
+
- Skips **assessment traffic**:
|
|
682
|
+
- Uses `request.META["HTTP_X_AEONIC_ASSESSMENT"]` to detect the header `X-Aeonic-Assessment: true`.
|
|
683
|
+
- When `AEONIC_DEBUG` is set, logs a message including optional `HTTP_X_AEONIC_JOB_ID`.
|
|
684
|
+
- Skips **blocked/quarantined agents**:
|
|
685
|
+
- Uses `should_collect_samples(agent_key)` with `agent_key = agent["name"]` or `"<METHOD>:<PATH>"`.
|
|
686
|
+
- When `AEONIC_DEBUG` is set and an agent is blocked, logs a message.
|
|
687
|
+
|
|
688
|
+
**Payload capture**
|
|
689
|
+
|
|
690
|
+
- **Request payload**:
|
|
691
|
+
- Read from `request.aeonic["req_payload"]`, which is set by the `with_agent` decorator.
|
|
692
|
+
- Serialized via `safe_serialize`.
|
|
693
|
+
- **Response payload**:
|
|
694
|
+
- Checks `response["Content-Type"]` header.
|
|
695
|
+
- If it contains `"json"`, attempts:
|
|
696
|
+
- Decode `response.content` as UTF‑8.
|
|
697
|
+
- Parse JSON and then `safe_serialize` the result.
|
|
698
|
+
- Any parsing or decoding error is swallowed; the SDK never breaks your response pipeline.
|
|
699
|
+
|
|
700
|
+
**Sample creation and buffering**
|
|
701
|
+
|
|
702
|
+
- Builds a sample dictionary identical to the FastAPI middleware:
|
|
703
|
+
- `{"req": req_payload, "res": res_payload, "timestamp": int(time()), "status_code": status_code}`.
|
|
704
|
+
- Reads SDK config via `get_config()` and uses `max_samples`, `api_key`, and `service_name`.
|
|
705
|
+
- For **2xx** responses:
|
|
706
|
+
- Uses a per-agent in-memory `SampleBuffer` in `_BUFFERS`, capped at `100` distinct agent keys with eviction of the oldest entry when full.
|
|
707
|
+
- When a buffer fills up (`max_samples`), calls `buffer.flush()` and enqueues the batch via `enqueue(...)`.
|
|
708
|
+
- For **non-2xx** responses:
|
|
709
|
+
- Calls `emit_error_sample(...)` immediately with a single sample.
|
|
710
|
+
|
|
711
|
+
---
|
|
712
|
+
|
|
713
|
+
### `aeonic.adapters.django.with_agent(agent: AgentContext)`
|
|
714
|
+
|
|
715
|
+
**Purpose**
|
|
716
|
+
|
|
717
|
+
Decorator that **marks a Django view as an agent route** and attaches agent metadata and payloads to `request.aeonic`.
|
|
718
|
+
|
|
719
|
+
**Declaration-time behavior**
|
|
720
|
+
|
|
721
|
+
- When the decorator is applied:
|
|
722
|
+
- Calls `_register_agent_declaration(agent, framework="django")`:
|
|
723
|
+
- Registers the agent in the global registry with `name`, `type`, `model`, `framework="django"`, `service_name`, and `route_path/http_method` initially set to `None`.
|
|
724
|
+
- Wraps the original view function with `functools.wraps`.
|
|
725
|
+
- Stores a copy of the agent context (with `"source": "manual"`) in an internal map keyed by the wrapped view’s ID.
|
|
726
|
+
- Attaches metadata to the wrapped view as `__aeonic_metadata__` for easier introspection later.
|
|
727
|
+
|
|
728
|
+
**Request-time behavior**
|
|
729
|
+
|
|
730
|
+
- The wrapped view:
|
|
731
|
+
- Tries to build `req_payload`:
|
|
732
|
+
- For `POST`, `PUT`, `PATCH`:
|
|
733
|
+
- Reads `CONTENT_TYPE` from `request.META`.
|
|
734
|
+
- If it contains `"json"` and `request.body` is non-empty:
|
|
735
|
+
- Decodes body as UTF‑8, parses JSON, and `safe_serialize`s the result.
|
|
736
|
+
- If body exists but is not JSON:
|
|
737
|
+
- Decodes as UTF‑8 string and serializes that.
|
|
738
|
+
- For `GET`:
|
|
739
|
+
- If `request.GET` has query parameters, serializes `dict(request.GET)`.
|
|
740
|
+
- If no query params, sets `req_payload` to an empty dict `{}`.
|
|
741
|
+
- On any exception, `req_payload` falls back to `None`.
|
|
742
|
+
- Sets:
|
|
743
|
+
- `request.aeonic = {"is_agent": True, "agent": {**agent, "source": "manual"}, "req_payload": req_payload}`.
|
|
744
|
+
- Calls and returns the original view function.
|
|
745
|
+
|
|
746
|
+
**Introspection helper: `get_agent_metadata(func)` (advanced)**
|
|
747
|
+
|
|
748
|
+
- `aeonic.adapters.django.get_agent_metadata(func)`:
|
|
749
|
+
- First checks the internal metadata map by ID.
|
|
750
|
+
- Then falls back to the `__aeonic_metadata__` attribute if present.
|
|
751
|
+
- Used by internal route introspection to match Django URL patterns to their agents.
|
|
752
|
+
- Typical SDK users do **not** need to call this directly, but it is useful for custom tooling.
|
|
753
|
+
|
|
754
|
+
---
|
|
755
|
+
|
|
756
|
+
### Summary of Public Entry Points
|
|
757
|
+
|
|
758
|
+
- **Initialization**
|
|
759
|
+
- `aeonic.init(config: dict)` – one-time SDK setup, background introspection, and optional AgentGuard registration.
|
|
760
|
+
- **FastAPI**
|
|
761
|
+
- `aeonic.adapters.fastapi.agent_middleware` – global middleware for observing and sampling agent routes.
|
|
762
|
+
- `aeonic.adapters.fastapi.with_agent(agent: AgentContext)` – dependency factory to mark FastAPI endpoints as agent routes and capture request bodies.
|
|
763
|
+
- **Django**
|
|
764
|
+
- `aeonic.adapters.django.agent_middleware` – middleware class for global observation and sampling of agent views.
|
|
765
|
+
- `aeonic.adapters.django.with_agent(agent: AgentContext)` – decorator to mark Django views as agent routes and capture request data.
|
|
766
|
+
- **Advanced (optional)**
|
|
767
|
+
- `aeonic.core.get_config()` – read the effective runtime SDK configuration.
|
|
768
|
+
- `aeonic.adapters.fastapi.get_agent_metadata(func)` / `aeonic.adapters.django.get_agent_metadata(func)` – used by introspection to map framework objects back to agent metadata.
|
|
769
|
+
|
|
770
|
+
You can map each function/middleware above to your integration: use the descriptions as guidance and the code snippets as copy-paste examples.
|