trusys 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.
- trusys-0.1.0/PKG-INFO +330 -0
- trusys-0.1.0/README.md +303 -0
- trusys-0.1.0/pyproject.toml +57 -0
- trusys-0.1.0/setup.cfg +4 -0
- trusys-0.1.0/tests/test_actions.py +679 -0
- trusys-0.1.0/tests/test_execution.py +134 -0
- trusys-0.1.0/tests/test_guard.py +321 -0
- trusys-0.1.0/tests/test_pii_scanner.py +310 -0
- trusys-0.1.0/tests/test_regex_scanner.py +236 -0
- trusys-0.1.0/tests/test_scanners.py +133 -0
- trusys-0.1.0/tests/test_types.py +197 -0
- trusys-0.1.0/trusys/__init__.py +82 -0
- trusys-0.1.0/trusys/actions/__init__.py +28 -0
- trusys-0.1.0/trusys/actions/base.py +40 -0
- trusys-0.1.0/trusys/actions/builtin.py +608 -0
- trusys-0.1.0/trusys/actions/resolver.py +155 -0
- trusys-0.1.0/trusys/core/__init__.py +6 -0
- trusys-0.1.0/trusys/core/config.py +54 -0
- trusys-0.1.0/trusys/core/guard.py +869 -0
- trusys-0.1.0/trusys/exceptions.py +117 -0
- trusys-0.1.0/trusys/execution/__init__.py +19 -0
- trusys-0.1.0/trusys/execution/runner.py +739 -0
- trusys-0.1.0/trusys/execution/strategies.py +424 -0
- trusys-0.1.0/trusys/results/__init__.py +7 -0
- trusys-0.1.0/trusys/results/aggregator.py +118 -0
- trusys-0.1.0/trusys/results/formatter.py +151 -0
- trusys-0.1.0/trusys/results/uploader.py +294 -0
- trusys-0.1.0/trusys/scanners/__init__.py +6 -0
- trusys-0.1.0/trusys/scanners/base.py +143 -0
- trusys-0.1.0/trusys/scanners/builtin/__init__.py +23 -0
- trusys-0.1.0/trusys/scanners/builtin/local/__init__.py +7 -0
- trusys-0.1.0/trusys/scanners/builtin/local/blocklist.py +352 -0
- trusys-0.1.0/trusys/scanners/builtin/local/pii.py +378 -0
- trusys-0.1.0/trusys/scanners/builtin/local/regex.py +277 -0
- trusys-0.1.0/trusys/scanners/builtin/remote/__init__.py +6 -0
- trusys-0.1.0/trusys/scanners/builtin/remote/content_safety.py +58 -0
- trusys-0.1.0/trusys/scanners/builtin/remote/prompt_injection.py +54 -0
- trusys-0.1.0/trusys/scanners/registry.py +229 -0
- trusys-0.1.0/trusys/scanners/remote.py +543 -0
- trusys-0.1.0/trusys/types.py +169 -0
- trusys-0.1.0/trusys/utils/__init__.py +5 -0
- trusys-0.1.0/trusys/utils/helpers.py +94 -0
- trusys-0.1.0/trusys/utils/logging.py +222 -0
- trusys-0.1.0/trusys.egg-info/PKG-INFO +330 -0
- trusys-0.1.0/trusys.egg-info/SOURCES.txt +46 -0
- trusys-0.1.0/trusys.egg-info/dependency_links.txt +1 -0
- trusys-0.1.0/trusys.egg-info/requires.txt +14 -0
- trusys-0.1.0/trusys.egg-info/top_level.txt +1 -0
trusys-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: trusys
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A modular Python library for AI guardrails with input/output scanning capabilities
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Keywords: ai,guardrails,llm,safety,scanning
|
|
7
|
+
Classifier: Intended Audience :: Developers
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: fuzzysearch>=0.7.0
|
|
16
|
+
Requires-Dist: httpx>=0.27.0
|
|
17
|
+
Requires-Dist: pyffx>=0.3.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
20
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
22
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
23
|
+
Requires-Dist: ruff>=0.1; extra == "dev"
|
|
24
|
+
Provides-Extra: pii
|
|
25
|
+
Requires-Dist: presidio-analyzer>=2.2.0; extra == "pii"
|
|
26
|
+
Requires-Dist: spacy>=3.0.0; extra == "pii"
|
|
27
|
+
|
|
28
|
+
# TruGuard
|
|
29
|
+
|
|
30
|
+
A Python library for AI guardrails. Configure your guardrails in the **Trusys portal**; in your application you run **`truguard init`** (or set credentials), call **`TruGuard.init()`**, then use the built-in **input** and **output** scanners. No manual scanner configuration in code — everything is managed from [console.trusys.ai](https://console.trusys.ai).
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **Portal-driven configuration**: Define input and output guardrails in the [Trusys Console](https://console.trusys.ai); your app receives config automatically.
|
|
35
|
+
- **Static input and output guards**: Use `TruGuard.input_guard` and `TruGuard.output_guard` to scan prompts and model responses.
|
|
36
|
+
- **No code-level scanner setup**: Scanners (regex, blocklist, PII, content safety, etc.) are configured in the portal, not in your codebase.
|
|
37
|
+
- **Parallel execution**: Scanners run concurrently for better performance.
|
|
38
|
+
- **Flexible actions**: Fix, mask, filter, fail, or ignore violations based on portal settings.
|
|
39
|
+
- **Async support**: Full async/await for I/O-bound scanning.
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install trusys
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Optional Dependencies
|
|
48
|
+
|
|
49
|
+
For PII (Personally Identifiable Information) detection when enabled in the portal:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install trusys[pii]
|
|
53
|
+
python -m spacy download en_core_web_lg
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Quick Start
|
|
57
|
+
|
|
58
|
+
### 1. Configure guardrails in the Trusys portal
|
|
59
|
+
|
|
60
|
+
1. Go to **[https://console.trusys.ai](https://console.trusys.ai)** and sign in.
|
|
61
|
+
2. Create or select an application.
|
|
62
|
+
3. Configure **input** and **output** guardrails (e.g. blocklists, PII, content safety, prompt injection).
|
|
63
|
+
4. Copy your **Application ID** and **API key** for the next step.
|
|
64
|
+
|
|
65
|
+
### 2. Initialize TruGuard in your project
|
|
66
|
+
|
|
67
|
+
Configure your Application ID and API key by setting these environment variables:
|
|
68
|
+
|
|
69
|
+
- `TRUSYS_APPLICATION_ID` – from the Trusys portal
|
|
70
|
+
- `TRUSYS_API_KEY` – from the Trusys portal
|
|
71
|
+
|
|
72
|
+
### 3. Use the input and output guards in code
|
|
73
|
+
|
|
74
|
+
Call `TruGuard.init()` once at application startup (e.g. in `main` or when your app loads). Then use the static **input** and **output** guards to scan content.
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from trusys import TruGuard
|
|
78
|
+
|
|
79
|
+
# One-time init at startup (uses TRUSYS_APPLICATION_ID and TRUSYS_API_KEY)
|
|
80
|
+
TruGuard.init()
|
|
81
|
+
|
|
82
|
+
# Scan user input (e.g. before sending to the LLM)
|
|
83
|
+
input_result = TruGuard.input_guard.scan(content={"prompt": "Hello world!"})
|
|
84
|
+
if not input_result.passed:
|
|
85
|
+
# Handle violations (e.g. block request or apply fixes)
|
|
86
|
+
for v in input_result.all_violations:
|
|
87
|
+
print(f"Input violation: {v.message}")
|
|
88
|
+
raise ValueError("Input failed guardrails")
|
|
89
|
+
|
|
90
|
+
# ... call your LLM ...
|
|
91
|
+
|
|
92
|
+
# Scan model output (e.g. before returning to the user)
|
|
93
|
+
output_result = TruGuard.output_guard.scan(
|
|
94
|
+
content={"prompt": "user prompt", "response": "model response"}
|
|
95
|
+
)
|
|
96
|
+
if not output_result.passed:
|
|
97
|
+
for v in output_result.all_violations:
|
|
98
|
+
print(f"Output violation: {v.message}")
|
|
99
|
+
raise ValueError("Output failed guardrails")
|
|
100
|
+
|
|
101
|
+
# Use output_result.final_content for the (possibly fixed) response if needed
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Async usage
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
import asyncio
|
|
108
|
+
from trusys import TruGuard
|
|
109
|
+
|
|
110
|
+
TruGuard.init()
|
|
111
|
+
|
|
112
|
+
async def main():
|
|
113
|
+
input_result = await TruGuard.input_guard.scan_async(content={"prompt": "Hello!"})
|
|
114
|
+
print(f"Input passed: {input_result.passed}")
|
|
115
|
+
|
|
116
|
+
output_result = await TruGuard.output_guard.scan_async(
|
|
117
|
+
content={"prompt": "Hi", "response": "Hi there!"}
|
|
118
|
+
)
|
|
119
|
+
print(f"Output passed: {output_result.passed}")
|
|
120
|
+
|
|
121
|
+
asyncio.run(main())
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Init options
|
|
125
|
+
|
|
126
|
+
You can pass credentials and options explicitly instead of (or in addition to) environment variables:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
TruGuard.init(
|
|
130
|
+
application_id="your-application-id",
|
|
131
|
+
api_key="your-api-key",
|
|
132
|
+
application_version="1.0.0", # optional
|
|
133
|
+
environment="production", # optional: dev, staging, prod
|
|
134
|
+
metadata={"service": "chat"}, # optional: attached to all result uploads
|
|
135
|
+
batch_interval=60.0, # seconds between result uploads (default 60)
|
|
136
|
+
batch_count=100, # max results per batch (default 100)
|
|
137
|
+
debug_print=False, # set True only for debugging (adds latency)
|
|
138
|
+
)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Guardrail configuration is fetched from the Trusys API at init and refreshed periodically in the background. No manual scanner configuration is required in your code.
|
|
142
|
+
|
|
143
|
+
### Metadata (init + per-scan)
|
|
144
|
+
|
|
145
|
+
You can attach metadata at init (app-wide) and per scan (e.g. `conversation_id`, `session_id`). Scan-level metadata is merged with init metadata; duplicate keys are overridden by the scan value. The merged metadata is included in result uploads to the backend.
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
# Init with app-level metadata
|
|
149
|
+
TruGuard.init(
|
|
150
|
+
application_id="...",
|
|
151
|
+
api_key="...",
|
|
152
|
+
metadata={"environment": "prod", "service": "chat"},
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Per-scan metadata: merged with init metadata for this scan
|
|
156
|
+
input_result = TruGuard.input_guard.scan(
|
|
157
|
+
content={"prompt": "Hello"},
|
|
158
|
+
metadata={"conversation_id": "conv-123", "session_id": "sess-456"},
|
|
159
|
+
)
|
|
160
|
+
# Uploaded result includes: environment, service, conversation_id, session_id
|
|
161
|
+
|
|
162
|
+
# Same for async
|
|
163
|
+
output_result = await TruGuard.output_guard.scan_async(
|
|
164
|
+
content={"prompt": "Hi", "response": "Hi there!"},
|
|
165
|
+
metadata={"conversation_id": "conv-123"},
|
|
166
|
+
)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Action types
|
|
170
|
+
|
|
171
|
+
When a scanner finds a violation, the action is determined by your portal configuration. Supported actions:
|
|
172
|
+
|
|
173
|
+
| Action | Behavior |
|
|
174
|
+
|----------|----------|
|
|
175
|
+
| `fix` | Apply the scanner’s suggested fix (e.g. mask PII with entity labels). |
|
|
176
|
+
| `mask` | Replace detected content with `****`. |
|
|
177
|
+
| `encrypt`| Replace with format-preserving encryption (FPE). |
|
|
178
|
+
| `filter` | Remove or replace problematic content. |
|
|
179
|
+
| `fail` | Raise `ScanFailedError` (scan fails). |
|
|
180
|
+
| `ignore` | Log and allow content through unchanged. |
|
|
181
|
+
|
|
182
|
+
**Format-preserving encryption (FPE)** uses the FF1/FFX algorithm. Set the `TRUSYS_FPE_KEY` environment variable if you need a custom encryption key. Decrypt with:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
from trusys.actions.builtin import decrypt_text
|
|
186
|
+
|
|
187
|
+
original = decrypt_text(encrypted_text, entity_type="PHONE_NUMBER")
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Result inspection
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
result = TruGuard.input_guard.scan(content={"prompt": "test"})
|
|
194
|
+
|
|
195
|
+
print(result.passed)
|
|
196
|
+
print(result.total_execution_time_ms)
|
|
197
|
+
|
|
198
|
+
for scan_result in result.results:
|
|
199
|
+
print(f"{scan_result.scanner_name}: {'PASS' if scan_result.passed else 'FAIL'}")
|
|
200
|
+
for v in scan_result.violations:
|
|
201
|
+
print(f" - [{v.severity.value}] {v.message}")
|
|
202
|
+
|
|
203
|
+
all_violations = result.all_violations
|
|
204
|
+
failed_scanners = result.failed_scanners
|
|
205
|
+
|
|
206
|
+
if result.final_content:
|
|
207
|
+
print("Modified content:", result.final_content)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Error handling
|
|
211
|
+
|
|
212
|
+
### When the action is `fail`
|
|
213
|
+
|
|
214
|
+
If a scanner is configured with **`on_fail="fail"`** in the portal and that scanner finds a violation, the library raises **`ScanFailedError`** instead of returning an `AggregatedResult`. Your code can catch this exception to handle the failure (e.g. block the request, return an error to the user, or log and retry).
|
|
215
|
+
|
|
216
|
+
**Exception attributes:**
|
|
217
|
+
|
|
218
|
+
| Attribute | Description |
|
|
219
|
+
|-----------|-------------|
|
|
220
|
+
| `scanner_name` | Name of the scanner that failed. |
|
|
221
|
+
| `violations` | List of `Violation` objects (rule, severity, message, metadata such as `matched_text`). |
|
|
222
|
+
| `message` | Human-readable summary (e.g. `"Scan failed with N violation(s)"`). |
|
|
223
|
+
| `aggregated_result` | Full `AggregatedResult` for the run (all scanner results, timing). Set by the library when re-raising; useful if you need full details when handling the error. |
|
|
224
|
+
|
|
225
|
+
**Result is always saved.** Before re-raising, the library queues the failed result and flushes it to the Trusys backend. So the failure is recorded even if your application does not catch the exception and exits — you do not need to catch the exception solely to “save” the result.
|
|
226
|
+
|
|
227
|
+
**Sync example:**
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from trusys.exceptions import ScanFailedError
|
|
231
|
+
|
|
232
|
+
try:
|
|
233
|
+
result = TruGuard.input_guard.scan(content={"prompt": "user input"})
|
|
234
|
+
# Use result as needed (result.passed, result.final_content, etc.)
|
|
235
|
+
except ScanFailedError as e:
|
|
236
|
+
print(f"Guardrail failed: {e.scanner_name}")
|
|
237
|
+
for v in e.violations:
|
|
238
|
+
print(f" - {v.message}")
|
|
239
|
+
# Optional: use full result (e.g. for logging or custom reporting)
|
|
240
|
+
if e.aggregated_result:
|
|
241
|
+
print(f"Failed scanners: {e.aggregated_result.failed_scanners}")
|
|
242
|
+
raise # or return an error response, etc.
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**Async example:**
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
from trusys.exceptions import ScanFailedError
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
result = await TruGuard.input_guard.scan_async(content={"prompt": "user input"})
|
|
252
|
+
except ScanFailedError as e:
|
|
253
|
+
print(f"Guardrail failed: {e.scanner_name}, violations: {len(e.violations)}")
|
|
254
|
+
raise
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Other errors
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
from trusys.exceptions import ScanFailedError, ScannerTimeoutError
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
result = TruGuard.input_guard.scan(content={"prompt": "content"})
|
|
264
|
+
except ScanFailedError as e:
|
|
265
|
+
print(f"Scan failed: {e.scanner_name}")
|
|
266
|
+
print(f"Violations: {e.violations}")
|
|
267
|
+
except ScannerTimeoutError as e:
|
|
268
|
+
print(f"Scanner {e.scanner_name} timed out after {e.timeout}s")
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Environment variables
|
|
272
|
+
|
|
273
|
+
| Variable | Description |
|
|
274
|
+
|----------|-------------|
|
|
275
|
+
| `TRUSYS_APPLICATION_ID` | Application ID from [console.trusys.ai](https://console.trusys.ai) (required for init). |
|
|
276
|
+
| `TRUSYS_API_KEY` | API key from the Trusys portal (required for init). |
|
|
277
|
+
| `TRUSYS_APPLICATION_VERSION` | Optional application version. |
|
|
278
|
+
| `TRUSYS_ENVIRONMENT` | Optional environment (e.g. `dev`, `staging`, `prod`). |
|
|
279
|
+
| `TRUSYS_BATCH_INTERVAL` | Seconds between batch uploads (default `60`). |
|
|
280
|
+
| `TRUSYS_BATCH_COUNT` | Max results per batch (default `100`). |
|
|
281
|
+
| `TRUSYS_DEBUG_PRINT` | Set to `true` for debug logging (adds latency). |
|
|
282
|
+
| `TRUSYS_FPE_KEY` | Optional key for format-preserving encryption. |
|
|
283
|
+
| `TRUSYS_API_HOST` | Backend API host (default `https://backend.trusys.ai`). |
|
|
284
|
+
| `TRUSYS_GUARDRAILS_HOST` | Guardrails API host (default `https://guard-api.trusys.ai`). |
|
|
285
|
+
|
|
286
|
+
## Examples
|
|
287
|
+
|
|
288
|
+
- **Guardrail flow**: `examples/guardrail_flow_example.py` – init, then one input scan and one output scan.
|
|
289
|
+
|
|
290
|
+
Run it (with `TRUSYS_APPLICATION_ID` and `TRUSYS_API_KEY` set):
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
python examples/guardrail_flow_example.py
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Development
|
|
297
|
+
|
|
298
|
+
### Install dev dependencies
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
pip install -e ".[dev]"
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Run tests
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
pytest
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Run tests with coverage
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
pytest --cov=trusys --cov-report=html
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Type checking
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
mypy trusys
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Linting
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
ruff check trusys
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## License
|
|
329
|
+
|
|
330
|
+
MIT License
|
trusys-0.1.0/README.md
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# TruGuard
|
|
2
|
+
|
|
3
|
+
A Python library for AI guardrails. Configure your guardrails in the **Trusys portal**; in your application you run **`truguard init`** (or set credentials), call **`TruGuard.init()`**, then use the built-in **input** and **output** scanners. No manual scanner configuration in code — everything is managed from [console.trusys.ai](https://console.trusys.ai).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Portal-driven configuration**: Define input and output guardrails in the [Trusys Console](https://console.trusys.ai); your app receives config automatically.
|
|
8
|
+
- **Static input and output guards**: Use `TruGuard.input_guard` and `TruGuard.output_guard` to scan prompts and model responses.
|
|
9
|
+
- **No code-level scanner setup**: Scanners (regex, blocklist, PII, content safety, etc.) are configured in the portal, not in your codebase.
|
|
10
|
+
- **Parallel execution**: Scanners run concurrently for better performance.
|
|
11
|
+
- **Flexible actions**: Fix, mask, filter, fail, or ignore violations based on portal settings.
|
|
12
|
+
- **Async support**: Full async/await for I/O-bound scanning.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install trusys
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Optional Dependencies
|
|
21
|
+
|
|
22
|
+
For PII (Personally Identifiable Information) detection when enabled in the portal:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install trusys[pii]
|
|
26
|
+
python -m spacy download en_core_web_lg
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### 1. Configure guardrails in the Trusys portal
|
|
32
|
+
|
|
33
|
+
1. Go to **[https://console.trusys.ai](https://console.trusys.ai)** and sign in.
|
|
34
|
+
2. Create or select an application.
|
|
35
|
+
3. Configure **input** and **output** guardrails (e.g. blocklists, PII, content safety, prompt injection).
|
|
36
|
+
4. Copy your **Application ID** and **API key** for the next step.
|
|
37
|
+
|
|
38
|
+
### 2. Initialize TruGuard in your project
|
|
39
|
+
|
|
40
|
+
Configure your Application ID and API key by setting these environment variables:
|
|
41
|
+
|
|
42
|
+
- `TRUSYS_APPLICATION_ID` – from the Trusys portal
|
|
43
|
+
- `TRUSYS_API_KEY` – from the Trusys portal
|
|
44
|
+
|
|
45
|
+
### 3. Use the input and output guards in code
|
|
46
|
+
|
|
47
|
+
Call `TruGuard.init()` once at application startup (e.g. in `main` or when your app loads). Then use the static **input** and **output** guards to scan content.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from trusys import TruGuard
|
|
51
|
+
|
|
52
|
+
# One-time init at startup (uses TRUSYS_APPLICATION_ID and TRUSYS_API_KEY)
|
|
53
|
+
TruGuard.init()
|
|
54
|
+
|
|
55
|
+
# Scan user input (e.g. before sending to the LLM)
|
|
56
|
+
input_result = TruGuard.input_guard.scan(content={"prompt": "Hello world!"})
|
|
57
|
+
if not input_result.passed:
|
|
58
|
+
# Handle violations (e.g. block request or apply fixes)
|
|
59
|
+
for v in input_result.all_violations:
|
|
60
|
+
print(f"Input violation: {v.message}")
|
|
61
|
+
raise ValueError("Input failed guardrails")
|
|
62
|
+
|
|
63
|
+
# ... call your LLM ...
|
|
64
|
+
|
|
65
|
+
# Scan model output (e.g. before returning to the user)
|
|
66
|
+
output_result = TruGuard.output_guard.scan(
|
|
67
|
+
content={"prompt": "user prompt", "response": "model response"}
|
|
68
|
+
)
|
|
69
|
+
if not output_result.passed:
|
|
70
|
+
for v in output_result.all_violations:
|
|
71
|
+
print(f"Output violation: {v.message}")
|
|
72
|
+
raise ValueError("Output failed guardrails")
|
|
73
|
+
|
|
74
|
+
# Use output_result.final_content for the (possibly fixed) response if needed
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Async usage
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import asyncio
|
|
81
|
+
from trusys import TruGuard
|
|
82
|
+
|
|
83
|
+
TruGuard.init()
|
|
84
|
+
|
|
85
|
+
async def main():
|
|
86
|
+
input_result = await TruGuard.input_guard.scan_async(content={"prompt": "Hello!"})
|
|
87
|
+
print(f"Input passed: {input_result.passed}")
|
|
88
|
+
|
|
89
|
+
output_result = await TruGuard.output_guard.scan_async(
|
|
90
|
+
content={"prompt": "Hi", "response": "Hi there!"}
|
|
91
|
+
)
|
|
92
|
+
print(f"Output passed: {output_result.passed}")
|
|
93
|
+
|
|
94
|
+
asyncio.run(main())
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Init options
|
|
98
|
+
|
|
99
|
+
You can pass credentials and options explicitly instead of (or in addition to) environment variables:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
TruGuard.init(
|
|
103
|
+
application_id="your-application-id",
|
|
104
|
+
api_key="your-api-key",
|
|
105
|
+
application_version="1.0.0", # optional
|
|
106
|
+
environment="production", # optional: dev, staging, prod
|
|
107
|
+
metadata={"service": "chat"}, # optional: attached to all result uploads
|
|
108
|
+
batch_interval=60.0, # seconds between result uploads (default 60)
|
|
109
|
+
batch_count=100, # max results per batch (default 100)
|
|
110
|
+
debug_print=False, # set True only for debugging (adds latency)
|
|
111
|
+
)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Guardrail configuration is fetched from the Trusys API at init and refreshed periodically in the background. No manual scanner configuration is required in your code.
|
|
115
|
+
|
|
116
|
+
### Metadata (init + per-scan)
|
|
117
|
+
|
|
118
|
+
You can attach metadata at init (app-wide) and per scan (e.g. `conversation_id`, `session_id`). Scan-level metadata is merged with init metadata; duplicate keys are overridden by the scan value. The merged metadata is included in result uploads to the backend.
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
# Init with app-level metadata
|
|
122
|
+
TruGuard.init(
|
|
123
|
+
application_id="...",
|
|
124
|
+
api_key="...",
|
|
125
|
+
metadata={"environment": "prod", "service": "chat"},
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Per-scan metadata: merged with init metadata for this scan
|
|
129
|
+
input_result = TruGuard.input_guard.scan(
|
|
130
|
+
content={"prompt": "Hello"},
|
|
131
|
+
metadata={"conversation_id": "conv-123", "session_id": "sess-456"},
|
|
132
|
+
)
|
|
133
|
+
# Uploaded result includes: environment, service, conversation_id, session_id
|
|
134
|
+
|
|
135
|
+
# Same for async
|
|
136
|
+
output_result = await TruGuard.output_guard.scan_async(
|
|
137
|
+
content={"prompt": "Hi", "response": "Hi there!"},
|
|
138
|
+
metadata={"conversation_id": "conv-123"},
|
|
139
|
+
)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Action types
|
|
143
|
+
|
|
144
|
+
When a scanner finds a violation, the action is determined by your portal configuration. Supported actions:
|
|
145
|
+
|
|
146
|
+
| Action | Behavior |
|
|
147
|
+
|----------|----------|
|
|
148
|
+
| `fix` | Apply the scanner’s suggested fix (e.g. mask PII with entity labels). |
|
|
149
|
+
| `mask` | Replace detected content with `****`. |
|
|
150
|
+
| `encrypt`| Replace with format-preserving encryption (FPE). |
|
|
151
|
+
| `filter` | Remove or replace problematic content. |
|
|
152
|
+
| `fail` | Raise `ScanFailedError` (scan fails). |
|
|
153
|
+
| `ignore` | Log and allow content through unchanged. |
|
|
154
|
+
|
|
155
|
+
**Format-preserving encryption (FPE)** uses the FF1/FFX algorithm. Set the `TRUSYS_FPE_KEY` environment variable if you need a custom encryption key. Decrypt with:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from trusys.actions.builtin import decrypt_text
|
|
159
|
+
|
|
160
|
+
original = decrypt_text(encrypted_text, entity_type="PHONE_NUMBER")
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Result inspection
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
result = TruGuard.input_guard.scan(content={"prompt": "test"})
|
|
167
|
+
|
|
168
|
+
print(result.passed)
|
|
169
|
+
print(result.total_execution_time_ms)
|
|
170
|
+
|
|
171
|
+
for scan_result in result.results:
|
|
172
|
+
print(f"{scan_result.scanner_name}: {'PASS' if scan_result.passed else 'FAIL'}")
|
|
173
|
+
for v in scan_result.violations:
|
|
174
|
+
print(f" - [{v.severity.value}] {v.message}")
|
|
175
|
+
|
|
176
|
+
all_violations = result.all_violations
|
|
177
|
+
failed_scanners = result.failed_scanners
|
|
178
|
+
|
|
179
|
+
if result.final_content:
|
|
180
|
+
print("Modified content:", result.final_content)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Error handling
|
|
184
|
+
|
|
185
|
+
### When the action is `fail`
|
|
186
|
+
|
|
187
|
+
If a scanner is configured with **`on_fail="fail"`** in the portal and that scanner finds a violation, the library raises **`ScanFailedError`** instead of returning an `AggregatedResult`. Your code can catch this exception to handle the failure (e.g. block the request, return an error to the user, or log and retry).
|
|
188
|
+
|
|
189
|
+
**Exception attributes:**
|
|
190
|
+
|
|
191
|
+
| Attribute | Description |
|
|
192
|
+
|-----------|-------------|
|
|
193
|
+
| `scanner_name` | Name of the scanner that failed. |
|
|
194
|
+
| `violations` | List of `Violation` objects (rule, severity, message, metadata such as `matched_text`). |
|
|
195
|
+
| `message` | Human-readable summary (e.g. `"Scan failed with N violation(s)"`). |
|
|
196
|
+
| `aggregated_result` | Full `AggregatedResult` for the run (all scanner results, timing). Set by the library when re-raising; useful if you need full details when handling the error. |
|
|
197
|
+
|
|
198
|
+
**Result is always saved.** Before re-raising, the library queues the failed result and flushes it to the Trusys backend. So the failure is recorded even if your application does not catch the exception and exits — you do not need to catch the exception solely to “save” the result.
|
|
199
|
+
|
|
200
|
+
**Sync example:**
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
from trusys.exceptions import ScanFailedError
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
result = TruGuard.input_guard.scan(content={"prompt": "user input"})
|
|
207
|
+
# Use result as needed (result.passed, result.final_content, etc.)
|
|
208
|
+
except ScanFailedError as e:
|
|
209
|
+
print(f"Guardrail failed: {e.scanner_name}")
|
|
210
|
+
for v in e.violations:
|
|
211
|
+
print(f" - {v.message}")
|
|
212
|
+
# Optional: use full result (e.g. for logging or custom reporting)
|
|
213
|
+
if e.aggregated_result:
|
|
214
|
+
print(f"Failed scanners: {e.aggregated_result.failed_scanners}")
|
|
215
|
+
raise # or return an error response, etc.
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Async example:**
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from trusys.exceptions import ScanFailedError
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
result = await TruGuard.input_guard.scan_async(content={"prompt": "user input"})
|
|
225
|
+
except ScanFailedError as e:
|
|
226
|
+
print(f"Guardrail failed: {e.scanner_name}, violations: {len(e.violations)}")
|
|
227
|
+
raise
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Other errors
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
from trusys.exceptions import ScanFailedError, ScannerTimeoutError
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
result = TruGuard.input_guard.scan(content={"prompt": "content"})
|
|
237
|
+
except ScanFailedError as e:
|
|
238
|
+
print(f"Scan failed: {e.scanner_name}")
|
|
239
|
+
print(f"Violations: {e.violations}")
|
|
240
|
+
except ScannerTimeoutError as e:
|
|
241
|
+
print(f"Scanner {e.scanner_name} timed out after {e.timeout}s")
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Environment variables
|
|
245
|
+
|
|
246
|
+
| Variable | Description |
|
|
247
|
+
|----------|-------------|
|
|
248
|
+
| `TRUSYS_APPLICATION_ID` | Application ID from [console.trusys.ai](https://console.trusys.ai) (required for init). |
|
|
249
|
+
| `TRUSYS_API_KEY` | API key from the Trusys portal (required for init). |
|
|
250
|
+
| `TRUSYS_APPLICATION_VERSION` | Optional application version. |
|
|
251
|
+
| `TRUSYS_ENVIRONMENT` | Optional environment (e.g. `dev`, `staging`, `prod`). |
|
|
252
|
+
| `TRUSYS_BATCH_INTERVAL` | Seconds between batch uploads (default `60`). |
|
|
253
|
+
| `TRUSYS_BATCH_COUNT` | Max results per batch (default `100`). |
|
|
254
|
+
| `TRUSYS_DEBUG_PRINT` | Set to `true` for debug logging (adds latency). |
|
|
255
|
+
| `TRUSYS_FPE_KEY` | Optional key for format-preserving encryption. |
|
|
256
|
+
| `TRUSYS_API_HOST` | Backend API host (default `https://backend.trusys.ai`). |
|
|
257
|
+
| `TRUSYS_GUARDRAILS_HOST` | Guardrails API host (default `https://guard-api.trusys.ai`). |
|
|
258
|
+
|
|
259
|
+
## Examples
|
|
260
|
+
|
|
261
|
+
- **Guardrail flow**: `examples/guardrail_flow_example.py` – init, then one input scan and one output scan.
|
|
262
|
+
|
|
263
|
+
Run it (with `TRUSYS_APPLICATION_ID` and `TRUSYS_API_KEY` set):
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
python examples/guardrail_flow_example.py
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Development
|
|
270
|
+
|
|
271
|
+
### Install dev dependencies
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
pip install -e ".[dev]"
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Run tests
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
pytest
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Run tests with coverage
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
pytest --cov=trusys --cov-report=html
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Type checking
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
mypy trusys
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Linting
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
ruff check trusys
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## License
|
|
302
|
+
|
|
303
|
+
MIT License
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "trusys"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A modular Python library for AI guardrails with input/output scanning capabilities"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.10",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
19
|
+
]
|
|
20
|
+
keywords = ["ai", "guardrails", "llm", "safety", "scanning"]
|
|
21
|
+
|
|
22
|
+
dependencies = [
|
|
23
|
+
"fuzzysearch>=0.7.0", # For fuzzy matching in BlockListScanner
|
|
24
|
+
"httpx>=0.27.0", # HTTP client for guardrail config fetch, save-results, and remote scanner calls
|
|
25
|
+
"pyffx>=0.3.0", # For FF1/FFX format-preserving encryption
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = [
|
|
30
|
+
"pytest>=7.0",
|
|
31
|
+
"pytest-asyncio>=0.21",
|
|
32
|
+
"pytest-cov>=4.0",
|
|
33
|
+
"mypy>=1.0",
|
|
34
|
+
"ruff>=0.1",
|
|
35
|
+
]
|
|
36
|
+
pii = [
|
|
37
|
+
"presidio-analyzer>=2.2.0",
|
|
38
|
+
"spacy>=3.0.0",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.packages.find]
|
|
42
|
+
where = ["."]
|
|
43
|
+
include = ["trusys*"]
|
|
44
|
+
|
|
45
|
+
[tool.pytest.ini_options]
|
|
46
|
+
asyncio_mode = "auto"
|
|
47
|
+
testpaths = ["tests"]
|
|
48
|
+
|
|
49
|
+
[tool.mypy]
|
|
50
|
+
python_version = "3.10"
|
|
51
|
+
strict = true
|
|
52
|
+
warn_return_any = true
|
|
53
|
+
warn_unused_ignores = true
|
|
54
|
+
|
|
55
|
+
[tool.ruff]
|
|
56
|
+
line-length = 100
|
|
57
|
+
target-version = "py310"
|
trusys-0.1.0/setup.cfg
ADDED