agentguard-governance-sdk 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.
- agentguard_governance_sdk-0.1.0/LICENSE +21 -0
- agentguard_governance_sdk-0.1.0/PKG-INFO +196 -0
- agentguard_governance_sdk-0.1.0/README.md +171 -0
- agentguard_governance_sdk-0.1.0/agentguard/__init__.py +27 -0
- agentguard_governance_sdk-0.1.0/agentguard/client.py +120 -0
- agentguard_governance_sdk-0.1.0/agentguard/exceptions.py +95 -0
- agentguard_governance_sdk-0.1.0/agentguard/guard.py +186 -0
- agentguard_governance_sdk-0.1.0/agentguard/models.py +35 -0
- agentguard_governance_sdk-0.1.0/agentguard_governance_sdk.egg-info/PKG-INFO +196 -0
- agentguard_governance_sdk-0.1.0/agentguard_governance_sdk.egg-info/SOURCES.txt +14 -0
- agentguard_governance_sdk-0.1.0/agentguard_governance_sdk.egg-info/dependency_links.txt +1 -0
- agentguard_governance_sdk-0.1.0/agentguard_governance_sdk.egg-info/requires.txt +2 -0
- agentguard_governance_sdk-0.1.0/agentguard_governance_sdk.egg-info/top_level.txt +1 -0
- agentguard_governance_sdk-0.1.0/pyproject.toml +39 -0
- agentguard_governance_sdk-0.1.0/setup.cfg +4 -0
- agentguard_governance_sdk-0.1.0/tests/test_sdk.py +329 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 AgentGuard Team
|
|
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,196 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentguard-governance-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Developer SDK for AgentGuard runtime governance and policy enforcement layer
|
|
5
|
+
Author: AgentGuard Team
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/agentguard/agentguard
|
|
8
|
+
Project-URL: Documentation, https://github.com/agentguard/agentguard/tree/main/sdk
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/agentguard/agentguard/issues
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Topic :: Security
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: httpx>=0.24.0
|
|
23
|
+
Requires-Dist: pydantic>=2.0.0
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# agentguard-governance-sdk
|
|
27
|
+
|
|
28
|
+
Official Python Developer SDK for the **AgentGuard** runtime governance and policy enforcement layer.
|
|
29
|
+
|
|
30
|
+
AgentGuard intercepts proposed AI agent tool calls before dispatch, evaluates them against organizational RBAC, risk policies, token/rate limits, and cost budgets, and logs decisions to a cryptographic tamper-evident audit vault.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install agentguard-governance-sdk
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Requirements:** Python 3.9+ with `httpx` and `pydantic`.
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Core Usage
|
|
46
|
+
|
|
47
|
+
### Standard Exception-Driven Pattern
|
|
48
|
+
|
|
49
|
+
By default, `client.guard(...)` returns a `GuardResult` on `ALLOW`, and raises structured exceptions when an action is blocked or queued for approval:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from agentguard import (
|
|
53
|
+
AgentGuard,
|
|
54
|
+
AgentGuardDenied,
|
|
55
|
+
AgentGuardPending,
|
|
56
|
+
AgentGuardAuthenticationError,
|
|
57
|
+
AgentGuardTimeout,
|
|
58
|
+
AgentGuardServerError,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Initialize client
|
|
62
|
+
client = AgentGuard(
|
|
63
|
+
api_key="ag_agent_your_key_here",
|
|
64
|
+
base_url="http://localhost:8000/api/v1", # Core Engine Gateway URL
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
result = client.guard(
|
|
69
|
+
tool="read_customer",
|
|
70
|
+
parameters={"customer_id": "CUST-1001"},
|
|
71
|
+
estimated_tokens=50,
|
|
72
|
+
estimated_cost=0.001,
|
|
73
|
+
)
|
|
74
|
+
if result.allowed:
|
|
75
|
+
print(f"Action allowed! Request ID: {result.request_id}")
|
|
76
|
+
# Safely execute the real tool call here
|
|
77
|
+
|
|
78
|
+
except AgentGuardDenied as e:
|
|
79
|
+
# Action was rejected by policy (e.g. CRITICAL risk, unpermitted tool, rate/budget exceeded)
|
|
80
|
+
print(f"Action DENIED: {e.reason} (request_id={e.request_id})")
|
|
81
|
+
|
|
82
|
+
except AgentGuardPending as e:
|
|
83
|
+
# Action requires human-in-the-loop (HITL) approval
|
|
84
|
+
print(f"Action paused for human approval: {e.reason} (request_id={e.request_id})")
|
|
85
|
+
|
|
86
|
+
except AgentGuardAuthenticationError as e:
|
|
87
|
+
print(f"Authentication failed (HTTP {e.status_code}): {e.detail}")
|
|
88
|
+
|
|
89
|
+
except AgentGuardTimeout as e:
|
|
90
|
+
print(f"Connection to AgentGuard timed out or was refused: {e}")
|
|
91
|
+
|
|
92
|
+
except AgentGuardServerError as e:
|
|
93
|
+
print(f"AgentGuard backend error (HTTP {e.status_code}): {e.detail}")
|
|
94
|
+
|
|
95
|
+
finally:
|
|
96
|
+
client.close()
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
### Context Manager Support
|
|
102
|
+
|
|
103
|
+
`AgentGuard` instances can be managed automatically via a `with` block:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from agentguard import AgentGuard
|
|
107
|
+
|
|
108
|
+
with AgentGuard(api_key="ag_agent_...") as client:
|
|
109
|
+
result = client.guard(tool="send_email", parameters={"to": "user@example.com"})
|
|
110
|
+
print("Allowed:", result.allowed)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
### Non-Raising / Boolean Return Mode
|
|
116
|
+
|
|
117
|
+
Pass `raise_for_status=False` to inspect decisions via the returned `GuardResult` without catching exceptions:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
with AgentGuard(api_key="ag_agent_...") as client:
|
|
121
|
+
result = client.guard(
|
|
122
|
+
tool="process_refund",
|
|
123
|
+
parameters={"amount": 5000},
|
|
124
|
+
raise_for_status=False,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if result.allowed:
|
|
128
|
+
print("Proceeding with refund...")
|
|
129
|
+
elif result.decision == "PENDING":
|
|
130
|
+
print(f"Queued for human review. Request ID: {result.request_id}")
|
|
131
|
+
elif result.decision == "DENY":
|
|
132
|
+
print(f"Refund denied: {result.reason}")
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## API Reference
|
|
138
|
+
|
|
139
|
+
### `AgentGuard` Constructor
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
AgentGuard(
|
|
143
|
+
api_key: str,
|
|
144
|
+
base_url: str = "http://localhost:8000/api/v1",
|
|
145
|
+
agent_id: Optional[Union[uuid.UUID, str]] = None,
|
|
146
|
+
timeout: float = 5.0,
|
|
147
|
+
max_retries: int = 1,
|
|
148
|
+
retry_backoff: float = 0.2,
|
|
149
|
+
http_client: Optional[httpx.Client] = None,
|
|
150
|
+
)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### `client.guard(...)` Parameters
|
|
154
|
+
|
|
155
|
+
| Parameter | Type | Default | Description |
|
|
156
|
+
|---|---|---|---|
|
|
157
|
+
| `tool` | `str` | *required* | Name of the tool to be executed (e.g. `"read_customer"`). |
|
|
158
|
+
| `parameters` | `dict` | `{}` | Key-value dictionary of arguments for the tool. |
|
|
159
|
+
| `action` | `str` | `"execute"` | Action verb associated with the tool call. |
|
|
160
|
+
| `agent_id` | `UUID \| str` | `None` | Optional override for the requesting agent UUID. |
|
|
161
|
+
| `estimated_tokens` | `int` | `0` | Estimated token usage for sliding-window token rate limiting. |
|
|
162
|
+
| `estimated_cost` | `float` | `0.0` | Estimated monetary spend for budget enforcement. |
|
|
163
|
+
| `metadata` | `dict` | `None` | Custom context or telemetry dictionary attached to audit log. |
|
|
164
|
+
| `timeout` | `float` | `None` | Per-request timeout override in seconds. |
|
|
165
|
+
| `max_retries` | `int` | `None` | Per-request network retry attempts (safe network failures only). |
|
|
166
|
+
| `raise_for_status` | `bool` | `True` | If `True`, raises `AgentGuardDenied` or `AgentGuardPending`. If `False`, returns `GuardResult` with `.allowed=False`. |
|
|
167
|
+
|
|
168
|
+
### `GuardResult` Object Attributes
|
|
169
|
+
|
|
170
|
+
- `allowed` (`bool`): `True` if permitted, `False` otherwise.
|
|
171
|
+
- `decision` (`str`): `"ALLOW"`, `"DENY"`, or `"PENDING"`.
|
|
172
|
+
- `request_id` (`str`): Server-generated UUID uniquely tracking this evaluation in the audit vault.
|
|
173
|
+
- `reason` (`str`): Human-readable explanation of policy decision.
|
|
174
|
+
- `raw_response` (`dict`): Raw JSON response dictionary from the gateway.
|
|
175
|
+
|
|
176
|
+
### Exception Hierarchy
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
AgentGuardError (Base Exception)
|
|
180
|
+
├── AgentGuardDenied (Action blocked by policy)
|
|
181
|
+
│ ├── reason: str
|
|
182
|
+
│ └── request_id: Optional[str]
|
|
183
|
+
├── AgentGuardPending (Action paused for human approval)
|
|
184
|
+
│ ├── reason: str
|
|
185
|
+
│ └── request_id: Optional[str]
|
|
186
|
+
├── AgentGuardAuthenticationError (HTTP 401/403)
|
|
187
|
+
│ ├── status_code: int
|
|
188
|
+
│ └── detail: Optional[str]
|
|
189
|
+
├── AgentGuardTimeout (Unreachable / network dropped)
|
|
190
|
+
│ └── message: str
|
|
191
|
+
└── AgentGuardServerError (HTTP 5xx / unexpected response)
|
|
192
|
+
├── status_code: int
|
|
193
|
+
├── detail: Optional[str]
|
|
194
|
+
└── raw_response: Optional[str]
|
|
195
|
+
```
|
|
196
|
+
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# agentguard-governance-sdk
|
|
2
|
+
|
|
3
|
+
Official Python Developer SDK for the **AgentGuard** runtime governance and policy enforcement layer.
|
|
4
|
+
|
|
5
|
+
AgentGuard intercepts proposed AI agent tool calls before dispatch, evaluates them against organizational RBAC, risk policies, token/rate limits, and cost budgets, and logs decisions to a cryptographic tamper-evident audit vault.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install agentguard-governance-sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Requirements:** Python 3.9+ with `httpx` and `pydantic`.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Core Usage
|
|
21
|
+
|
|
22
|
+
### Standard Exception-Driven Pattern
|
|
23
|
+
|
|
24
|
+
By default, `client.guard(...)` returns a `GuardResult` on `ALLOW`, and raises structured exceptions when an action is blocked or queued for approval:
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from agentguard import (
|
|
28
|
+
AgentGuard,
|
|
29
|
+
AgentGuardDenied,
|
|
30
|
+
AgentGuardPending,
|
|
31
|
+
AgentGuardAuthenticationError,
|
|
32
|
+
AgentGuardTimeout,
|
|
33
|
+
AgentGuardServerError,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Initialize client
|
|
37
|
+
client = AgentGuard(
|
|
38
|
+
api_key="ag_agent_your_key_here",
|
|
39
|
+
base_url="http://localhost:8000/api/v1", # Core Engine Gateway URL
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
result = client.guard(
|
|
44
|
+
tool="read_customer",
|
|
45
|
+
parameters={"customer_id": "CUST-1001"},
|
|
46
|
+
estimated_tokens=50,
|
|
47
|
+
estimated_cost=0.001,
|
|
48
|
+
)
|
|
49
|
+
if result.allowed:
|
|
50
|
+
print(f"Action allowed! Request ID: {result.request_id}")
|
|
51
|
+
# Safely execute the real tool call here
|
|
52
|
+
|
|
53
|
+
except AgentGuardDenied as e:
|
|
54
|
+
# Action was rejected by policy (e.g. CRITICAL risk, unpermitted tool, rate/budget exceeded)
|
|
55
|
+
print(f"Action DENIED: {e.reason} (request_id={e.request_id})")
|
|
56
|
+
|
|
57
|
+
except AgentGuardPending as e:
|
|
58
|
+
# Action requires human-in-the-loop (HITL) approval
|
|
59
|
+
print(f"Action paused for human approval: {e.reason} (request_id={e.request_id})")
|
|
60
|
+
|
|
61
|
+
except AgentGuardAuthenticationError as e:
|
|
62
|
+
print(f"Authentication failed (HTTP {e.status_code}): {e.detail}")
|
|
63
|
+
|
|
64
|
+
except AgentGuardTimeout as e:
|
|
65
|
+
print(f"Connection to AgentGuard timed out or was refused: {e}")
|
|
66
|
+
|
|
67
|
+
except AgentGuardServerError as e:
|
|
68
|
+
print(f"AgentGuard backend error (HTTP {e.status_code}): {e.detail}")
|
|
69
|
+
|
|
70
|
+
finally:
|
|
71
|
+
client.close()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
### Context Manager Support
|
|
77
|
+
|
|
78
|
+
`AgentGuard` instances can be managed automatically via a `with` block:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from agentguard import AgentGuard
|
|
82
|
+
|
|
83
|
+
with AgentGuard(api_key="ag_agent_...") as client:
|
|
84
|
+
result = client.guard(tool="send_email", parameters={"to": "user@example.com"})
|
|
85
|
+
print("Allowed:", result.allowed)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
### Non-Raising / Boolean Return Mode
|
|
91
|
+
|
|
92
|
+
Pass `raise_for_status=False` to inspect decisions via the returned `GuardResult` without catching exceptions:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
with AgentGuard(api_key="ag_agent_...") as client:
|
|
96
|
+
result = client.guard(
|
|
97
|
+
tool="process_refund",
|
|
98
|
+
parameters={"amount": 5000},
|
|
99
|
+
raise_for_status=False,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if result.allowed:
|
|
103
|
+
print("Proceeding with refund...")
|
|
104
|
+
elif result.decision == "PENDING":
|
|
105
|
+
print(f"Queued for human review. Request ID: {result.request_id}")
|
|
106
|
+
elif result.decision == "DENY":
|
|
107
|
+
print(f"Refund denied: {result.reason}")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## API Reference
|
|
113
|
+
|
|
114
|
+
### `AgentGuard` Constructor
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
AgentGuard(
|
|
118
|
+
api_key: str,
|
|
119
|
+
base_url: str = "http://localhost:8000/api/v1",
|
|
120
|
+
agent_id: Optional[Union[uuid.UUID, str]] = None,
|
|
121
|
+
timeout: float = 5.0,
|
|
122
|
+
max_retries: int = 1,
|
|
123
|
+
retry_backoff: float = 0.2,
|
|
124
|
+
http_client: Optional[httpx.Client] = None,
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### `client.guard(...)` Parameters
|
|
129
|
+
|
|
130
|
+
| Parameter | Type | Default | Description |
|
|
131
|
+
|---|---|---|---|
|
|
132
|
+
| `tool` | `str` | *required* | Name of the tool to be executed (e.g. `"read_customer"`). |
|
|
133
|
+
| `parameters` | `dict` | `{}` | Key-value dictionary of arguments for the tool. |
|
|
134
|
+
| `action` | `str` | `"execute"` | Action verb associated with the tool call. |
|
|
135
|
+
| `agent_id` | `UUID \| str` | `None` | Optional override for the requesting agent UUID. |
|
|
136
|
+
| `estimated_tokens` | `int` | `0` | Estimated token usage for sliding-window token rate limiting. |
|
|
137
|
+
| `estimated_cost` | `float` | `0.0` | Estimated monetary spend for budget enforcement. |
|
|
138
|
+
| `metadata` | `dict` | `None` | Custom context or telemetry dictionary attached to audit log. |
|
|
139
|
+
| `timeout` | `float` | `None` | Per-request timeout override in seconds. |
|
|
140
|
+
| `max_retries` | `int` | `None` | Per-request network retry attempts (safe network failures only). |
|
|
141
|
+
| `raise_for_status` | `bool` | `True` | If `True`, raises `AgentGuardDenied` or `AgentGuardPending`. If `False`, returns `GuardResult` with `.allowed=False`. |
|
|
142
|
+
|
|
143
|
+
### `GuardResult` Object Attributes
|
|
144
|
+
|
|
145
|
+
- `allowed` (`bool`): `True` if permitted, `False` otherwise.
|
|
146
|
+
- `decision` (`str`): `"ALLOW"`, `"DENY"`, or `"PENDING"`.
|
|
147
|
+
- `request_id` (`str`): Server-generated UUID uniquely tracking this evaluation in the audit vault.
|
|
148
|
+
- `reason` (`str`): Human-readable explanation of policy decision.
|
|
149
|
+
- `raw_response` (`dict`): Raw JSON response dictionary from the gateway.
|
|
150
|
+
|
|
151
|
+
### Exception Hierarchy
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
AgentGuardError (Base Exception)
|
|
155
|
+
├── AgentGuardDenied (Action blocked by policy)
|
|
156
|
+
│ ├── reason: str
|
|
157
|
+
│ └── request_id: Optional[str]
|
|
158
|
+
├── AgentGuardPending (Action paused for human approval)
|
|
159
|
+
│ ├── reason: str
|
|
160
|
+
│ └── request_id: Optional[str]
|
|
161
|
+
├── AgentGuardAuthenticationError (HTTP 401/403)
|
|
162
|
+
│ ├── status_code: int
|
|
163
|
+
│ └── detail: Optional[str]
|
|
164
|
+
├── AgentGuardTimeout (Unreachable / network dropped)
|
|
165
|
+
│ └── message: str
|
|
166
|
+
└── AgentGuardServerError (HTTP 5xx / unexpected response)
|
|
167
|
+
├── status_code: int
|
|
168
|
+
├── detail: Optional[str]
|
|
169
|
+
└── raw_response: Optional[str]
|
|
170
|
+
```
|
|
171
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""AgentGuard Python SDK.
|
|
2
|
+
|
|
3
|
+
Framework-Agnostic Runtime Governance Layer for Autonomous AI Agents.
|
|
4
|
+
"""
|
|
5
|
+
from agentguard.client import AgentGuard
|
|
6
|
+
from agentguard.exceptions import (
|
|
7
|
+
AgentGuardAuthenticationError,
|
|
8
|
+
AgentGuardDenied,
|
|
9
|
+
AgentGuardError,
|
|
10
|
+
AgentGuardPending,
|
|
11
|
+
AgentGuardServerError,
|
|
12
|
+
AgentGuardTimeout,
|
|
13
|
+
)
|
|
14
|
+
from agentguard.models import GuardRequest, GuardResult
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"AgentGuard",
|
|
18
|
+
"GuardRequest",
|
|
19
|
+
"GuardResult",
|
|
20
|
+
"AgentGuardError",
|
|
21
|
+
"AgentGuardDenied",
|
|
22
|
+
"AgentGuardPending",
|
|
23
|
+
"AgentGuardAuthenticationError",
|
|
24
|
+
"AgentGuardTimeout",
|
|
25
|
+
"AgentGuardServerError",
|
|
26
|
+
]
|
|
27
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""AgentGuard client for governing autonomous agent tool calls over HTTP."""
|
|
2
|
+
import uuid
|
|
3
|
+
from typing import Any, Optional, Union
|
|
4
|
+
import httpx
|
|
5
|
+
|
|
6
|
+
from agentguard.guard import evaluate_guard
|
|
7
|
+
from agentguard.models import GuardResult
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AgentGuard:
|
|
11
|
+
"""Synchronous client for the AgentGuard Core Engine.
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
client = AgentGuard(api_key="ag_live_...")
|
|
15
|
+
result = client.guard(tool="read_customer", parameters={"customer_id": "123"})
|
|
16
|
+
if result.allowed:
|
|
17
|
+
# execute tool
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
By default, blocked decisions raise structured exceptions (AgentGuardDenied,
|
|
21
|
+
AgentGuardPending). Pass `raise_for_status=False` to handle decisions via
|
|
22
|
+
the boolean `.allowed` attribute instead.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
api_key: str,
|
|
28
|
+
base_url: str = "http://localhost:8000/api/v1",
|
|
29
|
+
agent_id: Optional[Union[uuid.UUID, str]] = None,
|
|
30
|
+
timeout: float = 5.0,
|
|
31
|
+
max_retries: int = 1,
|
|
32
|
+
retry_backoff: float = 0.2,
|
|
33
|
+
http_client: Optional[httpx.Client] = None,
|
|
34
|
+
) -> None:
|
|
35
|
+
self.api_key = api_key
|
|
36
|
+
self.base_url = base_url.rstrip("/")
|
|
37
|
+
self.agent_id = agent_id
|
|
38
|
+
self.timeout = timeout
|
|
39
|
+
self.max_retries = max_retries
|
|
40
|
+
self.retry_backoff = retry_backoff
|
|
41
|
+
|
|
42
|
+
self._headers = {
|
|
43
|
+
"X-API-Key": self.api_key,
|
|
44
|
+
"Content-Type": "application/json",
|
|
45
|
+
"User-Agent": "agentguard-python-sdk/0.1.0",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
self._client = http_client or httpx.Client(timeout=timeout)
|
|
49
|
+
self._owns_client = http_client is None
|
|
50
|
+
|
|
51
|
+
def guard(
|
|
52
|
+
self,
|
|
53
|
+
tool: str,
|
|
54
|
+
parameters: Optional[dict[str, Any]] = None,
|
|
55
|
+
*,
|
|
56
|
+
action: str = "execute",
|
|
57
|
+
agent_id: Optional[Union[uuid.UUID, str]] = None,
|
|
58
|
+
estimated_tokens: int = 0,
|
|
59
|
+
estimated_cost: float = 0.0,
|
|
60
|
+
metadata: Optional[dict[str, Any]] = None,
|
|
61
|
+
timeout: Optional[float] = None,
|
|
62
|
+
max_retries: Optional[int] = None,
|
|
63
|
+
raise_for_status: bool = True,
|
|
64
|
+
) -> GuardResult:
|
|
65
|
+
"""Evaluate a proposed tool call through AgentGuard.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
tool: Name of the tool to be executed (e.g. "read_customer").
|
|
69
|
+
parameters: Key-value parameters intended for the tool.
|
|
70
|
+
action: Action verb (default: "execute").
|
|
71
|
+
agent_id: Optional override for the calling agent UUID.
|
|
72
|
+
estimated_tokens: Estimated token usage for cost enforcement.
|
|
73
|
+
estimated_cost: Estimated monetary cost for budget caps.
|
|
74
|
+
metadata: Custom context metadata attached to the request.
|
|
75
|
+
timeout: Per-request timeout in seconds (overrides client default).
|
|
76
|
+
max_retries: Per-request network retry attempts.
|
|
77
|
+
raise_for_status: If True (default), raises AgentGuardDenied or
|
|
78
|
+
AgentGuardPending. If False, returns GuardResult with .allowed=False.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
GuardResult with .allowed=True, .decision, .request_id, and .reason.
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
AgentGuardDenied: If policy blocks the action.
|
|
85
|
+
AgentGuardPending: If action requires human review (HITL).
|
|
86
|
+
AgentGuardAuthenticationError: If API key is invalid or revoked.
|
|
87
|
+
AgentGuardTimeout: If the backend is unreachable or times out.
|
|
88
|
+
AgentGuardServerError: If the backend encounters an internal error.
|
|
89
|
+
"""
|
|
90
|
+
effective_agent_id = agent_id or self.agent_id
|
|
91
|
+
effective_timeout = timeout if timeout is not None else self.timeout
|
|
92
|
+
effective_retries = max_retries if max_retries is not None else self.max_retries
|
|
93
|
+
|
|
94
|
+
return evaluate_guard(
|
|
95
|
+
client=self._client,
|
|
96
|
+
base_url=self.base_url,
|
|
97
|
+
headers=self._headers,
|
|
98
|
+
tool=tool,
|
|
99
|
+
parameters=parameters or {},
|
|
100
|
+
action=action,
|
|
101
|
+
agent_id=effective_agent_id,
|
|
102
|
+
estimated_tokens=estimated_tokens,
|
|
103
|
+
estimated_cost=estimated_cost,
|
|
104
|
+
metadata=metadata,
|
|
105
|
+
timeout=effective_timeout,
|
|
106
|
+
max_retries=effective_retries,
|
|
107
|
+
retry_backoff=self.retry_backoff,
|
|
108
|
+
raise_for_status=raise_for_status,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def close(self) -> None:
|
|
112
|
+
"""Close the underlying HTTP client if owned by this instance."""
|
|
113
|
+
if self._owns_client and not self._client.is_closed:
|
|
114
|
+
self._client.close()
|
|
115
|
+
|
|
116
|
+
def __enter__(self) -> "AgentGuard":
|
|
117
|
+
return self
|
|
118
|
+
|
|
119
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
120
|
+
self.close()
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Structured exceptions for the AgentGuard SDK.
|
|
2
|
+
|
|
3
|
+
All SDK exceptions inherit from AgentGuardError so callers can catch broadly or specifically.
|
|
4
|
+
"""
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AgentGuardError(Exception):
|
|
9
|
+
"""Base exception for all AgentGuard SDK errors."""
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AgentGuardDenied(AgentGuardError):
|
|
14
|
+
"""Raised when a proposed tool call is blocked (DENIED) by AgentGuard governance.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
reason: Human-readable explanation of why the action was denied.
|
|
18
|
+
request_id: Server-assigned UUID tracking the decision in the audit vault.
|
|
19
|
+
"""
|
|
20
|
+
def __init__(self, reason: str, request_id: Optional[str] = None) -> None:
|
|
21
|
+
self.reason = reason
|
|
22
|
+
self.request_id = request_id
|
|
23
|
+
msg = f"AgentGuard denied tool call: {reason}"
|
|
24
|
+
if request_id:
|
|
25
|
+
msg += f" (request_id={request_id})"
|
|
26
|
+
super().__init__(msg)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AgentGuardPending(AgentGuardError):
|
|
30
|
+
"""Raised when a proposed tool call requires Human-in-the-Loop (HITL) approval (PENDING).
|
|
31
|
+
|
|
32
|
+
This is not an unexpected failure; it indicates the agent must pause until a human
|
|
33
|
+
reviews the request. The request_id can be used to track or poll approval status.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
reason: Policy explanation detailing why human review was flagged.
|
|
37
|
+
request_id: Server-assigned UUID representing the pending HITL request.
|
|
38
|
+
"""
|
|
39
|
+
def __init__(self, reason: str, request_id: Optional[str] = None) -> None:
|
|
40
|
+
self.reason = reason
|
|
41
|
+
self.request_id = request_id
|
|
42
|
+
msg = f"AgentGuard queued action for human approval: {reason}"
|
|
43
|
+
if request_id:
|
|
44
|
+
msg += f" (request_id={request_id})"
|
|
45
|
+
super().__init__(msg)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class AgentGuardAuthenticationError(AgentGuardError):
|
|
49
|
+
"""Raised when the backend rejects client credentials (HTTP 401 Unauthorized or 403 Forbidden).
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
status_code: HTTP status code received (typically 401 or 403).
|
|
53
|
+
detail: Error details returned by the backend.
|
|
54
|
+
"""
|
|
55
|
+
def __init__(self, status_code: int = 401, detail: Optional[str] = None) -> None:
|
|
56
|
+
self.status_code = status_code
|
|
57
|
+
self.detail = detail
|
|
58
|
+
msg = f"AgentGuard authentication failed (HTTP {status_code})"
|
|
59
|
+
if detail:
|
|
60
|
+
msg += f": {detail}"
|
|
61
|
+
super().__init__(msg)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class AgentGuardTimeout(AgentGuardError):
|
|
65
|
+
"""Raised when AgentGuard cannot be reached within the configured timeout.
|
|
66
|
+
|
|
67
|
+
NOTE: Raised for both connection timeouts and connection refusals — treat as
|
|
68
|
+
'could not reach the backend', not literally 'timed out'.
|
|
69
|
+
"""
|
|
70
|
+
def __init__(self, message: str = "Request to AgentGuard timed out or connection refused") -> None:
|
|
71
|
+
self.message = message
|
|
72
|
+
super().__init__(message)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class AgentGuardServerError(AgentGuardError):
|
|
76
|
+
"""Raised when the AgentGuard backend returns an unexpected 5xx response or unparseable payload.
|
|
77
|
+
|
|
78
|
+
Attributes:
|
|
79
|
+
status_code: HTTP response status code.
|
|
80
|
+
detail: Backend error detail message if available.
|
|
81
|
+
raw_response: Raw response body string for debugging.
|
|
82
|
+
"""
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
status_code: int,
|
|
86
|
+
detail: Optional[str] = None,
|
|
87
|
+
raw_response: Optional[str] = None,
|
|
88
|
+
) -> None:
|
|
89
|
+
self.status_code = status_code
|
|
90
|
+
self.detail = detail
|
|
91
|
+
self.raw_response = raw_response
|
|
92
|
+
msg = f"AgentGuard backend error (HTTP {status_code})"
|
|
93
|
+
if detail:
|
|
94
|
+
msg += f": {detail}"
|
|
95
|
+
super().__init__(msg)
|