agentrust-py 0.0.3a1__tar.gz → 0.0.4__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.
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/PKG-INFO +4 -4
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/__init__.py +1 -1
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/adapters/crewai.py +5 -5
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/adapters/langgraph.py +6 -3
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/autoload.py +1 -1
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/client.py +43 -63
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/config.py +1 -42
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/models.py +8 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/pyproject.toml +3 -3
- agentrust_py-0.0.4/tests/_cfg.py +32 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/tests/conftest.py +14 -17
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/tests/test_adapters.py +33 -28
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/tests/test_async_advanced.py +71 -63
- agentrust_py-0.0.4/tests/test_callback_url.py +184 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/tests/test_chaos.py +17 -15
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/tests/test_client.py +16 -15
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/tests/test_config.py +1 -1
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/tests/test_decorator.py +4 -2
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/tests/test_embedded.py +10 -3
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/tests/test_hooks.py +41 -41
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/.gitignore +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/CHANGELOG.md +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/LICENSE +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/README.md +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust/__init__.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/_evaluations.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/_real_validator.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/adapters/__init__.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/adapters/autogen.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/adapters/claude_agents.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/adapters/mcp.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/adapters/openai_agents.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/audit/__init__.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/audit/logger.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/auth.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/auto.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/classifier/__init__.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/classifier/engine.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/classifier/profiles.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/classifier/system_prompt.md +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/cli.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/cli_certify.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/decorator.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/embedded.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/golden_suite.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/governance/__init__.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/governance/hitl.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/governance/kernel.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/governance/kill_switch.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/governance/modes.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/hooks.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/manifest.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/notifications/__init__.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/notifications/discord.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/notifications/dispatcher.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/notifications/slack.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/policy/__init__.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/policy/backends/__init__.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/policy/backends/native.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/policy/backends/opa.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/policy/engine.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/policy/loader.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/py.typed +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/queue_replay.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/tiers.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/tool_tracking.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/version_negotiation.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/agentrust_sdk/webhooks.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/tests/__init__.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/tests/test_cli_certify.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/tests/test_golden_suite.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/tests/test_manifest.py +0 -0
- {agentrust_py-0.0.3a1 → agentrust_py-0.0.4}/tests/test_webhooks.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: agentrust-py
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: AgentTrust Edge — Full Governance Platform. Policy Engine + MAD Classifier + HITL + Kill Switch.
|
|
5
5
|
Project-URL: Homepage, https://agentrust.io
|
|
6
6
|
Project-URL: Documentation, https://docs.agentrust.io
|
|
7
|
-
Project-URL: Repository, https://github.com/
|
|
8
|
-
Project-URL: Changelog, https://github.com/
|
|
7
|
+
Project-URL: Repository, https://github.com/agent-trust-os/agent-trust-sdk
|
|
8
|
+
Project-URL: Changelog, https://github.com/agent-trust-os/agent-trust-sdk/blob/main/CHANGELOG.md
|
|
9
9
|
License: Apache-2.0
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Keywords: agents,ai,audit,compliance,governance,llm,safety,trust
|
|
@@ -8,9 +8,6 @@ from __future__ import annotations
|
|
|
8
8
|
import logging
|
|
9
9
|
from typing import Any, Callable
|
|
10
10
|
|
|
11
|
-
from agentrust_sdk.client import AgentTrustClient
|
|
12
|
-
from agentrust_sdk.decorator import BlockedError, _check_decision
|
|
13
|
-
|
|
14
11
|
logger = logging.getLogger(__name__)
|
|
15
12
|
|
|
16
13
|
|
|
@@ -51,7 +48,7 @@ class AgentTrustCallback:
|
|
|
51
48
|
base_url: str = "http://localhost:8000",
|
|
52
49
|
api_key: str | None = None,
|
|
53
50
|
user: str = "crewai",
|
|
54
|
-
block_on_block: bool =
|
|
51
|
+
block_on_block: bool = True,
|
|
55
52
|
block_on_review: bool = False,
|
|
56
53
|
framework: str = "CrewAI",
|
|
57
54
|
) -> None:
|
|
@@ -66,6 +63,9 @@ class AgentTrustCallback:
|
|
|
66
63
|
self._last_result: Any = None
|
|
67
64
|
|
|
68
65
|
def on_task_complete(self, output: Any) -> Any:
|
|
66
|
+
from agentrust_sdk.client import AgentTrustClient
|
|
67
|
+
from agentrust_sdk.decorator import BlockedError, _check_decision
|
|
68
|
+
|
|
69
69
|
raw = output
|
|
70
70
|
if hasattr(output, "raw"):
|
|
71
71
|
raw = output.raw
|
|
@@ -80,8 +80,8 @@ class AgentTrustCallback:
|
|
|
80
80
|
output=payload,
|
|
81
81
|
framework=self._framework,
|
|
82
82
|
)
|
|
83
|
-
self._last_result = resp.model_dump()
|
|
84
83
|
_check_decision(resp, self._block_on_block, self._block_on_review)
|
|
84
|
+
self._last_result = resp
|
|
85
85
|
except BlockedError:
|
|
86
86
|
raise
|
|
87
87
|
except Exception as exc:
|
|
@@ -8,9 +8,6 @@ from __future__ import annotations
|
|
|
8
8
|
import logging
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
|
-
from agentrust_sdk.client import AgentTrustClient, AsyncAgentTrustClient
|
|
12
|
-
from agentrust_sdk.decorator import BlockedError, _check_decision
|
|
13
|
-
|
|
14
11
|
logger = logging.getLogger(__name__)
|
|
15
12
|
|
|
16
13
|
|
|
@@ -68,6 +65,9 @@ class AgentTrustNode:
|
|
|
68
65
|
self._framework = framework
|
|
69
66
|
|
|
70
67
|
def __call__(self, state: dict[str, Any]) -> dict[str, Any]:
|
|
68
|
+
from agentrust_sdk.client import AgentTrustClient
|
|
69
|
+
from agentrust_sdk.decorator import BlockedError, _check_decision
|
|
70
|
+
|
|
71
71
|
output = state.get(self._output_key, {})
|
|
72
72
|
if not isinstance(output, dict):
|
|
73
73
|
output = {"result": str(output)}
|
|
@@ -90,6 +90,9 @@ class AgentTrustNode:
|
|
|
90
90
|
return state
|
|
91
91
|
|
|
92
92
|
async def ainvoke(self, state: dict[str, Any]) -> dict[str, Any]:
|
|
93
|
+
from agentrust_sdk.client import AsyncAgentTrustClient
|
|
94
|
+
from agentrust_sdk.decorator import BlockedError, _check_decision
|
|
95
|
+
|
|
93
96
|
output = state.get(self._output_key, {})
|
|
94
97
|
if not isinstance(output, dict):
|
|
95
98
|
output = {"result": str(output)}
|
|
@@ -28,7 +28,7 @@ Three ways to activate without editing application code
|
|
|
28
28
|
|
|
29
29
|
**5. `[autoload]` setuptools extra:**
|
|
30
30
|
|
|
31
|
-
pip install agentrust-
|
|
31
|
+
pip install agentrust-py[autoload]
|
|
32
32
|
|
|
33
33
|
Then in your app's own `sitecustomize.py` or startup:
|
|
34
34
|
import agentrust_sdk.autoload
|
|
@@ -64,6 +64,7 @@ except ImportError:
|
|
|
64
64
|
try:
|
|
65
65
|
from tenacity import (
|
|
66
66
|
retry,
|
|
67
|
+
retry_if_exception_type,
|
|
67
68
|
stop_after_attempt,
|
|
68
69
|
wait_exponential,
|
|
69
70
|
)
|
|
@@ -123,14 +124,16 @@ def _build_gateway_payload(req: ValidateRequest) -> dict[str, Any]:
|
|
|
123
124
|
"sdk_version": SDK_CONFIG.sdk_version,
|
|
124
125
|
"sdk_min_gateway_version": SDK_CONFIG.min_gateway_version,
|
|
125
126
|
}
|
|
127
|
+
if req.callback_url:
|
|
128
|
+
payload["callback_url"] = req.callback_url
|
|
126
129
|
return payload
|
|
127
130
|
|
|
128
131
|
|
|
129
132
|
def _build_pre_check_payload(
|
|
130
133
|
agent_id: str, action: str, params: dict[str, Any], user: str, input: str,
|
|
131
|
-
framework: str, model: str,
|
|
134
|
+
framework: str, model: str, callback_url: str | None = None,
|
|
132
135
|
) -> dict[str, Any]:
|
|
133
|
-
|
|
136
|
+
payload = {
|
|
134
137
|
"agent_id": agent_id,
|
|
135
138
|
"framework": framework,
|
|
136
139
|
"action": action,
|
|
@@ -139,11 +142,15 @@ def _build_pre_check_payload(
|
|
|
139
142
|
"input": input,
|
|
140
143
|
"model": model,
|
|
141
144
|
}
|
|
145
|
+
if callback_url:
|
|
146
|
+
payload["callback_url"] = callback_url
|
|
147
|
+
return payload
|
|
142
148
|
|
|
143
149
|
|
|
144
150
|
def _build_post_check_payload(
|
|
145
151
|
pre_check_envelope_id: str, output: dict[str, Any], model: str,
|
|
146
152
|
tools_called: list[dict[str, Any]], latency_ms: float, tokens: int,
|
|
153
|
+
callback_url: str | None = None,
|
|
147
154
|
) -> dict[str, Any]:
|
|
148
155
|
tool_results = [
|
|
149
156
|
{
|
|
@@ -155,7 +162,7 @@ def _build_post_check_payload(
|
|
|
155
162
|
for t in tools_called
|
|
156
163
|
if t.get("result") is not None or t.get("error") is not None
|
|
157
164
|
]
|
|
158
|
-
|
|
165
|
+
payload = {
|
|
159
166
|
"pre_check_envelope_id": pre_check_envelope_id,
|
|
160
167
|
"execution": {
|
|
161
168
|
"model": model,
|
|
@@ -167,6 +174,9 @@ def _build_post_check_payload(
|
|
|
167
174
|
"output": output,
|
|
168
175
|
"sdk_version": SDK_CONFIG.sdk_version,
|
|
169
176
|
}
|
|
177
|
+
if callback_url:
|
|
178
|
+
payload["callback_url"] = callback_url
|
|
179
|
+
return payload
|
|
170
180
|
|
|
171
181
|
|
|
172
182
|
def _warn_tier(capability: Capability) -> None:
|
|
@@ -175,7 +185,7 @@ def _warn_tier(capability: Capability) -> None:
|
|
|
175
185
|
|
|
176
186
|
|
|
177
187
|
def _noop_response() -> ValidateResponse:
|
|
178
|
-
"""Return a pass-through ValidateResponse used when SDK is disabled
|
|
188
|
+
"""Return a pass-through ValidateResponse used when SDK is disabled."""
|
|
179
189
|
from .models import DecisionResult, RiskResult, ValidationResult
|
|
180
190
|
return ValidateResponse(
|
|
181
191
|
envelope_id="disabled",
|
|
@@ -187,19 +197,6 @@ def _noop_response() -> ValidateResponse:
|
|
|
187
197
|
)
|
|
188
198
|
|
|
189
199
|
|
|
190
|
-
def _unavailable_response() -> ValidateResponse:
|
|
191
|
-
"""Return a pass-through ValidateResponse used when gateway is unreachable."""
|
|
192
|
-
from .models import DecisionResult, RiskResult, ValidationResult
|
|
193
|
-
return ValidateResponse(
|
|
194
|
-
envelope_id="unavailable",
|
|
195
|
-
validation=ValidationResult(schema_score=100.0, final_confidence=100.0, failures=[]),
|
|
196
|
-
risk=RiskResult(),
|
|
197
|
-
decision=DecisionResult(outcome="approve", reason="AgentTrust gateway unreachable — fail-open"),
|
|
198
|
-
latency_ms=0.0,
|
|
199
|
-
tier_info="unavailable",
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
|
|
203
200
|
# ---------------------------------------------------------------------------
|
|
204
201
|
# Local queue for failure_mode=queue
|
|
205
202
|
# ---------------------------------------------------------------------------
|
|
@@ -259,7 +256,7 @@ def drain_queue(
|
|
|
259
256
|
|
|
260
257
|
headers: dict[str, str] = {
|
|
261
258
|
"Content-Type": "application/json",
|
|
262
|
-
"X-
|
|
259
|
+
"X-AgentTrust-SDK-Version": SDK_CONFIG.sdk_version,
|
|
263
260
|
}
|
|
264
261
|
if _key:
|
|
265
262
|
headers["X-AgentTrust-Token"] = _key
|
|
@@ -288,22 +285,12 @@ def drain_queue(
|
|
|
288
285
|
# Retry helper
|
|
289
286
|
# ---------------------------------------------------------------------------
|
|
290
287
|
|
|
291
|
-
def _is_retryable_error(exc: Exception) -> bool:
|
|
292
|
-
"""Return True for transport/timeout errors and 5xx HTTP status errors."""
|
|
293
|
-
if isinstance(exc, (httpx.TransportError, httpx.TimeoutException)):
|
|
294
|
-
return True
|
|
295
|
-
if isinstance(exc, httpx.HTTPStatusError) and exc.response.status_code >= 500:
|
|
296
|
-
return True
|
|
297
|
-
return False
|
|
298
|
-
|
|
299
|
-
|
|
300
288
|
def _make_sync_retry():
|
|
301
289
|
"""Return a tenacity retry decorator for sync HTTP calls, or identity if tenacity missing."""
|
|
302
290
|
if not _HAS_TENACITY:
|
|
303
291
|
return lambda f: f
|
|
304
|
-
from tenacity import retry_if_exception
|
|
305
292
|
return retry(
|
|
306
|
-
retry=
|
|
293
|
+
retry=retry_if_exception_type((httpx.TransportError, httpx.TimeoutException)),
|
|
307
294
|
stop=stop_after_attempt(SDK_CONFIG.retry_attempts),
|
|
308
295
|
wait=wait_exponential(multiplier=SDK_CONFIG.retry_backoff_sec, min=0.1, max=30),
|
|
309
296
|
reraise=True,
|
|
@@ -313,9 +300,8 @@ def _make_sync_retry():
|
|
|
313
300
|
def _make_async_retry():
|
|
314
301
|
if not _HAS_TENACITY:
|
|
315
302
|
return lambda f: f
|
|
316
|
-
from tenacity import retry_if_exception
|
|
317
303
|
return retry(
|
|
318
|
-
retry=
|
|
304
|
+
retry=retry_if_exception_type((httpx.TransportError, httpx.TimeoutException)),
|
|
319
305
|
stop=stop_after_attempt(SDK_CONFIG.retry_attempts),
|
|
320
306
|
wait=wait_exponential(multiplier=SDK_CONFIG.retry_backoff_sec, min=0.1, max=30),
|
|
321
307
|
reraise=True,
|
|
@@ -378,7 +364,7 @@ class AgentTrustClient:
|
|
|
378
364
|
|
|
379
365
|
headers = {
|
|
380
366
|
"Content-Type": "application/json",
|
|
381
|
-
"X-
|
|
367
|
+
"X-AgentTrust-SDK-Version": SDK_CONFIG.sdk_version,
|
|
382
368
|
}
|
|
383
369
|
if self._key_info.key:
|
|
384
370
|
headers["X-AgentTrust-Token"] = self._key_info.key
|
|
@@ -458,12 +444,12 @@ class AgentTrustClient:
|
|
|
458
444
|
framework: str = "REST",
|
|
459
445
|
model: str = "unknown",
|
|
460
446
|
tools_called: list[dict] | None = None,
|
|
461
|
-
tool_results: list[dict] | None = None,
|
|
462
447
|
latency_ms: float = 0.0,
|
|
463
448
|
tokens: int = 0,
|
|
464
449
|
parent_envelope_id: str | None = None,
|
|
465
450
|
session_id: str | None = None,
|
|
466
451
|
metadata: dict[str, Any] | None = None,
|
|
452
|
+
callback_url: str | None = None,
|
|
467
453
|
) -> ValidateResponse:
|
|
468
454
|
# ── kill-switch ──────────────────────────────────────────────────────
|
|
469
455
|
if not SDK_CONFIG.enabled:
|
|
@@ -484,15 +470,13 @@ class AgentTrustClient:
|
|
|
484
470
|
latency_ms=latency_ms, tokens=tokens,
|
|
485
471
|
parent_envelope_id=parent_envelope_id,
|
|
486
472
|
session_id=session_id, metadata=metadata or {},
|
|
473
|
+
callback_url=callback_url,
|
|
487
474
|
)
|
|
488
475
|
|
|
489
476
|
if self._key_info.tier == Tier.OSS:
|
|
490
477
|
return _oss_schema_only(req)
|
|
491
478
|
|
|
492
479
|
payload = _build_gateway_payload(req)
|
|
493
|
-
# If caller provided explicit tool_results, override the auto-derived ones.
|
|
494
|
-
if tool_results is not None:
|
|
495
|
-
payload["execution"]["tool_results"] = tool_results
|
|
496
480
|
result = self._call_with_resilience(payload, req)
|
|
497
481
|
|
|
498
482
|
# ── Webhook fan-out (Team tier and above) ────────────────────────────
|
|
@@ -526,6 +510,7 @@ class AgentTrustClient:
|
|
|
526
510
|
input: str = "",
|
|
527
511
|
framework: str = "REST",
|
|
528
512
|
model: str = "unknown",
|
|
513
|
+
callback_url: str | None = None,
|
|
529
514
|
) -> ValidateResponse:
|
|
530
515
|
""""Can we call this?" — deterministic pre-check, before the real call executes.
|
|
531
516
|
|
|
@@ -538,7 +523,7 @@ class AgentTrustClient:
|
|
|
538
523
|
return _noop_response()
|
|
539
524
|
|
|
540
525
|
payload = _build_pre_check_payload(
|
|
541
|
-
agent_id, action, params or {}, user, input, framework, model
|
|
526
|
+
agent_id, action, params or {}, user, input, framework, model, callback_url
|
|
542
527
|
)
|
|
543
528
|
|
|
544
529
|
@_make_sync_retry()
|
|
@@ -547,7 +532,7 @@ class AgentTrustClient:
|
|
|
547
532
|
if resp.status_code == 426:
|
|
548
533
|
raise GatewayVersionError(
|
|
549
534
|
f"Gateway requires SDK upgrade. Current SDK: {SDK_CONFIG.sdk_version}. "
|
|
550
|
-
f"Upgrade: pip install --upgrade agentrust-
|
|
535
|
+
f"Upgrade: pip install --upgrade agentrust-py"
|
|
551
536
|
)
|
|
552
537
|
resp.raise_for_status()
|
|
553
538
|
return ValidateResponse.model_validate(resp.json())
|
|
@@ -569,6 +554,7 @@ class AgentTrustClient:
|
|
|
569
554
|
tools_called: list[dict[str, Any]] | None = None,
|
|
570
555
|
latency_ms: float = 0.0,
|
|
571
556
|
tokens: int = 0,
|
|
557
|
+
callback_url: str | None = None,
|
|
572
558
|
) -> ValidateResponse:
|
|
573
559
|
""""Is this output safe to release?" — governs release only; it
|
|
574
560
|
cannot undo whatever the pre-check already approved."""
|
|
@@ -576,7 +562,8 @@ class AgentTrustClient:
|
|
|
576
562
|
return _noop_response()
|
|
577
563
|
|
|
578
564
|
payload = _build_post_check_payload(
|
|
579
|
-
pre_check_envelope_id, output or {}, model, tools_called or [], latency_ms, tokens
|
|
565
|
+
pre_check_envelope_id, output or {}, model, tools_called or [], latency_ms, tokens,
|
|
566
|
+
callback_url,
|
|
580
567
|
)
|
|
581
568
|
|
|
582
569
|
@_make_sync_retry()
|
|
@@ -585,7 +572,7 @@ class AgentTrustClient:
|
|
|
585
572
|
if resp.status_code == 426:
|
|
586
573
|
raise GatewayVersionError(
|
|
587
574
|
f"Gateway requires SDK upgrade. Current SDK: {SDK_CONFIG.sdk_version}. "
|
|
588
|
-
f"Upgrade: pip install --upgrade agentrust-
|
|
575
|
+
f"Upgrade: pip install --upgrade agentrust-py"
|
|
589
576
|
)
|
|
590
577
|
resp.raise_for_status()
|
|
591
578
|
return ValidateResponse.model_validate(resp.json())
|
|
@@ -607,17 +594,13 @@ class AgentTrustClient:
|
|
|
607
594
|
def _do_request() -> ValidateResponse:
|
|
608
595
|
@_make_sync_retry()
|
|
609
596
|
def _inner():
|
|
610
|
-
resp = self._http.post(
|
|
611
|
-
_VALIDATE_PATH,
|
|
612
|
-
json=payload,
|
|
613
|
-
headers={"X-Agent-ID": req.agent_id},
|
|
614
|
-
)
|
|
597
|
+
resp = self._http.post(_VALIDATE_PATH, json=payload)
|
|
615
598
|
# 426 = version mismatch
|
|
616
599
|
if resp.status_code == 426:
|
|
617
600
|
raise GatewayVersionError(
|
|
618
601
|
f"Gateway requires SDK upgrade. "
|
|
619
602
|
f"Current SDK: {SDK_CONFIG.sdk_version}. "
|
|
620
|
-
f"Upgrade: pip install --upgrade agentrust-
|
|
603
|
+
f"Upgrade: pip install --upgrade agentrust-py"
|
|
621
604
|
)
|
|
622
605
|
resp.raise_for_status()
|
|
623
606
|
return ValidateResponse.model_validate(resp.json())
|
|
@@ -662,7 +645,7 @@ class AgentTrustClient:
|
|
|
662
645
|
_enqueue_failed_request(payload)
|
|
663
646
|
else: # open (default)
|
|
664
647
|
logger.warning("[AgentTrust] Gateway unreachable (fail-open): %s", exc)
|
|
665
|
-
return
|
|
648
|
+
return _noop_response()
|
|
666
649
|
|
|
667
650
|
def close(self) -> None:
|
|
668
651
|
self._http.close()
|
|
@@ -715,7 +698,7 @@ class AsyncAgentTrustClient:
|
|
|
715
698
|
|
|
716
699
|
headers = {
|
|
717
700
|
"Content-Type": "application/json",
|
|
718
|
-
"X-
|
|
701
|
+
"X-AgentTrust-SDK-Version": SDK_CONFIG.sdk_version,
|
|
719
702
|
}
|
|
720
703
|
if self._key_info.key:
|
|
721
704
|
headers["X-AgentTrust-Token"] = self._key_info.key
|
|
@@ -763,12 +746,12 @@ class AsyncAgentTrustClient:
|
|
|
763
746
|
framework: str = "REST",
|
|
764
747
|
model: str = "unknown",
|
|
765
748
|
tools_called: list[dict] | None = None,
|
|
766
|
-
tool_results: list[dict] | None = None,
|
|
767
749
|
latency_ms: float = 0.0,
|
|
768
750
|
tokens: int = 0,
|
|
769
751
|
parent_envelope_id: str | None = None,
|
|
770
752
|
session_id: str | None = None,
|
|
771
753
|
metadata: dict[str, Any] | None = None,
|
|
754
|
+
callback_url: str | None = None,
|
|
772
755
|
) -> ValidateResponse:
|
|
773
756
|
if not SDK_CONFIG.enabled:
|
|
774
757
|
return _noop_response()
|
|
@@ -788,15 +771,13 @@ class AsyncAgentTrustClient:
|
|
|
788
771
|
latency_ms=latency_ms, tokens=tokens,
|
|
789
772
|
parent_envelope_id=parent_envelope_id,
|
|
790
773
|
session_id=session_id, metadata=metadata or {},
|
|
774
|
+
callback_url=callback_url,
|
|
791
775
|
)
|
|
792
776
|
|
|
793
777
|
if self._key_info.tier == Tier.OSS:
|
|
794
778
|
return _oss_schema_only(req)
|
|
795
779
|
|
|
796
780
|
payload = _build_gateway_payload(req)
|
|
797
|
-
# If caller provided explicit tool_results, override the auto-derived ones.
|
|
798
|
-
if tool_results is not None:
|
|
799
|
-
payload["execution"]["tool_results"] = tool_results
|
|
800
781
|
result = await self._call_with_resilience(payload, req)
|
|
801
782
|
|
|
802
783
|
# ── Async webhook fan-out (Team tier and above) ───────────────────────
|
|
@@ -831,13 +812,14 @@ class AsyncAgentTrustClient:
|
|
|
831
812
|
input: str = "",
|
|
832
813
|
framework: str = "REST",
|
|
833
814
|
model: str = "unknown",
|
|
815
|
+
callback_url: str | None = None,
|
|
834
816
|
) -> ValidateResponse:
|
|
835
817
|
"""Async counterpart of AgentTrustClient.pre_check — see that docstring."""
|
|
836
818
|
if not SDK_CONFIG.enabled:
|
|
837
819
|
return _noop_response()
|
|
838
820
|
|
|
839
821
|
payload = _build_pre_check_payload(
|
|
840
|
-
agent_id, action, params or {}, user, input, framework, model
|
|
822
|
+
agent_id, action, params or {}, user, input, framework, model, callback_url
|
|
841
823
|
)
|
|
842
824
|
|
|
843
825
|
@_make_async_retry()
|
|
@@ -846,7 +828,7 @@ class AsyncAgentTrustClient:
|
|
|
846
828
|
if resp.status_code == 426:
|
|
847
829
|
raise GatewayVersionError(
|
|
848
830
|
f"Gateway requires SDK upgrade. Current SDK: {SDK_CONFIG.sdk_version}. "
|
|
849
|
-
f"Upgrade: pip install --upgrade agentrust-
|
|
831
|
+
f"Upgrade: pip install --upgrade agentrust-py"
|
|
850
832
|
)
|
|
851
833
|
resp.raise_for_status()
|
|
852
834
|
return ValidateResponse.model_validate(resp.json())
|
|
@@ -868,13 +850,15 @@ class AsyncAgentTrustClient:
|
|
|
868
850
|
tools_called: list[dict[str, Any]] | None = None,
|
|
869
851
|
latency_ms: float = 0.0,
|
|
870
852
|
tokens: int = 0,
|
|
853
|
+
callback_url: str | None = None,
|
|
871
854
|
) -> ValidateResponse:
|
|
872
855
|
"""Async counterpart of AgentTrustClient.post_check — see that docstring."""
|
|
873
856
|
if not SDK_CONFIG.enabled:
|
|
874
857
|
return _noop_response()
|
|
875
858
|
|
|
876
859
|
payload = _build_post_check_payload(
|
|
877
|
-
pre_check_envelope_id, output or {}, model, tools_called or [], latency_ms, tokens
|
|
860
|
+
pre_check_envelope_id, output or {}, model, tools_called or [], latency_ms, tokens,
|
|
861
|
+
callback_url,
|
|
878
862
|
)
|
|
879
863
|
|
|
880
864
|
@_make_async_retry()
|
|
@@ -883,7 +867,7 @@ class AsyncAgentTrustClient:
|
|
|
883
867
|
if resp.status_code == 426:
|
|
884
868
|
raise GatewayVersionError(
|
|
885
869
|
f"Gateway requires SDK upgrade. Current SDK: {SDK_CONFIG.sdk_version}. "
|
|
886
|
-
f"Upgrade: pip install --upgrade agentrust-
|
|
870
|
+
f"Upgrade: pip install --upgrade agentrust-py"
|
|
887
871
|
)
|
|
888
872
|
resp.raise_for_status()
|
|
889
873
|
return ValidateResponse.model_validate(resp.json())
|
|
@@ -904,15 +888,11 @@ class AsyncAgentTrustClient:
|
|
|
904
888
|
async def _do_request() -> ValidateResponse:
|
|
905
889
|
@_make_async_retry()
|
|
906
890
|
async def _inner():
|
|
907
|
-
resp = await self._http.post(
|
|
908
|
-
_VALIDATE_PATH,
|
|
909
|
-
json=payload,
|
|
910
|
-
headers={"X-Agent-ID": req.agent_id},
|
|
911
|
-
)
|
|
891
|
+
resp = await self._http.post(_VALIDATE_PATH, json=payload)
|
|
912
892
|
if resp.status_code == 426:
|
|
913
893
|
raise GatewayVersionError(
|
|
914
894
|
f"Gateway requires SDK upgrade. Current: {SDK_CONFIG.sdk_version}. "
|
|
915
|
-
f"Upgrade: pip install --upgrade agentrust-
|
|
895
|
+
f"Upgrade: pip install --upgrade agentrust-py"
|
|
916
896
|
)
|
|
917
897
|
resp.raise_for_status()
|
|
918
898
|
return ValidateResponse.model_validate(resp.json())
|
|
@@ -956,7 +936,7 @@ class AsyncAgentTrustClient:
|
|
|
956
936
|
_enqueue_failed_request(payload)
|
|
957
937
|
else:
|
|
958
938
|
logger.warning("[AgentTrust] Async gateway unreachable (fail-open): %s", exc)
|
|
959
|
-
return
|
|
939
|
+
return _noop_response()
|
|
960
940
|
|
|
961
941
|
async def close(self) -> None:
|
|
962
942
|
await self._http.aclose()
|
|
@@ -47,20 +47,9 @@ from pathlib import Path
|
|
|
47
47
|
logger = logging.getLogger(__name__)
|
|
48
48
|
|
|
49
49
|
_VALID_FAILURE_MODES = frozenset({"open", "closed", "queue"})
|
|
50
|
-
_SDK_VERSION = "0.0.
|
|
50
|
+
_SDK_VERSION = "0.0.4"
|
|
51
51
|
_MIN_GATEWAY_VERSION = "0.0.1a1"
|
|
52
52
|
|
|
53
|
-
# Keys that tests may override via SDK_CONFIG.__dict__["key"] = value.
|
|
54
|
-
# __getattribute__ checks instance __dict__ first for these keys so that
|
|
55
|
-
# direct dict assignment works as an in-process override (tests and runtime
|
|
56
|
-
# overrides) without needing to mutate os.environ.
|
|
57
|
-
_OVERRIDABLE_KEYS = frozenset({
|
|
58
|
-
"enabled", "gateway_url", "api_key", "timeout_sec", "retry_attempts",
|
|
59
|
-
"retry_backoff_sec", "failure_mode", "queue_db",
|
|
60
|
-
"webhook_url", "webhook_events", "webhook_db", "webhook_timeout",
|
|
61
|
-
"webhook_auto_dispatch",
|
|
62
|
-
})
|
|
63
|
-
|
|
64
53
|
|
|
65
54
|
def _bool(key: str, default: bool) -> bool:
|
|
66
55
|
val = os.environ.get(key, "").lower()
|
|
@@ -102,12 +91,6 @@ class _SDKConfig:
|
|
|
102
91
|
on each attribute access so runtime mutations (secrets managers, test
|
|
103
92
|
fixtures) take effect without a process restart.
|
|
104
93
|
|
|
105
|
-
In-process overrides (for tests or runtime hot-patching) are supported by
|
|
106
|
-
directly writing to the instance dict:
|
|
107
|
-
|
|
108
|
-
SDK_CONFIG.__dict__["failure_mode"] = "closed" # overrides env var
|
|
109
|
-
del SDK_CONFIG.__dict__["failure_mode"] # restore env-var reading
|
|
110
|
-
|
|
111
94
|
Call reload() to invalidate any cached state (currently a no-op since
|
|
112
95
|
nothing is cached, but provided for forward-compat and test ergonomics).
|
|
113
96
|
"""
|
|
@@ -116,31 +99,10 @@ class _SDKConfig:
|
|
|
116
99
|
sdk_version: str = _SDK_VERSION
|
|
117
100
|
min_gateway_version: str = _MIN_GATEWAY_VERSION
|
|
118
101
|
|
|
119
|
-
# ---------------------------------------------------------------------------
|
|
120
|
-
# Instance-dict override support
|
|
121
|
-
# ---------------------------------------------------------------------------
|
|
122
|
-
|
|
123
|
-
def __getattribute__(self, name: str): # type: ignore[override]
|
|
124
|
-
# For the known config keys, honour a value stored directly in the
|
|
125
|
-
# instance __dict__ first (written by tests or runtime overrides via
|
|
126
|
-
# SDK_CONFIG.__dict__["key"] = value). This lets test code bypass
|
|
127
|
-
# env-var mutation while keeping the singleton's lazy-env behaviour for
|
|
128
|
-
# every key that has NOT been explicitly overridden.
|
|
129
|
-
if name in _OVERRIDABLE_KEYS:
|
|
130
|
-
instance_dict = object.__getattribute__(self, "__dict__")
|
|
131
|
-
if name in instance_dict:
|
|
132
|
-
return instance_dict[name]
|
|
133
|
-
return object.__getattribute__(self, name)
|
|
134
|
-
|
|
135
102
|
@property
|
|
136
103
|
def enabled(self) -> bool:
|
|
137
104
|
return _bool("AGENTRUST_ENABLED", True)
|
|
138
105
|
|
|
139
|
-
@enabled.setter
|
|
140
|
-
def enabled(self, value: bool) -> None:
|
|
141
|
-
# Honour direct attribute assignment (used by monkeypatch.setattr).
|
|
142
|
-
os.environ["AGENTRUST_ENABLED"] = "true" if value else "false"
|
|
143
|
-
|
|
144
106
|
@property
|
|
145
107
|
def gateway_url(self) -> str:
|
|
146
108
|
return os.environ.get("AGENTRUST_GATEWAY_URL", "http://localhost:8000")
|
|
@@ -241,6 +203,3 @@ class _SDKConfig:
|
|
|
241
203
|
|
|
242
204
|
# Module-level singleton — import as `from .config import SDK_CONFIG`
|
|
243
205
|
SDK_CONFIG = _SDKConfig()
|
|
244
|
-
|
|
245
|
-
# Public alias (some tests import `SDKConfig` without the leading underscore).
|
|
246
|
-
SDKConfig = _SDKConfig
|
|
@@ -35,6 +35,11 @@ class DecisionResult(BaseModel):
|
|
|
35
35
|
outcome: str = "pending" # "pending" = not computed (tier too low)
|
|
36
36
|
reason: str = ""
|
|
37
37
|
policy_version: str = ""
|
|
38
|
+
# Set by newer gateways; absent (None / False) from older ones.
|
|
39
|
+
mode: Optional[str] = None # alert | hitl | block, when the agent's mode applied
|
|
40
|
+
original_outcome: Optional[str] = None # what the engine decided before the mode changed it
|
|
41
|
+
reason_code: Optional[str] = None # e.g. low_confidence, tool_trust, mode_review
|
|
42
|
+
hard_gate: bool = False # True: no mode can soften this block
|
|
38
43
|
|
|
39
44
|
|
|
40
45
|
class ValidateRequest(BaseModel):
|
|
@@ -51,6 +56,9 @@ class ValidateRequest(BaseModel):
|
|
|
51
56
|
tokens: int = 0
|
|
52
57
|
session_id: Optional[str] = None
|
|
53
58
|
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
59
|
+
# Where the gateway should send one signed message if a person later decides
|
|
60
|
+
# on the review case this check creates. Optional.
|
|
61
|
+
callback_url: Optional[str] = None
|
|
54
62
|
|
|
55
63
|
|
|
56
64
|
class ValidateResponse(BaseModel):
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentrust-py"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.4"
|
|
8
8
|
description = "AgentTrust Edge — Full Governance Platform. Policy Engine + MAD Classifier + HITL + Kill Switch."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
@@ -75,8 +75,8 @@ agentrust = "agentrust_sdk.cli:main"
|
|
|
75
75
|
[project.urls]
|
|
76
76
|
Homepage = "https://agentrust.io"
|
|
77
77
|
Documentation = "https://docs.agentrust.io"
|
|
78
|
-
Repository = "https://github.com/
|
|
79
|
-
Changelog = "https://github.com/
|
|
78
|
+
Repository = "https://github.com/agent-trust-os/agent-trust-sdk"
|
|
79
|
+
Changelog = "https://github.com/agent-trust-os/agent-trust-sdk/blob/main/CHANGELOG.md"
|
|
80
80
|
|
|
81
81
|
[tool.hatch.build.targets.wheel]
|
|
82
82
|
packages = ["agentrust_sdk", "agentrust"]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Helpers to override SDK_CONFIG values.
|
|
2
|
+
|
|
3
|
+
SDK_CONFIG properties are read-only and resolved from environment variables on
|
|
4
|
+
each access, so overrides go through os.environ. The autouse ``reset_env``
|
|
5
|
+
fixture in conftest.py restores the keys it manages; ``restore_env`` (autouse,
|
|
6
|
+
below in conftest) restores the rest.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
_ENV = {
|
|
14
|
+
"enabled": "AGENTRUST_ENABLED",
|
|
15
|
+
"failure_mode": "AGENTRUST_FAILURE_MODE",
|
|
16
|
+
"retry_attempts": "AGENTRUST_RETRY_ATTEMPTS",
|
|
17
|
+
"queue_db": "AGENTRUST_QUEUE_DB",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def set_cfg(name: str, value) -> None:
|
|
22
|
+
key = _ENV[name]
|
|
23
|
+
if value is None:
|
|
24
|
+
os.environ.pop(key, None)
|
|
25
|
+
elif isinstance(value, bool):
|
|
26
|
+
os.environ[key] = "true" if value else "false"
|
|
27
|
+
else:
|
|
28
|
+
os.environ[key] = str(value)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def unset_cfg(name: str) -> None:
|
|
32
|
+
os.environ.pop(_ENV[name], None)
|
|
@@ -4,18 +4,9 @@ from __future__ import annotations
|
|
|
4
4
|
import os
|
|
5
5
|
import pytest
|
|
6
6
|
|
|
7
|
-
# Config keys that tests may override via SDK_CONFIG.__dict__ — cleared before
|
|
8
|
-
# each test so no state leaks between tests.
|
|
9
|
-
_SDK_CONFIG_OVERRIDE_KEYS = (
|
|
10
|
-
"enabled", "gateway_url", "api_key", "timeout_sec", "retry_attempts",
|
|
11
|
-
"retry_backoff_sec", "failure_mode", "queue_db",
|
|
12
|
-
"webhook_url", "webhook_events", "webhook_db", "webhook_timeout",
|
|
13
|
-
"webhook_auto_dispatch",
|
|
14
|
-
)
|
|
15
|
-
|
|
16
7
|
|
|
17
8
|
@pytest.fixture(autouse=True)
|
|
18
|
-
def reset_env(monkeypatch):
|
|
9
|
+
def reset_env(monkeypatch, tmp_path_factory):
|
|
19
10
|
"""Ensure each test starts with a clean environment."""
|
|
20
11
|
for key in (
|
|
21
12
|
"AGENTRUST_ENABLED",
|
|
@@ -24,15 +15,15 @@ def reset_env(monkeypatch):
|
|
|
24
15
|
"AGENTRUST_RETRY_ATTEMPTS",
|
|
25
16
|
"AGENTRUST_GATEWAY_URL",
|
|
26
17
|
"AGENTRUST_KEY",
|
|
18
|
+
"AGENTRUST_QUEUE_DB",
|
|
19
|
+
"AGENTRUST_RETRY_BACKOFF",
|
|
27
20
|
):
|
|
28
21
|
monkeypatch.delenv(key, raising=False)
|
|
29
|
-
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
SDK_CONFIG.__dict__.pop(key, None)
|
|
35
|
-
|
|
22
|
+
# Isolate from any developer key in ~/.agentrust/config.yaml or a project
|
|
23
|
+
# .agentrust/config.yaml so key-less (OSS) behaviour is deterministic.
|
|
24
|
+
from agentrust_sdk import auth
|
|
25
|
+
monkeypatch.setattr(auth, "_GLOBAL_CONFIG", tmp_path_factory.mktemp("noconf") / "config.yaml")
|
|
26
|
+
monkeypatch.setattr(auth, "_project_config", lambda: None)
|
|
36
27
|
yield
|
|
37
28
|
|
|
38
29
|
|
|
@@ -78,3 +69,9 @@ def mock_block_response() -> dict:
|
|
|
78
69
|
"confidence_rationale": None,
|
|
79
70
|
"trust_chain": None,
|
|
80
71
|
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.fixture
|
|
75
|
+
def gateway_key(monkeypatch):
|
|
76
|
+
"""A syntactically valid non-OSS key so clients take the HTTP path."""
|
|
77
|
+
monkeypatch.setenv("AGENTRUST_KEY", "at_free_test")
|