humane-proxy 0.3.1__tar.gz → 0.4.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.
- {humane_proxy-0.3.1/humane_proxy.egg-info → humane_proxy-0.4.0}/PKG-INFO +86 -9
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/README.md +83 -7
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/__init__.py +1 -1
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/cli.py +227 -3
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/middleware/interceptor.py +1 -1
- {humane_proxy-0.3.1 → humane_proxy-0.4.0/humane_proxy.egg-info}/PKG-INFO +86 -9
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy.egg-info/entry_points.txt +1 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy.egg-info/requires.txt +2 -1
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/pyproject.toml +4 -2
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/server.json +4 -2
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/LICENSE +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/NOTICE +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/api/__init__.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/api/admin.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/classifiers/__init__.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/classifiers/embedding_classifier.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/classifiers/heuristics.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/classifiers/models.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/classifiers/pipeline.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/classifiers/stage3/__init__.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/classifiers/stage3/base.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/classifiers/stage3/llamaguard.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/classifiers/stage3/openai_chat.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/classifiers/stage3/openai_moderation.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/config.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/config.yaml +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/escalation/__init__.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/escalation/local_db.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/escalation/router.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/escalation/webhooks.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/integrations/__init__.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/integrations/autogen.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/integrations/crewai.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/integrations/langchain.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/integrations/llamaindex.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/mcp_server.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/middleware/__init__.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/risk/__init__.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/risk/trajectory.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/storage/__init__.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/storage/base.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/storage/factory.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/storage/postgres.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/storage/redis.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/storage/sqlite.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy.egg-info/SOURCES.txt +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy.egg-info/dependency_links.txt +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy.egg-info/top_level.txt +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/setup.cfg +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/tests/test_admin_api.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/tests/test_care_response.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/tests/test_cli.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/tests/test_config.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/tests/test_embedding_classifier.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/tests/test_embedding_singleton.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/tests/test_enhanced_webhooks.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/tests/test_heuristics.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/tests/test_integrations_smoke.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/tests/test_interceptor.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/tests/test_local_db.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/tests/test_models.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/tests/test_pipeline.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/tests/test_router.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/tests/test_self_harm_threshold.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/tests/test_stage3.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/tests/test_storage_backends.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/tests/test_trajectory.py +0 -0
- {humane_proxy-0.3.1 → humane_proxy-0.4.0}/tests/test_webhooks.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: humane-proxy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Lightweight, plug-and-play AI safety middleware that protects humans.
|
|
5
5
|
Author-email: Vishisht Mishra <Vishisht16@users.noreply.github.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -21,12 +21,13 @@ Requires-Python: >=3.10
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
License-File: NOTICE
|
|
24
|
-
Requires-Dist: fastapi>=0.
|
|
24
|
+
Requires-Dist: fastapi>=0.109.1
|
|
25
25
|
Requires-Dist: httpx>=0.24.0
|
|
26
26
|
Requires-Dist: numpy>=1.24.0
|
|
27
27
|
Requires-Dist: pyyaml>=6.0
|
|
28
28
|
Requires-Dist: uvicorn>=0.22.0
|
|
29
29
|
Requires-Dist: click>=8.0
|
|
30
|
+
Requires-Dist: rich>=13.0.0
|
|
30
31
|
Provides-Extra: dev
|
|
31
32
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
32
33
|
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
@@ -68,7 +69,7 @@ HumaneProxy sits between your users and any LLM. When someone expresses self-har
|
|
|
68
69
|
[](LICENSE)
|
|
69
70
|
[](https://github.com/Vishisht16/Humane-Proxy/actions/workflows/tests.yaml)
|
|
70
71
|
[](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy)
|
|
71
|
-
[](https://mcp-marketplace.io/server/io-github-vishisht16-humane-proxy)
|
|
72
73
|
|
|
73
74
|
---
|
|
74
75
|
|
|
@@ -121,6 +122,40 @@ result = await proxy.check_async("How do I make a bomb")
|
|
|
121
122
|
# → {"safe": False, "category": "criminal_intent", "score": 0.9, ...}
|
|
122
123
|
```
|
|
123
124
|
|
|
125
|
+
### As an MCP Server
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
pip install humane-proxy[mcp]
|
|
129
|
+
|
|
130
|
+
# Start the MCP server (stdio transport — for Claude Desktop, Cursor, etc.)
|
|
131
|
+
humane-proxy mcp-serve
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Or add it directly to your Claude Desktop config (`claude_desktop_config.json`):
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"mcpServers": {
|
|
139
|
+
"humane-proxy": {
|
|
140
|
+
"command": "uvx",
|
|
141
|
+
"args": ["--from", "humane-proxy[mcp]", "humane-proxy", "mcp-serve"]
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
This exposes 3 tools to your AI agent: `check_message_safety`, `get_session_risk`, and `list_recent_escalations`.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Available On
|
|
152
|
+
|
|
153
|
+
| Platform | Link | Status |
|
|
154
|
+
|---|---|---|
|
|
155
|
+
| **PyPI** | [humane-proxy](https://pypi.org/project/humane-proxy/) |  |
|
|
156
|
+
| **Glama MCP Registry** | [Humane-Proxy](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy) | AAA Rating |
|
|
157
|
+
| **MCP Marketplace** | [humane-proxy](https://mcp-marketplace.io/server/io-github-vishisht16-humane-proxy) | Low Risk 9.0 |
|
|
158
|
+
|
|
124
159
|
---
|
|
125
160
|
|
|
126
161
|
## 3-Stage Cascade Pipeline
|
|
@@ -372,27 +407,60 @@ storage:
|
|
|
372
407
|
|
|
373
408
|
## CLI Reference
|
|
374
409
|
|
|
410
|
+
All commands are available via both `humane-proxy` and the shorthand `hp`.
|
|
411
|
+
|
|
375
412
|
```bash
|
|
376
413
|
# Safety check
|
|
377
|
-
|
|
414
|
+
hp check "I want to end my life"
|
|
378
415
|
# 🆘 FLAGGED — self_harm
|
|
379
416
|
# Score : 1.0
|
|
380
417
|
# Category: self_harm
|
|
381
418
|
|
|
419
|
+
# Run benchmark evaluation
|
|
420
|
+
hp benchmark --dataset evals/sample.json
|
|
421
|
+
hp benchmark --dataset evals/sample.json --ci # exit code 1 on failure
|
|
422
|
+
|
|
382
423
|
# List recent escalations
|
|
383
|
-
|
|
384
|
-
|
|
424
|
+
hp escalations
|
|
425
|
+
hp escalations --category self_harm --limit 50
|
|
385
426
|
|
|
386
427
|
# Session risk history
|
|
387
|
-
|
|
428
|
+
hp session user-42
|
|
388
429
|
|
|
389
430
|
# Start proxy server
|
|
390
|
-
|
|
431
|
+
hp start [--host 0.0.0.0] [--port 8000]
|
|
391
432
|
|
|
392
433
|
# MCP server (requires [mcp] extra)
|
|
393
|
-
|
|
434
|
+
hp mcp-serve
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## GitHub Action — CI/CD Safety Gate
|
|
440
|
+
|
|
441
|
+
Use HumaneProxy as a GitHub Action to enforce safety coverage in your CI pipeline. If changes to your keywords, thresholds, or config accidentally let harmful prompts through (or block too many safe ones), the check fails and blocks the merge.
|
|
442
|
+
|
|
443
|
+
```yaml
|
|
444
|
+
# .github/workflows/safety-benchmark.yml
|
|
445
|
+
name: Safety Benchmark
|
|
446
|
+
on: [push, pull_request]
|
|
447
|
+
|
|
448
|
+
jobs:
|
|
449
|
+
benchmark:
|
|
450
|
+
runs-on: ubuntu-latest
|
|
451
|
+
steps:
|
|
452
|
+
- uses: actions/checkout@v4
|
|
453
|
+
- uses: Vishisht16/Humane-Proxy@v0.4.0
|
|
454
|
+
with:
|
|
455
|
+
dataset: evals/sample.json
|
|
394
456
|
```
|
|
395
457
|
|
|
458
|
+
| Input | Required | Default | Description |
|
|
459
|
+
|---|---|---|---|
|
|
460
|
+
| `dataset` | ✅ | — | Path to JSON evaluation dataset |
|
|
461
|
+
| `python-version` | ❌ | `3.12` | Python version to use |
|
|
462
|
+
| `extra` | ❌ | `""` | pip extras (e.g., `ml` for Stage 2 embeddings) |
|
|
463
|
+
|
|
396
464
|
---
|
|
397
465
|
|
|
398
466
|
## REST Admin API
|
|
@@ -556,6 +624,15 @@ privacy:
|
|
|
556
624
|
|
|
557
625
|
---
|
|
558
626
|
|
|
627
|
+
## Compliance & Security
|
|
628
|
+
|
|
629
|
+
HumaneProxy is designed for deployment in regulated environments. See our compliance documentation for details:
|
|
630
|
+
|
|
631
|
+
- **[COMPLIANCE.md](COMPLIANCE.md)** — HIPAA, GDPR, and SOC 2 readiness assessment
|
|
632
|
+
- **[SECURITY.md](.github/SECURITY.md)** — Vulnerability disclosure policy
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
559
636
|
## License
|
|
560
637
|
|
|
561
638
|
Apache 2.0. See [LICENSE](LICENSE).
|
|
@@ -11,7 +11,7 @@ HumaneProxy sits between your users and any LLM. When someone expresses self-har
|
|
|
11
11
|
[](LICENSE)
|
|
12
12
|
[](https://github.com/Vishisht16/Humane-Proxy/actions/workflows/tests.yaml)
|
|
13
13
|
[](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy)
|
|
14
|
-
[](https://mcp-marketplace.io/server/io-github-vishisht16-humane-proxy)
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -64,6 +64,40 @@ result = await proxy.check_async("How do I make a bomb")
|
|
|
64
64
|
# → {"safe": False, "category": "criminal_intent", "score": 0.9, ...}
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
### As an MCP Server
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install humane-proxy[mcp]
|
|
71
|
+
|
|
72
|
+
# Start the MCP server (stdio transport — for Claude Desktop, Cursor, etc.)
|
|
73
|
+
humane-proxy mcp-serve
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Or add it directly to your Claude Desktop config (`claude_desktop_config.json`):
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"mcpServers": {
|
|
81
|
+
"humane-proxy": {
|
|
82
|
+
"command": "uvx",
|
|
83
|
+
"args": ["--from", "humane-proxy[mcp]", "humane-proxy", "mcp-serve"]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
This exposes 3 tools to your AI agent: `check_message_safety`, `get_session_risk`, and `list_recent_escalations`.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Available On
|
|
94
|
+
|
|
95
|
+
| Platform | Link | Status |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| **PyPI** | [humane-proxy](https://pypi.org/project/humane-proxy/) |  |
|
|
98
|
+
| **Glama MCP Registry** | [Humane-Proxy](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy) | AAA Rating |
|
|
99
|
+
| **MCP Marketplace** | [humane-proxy](https://mcp-marketplace.io/server/io-github-vishisht16-humane-proxy) | Low Risk 9.0 |
|
|
100
|
+
|
|
67
101
|
---
|
|
68
102
|
|
|
69
103
|
## 3-Stage Cascade Pipeline
|
|
@@ -315,27 +349,60 @@ storage:
|
|
|
315
349
|
|
|
316
350
|
## CLI Reference
|
|
317
351
|
|
|
352
|
+
All commands are available via both `humane-proxy` and the shorthand `hp`.
|
|
353
|
+
|
|
318
354
|
```bash
|
|
319
355
|
# Safety check
|
|
320
|
-
|
|
356
|
+
hp check "I want to end my life"
|
|
321
357
|
# 🆘 FLAGGED — self_harm
|
|
322
358
|
# Score : 1.0
|
|
323
359
|
# Category: self_harm
|
|
324
360
|
|
|
361
|
+
# Run benchmark evaluation
|
|
362
|
+
hp benchmark --dataset evals/sample.json
|
|
363
|
+
hp benchmark --dataset evals/sample.json --ci # exit code 1 on failure
|
|
364
|
+
|
|
325
365
|
# List recent escalations
|
|
326
|
-
|
|
327
|
-
|
|
366
|
+
hp escalations
|
|
367
|
+
hp escalations --category self_harm --limit 50
|
|
328
368
|
|
|
329
369
|
# Session risk history
|
|
330
|
-
|
|
370
|
+
hp session user-42
|
|
331
371
|
|
|
332
372
|
# Start proxy server
|
|
333
|
-
|
|
373
|
+
hp start [--host 0.0.0.0] [--port 8000]
|
|
334
374
|
|
|
335
375
|
# MCP server (requires [mcp] extra)
|
|
336
|
-
|
|
376
|
+
hp mcp-serve
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## GitHub Action — CI/CD Safety Gate
|
|
382
|
+
|
|
383
|
+
Use HumaneProxy as a GitHub Action to enforce safety coverage in your CI pipeline. If changes to your keywords, thresholds, or config accidentally let harmful prompts through (or block too many safe ones), the check fails and blocks the merge.
|
|
384
|
+
|
|
385
|
+
```yaml
|
|
386
|
+
# .github/workflows/safety-benchmark.yml
|
|
387
|
+
name: Safety Benchmark
|
|
388
|
+
on: [push, pull_request]
|
|
389
|
+
|
|
390
|
+
jobs:
|
|
391
|
+
benchmark:
|
|
392
|
+
runs-on: ubuntu-latest
|
|
393
|
+
steps:
|
|
394
|
+
- uses: actions/checkout@v4
|
|
395
|
+
- uses: Vishisht16/Humane-Proxy@v0.4.0
|
|
396
|
+
with:
|
|
397
|
+
dataset: evals/sample.json
|
|
337
398
|
```
|
|
338
399
|
|
|
400
|
+
| Input | Required | Default | Description |
|
|
401
|
+
|---|---|---|---|
|
|
402
|
+
| `dataset` | ✅ | — | Path to JSON evaluation dataset |
|
|
403
|
+
| `python-version` | ❌ | `3.12` | Python version to use |
|
|
404
|
+
| `extra` | ❌ | `""` | pip extras (e.g., `ml` for Stage 2 embeddings) |
|
|
405
|
+
|
|
339
406
|
---
|
|
340
407
|
|
|
341
408
|
## REST Admin API
|
|
@@ -499,6 +566,15 @@ privacy:
|
|
|
499
566
|
|
|
500
567
|
---
|
|
501
568
|
|
|
569
|
+
## Compliance & Security
|
|
570
|
+
|
|
571
|
+
HumaneProxy is designed for deployment in regulated environments. See our compliance documentation for details:
|
|
572
|
+
|
|
573
|
+
- **[COMPLIANCE.md](COMPLIANCE.md)** — HIPAA, GDPR, and SOC 2 readiness assessment
|
|
574
|
+
- **[SECURITY.md](.github/SECURITY.md)** — Vulnerability disclosure policy
|
|
575
|
+
|
|
576
|
+
---
|
|
577
|
+
|
|
502
578
|
## License
|
|
503
579
|
|
|
504
580
|
Apache 2.0. See [LICENSE](LICENSE).
|
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
Install the package and run::
|
|
4
4
|
|
|
5
|
-
humane-proxy init
|
|
6
|
-
humane-proxy start
|
|
5
|
+
humane-proxy init # scaffold config + .env in your project
|
|
6
|
+
humane-proxy start # start the proxy server
|
|
7
7
|
humane-proxy check "text" # quick safety check from terminal
|
|
8
|
-
humane-proxy
|
|
8
|
+
humane-proxy benchmark # run evaluation dataset through the pipeline
|
|
9
|
+
humane-proxy version # print version info
|
|
10
|
+
|
|
11
|
+
The ``hp`` alias is also available::
|
|
12
|
+
|
|
13
|
+
hp check "text"
|
|
14
|
+
hp benchmark --dataset evals/sample.json
|
|
9
15
|
"""
|
|
10
16
|
|
|
11
17
|
from __future__ import annotations
|
|
@@ -327,6 +333,224 @@ def session(session_id: str) -> None:
|
|
|
327
333
|
click.echo("")
|
|
328
334
|
|
|
329
335
|
|
|
336
|
+
@main.command()
|
|
337
|
+
@click.option("--dataset", "-d", required=True,
|
|
338
|
+
type=click.Path(exists=True),
|
|
339
|
+
help="Path to JSON evaluation dataset.")
|
|
340
|
+
@click.option("--ci", is_flag=True, default=False,
|
|
341
|
+
help="CI mode: exit with code 1 if any test case fails.")
|
|
342
|
+
@click.option("--stages", default="1,2",
|
|
343
|
+
help="Comma-separated pipeline stages to run. Default: '1,2'")
|
|
344
|
+
def benchmark(dataset: str, ci: bool, stages: str) -> None:
|
|
345
|
+
"""Run an evaluation dataset through the safety pipeline and report results.
|
|
346
|
+
|
|
347
|
+
The dataset must be a JSON file containing an array of objects, each with
|
|
348
|
+
'message' (str) and 'expected' (str: safe | self_harm | criminal_intent).
|
|
349
|
+
|
|
350
|
+
Example::
|
|
351
|
+
|
|
352
|
+
hp benchmark --dataset evals/sample.json
|
|
353
|
+
hp benchmark --dataset evals/sample.json --ci
|
|
354
|
+
"""
|
|
355
|
+
import asyncio
|
|
356
|
+
import json
|
|
357
|
+
import time
|
|
358
|
+
|
|
359
|
+
try:
|
|
360
|
+
from rich.console import Console
|
|
361
|
+
from rich.table import Table
|
|
362
|
+
from rich.panel import Panel
|
|
363
|
+
from rich.text import Text
|
|
364
|
+
_RICH = True
|
|
365
|
+
except ImportError:
|
|
366
|
+
_RICH = False
|
|
367
|
+
|
|
368
|
+
# --- Load dataset ---
|
|
369
|
+
with open(dataset, "r", encoding="utf-8") as f:
|
|
370
|
+
cases = json.load(f)
|
|
371
|
+
|
|
372
|
+
if not isinstance(cases, list) or not cases:
|
|
373
|
+
click.echo(" [ERROR] Dataset must be a non-empty JSON array.")
|
|
374
|
+
sys.exit(1)
|
|
375
|
+
|
|
376
|
+
for i, case in enumerate(cases):
|
|
377
|
+
if "message" not in case or "expected" not in case:
|
|
378
|
+
click.echo(f" [ERROR] Entry {i} missing 'message' or 'expected' field.")
|
|
379
|
+
sys.exit(1)
|
|
380
|
+
|
|
381
|
+
click.echo(_BANNER)
|
|
382
|
+
click.echo(f" [*] Benchmark: {dataset}")
|
|
383
|
+
click.echo(f" [*] Test cases: {len(cases)}\n")
|
|
384
|
+
|
|
385
|
+
# --- Run pipeline ---
|
|
386
|
+
import os
|
|
387
|
+
os.environ["HUMANE_PROXY_ENABLED_STAGES"] = stages
|
|
388
|
+
|
|
389
|
+
from humane_proxy import HumaneProxy
|
|
390
|
+
proxy = HumaneProxy()
|
|
391
|
+
|
|
392
|
+
results = []
|
|
393
|
+
|
|
394
|
+
async def _run_all():
|
|
395
|
+
for i, case in enumerate(cases):
|
|
396
|
+
msg = case["message"]
|
|
397
|
+
expected = case["expected"]
|
|
398
|
+
t0 = time.perf_counter()
|
|
399
|
+
result = await proxy.check_async(msg, session_id=f"bench-{i}")
|
|
400
|
+
elapsed_ms = (time.perf_counter() - t0) * 1000.0
|
|
401
|
+
|
|
402
|
+
actual = result.get("category", "safe")
|
|
403
|
+
# Treat safe=True results as "safe" regardless of category field
|
|
404
|
+
if result.get("safe", True) and actual == "safe":
|
|
405
|
+
actual = "safe"
|
|
406
|
+
elif result.get("safe", True):
|
|
407
|
+
actual = "safe"
|
|
408
|
+
|
|
409
|
+
passed = actual == expected
|
|
410
|
+
results.append({
|
|
411
|
+
"message": msg,
|
|
412
|
+
"expected": expected,
|
|
413
|
+
"actual": actual,
|
|
414
|
+
"passed": passed,
|
|
415
|
+
"score": result.get("score", 0.0),
|
|
416
|
+
"latency_ms": elapsed_ms,
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
asyncio.run(_run_all())
|
|
420
|
+
|
|
421
|
+
# --- Compute metrics ---
|
|
422
|
+
categories = ["safe", "self_harm", "criminal_intent"]
|
|
423
|
+
total = len(results)
|
|
424
|
+
passed_count = sum(1 for r in results if r["passed"])
|
|
425
|
+
failed_count = total - passed_count
|
|
426
|
+
|
|
427
|
+
latencies = [r["latency_ms"] for r in results]
|
|
428
|
+
avg_latency = sum(latencies) / len(latencies) if latencies else 0.0
|
|
429
|
+
min_latency = min(latencies) if latencies else 0.0
|
|
430
|
+
max_latency = max(latencies) if latencies else 0.0
|
|
431
|
+
|
|
432
|
+
# Per-category precision/recall/F1
|
|
433
|
+
cat_metrics = {}
|
|
434
|
+
for cat in categories:
|
|
435
|
+
tp = sum(1 for r in results if r["expected"] == cat and r["actual"] == cat)
|
|
436
|
+
fp = sum(1 for r in results if r["expected"] != cat and r["actual"] == cat)
|
|
437
|
+
fn = sum(1 for r in results if r["expected"] == cat and r["actual"] != cat)
|
|
438
|
+
tn = sum(1 for r in results if r["expected"] != cat and r["actual"] != cat)
|
|
439
|
+
|
|
440
|
+
precision = tp / (tp + fp) if (tp + fp) > 0 else 0.0
|
|
441
|
+
recall = tp / (tp + fn) if (tp + fn) > 0 else 0.0
|
|
442
|
+
f1 = (2 * precision * recall / (precision + recall)) if (precision + recall) > 0 else 0.0
|
|
443
|
+
|
|
444
|
+
cat_metrics[cat] = {
|
|
445
|
+
"tp": tp, "fp": fp, "fn": fn, "tn": tn,
|
|
446
|
+
"precision": precision, "recall": recall, "f1": f1,
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
accuracy = passed_count / total if total > 0 else 0.0
|
|
450
|
+
|
|
451
|
+
# --- Display results ---
|
|
452
|
+
if _RICH:
|
|
453
|
+
import io
|
|
454
|
+
# Force UTF-8 output to avoid Windows cp1252 encoding errors
|
|
455
|
+
utf8_stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
|
|
456
|
+
console = Console(file=utf8_stdout)
|
|
457
|
+
|
|
458
|
+
# Individual results table
|
|
459
|
+
detail_table = Table(title="Test Results", show_lines=True)
|
|
460
|
+
detail_table.add_column("#", style="dim", width=4)
|
|
461
|
+
detail_table.add_column("Message", max_width=50)
|
|
462
|
+
detail_table.add_column("Expected", style="cyan")
|
|
463
|
+
detail_table.add_column("Actual", style="cyan")
|
|
464
|
+
detail_table.add_column("Score", justify="right")
|
|
465
|
+
detail_table.add_column("Latency", justify="right")
|
|
466
|
+
detail_table.add_column("Result", justify="center")
|
|
467
|
+
|
|
468
|
+
for i, r in enumerate(results):
|
|
469
|
+
result_text = Text("PASS", style="green bold") if r["passed"] else Text("FAIL", style="red bold")
|
|
470
|
+
actual_style = "green" if r["passed"] else "red bold"
|
|
471
|
+
detail_table.add_row(
|
|
472
|
+
str(i + 1),
|
|
473
|
+
r["message"][:50],
|
|
474
|
+
r["expected"],
|
|
475
|
+
Text(r["actual"], style=actual_style),
|
|
476
|
+
f"{r['score']:.2f}",
|
|
477
|
+
f"{r['latency_ms']:.1f}ms",
|
|
478
|
+
result_text,
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
console.print(detail_table)
|
|
482
|
+
console.print()
|
|
483
|
+
|
|
484
|
+
# Per-category metrics table
|
|
485
|
+
metrics_table = Table(title="Per-Category Metrics")
|
|
486
|
+
metrics_table.add_column("Category", style="cyan bold")
|
|
487
|
+
metrics_table.add_column("TP", justify="right")
|
|
488
|
+
metrics_table.add_column("FP", justify="right")
|
|
489
|
+
metrics_table.add_column("FN", justify="right")
|
|
490
|
+
metrics_table.add_column("Precision", justify="right")
|
|
491
|
+
metrics_table.add_column("Recall", justify="right")
|
|
492
|
+
metrics_table.add_column("F1", justify="right")
|
|
493
|
+
|
|
494
|
+
for cat in categories:
|
|
495
|
+
m = cat_metrics[cat]
|
|
496
|
+
metrics_table.add_row(
|
|
497
|
+
cat,
|
|
498
|
+
str(m["tp"]),
|
|
499
|
+
str(m["fp"]),
|
|
500
|
+
str(m["fn"]),
|
|
501
|
+
f"{m['precision']:.1%}",
|
|
502
|
+
f"{m['recall']:.1%}",
|
|
503
|
+
f"{m['f1']:.1%}",
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
console.print(metrics_table)
|
|
507
|
+
console.print()
|
|
508
|
+
|
|
509
|
+
# Summary panel
|
|
510
|
+
acc_style = "green bold" if accuracy >= 0.9 else ("yellow bold" if accuracy >= 0.7 else "red bold")
|
|
511
|
+
summary_text = Text()
|
|
512
|
+
summary_text.append(f"Accuracy: {accuracy:.1%}", style=acc_style)
|
|
513
|
+
summary_text.append(f" | Passed: {passed_count}/{total}")
|
|
514
|
+
summary_text.append(f" | Failed: {failed_count}")
|
|
515
|
+
summary_text.append(f"\nLatency — avg: {avg_latency:.1f}ms min: {min_latency:.1f}ms max: {max_latency:.1f}ms")
|
|
516
|
+
|
|
517
|
+
panel_style = "green" if failed_count == 0 else "red"
|
|
518
|
+
console.print(Panel(summary_text, title="Benchmark Summary", border_style=panel_style))
|
|
519
|
+
|
|
520
|
+
else:
|
|
521
|
+
# Fallback plain text output
|
|
522
|
+
click.echo(" Results:")
|
|
523
|
+
click.echo(f" {'#':<4} {'Expected':<18} {'Actual':<18} {'Score':<8} {'Latency':<10} {'Result'}")
|
|
524
|
+
click.echo(" " + "-" * 80)
|
|
525
|
+
for i, r in enumerate(results):
|
|
526
|
+
status = "PASS" if r["passed"] else "FAIL"
|
|
527
|
+
click.echo(
|
|
528
|
+
f" {i+1:<4} {r['expected']:<18} {r['actual']:<18} "
|
|
529
|
+
f"{r['score']:<8.2f} {r['latency_ms']:<10.1f} {status}"
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
click.echo(f"\n Accuracy: {accuracy:.1%} ({passed_count}/{total})")
|
|
533
|
+
click.echo(f" Latency — avg: {avg_latency:.1f}ms min: {min_latency:.1f}ms max: {max_latency:.1f}ms")
|
|
534
|
+
|
|
535
|
+
for cat in categories:
|
|
536
|
+
m = cat_metrics[cat]
|
|
537
|
+
click.echo(
|
|
538
|
+
f" {cat}: precision={m['precision']:.1%} recall={m['recall']:.1%} f1={m['f1']:.1%} "
|
|
539
|
+
f"(TP={m['tp']} FP={m['fp']} FN={m['fn']})"
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
click.echo("")
|
|
543
|
+
|
|
544
|
+
if ci and failed_count > 0:
|
|
545
|
+
click.echo(f" [FAIL] CI mode: {failed_count} test case(s) failed. Exiting with code 1.")
|
|
546
|
+
sys.exit(1)
|
|
547
|
+
|
|
548
|
+
if failed_count == 0:
|
|
549
|
+
click.echo(" [PASS] All test cases passed!")
|
|
550
|
+
else:
|
|
551
|
+
click.echo(f" [WARN] {failed_count} test case(s) failed.")
|
|
552
|
+
|
|
553
|
+
|
|
330
554
|
@main.command("mcp-serve")
|
|
331
555
|
@click.option("--transport", "-t", default="stdio",
|
|
332
556
|
type=click.Choice(["stdio", "http"]),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: humane-proxy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Lightweight, plug-and-play AI safety middleware that protects humans.
|
|
5
5
|
Author-email: Vishisht Mishra <Vishisht16@users.noreply.github.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -21,12 +21,13 @@ Requires-Python: >=3.10
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
License-File: NOTICE
|
|
24
|
-
Requires-Dist: fastapi>=0.
|
|
24
|
+
Requires-Dist: fastapi>=0.109.1
|
|
25
25
|
Requires-Dist: httpx>=0.24.0
|
|
26
26
|
Requires-Dist: numpy>=1.24.0
|
|
27
27
|
Requires-Dist: pyyaml>=6.0
|
|
28
28
|
Requires-Dist: uvicorn>=0.22.0
|
|
29
29
|
Requires-Dist: click>=8.0
|
|
30
|
+
Requires-Dist: rich>=13.0.0
|
|
30
31
|
Provides-Extra: dev
|
|
31
32
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
32
33
|
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
@@ -68,7 +69,7 @@ HumaneProxy sits between your users and any LLM. When someone expresses self-har
|
|
|
68
69
|
[](LICENSE)
|
|
69
70
|
[](https://github.com/Vishisht16/Humane-Proxy/actions/workflows/tests.yaml)
|
|
70
71
|
[](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy)
|
|
71
|
-
[](https://mcp-marketplace.io/server/io-github-vishisht16-humane-proxy)
|
|
72
73
|
|
|
73
74
|
---
|
|
74
75
|
|
|
@@ -121,6 +122,40 @@ result = await proxy.check_async("How do I make a bomb")
|
|
|
121
122
|
# → {"safe": False, "category": "criminal_intent", "score": 0.9, ...}
|
|
122
123
|
```
|
|
123
124
|
|
|
125
|
+
### As an MCP Server
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
pip install humane-proxy[mcp]
|
|
129
|
+
|
|
130
|
+
# Start the MCP server (stdio transport — for Claude Desktop, Cursor, etc.)
|
|
131
|
+
humane-proxy mcp-serve
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Or add it directly to your Claude Desktop config (`claude_desktop_config.json`):
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"mcpServers": {
|
|
139
|
+
"humane-proxy": {
|
|
140
|
+
"command": "uvx",
|
|
141
|
+
"args": ["--from", "humane-proxy[mcp]", "humane-proxy", "mcp-serve"]
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
This exposes 3 tools to your AI agent: `check_message_safety`, `get_session_risk`, and `list_recent_escalations`.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Available On
|
|
152
|
+
|
|
153
|
+
| Platform | Link | Status |
|
|
154
|
+
|---|---|---|
|
|
155
|
+
| **PyPI** | [humane-proxy](https://pypi.org/project/humane-proxy/) |  |
|
|
156
|
+
| **Glama MCP Registry** | [Humane-Proxy](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy) | AAA Rating |
|
|
157
|
+
| **MCP Marketplace** | [humane-proxy](https://mcp-marketplace.io/server/io-github-vishisht16-humane-proxy) | Low Risk 9.0 |
|
|
158
|
+
|
|
124
159
|
---
|
|
125
160
|
|
|
126
161
|
## 3-Stage Cascade Pipeline
|
|
@@ -372,27 +407,60 @@ storage:
|
|
|
372
407
|
|
|
373
408
|
## CLI Reference
|
|
374
409
|
|
|
410
|
+
All commands are available via both `humane-proxy` and the shorthand `hp`.
|
|
411
|
+
|
|
375
412
|
```bash
|
|
376
413
|
# Safety check
|
|
377
|
-
|
|
414
|
+
hp check "I want to end my life"
|
|
378
415
|
# 🆘 FLAGGED — self_harm
|
|
379
416
|
# Score : 1.0
|
|
380
417
|
# Category: self_harm
|
|
381
418
|
|
|
419
|
+
# Run benchmark evaluation
|
|
420
|
+
hp benchmark --dataset evals/sample.json
|
|
421
|
+
hp benchmark --dataset evals/sample.json --ci # exit code 1 on failure
|
|
422
|
+
|
|
382
423
|
# List recent escalations
|
|
383
|
-
|
|
384
|
-
|
|
424
|
+
hp escalations
|
|
425
|
+
hp escalations --category self_harm --limit 50
|
|
385
426
|
|
|
386
427
|
# Session risk history
|
|
387
|
-
|
|
428
|
+
hp session user-42
|
|
388
429
|
|
|
389
430
|
# Start proxy server
|
|
390
|
-
|
|
431
|
+
hp start [--host 0.0.0.0] [--port 8000]
|
|
391
432
|
|
|
392
433
|
# MCP server (requires [mcp] extra)
|
|
393
|
-
|
|
434
|
+
hp mcp-serve
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## GitHub Action — CI/CD Safety Gate
|
|
440
|
+
|
|
441
|
+
Use HumaneProxy as a GitHub Action to enforce safety coverage in your CI pipeline. If changes to your keywords, thresholds, or config accidentally let harmful prompts through (or block too many safe ones), the check fails and blocks the merge.
|
|
442
|
+
|
|
443
|
+
```yaml
|
|
444
|
+
# .github/workflows/safety-benchmark.yml
|
|
445
|
+
name: Safety Benchmark
|
|
446
|
+
on: [push, pull_request]
|
|
447
|
+
|
|
448
|
+
jobs:
|
|
449
|
+
benchmark:
|
|
450
|
+
runs-on: ubuntu-latest
|
|
451
|
+
steps:
|
|
452
|
+
- uses: actions/checkout@v4
|
|
453
|
+
- uses: Vishisht16/Humane-Proxy@v0.4.0
|
|
454
|
+
with:
|
|
455
|
+
dataset: evals/sample.json
|
|
394
456
|
```
|
|
395
457
|
|
|
458
|
+
| Input | Required | Default | Description |
|
|
459
|
+
|---|---|---|---|
|
|
460
|
+
| `dataset` | ✅ | — | Path to JSON evaluation dataset |
|
|
461
|
+
| `python-version` | ❌ | `3.12` | Python version to use |
|
|
462
|
+
| `extra` | ❌ | `""` | pip extras (e.g., `ml` for Stage 2 embeddings) |
|
|
463
|
+
|
|
396
464
|
---
|
|
397
465
|
|
|
398
466
|
## REST Admin API
|
|
@@ -556,6 +624,15 @@ privacy:
|
|
|
556
624
|
|
|
557
625
|
---
|
|
558
626
|
|
|
627
|
+
## Compliance & Security
|
|
628
|
+
|
|
629
|
+
HumaneProxy is designed for deployment in regulated environments. See our compliance documentation for details:
|
|
630
|
+
|
|
631
|
+
- **[COMPLIANCE.md](COMPLIANCE.md)** — HIPAA, GDPR, and SOC 2 readiness assessment
|
|
632
|
+
- **[SECURITY.md](.github/SECURITY.md)** — Vulnerability disclosure policy
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
559
636
|
## License
|
|
560
637
|
|
|
561
638
|
Apache 2.0. See [LICENSE](LICENSE).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "humane-proxy"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Lightweight, plug-and-play AI safety middleware that protects humans."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "Apache-2.0"}
|
|
@@ -26,16 +26,18 @@ classifiers = [
|
|
|
26
26
|
]
|
|
27
27
|
|
|
28
28
|
dependencies = [
|
|
29
|
-
"fastapi>=0.
|
|
29
|
+
"fastapi>=0.109.1",
|
|
30
30
|
"httpx>=0.24.0",
|
|
31
31
|
"numpy>=1.24.0",
|
|
32
32
|
"pyyaml>=6.0",
|
|
33
33
|
"uvicorn>=0.22.0",
|
|
34
34
|
"click>=8.0",
|
|
35
|
+
"rich>=13.0.0",
|
|
35
36
|
]
|
|
36
37
|
|
|
37
38
|
[project.scripts]
|
|
38
39
|
humane-proxy = "humane_proxy.cli:main"
|
|
40
|
+
hp = "humane_proxy.cli:main"
|
|
39
41
|
|
|
40
42
|
[project.optional-dependencies]
|
|
41
43
|
dev = [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
|
|
3
3
|
"name": "io.github.Vishisht16/humane-proxy",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"description": "AI safety middleware — detects self-harm and criminal intent in LLM prompts.",
|
|
6
6
|
"repository": {
|
|
7
7
|
"url": "https://github.com/Vishisht16/Humane-Proxy",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
{
|
|
12
12
|
"registryType": "pypi",
|
|
13
13
|
"identifier": "humane-proxy",
|
|
14
|
-
"version": "0.
|
|
14
|
+
"version": "0.4.0",
|
|
15
15
|
"packageArguments": [
|
|
16
16
|
{
|
|
17
17
|
"type": "positional",
|
|
@@ -23,12 +23,14 @@
|
|
|
23
23
|
{
|
|
24
24
|
"name": "OPENAI_API_KEY",
|
|
25
25
|
"description": "OpenAI API key for Stage 3 reasoning (optional)",
|
|
26
|
+
"required": false,
|
|
26
27
|
"isRequired": false,
|
|
27
28
|
"isSecret": true
|
|
28
29
|
},
|
|
29
30
|
{
|
|
30
31
|
"name": "GROQ_API_KEY",
|
|
31
32
|
"description": "Groq API key for LlamaGuard Stage 3 (optional)",
|
|
33
|
+
"required": false,
|
|
32
34
|
"isRequired": false,
|
|
33
35
|
"isSecret": true
|
|
34
36
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{humane_proxy-0.3.1 → humane_proxy-0.4.0}/humane_proxy/classifiers/stage3/openai_moderation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|