risk-mirror 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- risk_mirror/__init__.py +28 -0
- risk_mirror/client.py +346 -0
- risk_mirror/types.py +135 -0
- risk_mirror-1.0.0.dist-info/METADATA +74 -0
- risk_mirror-1.0.0.dist-info/RECORD +7 -0
- risk_mirror-1.0.0.dist-info/WHEEL +5 -0
- risk_mirror-1.0.0.dist-info/top_level.txt +1 -0
risk_mirror/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Risk Mirror SDK - Python
|
|
3
|
+
========================
|
|
4
|
+
Deterministic, stateless AI safety toolkit
|
|
5
|
+
|
|
6
|
+
US-0001 to US-0020: SDKs (JS/Python) feature
|
|
7
|
+
"""
|
|
8
|
+
from .client import RiskMirror, RiskMirrorError
|
|
9
|
+
from .types import (
|
|
10
|
+
ScanResponse,
|
|
11
|
+
ScanPolicy,
|
|
12
|
+
Finding,
|
|
13
|
+
AuditSummary,
|
|
14
|
+
Verdict,
|
|
15
|
+
Severity,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__version__ = "1.0.0"
|
|
19
|
+
__all__ = [
|
|
20
|
+
"RiskMirror",
|
|
21
|
+
"RiskMirrorError",
|
|
22
|
+
"ScanResponse",
|
|
23
|
+
"ScanPolicy",
|
|
24
|
+
"Finding",
|
|
25
|
+
"AuditSummary",
|
|
26
|
+
"Verdict",
|
|
27
|
+
"Severity",
|
|
28
|
+
]
|
risk_mirror/client.py
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Risk Mirror SDK - Python Client
|
|
3
|
+
================================
|
|
4
|
+
Deterministic, stateless AI safety toolkit
|
|
5
|
+
Drop-in integration for prompt security
|
|
6
|
+
|
|
7
|
+
US-0001: Core Behavior
|
|
8
|
+
US-0002: Policy Controls
|
|
9
|
+
US-0006: Edge Cases
|
|
10
|
+
US-0007: Performance
|
|
11
|
+
US-0008: Security
|
|
12
|
+
US-0009: Audit Evidence
|
|
13
|
+
US-0010: Privacy (no storage)
|
|
14
|
+
US-0011: Compliance
|
|
15
|
+
US-0012: Rate Limits
|
|
16
|
+
US-0013: Logging
|
|
17
|
+
US-0015: Rollback
|
|
18
|
+
US-0020: Error Handling
|
|
19
|
+
"""
|
|
20
|
+
import time
|
|
21
|
+
import logging
|
|
22
|
+
from typing import Optional, Callable, Dict, Any
|
|
23
|
+
from urllib.request import Request, urlopen
|
|
24
|
+
from urllib.error import HTTPError, URLError
|
|
25
|
+
import json
|
|
26
|
+
|
|
27
|
+
from .types import ScanResponse, ScanPolicy, OptimizeResponse, Verdict
|
|
28
|
+
|
|
29
|
+
# Constants
|
|
30
|
+
DEFAULT_BASE_URL = "https://risk-mirror-auth.anonymous617461746174.workers.dev"
|
|
31
|
+
DEFAULT_TIMEOUT = 30
|
|
32
|
+
DEFAULT_RETRIES = 3
|
|
33
|
+
SDK_VERSION = "1.0.0"
|
|
34
|
+
ENGINE_VERSION = "6.0-ULTRA"
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger("risk_mirror")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RiskMirrorError(Exception):
|
|
40
|
+
"""Base exception for Risk Mirror SDK.
|
|
41
|
+
|
|
42
|
+
US-0020: Error Handling
|
|
43
|
+
"""
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
code: str,
|
|
47
|
+
message: str,
|
|
48
|
+
retryable: bool = False,
|
|
49
|
+
status_code: Optional[int] = None
|
|
50
|
+
):
|
|
51
|
+
super().__init__(message)
|
|
52
|
+
self.code = code
|
|
53
|
+
self.message = message
|
|
54
|
+
self.retryable = retryable
|
|
55
|
+
self.status_code = status_code
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class RiskMirror:
|
|
59
|
+
"""
|
|
60
|
+
Risk Mirror SDK Client
|
|
61
|
+
======================
|
|
62
|
+
Deterministic AI safety scanning with zero content storage.
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
>>> client = RiskMirror(api_key="your-key")
|
|
66
|
+
>>> result = client.scan("Check this prompt for safety")
|
|
67
|
+
>>> print(result.verdict)
|
|
68
|
+
'SAFE'
|
|
69
|
+
|
|
70
|
+
US-0001: Core Behavior
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
76
|
+
api_key: Optional[str] = None,
|
|
77
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
78
|
+
retries: int = DEFAULT_RETRIES,
|
|
79
|
+
debug: bool = False,
|
|
80
|
+
):
|
|
81
|
+
"""
|
|
82
|
+
Initialize the Risk Mirror client.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
base_url: API base URL
|
|
86
|
+
api_key: Optional API key for authentication
|
|
87
|
+
timeout: Request timeout in seconds
|
|
88
|
+
retries: Number of retries on failure
|
|
89
|
+
debug: Enable debug logging
|
|
90
|
+
"""
|
|
91
|
+
self.base_url = base_url.rstrip("/")
|
|
92
|
+
self.api_key = api_key
|
|
93
|
+
self.timeout = timeout
|
|
94
|
+
self.retries = retries
|
|
95
|
+
self.debug = debug
|
|
96
|
+
self._telemetry_callback: Optional[Callable[[Dict[str, Any]], None]] = None
|
|
97
|
+
|
|
98
|
+
if debug:
|
|
99
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
100
|
+
|
|
101
|
+
def on_telemetry(self, callback: Callable[[Dict[str, Any]], None]) -> None:
|
|
102
|
+
"""
|
|
103
|
+
Set telemetry callback (receives no content, only metadata).
|
|
104
|
+
|
|
105
|
+
US-0018: Analytics without storing content
|
|
106
|
+
"""
|
|
107
|
+
self._telemetry_callback = callback
|
|
108
|
+
|
|
109
|
+
def _log(self, msg: str, *args: Any) -> None:
|
|
110
|
+
if self.debug:
|
|
111
|
+
logger.debug(f"[RiskMirror] {msg}", *args)
|
|
112
|
+
|
|
113
|
+
def _request(
|
|
114
|
+
self,
|
|
115
|
+
endpoint: str,
|
|
116
|
+
method: str = "POST",
|
|
117
|
+
body: Optional[Dict[str, Any]] = None
|
|
118
|
+
) -> Dict[str, Any]:
|
|
119
|
+
"""Make HTTP request with retry logic.
|
|
120
|
+
|
|
121
|
+
US-0012: Rate Limits
|
|
122
|
+
US-0020: Error Handling
|
|
123
|
+
"""
|
|
124
|
+
url = f"{self.base_url}{endpoint}"
|
|
125
|
+
headers = {
|
|
126
|
+
"Content-Type": "application/json",
|
|
127
|
+
"X-SDK-Version": SDK_VERSION,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if self.api_key:
|
|
131
|
+
headers["X-API-Key"] = self.api_key
|
|
132
|
+
|
|
133
|
+
last_error: Optional[Exception] = None
|
|
134
|
+
|
|
135
|
+
for attempt in range(self.retries + 1):
|
|
136
|
+
try:
|
|
137
|
+
self._log(f"Request attempt {attempt + 1}: {method} {endpoint}")
|
|
138
|
+
|
|
139
|
+
data = json.dumps(body).encode("utf-8") if body else None
|
|
140
|
+
req = Request(url, data=data, headers=headers, method=method)
|
|
141
|
+
|
|
142
|
+
with urlopen(req, timeout=self.timeout) as response:
|
|
143
|
+
return json.loads(response.read().decode("utf-8"))
|
|
144
|
+
|
|
145
|
+
except HTTPError as e:
|
|
146
|
+
last_error = e
|
|
147
|
+
|
|
148
|
+
# US-0012: Rate limit handling
|
|
149
|
+
if e.code == 429:
|
|
150
|
+
retry_after = e.headers.get("Retry-After", "1")
|
|
151
|
+
wait_secs = int(retry_after)
|
|
152
|
+
self._log(f"Rate limited, waiting {wait_secs}s")
|
|
153
|
+
time.sleep(wait_secs)
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
# Retryable server errors
|
|
157
|
+
if e.code >= 500 and attempt < self.retries:
|
|
158
|
+
wait_secs = min(2 ** attempt, 10)
|
|
159
|
+
self._log(f"Server error {e.code}, retry in {wait_secs}s")
|
|
160
|
+
time.sleep(wait_secs)
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
raise RiskMirrorError(
|
|
164
|
+
code="API_ERROR",
|
|
165
|
+
message=f"API returned {e.code}: {e.read().decode('utf-8', errors='ignore')}",
|
|
166
|
+
retryable=e.code >= 500,
|
|
167
|
+
status_code=e.code
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
except URLError as e:
|
|
171
|
+
last_error = e
|
|
172
|
+
if attempt < self.retries:
|
|
173
|
+
wait_secs = min(2 ** attempt, 10)
|
|
174
|
+
self._log(f"Network error, retry in {wait_secs}s: {e.reason}")
|
|
175
|
+
time.sleep(wait_secs)
|
|
176
|
+
continue
|
|
177
|
+
raise RiskMirrorError(
|
|
178
|
+
code="NETWORK_ERROR",
|
|
179
|
+
message=str(e.reason),
|
|
180
|
+
retryable=True
|
|
181
|
+
)
|
|
182
|
+
except Exception as e:
|
|
183
|
+
last_error = e
|
|
184
|
+
raise RiskMirrorError(
|
|
185
|
+
code="UNKNOWN_ERROR",
|
|
186
|
+
message=str(e),
|
|
187
|
+
retryable=False
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
raise last_error or RiskMirrorError("UNKNOWN_ERROR", "Request failed", False)
|
|
191
|
+
|
|
192
|
+
def scan(
|
|
193
|
+
self,
|
|
194
|
+
input_text: str,
|
|
195
|
+
policy: Optional[ScanPolicy] = None,
|
|
196
|
+
mode: str = "default"
|
|
197
|
+
) -> ScanResponse:
|
|
198
|
+
"""
|
|
199
|
+
Scan input for safety issues.
|
|
200
|
+
|
|
201
|
+
US-0001: Core behavior - deterministic scanning
|
|
202
|
+
US-0002: Policy controls - configurable detection
|
|
203
|
+
US-0010: Privacy - no content storage
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
input_text: Text to scan
|
|
207
|
+
policy: Optional policy configuration
|
|
208
|
+
mode: Scan mode (default, strict, paranoid)
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
ScanResponse with verdict, findings, and safe_output
|
|
212
|
+
|
|
213
|
+
Raises:
|
|
214
|
+
RiskMirrorError: On validation or API errors
|
|
215
|
+
"""
|
|
216
|
+
start = time.perf_counter()
|
|
217
|
+
|
|
218
|
+
# US-0006: Edge cases - input validation
|
|
219
|
+
if not input_text or not isinstance(input_text, str):
|
|
220
|
+
raise RiskMirrorError(
|
|
221
|
+
code="INVALID_INPUT",
|
|
222
|
+
message="Input must be a non-empty string",
|
|
223
|
+
retryable=False
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# US-0008: Security - size limit
|
|
227
|
+
max_length = policy.max_length if policy else 100000
|
|
228
|
+
if len(input_text) > max_length:
|
|
229
|
+
raise RiskMirrorError(
|
|
230
|
+
code="INPUT_TOO_LARGE",
|
|
231
|
+
message=f"Input exceeds maximum length of {max_length} characters",
|
|
232
|
+
retryable=False
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# Build request
|
|
236
|
+
policy = policy or ScanPolicy()
|
|
237
|
+
request_body = {
|
|
238
|
+
"prompt": input_text,
|
|
239
|
+
"policy": {
|
|
240
|
+
"detect_pii": policy.pii,
|
|
241
|
+
"detect_secrets": policy.secrets,
|
|
242
|
+
"detect_injection": policy.injection,
|
|
243
|
+
},
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if policy.pii_classes:
|
|
247
|
+
request_body["policy"]["pii_classes"] = policy.pii_classes
|
|
248
|
+
|
|
249
|
+
response_data = self._request("/firewall/audit", "POST", request_body)
|
|
250
|
+
|
|
251
|
+
latency_ms = (time.perf_counter() - start) * 1000
|
|
252
|
+
response_data["latency_ms"] = latency_ms
|
|
253
|
+
response_data["privacy"] = {"stateless": True, "content_logged": False}
|
|
254
|
+
|
|
255
|
+
# US-0018: Analytics telemetry (no content)
|
|
256
|
+
if self._telemetry_callback:
|
|
257
|
+
self._telemetry_callback({
|
|
258
|
+
"operation": "scan",
|
|
259
|
+
"verdict": response_data.get("verdict", "SAFE"),
|
|
260
|
+
"findings_count": len(response_data.get("findings", [])),
|
|
261
|
+
"latency_ms": latency_ms,
|
|
262
|
+
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
return ScanResponse.from_dict(response_data)
|
|
266
|
+
|
|
267
|
+
def audit(
|
|
268
|
+
self,
|
|
269
|
+
input_text: str,
|
|
270
|
+
policy: Optional[ScanPolicy] = None
|
|
271
|
+
) -> ScanResponse:
|
|
272
|
+
"""
|
|
273
|
+
Audit scan for compliance reporting.
|
|
274
|
+
|
|
275
|
+
US-0009: Audit Evidence
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
input_text: Text to audit
|
|
279
|
+
policy: Optional policy configuration
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
ScanResponse with detailed audit evidence
|
|
283
|
+
"""
|
|
284
|
+
return self.scan(input_text, policy, mode="strict")
|
|
285
|
+
|
|
286
|
+
def optimize(
|
|
287
|
+
self,
|
|
288
|
+
input_text: str,
|
|
289
|
+
mode: str = "compress"
|
|
290
|
+
) -> OptimizeResponse:
|
|
291
|
+
"""
|
|
292
|
+
Optimize prompt for token efficiency.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
input_text: Text to optimize
|
|
296
|
+
mode: compress, refine, or strip
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
OptimizeResponse with optimized text
|
|
300
|
+
"""
|
|
301
|
+
if not input_text or not isinstance(input_text, str):
|
|
302
|
+
raise RiskMirrorError(
|
|
303
|
+
code="INVALID_INPUT",
|
|
304
|
+
message="Input must be a non-empty string",
|
|
305
|
+
retryable=False
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
response_data = self._request("/optimize/prompt", "POST", {
|
|
309
|
+
"prompt": input_text,
|
|
310
|
+
"mode": mode,
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
return OptimizeResponse.from_dict(response_data)
|
|
314
|
+
|
|
315
|
+
def get_version(self) -> Dict[str, str]:
|
|
316
|
+
"""
|
|
317
|
+
Get SDK and engine version.
|
|
318
|
+
|
|
319
|
+
US-0015: Rollback - version tracking
|
|
320
|
+
"""
|
|
321
|
+
return {
|
|
322
|
+
"sdk": SDK_VERSION,
|
|
323
|
+
"engine": ENGINE_VERSION,
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
# ============ Quick Helpers ============
|
|
328
|
+
_default_client: Optional[RiskMirror] = None
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def configure(**kwargs: Any) -> None:
|
|
332
|
+
"""Configure the default client."""
|
|
333
|
+
global _default_client
|
|
334
|
+
_default_client = RiskMirror(**kwargs)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def scan(
|
|
338
|
+
input_text: str,
|
|
339
|
+
policy: Optional[ScanPolicy] = None,
|
|
340
|
+
mode: str = "default"
|
|
341
|
+
) -> ScanResponse:
|
|
342
|
+
"""Quick scan using default client."""
|
|
343
|
+
global _default_client
|
|
344
|
+
if _default_client is None:
|
|
345
|
+
_default_client = RiskMirror()
|
|
346
|
+
return _default_client.scan(input_text, policy, mode)
|
risk_mirror/types.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Risk Mirror SDK - Type Definitions
|
|
3
|
+
==================================
|
|
4
|
+
Dataclasses for SDK request/response models
|
|
5
|
+
|
|
6
|
+
US-0004: API Surface
|
|
7
|
+
US-0005: Data Model
|
|
8
|
+
"""
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import List, Optional, Dict, Any, Literal
|
|
11
|
+
from enum import Enum
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Verdict(str, Enum):
|
|
15
|
+
SAFE = "SAFE"
|
|
16
|
+
REVIEW = "REVIEW"
|
|
17
|
+
HIGH_RISK = "HIGH_RISK"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Severity(str, Enum):
|
|
21
|
+
LOW = "LOW"
|
|
22
|
+
MED = "MED"
|
|
23
|
+
HIGH = "HIGH"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class Finding:
|
|
28
|
+
"""A single finding from the scan."""
|
|
29
|
+
category: str
|
|
30
|
+
severity: Severity
|
|
31
|
+
count: int
|
|
32
|
+
match: Optional[str] = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class AuditSummary:
|
|
37
|
+
"""Summary of modifications made."""
|
|
38
|
+
phrases_removed: int = 0
|
|
39
|
+
pii_redacted: int = 0
|
|
40
|
+
secrets_masked: int = 0
|
|
41
|
+
injections_blocked: int = 0
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class ScanPolicy:
|
|
46
|
+
"""Configuration for scan behavior.
|
|
47
|
+
|
|
48
|
+
US-0002: Policy Controls
|
|
49
|
+
"""
|
|
50
|
+
pii: bool = True
|
|
51
|
+
secrets: bool = True
|
|
52
|
+
injection: bool = True
|
|
53
|
+
pii_classes: Optional[List[str]] = None
|
|
54
|
+
max_length: int = 100000
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class Privacy:
|
|
59
|
+
"""Privacy guarantees."""
|
|
60
|
+
stateless: bool = True
|
|
61
|
+
content_logged: bool = False
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class ScanResponse:
|
|
66
|
+
"""Response from scan operation.
|
|
67
|
+
|
|
68
|
+
US-0009: Audit Evidence
|
|
69
|
+
US-0010: Privacy
|
|
70
|
+
US-0011: Compliance
|
|
71
|
+
"""
|
|
72
|
+
verdict: Verdict
|
|
73
|
+
findings: List[Finding]
|
|
74
|
+
safe_output: str
|
|
75
|
+
audit_summary: AuditSummary
|
|
76
|
+
policy_hash: str
|
|
77
|
+
engine_version: str
|
|
78
|
+
latency_ms: float
|
|
79
|
+
privacy: Optional[Privacy] = None
|
|
80
|
+
compliance_tags: List[str] = field(default_factory=list)
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def from_dict(cls, data: Dict[str, Any]) -> "ScanResponse":
|
|
84
|
+
"""Create ScanResponse from API response dict."""
|
|
85
|
+
findings = [
|
|
86
|
+
Finding(
|
|
87
|
+
category=f.get("category", "unknown"),
|
|
88
|
+
severity=Severity(f.get("severity", "LOW")),
|
|
89
|
+
count=f.get("count", 1),
|
|
90
|
+
match=f.get("match"),
|
|
91
|
+
)
|
|
92
|
+
for f in data.get("findings", [])
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
audit_data = data.get("audit_summary", {})
|
|
96
|
+
audit_summary = AuditSummary(
|
|
97
|
+
phrases_removed=audit_data.get("phrases_removed", 0),
|
|
98
|
+
pii_redacted=audit_data.get("pii_redacted", 0),
|
|
99
|
+
secrets_masked=audit_data.get("secrets_masked", 0),
|
|
100
|
+
injections_blocked=audit_data.get("injections_blocked", 0),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
privacy_data = data.get("privacy")
|
|
104
|
+
privacy = Privacy(
|
|
105
|
+
stateless=privacy_data.get("stateless", True),
|
|
106
|
+
content_logged=privacy_data.get("content_logged", False),
|
|
107
|
+
) if privacy_data else Privacy()
|
|
108
|
+
|
|
109
|
+
return cls(
|
|
110
|
+
verdict=Verdict(data.get("verdict", "SAFE")),
|
|
111
|
+
findings=findings,
|
|
112
|
+
safe_output=data.get("safe_output", ""),
|
|
113
|
+
audit_summary=audit_summary,
|
|
114
|
+
policy_hash=data.get("policy_hash", ""),
|
|
115
|
+
engine_version=data.get("engine_version", "6.0-ULTRA"),
|
|
116
|
+
latency_ms=data.get("latency_ms", 0.0),
|
|
117
|
+
privacy=privacy,
|
|
118
|
+
compliance_tags=data.get("compliance_tags", []),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass
|
|
123
|
+
class OptimizeResponse:
|
|
124
|
+
"""Response from optimize operation."""
|
|
125
|
+
optimized: str
|
|
126
|
+
tokens_saved: int
|
|
127
|
+
compression_ratio: float
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def from_dict(cls, data: Dict[str, Any]) -> "OptimizeResponse":
|
|
131
|
+
return cls(
|
|
132
|
+
optimized=data.get("optimized", ""),
|
|
133
|
+
tokens_saved=data.get("tokens_saved", 0),
|
|
134
|
+
compression_ratio=data.get("compression_ratio", 1.0),
|
|
135
|
+
)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: risk-mirror
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Deterministic AI Safety Toolkit - Python SDK
|
|
5
|
+
Home-page: https://github.com/myProjectsRavi/risk-mirror-core
|
|
6
|
+
Author: RTN Labs
|
|
7
|
+
Author-email: support@risk-mirror.com
|
|
8
|
+
Keywords: ai-safety,pii,secrets,prompt-security,deterministic
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Security
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
26
|
+
Requires-Dist: mypy; extra == "dev"
|
|
27
|
+
Dynamic: author
|
|
28
|
+
Dynamic: author-email
|
|
29
|
+
Dynamic: classifier
|
|
30
|
+
Dynamic: description
|
|
31
|
+
Dynamic: description-content-type
|
|
32
|
+
Dynamic: home-page
|
|
33
|
+
Dynamic: keywords
|
|
34
|
+
Dynamic: provides-extra
|
|
35
|
+
Dynamic: requires-python
|
|
36
|
+
Dynamic: summary
|
|
37
|
+
|
|
38
|
+
# Risk Mirror SDK - Python
|
|
39
|
+
|
|
40
|
+
Deterministic AI Safety Toolkit for prompt security.
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install risk-mirror
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from risk_mirror import RiskMirror
|
|
52
|
+
|
|
53
|
+
# Initialize client
|
|
54
|
+
client = RiskMirror(api_key="your-api-key")
|
|
55
|
+
|
|
56
|
+
# Scan for safety issues
|
|
57
|
+
result = client.scan("Check this text for PII and secrets")
|
|
58
|
+
|
|
59
|
+
print(result.verdict) # SAFE, REVIEW, or HIGH_RISK
|
|
60
|
+
print(result.findings) # List of findings
|
|
61
|
+
print(result.safe_output) # Redacted text
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Features
|
|
65
|
+
|
|
66
|
+
- **PII Detection**: Email, phone, SSN, etc.
|
|
67
|
+
- **Secrets Detection**: API keys, tokens, passwords
|
|
68
|
+
- **Injection Detection**: Prompt injection attempts
|
|
69
|
+
- **Zero Storage**: No content is stored
|
|
70
|
+
- **Deterministic**: Same input = same output
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
risk_mirror/__init__.py,sha256=-rkpepWwQeJ8LLYUNg8xwWIUYrZ0g7q6jAuOHw5vVbE,492
|
|
2
|
+
risk_mirror/client.py,sha256=Cge24tZukgPYKHQASYlDuAhkoxxrALHyk54ar4uJ4ME,10764
|
|
3
|
+
risk_mirror/types.py,sha256=-QLCxNTRBGXR8cSHrDRl7Z97sPfU2OaM7SXB3wZtU0Q,3599
|
|
4
|
+
risk_mirror-1.0.0.dist-info/METADATA,sha256=GarpjZrNHW-ci8hXsNyp44IcZLT7XJ-TG2-ui_FoSQk,2066
|
|
5
|
+
risk_mirror-1.0.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
6
|
+
risk_mirror-1.0.0.dist-info/top_level.txt,sha256=BtKjDWugw6OoRR_DSXHJ0qxiqQxcul69MFko0szkZ-I,12
|
|
7
|
+
risk_mirror-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
risk_mirror
|