safentic 1.0.4__py3-none-any.whl → 1.0.6__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.
- safentic/__init__.py +5 -8
- safentic/_internal/errors.py +26 -0
- safentic/adapters/mcp_adapter.py +46 -0
- safentic/cli/__init__.py +3 -0
- safentic/cli/commands/check_tool.py +47 -0
- safentic/cli/commands/logs.py +66 -0
- safentic/cli/commands/validate_policy.py +59 -0
- safentic/cli/main.py +153 -0
- safentic/cli/utils.py +169 -0
- safentic/config.py +2 -2
- safentic/decorators.py +49 -0
- safentic/helper/helper.py +212 -0
- safentic/layer.py +96 -56
- safentic/logger/audit.py +181 -83
- safentic/policy_enforcer.py +116 -0
- safentic/policy_engine.py +141 -0
- safentic/verifiers/llm_verifier.py +238 -0
- safentic-1.0.6.dist-info/METADATA +193 -0
- safentic-1.0.6.dist-info/RECORD +29 -0
- {safentic-1.0.4.dist-info → safentic-1.0.6.dist-info}/WHEEL +1 -1
- safentic-1.0.6.dist-info/entry_points.txt +2 -0
- {safentic → safentic-1.0.6.dist-info/licenses}/LICENSE.txt +36 -36
- safentic-1.0.6.dist-info/top_level.txt +2 -0
- safentic_poc/backend/api/main.py +164 -0
- safentic/engine.py +0 -80
- safentic/helper/auth.py +0 -12
- safentic/policies/__init__.py +0 -3
- safentic/policies/example_policy.txt +0 -33
- safentic/policies/policy.yaml +0 -49
- safentic/policy.py +0 -106
- safentic/verifiers/sentence_verifier.py +0 -69
- safentic-1.0.4.dist-info/METADATA +0 -60
- safentic-1.0.4.dist-info/RECORD +0 -22
- safentic-1.0.4.dist-info/top_level.txt +0 -2
- tests/test_all.py +0 -127
- {tests → safentic_poc/backend}/__init__.py +0 -0
- /safentic/policies/.gitkeep → /safentic_poc/backend/api/__init__.py +0 -0
@@ -1,60 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: safentic
|
3
|
-
Version: 1.0.4
|
4
|
-
Summary: Safentic SDK for behavior analysis
|
5
|
-
Home-page: https://safentic.com
|
6
|
-
Author: Safentic
|
7
|
-
Author-email: contact@safentic.com
|
8
|
-
License: Proprietary :: Safentic Commercial License
|
9
|
-
Classifier: Programming Language :: Python :: 3
|
10
|
-
Classifier: License :: Other/Proprietary License
|
11
|
-
Classifier: Operating System :: OS Independent
|
12
|
-
Description-Content-Type: text/markdown
|
13
|
-
Requires-Dist: requests
|
14
|
-
Requires-Dist: PyYAML
|
15
|
-
Requires-Dist: sentence-transformers==3.2.1
|
16
|
-
Requires-Dist: sqlalchemy
|
17
|
-
Requires-Dist: python-dotenv
|
18
|
-
Dynamic: author
|
19
|
-
Dynamic: author-email
|
20
|
-
Dynamic: classifier
|
21
|
-
Dynamic: description
|
22
|
-
Dynamic: description-content-type
|
23
|
-
Dynamic: home-page
|
24
|
-
Dynamic: license
|
25
|
-
Dynamic: requires-dist
|
26
|
-
Dynamic: summary
|
27
|
-
|
28
|
-
# Safentic SDK
|
29
|
-
|
30
|
-
Safentic is a runtime guardrail SDK for agentic AI systems.
|
31
|
-
It intercepts and evaluates unsafe tool calls between agent **intent** and **execution**, enforcing custom safety policies.
|
32
|
-
|
33
|
-
---
|
34
|
-
|
35
|
-
## Installation
|
36
|
-
|
37
|
-
Install from PyPI:
|
38
|
-
|
39
|
-
pip install safentic
|
40
|
-
|
41
|
-
---
|
42
|
-
|
43
|
-
## API Key Required
|
44
|
-
|
45
|
-
Safentic requires a valid API key to function.
|
46
|
-
To obtain one, contact: contact@safentic.com
|
47
|
-
|
48
|
-
---
|
49
|
-
|
50
|
-
## Quick Start
|
51
|
-
|
52
|
-
```python
|
53
|
-
from safentic import SafetyLayer, SafenticError
|
54
|
-
|
55
|
-
layer = SafetyLayer(api_key="your-api-key", agent_id="agent-007")
|
56
|
-
|
57
|
-
try:
|
58
|
-
layer.protect("send_email", {"body": "Refund me now!"})
|
59
|
-
except SafenticError as e:
|
60
|
-
print("Blocked by policy:", e)
|
safentic-1.0.4.dist-info/RECORD
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
safentic/LICENSE.txt,sha256=xl3AZ2rkiOG5qE01SPRBgoW5Ib5YKZQeszh6OlvKePk,2330
|
2
|
-
safentic/__init__.py,sha256=ZCchAM13KAYuwaA570PTLCuwP4956ov_oZVTOxKy99E,132
|
3
|
-
safentic/config.py,sha256=V6c8Fz0t-Ja278kjCrQMlGPBQ4Hj830t3q7U7oM4Q4k,90
|
4
|
-
safentic/engine.py,sha256=-a90x70SY15WkOIkgxoPVLs_9xGsf4Krj-CmpoMs6tE,2597
|
5
|
-
safentic/layer.py,sha256=hU6BTjvIbCSPLQrCnfEs2KKCR3JeOzIdWxCIlffk-5c,1876
|
6
|
-
safentic/policy.py,sha256=ApAAAxiWb_M5TUTtYKk10BVWEy4xViSbM8ikocIqWoI,4111
|
7
|
-
safentic/helper/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
-
safentic/helper/auth.py,sha256=evhikKRTtGXYp0YKkdntBM4bUjdUTtvJ2nA5YLt2IIA,391
|
9
|
-
safentic/logger/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
-
safentic/logger/audit.py,sha256=GAqZFVPERKyuugmGn6G1-fg9QLaHqN1aWl0S_J3dYXI,2947
|
11
|
-
safentic/policies/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
|
-
safentic/policies/__init__.py,sha256=s37JO8wBcNstqvPif1q2Dw46imddeZZJBo-f3mSfd58,69
|
13
|
-
safentic/policies/example_policy.txt,sha256=Vkv5p2Kcyppijl8BD--P1APy2cBgWrYCrKzfo2pW0lo,1955
|
14
|
-
safentic/policies/policy.yaml,sha256=EoDjvloG56WGNjqNV18xytcCr2v4I0KG6uSx6_yTVAg,1241
|
15
|
-
safentic/verifiers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
|
-
safentic/verifiers/sentence_verifier.py,sha256=jgObZ1t4UCYHYIfNayI0n69hVBWOHc9GAhpJsDbiD2c,2376
|
17
|
-
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
|
-
tests/test_all.py,sha256=oKq-vbo_THyoupx1r7aoNqIAb72rH2t9uyUJtpbDOyQ,5642
|
19
|
-
safentic-1.0.4.dist-info/METADATA,sha256=rYcmXmYpFcFakpSGI9QSQkxhDvk2GH8vxkoCgKto590,1477
|
20
|
-
safentic-1.0.4.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
|
21
|
-
safentic-1.0.4.dist-info/top_level.txt,sha256=8bLyq7i9we7XEcbDYLHTUfR3IIpICeMfG8NWAHCLU5s,15
|
22
|
-
safentic-1.0.4.dist-info/RECORD,,
|
tests/test_all.py
DELETED
@@ -1,127 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import math
|
3
|
-
import pytest
|
4
|
-
from unittest import mock
|
5
|
-
|
6
|
-
from safentic.policy import PolicyEngine
|
7
|
-
from safentic.engine import PolicyEnforcer
|
8
|
-
from safentic.layer import SafetyLayer, SafenticError
|
9
|
-
from safentic.verifiers.sentence_verifier import SentenceTransformerVerifier
|
10
|
-
from safentic.logger.audit import AuditLogger
|
11
|
-
|
12
|
-
os.environ["SAFE_AUDIT_LOG"] = "0"
|
13
|
-
|
14
|
-
TESTS = {
|
15
|
-
"valid_excerpt": "Here is our refund policy: any cancellation within 30 days of purchase receives a full refund.",
|
16
|
-
"hallucination_one_device": "According to our refund policy, every user may only refund one device per subscription.",
|
17
|
-
"made_up_timeframe": "Refund policy: you must request a refund within 24 hours of purchase.",
|
18
|
-
"generic_refund": "I’m sorry for the trouble—please let us know more about your refund issue.",
|
19
|
-
}
|
20
|
-
|
21
|
-
@pytest.fixture
|
22
|
-
def policy_engine():
|
23
|
-
return PolicyEngine()
|
24
|
-
|
25
|
-
@pytest.fixture
|
26
|
-
def enforcer():
|
27
|
-
return PolicyEnforcer()
|
28
|
-
|
29
|
-
@pytest.mark.parametrize("case, expected_contains", [
|
30
|
-
("valid_excerpt", "refund"),
|
31
|
-
("generic_refund", "sorry"),
|
32
|
-
("hallucination_one_device", "one device per subscription"),
|
33
|
-
("made_up_timeframe", "24 hours"),
|
34
|
-
])
|
35
|
-
def test_policy_engine_evaluate(policy_engine, case, expected_contains):
|
36
|
-
reason = policy_engine.evaluate_policy("send_email", {"body": TESTS[case]})
|
37
|
-
assert expected_contains in reason.lower()
|
38
|
-
|
39
|
-
def test_policy_engine_skips_empty(policy_engine):
|
40
|
-
assert policy_engine.evaluate_policy("send_email", {"body": ""}) is None
|
41
|
-
|
42
|
-
def test_policy_engine_unknown_rule_type(policy_engine):
|
43
|
-
policy_engine.policy_cfg = {
|
44
|
-
"tools": {"send_email": {"checks": [{"type": "unknown_type"}]}}
|
45
|
-
}
|
46
|
-
assert policy_engine.evaluate_policy("send_email", {"body": "test"}) is None
|
47
|
-
|
48
|
-
def test_policy_engine_malformed_semantic(policy_engine):
|
49
|
-
policy_engine.policy_cfg = {
|
50
|
-
"tools": {"send_email": {"checks": [{"type": "semantic", "trigger_phrases": ["refund"]}]}}
|
51
|
-
}
|
52
|
-
assert policy_engine.evaluate_policy("send_email", {"body": "refund policy applies"}) is None
|
53
|
-
|
54
|
-
def test_enforcer_blocks_expected_cases(enforcer):
|
55
|
-
agent_id = "agent-block"
|
56
|
-
for case in ["valid_excerpt", "generic_refund"]:
|
57
|
-
result = enforcer.enforce(agent_id, "send_email", {"body": TESTS[case]})
|
58
|
-
assert not result["allowed"]
|
59
|
-
|
60
|
-
def test_enforcer_blocks_and_resets(enforcer):
|
61
|
-
agent_id = "agent-reset"
|
62
|
-
res = enforcer.enforce(agent_id, "send_email", {"body": TESTS["hallucination_one_device"]})
|
63
|
-
assert not res["allowed"]
|
64
|
-
repeat = enforcer.enforce(agent_id, "send_email", {"body": TESTS["hallucination_one_device"]})
|
65
|
-
assert "previously blocked" in repeat["reason"].lower()
|
66
|
-
enforcer.reset(agent_id)
|
67
|
-
assert agent_id not in enforcer.agent_states
|
68
|
-
|
69
|
-
def test_safety_layer_blocks_and_raises():
|
70
|
-
layer = SafetyLayer(api_key="demo-1234", agent_id="safety-1", raise_on_block=True)
|
71
|
-
with pytest.raises(SafenticError):
|
72
|
-
layer.protect("send_email", {"body": TESTS["made_up_timeframe"]})
|
73
|
-
|
74
|
-
def test_safety_layer_returns_result():
|
75
|
-
safe_input = {"body": "This is a neutral and policy-safe email message."}
|
76
|
-
layer = SafetyLayer(api_key="demo-1234", agent_id="safety-2", raise_on_block=False)
|
77
|
-
assert layer.protect("send_email", safe_input)["allowed"]
|
78
|
-
|
79
|
-
@mock.patch("safentic.verifiers.sentence_verifier.SentenceTransformer")
|
80
|
-
def test_similarity_score_consistency(mock_model_class):
|
81
|
-
mock_model = mock_model_class.return_value
|
82
|
-
mock_model.encode.return_value = [[1.0], [1.0]]
|
83
|
-
verifier = SentenceTransformerVerifier()
|
84
|
-
score = verifier.similarity("a", "a")
|
85
|
-
assert math.isclose(score, 1.0, abs_tol=1e-4)
|
86
|
-
|
87
|
-
@mock.patch("safentic.verifiers.sentence_verifier.SentenceTransformer")
|
88
|
-
@pytest.mark.parametrize("a,b,score,expected", [
|
89
|
-
("a", "a", 0.95, "allow"),
|
90
|
-
("a", "b", 0.5, "verify"),
|
91
|
-
("x", "y", 0.1, "block"),
|
92
|
-
])
|
93
|
-
def test_verifier_threshold_behavior(mock_model_class, a, b, score, expected):
|
94
|
-
mock_model = mock_model_class.return_value
|
95
|
-
with mock.patch("safentic.verifiers.sentence_verifier.util.cos_sim") as mock_cos_sim:
|
96
|
-
mock_cos_sim.return_value = mock.Mock()
|
97
|
-
mock_cos_sim.return_value.item.return_value = score
|
98
|
-
|
99
|
-
verifier = SentenceTransformerVerifier(low_threshold=0.2, high_threshold=0.9)
|
100
|
-
result = verifier.decision(a, b)
|
101
|
-
assert result == expected
|
102
|
-
|
103
|
-
@mock.patch("safentic.verifiers.sentence_verifier.SentenceTransformer")
|
104
|
-
def test_verifier_explains_score(mock_model_class):
|
105
|
-
mock_model = mock_model_class.return_value
|
106
|
-
mock_model.encode.return_value = [[1.0], [1.0]]
|
107
|
-
verifier = SentenceTransformerVerifier()
|
108
|
-
assert "Semantic similarity" in verifier.explain("a", "a")
|
109
|
-
|
110
|
-
@mock.patch("safentic.logger.audit.open", new_callable=mock.mock_open)
|
111
|
-
def test_logger_set_level_and_log(mock_open_file):
|
112
|
-
os.environ["SAFE_AUDIT_LOG"] = "1"
|
113
|
-
logger = AuditLogger(config={"txt": "dummy.txt", "json": "dummy.jsonl"})
|
114
|
-
logger.set_level("DEBUG")
|
115
|
-
logger.log(agent_id="mock-agent", tool="send_email", allowed=True)
|
116
|
-
logger.log(agent_id="mock-agent", tool="send_email", allowed=False, reason="test")
|
117
|
-
handle = mock_open_file()
|
118
|
-
handle.write.assert_called()
|
119
|
-
|
120
|
-
def test_logger_invalid_level():
|
121
|
-
with pytest.raises(ValueError):
|
122
|
-
AuditLogger().set_level("FAKE")
|
123
|
-
|
124
|
-
@mock.patch("safentic.logger.audit.open", side_effect=OSError("mocked failure"))
|
125
|
-
def test_logger_gracefully_fails_json_write(mock_open):
|
126
|
-
logger = AuditLogger(config={"txt": "ok.txt", "json": "fail.jsonl"})
|
127
|
-
logger.log("agent", "tool", False, reason="test")
|
File without changes
|
File without changes
|