humane-proxy 0.2.3__tar.gz → 0.3.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.2.3 → humane_proxy-0.3.0}/PKG-INFO +69 -12
- humane_proxy-0.2.3/humane_proxy.egg-info/PKG-INFO → humane_proxy-0.3.0/README.md +56 -56
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/__init__.py +1 -1
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/api/admin.py +166 -2
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/embedding_classifier.py +104 -11
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/pipeline.py +14 -3
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/config.py +5 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/config.yaml +32 -0
- humane_proxy-0.3.0/humane_proxy/escalation/local_db.py +68 -0
- humane_proxy-0.3.0/humane_proxy/integrations/autogen.py +160 -0
- humane_proxy-0.3.0/humane_proxy/integrations/crewai.py +131 -0
- humane_proxy-0.3.0/humane_proxy/integrations/llamaindex.py +138 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/mcp_server.py +1 -6
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/middleware/interceptor.py +1 -1
- humane_proxy-0.3.0/humane_proxy/storage/__init__.py +15 -0
- humane_proxy-0.3.0/humane_proxy/storage/base.py +75 -0
- humane_proxy-0.3.0/humane_proxy/storage/factory.py +75 -0
- humane_proxy-0.3.0/humane_proxy/storage/postgres.py +212 -0
- humane_proxy-0.3.0/humane_proxy/storage/redis.py +193 -0
- humane_proxy-0.3.0/humane_proxy/storage/sqlite.py +242 -0
- humane_proxy-0.2.3/README.md → humane_proxy-0.3.0/humane_proxy.egg-info/PKG-INFO +113 -10
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy.egg-info/SOURCES.txt +13 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy.egg-info/requires.txt +17 -1
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/pyproject.toml +18 -2
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/server.json +3 -3
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_embedding_classifier.py +8 -2
- humane_proxy-0.3.0/tests/test_embedding_singleton.py +77 -0
- humane_proxy-0.3.0/tests/test_integrations_smoke.py +76 -0
- humane_proxy-0.3.0/tests/test_self_harm_threshold.py +51 -0
- humane_proxy-0.3.0/tests/test_storage_backends.py +81 -0
- humane_proxy-0.2.3/humane_proxy/escalation/local_db.py +0 -170
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/LICENSE +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/NOTICE +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/api/__init__.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/__init__.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/heuristics.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/models.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/stage3/__init__.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/stage3/base.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/stage3/llamaguard.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/stage3/openai_chat.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/stage3/openai_moderation.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/cli.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/escalation/__init__.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/escalation/router.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/escalation/webhooks.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/integrations/__init__.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/integrations/langchain.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/middleware/__init__.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/risk/__init__.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/risk/trajectory.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy.egg-info/dependency_links.txt +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy.egg-info/entry_points.txt +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy.egg-info/top_level.txt +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/setup.cfg +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_admin_api.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_care_response.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_cli.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_config.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_enhanced_webhooks.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_heuristics.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_interceptor.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_local_db.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_models.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_pipeline.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_router.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_stage3.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_trajectory.py +0 -0
- {humane_proxy-0.2.3 → humane_proxy-0.3.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.3.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
|
|
@@ -40,8 +40,19 @@ Requires-Dist: humane-proxy[mcp]; extra == "langchain"
|
|
|
40
40
|
Requires-Dist: langchain-mcp-adapters>=0.1.0; extra == "langchain"
|
|
41
41
|
Provides-Extra: webhooks
|
|
42
42
|
Requires-Dist: httpx>=0.24.0; extra == "webhooks"
|
|
43
|
+
Provides-Extra: redis
|
|
44
|
+
Requires-Dist: redis>=5.0; extra == "redis"
|
|
45
|
+
Provides-Extra: postgres
|
|
46
|
+
Requires-Dist: psycopg[binary]>=3.1; extra == "postgres"
|
|
47
|
+
Requires-Dist: psycopg_pool>=3.1; extra == "postgres"
|
|
48
|
+
Provides-Extra: llamaindex
|
|
49
|
+
Requires-Dist: llama-index-core>=0.10; extra == "llamaindex"
|
|
50
|
+
Provides-Extra: crewai
|
|
51
|
+
Requires-Dist: crewai[tools]>=0.80; extra == "crewai"
|
|
52
|
+
Provides-Extra: autogen
|
|
53
|
+
Requires-Dist: autogen-agentchat>=0.4; extra == "autogen"
|
|
43
54
|
Provides-Extra: all
|
|
44
|
-
Requires-Dist: humane-proxy[langchain,mcp,ml]; extra == "all"
|
|
55
|
+
Requires-Dist: humane-proxy[autogen,crewai,langchain,llamaindex,mcp,ml,postgres,redis]; extra == "all"
|
|
45
56
|
Dynamic: license-file
|
|
46
57
|
|
|
47
58
|
# 🛡️ HumaneProxy
|
|
@@ -56,8 +67,8 @@ HumaneProxy sits between your users and any LLM. When someone expresses self-har
|
|
|
56
67
|
[](https://pypi.org/project/humane-proxy/)
|
|
57
68
|
[](LICENSE)
|
|
58
69
|
[](https://github.com/Vishisht16/Humane-Proxy/actions/workflows/tests.yaml)
|
|
59
|
-
[](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy)
|
|
60
70
|
[](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy)
|
|
71
|
+
[](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy)
|
|
61
72
|
|
|
62
73
|
---
|
|
63
74
|
|
|
@@ -234,6 +245,10 @@ HumaneProxy returns an empathetic message with crisis resources for 10+ countrie
|
|
|
234
245
|
safety:
|
|
235
246
|
categories:
|
|
236
247
|
self_harm:
|
|
248
|
+
# Self-harm escalation threshold (0.0 to 1.0).
|
|
249
|
+
# Scores below this are downgraded to safe.
|
|
250
|
+
escalate_threshold: 0.5
|
|
251
|
+
|
|
237
252
|
response_mode: "block" # default
|
|
238
253
|
|
|
239
254
|
# Optional: override the built-in message
|
|
@@ -284,6 +299,10 @@ escalation:
|
|
|
284
299
|
to:
|
|
285
300
|
- "safety-team@yourorg.com"
|
|
286
301
|
- "oncall@yourorg.com"
|
|
302
|
+
|
|
303
|
+
# Swappable Storage Backend (sqlite config default, redis/postgres optional)
|
|
304
|
+
storage:
|
|
305
|
+
backend: "sqlite" # or "redis", "postgres"
|
|
287
306
|
```
|
|
288
307
|
|
|
289
308
|
---
|
|
@@ -333,10 +352,13 @@ curl -X DELETE http://localhost:8000/admin/sessions/user-42 \
|
|
|
333
352
|
|
|
334
353
|
| Endpoint | Description |
|
|
335
354
|
|---|---|
|
|
336
|
-
| `GET /admin/
|
|
355
|
+
| `GET /admin/health` | Health check (no auth required) |
|
|
356
|
+
| `GET /admin/config` | Active config view (secrets redacted) |
|
|
357
|
+
| `GET /admin/escalations` | Paginated list, filterable by `category`, `session_id`, `date`, sortable |
|
|
358
|
+
| `GET /admin/escalations/export` | CSV export of escalations |
|
|
337
359
|
| `GET /admin/escalations/{id}` | Single escalation detail |
|
|
338
360
|
| `GET /admin/sessions/{id}/risk` | Session history + trajectory |
|
|
339
|
-
| `GET /admin/stats` | Aggregate counts
|
|
361
|
+
| `GET /admin/stats` | Aggregate counts, top sessions, hourly breakdown |
|
|
340
362
|
| `DELETE /admin/sessions/{id}` | Delete all session records |
|
|
341
363
|
|
|
342
364
|
---
|
|
@@ -361,10 +383,38 @@ Available on the [Official MCP Registry](https://registry.modelcontextprotocol.i
|
|
|
361
383
|
|
|
362
384
|
---
|
|
363
385
|
|
|
364
|
-
##
|
|
386
|
+
## AI Agent Integrations
|
|
387
|
+
|
|
388
|
+
HumaneProxy tools can be natively plugged into standard agentic frameworks:
|
|
389
|
+
|
|
390
|
+
### LlamaIndex
|
|
391
|
+
```bash
|
|
392
|
+
pip install humane-proxy[llamaindex]
|
|
393
|
+
```
|
|
394
|
+
```python
|
|
395
|
+
from humane_proxy.integrations.llamaindex import get_safety_tools
|
|
396
|
+
tools = get_safety_tools() # Native FunctionTool instances
|
|
397
|
+
```
|
|
365
398
|
|
|
366
|
-
|
|
399
|
+
### CrewAI
|
|
400
|
+
```bash
|
|
401
|
+
pip install humane-proxy[crewai]
|
|
402
|
+
```
|
|
403
|
+
```python
|
|
404
|
+
from humane_proxy.integrations.crewai import get_safety_tools
|
|
405
|
+
tools = get_safety_tools() # Native BaseTool subclass instances
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### AutoGen (AG2)
|
|
409
|
+
```bash
|
|
410
|
+
pip install humane-proxy[autogen]
|
|
411
|
+
```
|
|
412
|
+
```python
|
|
413
|
+
from humane_proxy.integrations.autogen import register_safety_tools
|
|
414
|
+
register_safety_tools(assistant, user_proxy)
|
|
415
|
+
```
|
|
367
416
|
|
|
417
|
+
### LangChain
|
|
368
418
|
```bash
|
|
369
419
|
pip install humane-proxy[langchain]
|
|
370
420
|
```
|
|
@@ -390,15 +440,17 @@ All values can be set in `humane_proxy.yaml` (project root) or via `HUMANE_PROXY
|
|
|
390
440
|
| YAML key | Env var | Default | Description |
|
|
391
441
|
|---|---|---|---|
|
|
392
442
|
| `safety.risk_threshold` | `HUMANE_PROXY_RISK_THRESHOLD` | `0.7` | Score threshold for criminal_intent escalation |
|
|
393
|
-
| `safety.
|
|
443
|
+
| `safety.categories.self_harm.escalate_threshold` | `HUMANE_PROXY_SELF_HARM_THRESHOLD` | `0.5` | Score threshold for self_harm escalation |
|
|
444
|
+
| `safety.spike_boost` | `HUMANE_PROXY_SPIKE_BOOST` | `0.25` | Score boost on trajectory spike |
|
|
394
445
|
| `server.port` | `HUMANE_PROXY_PORT` | `8000` | Proxy port |
|
|
395
|
-
| `pipeline.enabled_stages` | `HUMANE_PROXY_ENABLED_STAGES` | `[1]` | Active stages |
|
|
446
|
+
| `pipeline.enabled_stages` | `HUMANE_PROXY_ENABLED_STAGES` | `[1]` | Active stages (e.g. `1,2,3`) |
|
|
396
447
|
| `pipeline.stage1_ceiling` | `HUMANE_PROXY_STAGE1_CEILING` | `0.3` | Early exit after Stage 1 |
|
|
397
448
|
| `pipeline.stage2_ceiling` | `HUMANE_PROXY_STAGE2_CEILING` | `0.4` | Early exit after Stage 2 |
|
|
398
449
|
| `stage3.provider` | `HUMANE_PROXY_STAGE3_PROVIDER` | `"auto"` | Stage 3 provider |
|
|
399
450
|
| `stage3.timeout` | `HUMANE_PROXY_STAGE3_TIMEOUT` | `10` | Stage 3 timeout (s) |
|
|
400
451
|
| `privacy.store_message_text` | — | `false` | Store raw text (vs SHA-256 hash) |
|
|
401
|
-
| `escalation.rate_limit_max` |
|
|
452
|
+
| `escalation.rate_limit_max` | `HUMANE_PROXY_RATE_LIMIT_MAX` | `3` | Max alerts per session/window |
|
|
453
|
+
| `storage.backend` | `HUMANE_PROXY_STORAGE_BACKEND` | `"sqlite"` | `"sqlite"`, `"redis"`, `"postgres"` |
|
|
402
454
|
| `safety.categories.self_harm.response_mode` | — | `"block"` | `"block"` or `"forward"` |
|
|
403
455
|
|
|
404
456
|
---
|
|
@@ -428,11 +480,16 @@ privacy:
|
|
|
428
480
|
|
|
429
481
|
| Extra | Command | What it adds |
|
|
430
482
|
|---|---|---|
|
|
431
|
-
| *(none)* | `pip install humane-proxy` | Stage 1 heuristics +
|
|
483
|
+
| *(none)* | `pip install humane-proxy` | Stage 1 heuristics + default SQLite storage |
|
|
432
484
|
| `ml` | `pip install humane-proxy[ml]` | Stage 2 semantic embeddings (`sentence-transformers`) |
|
|
433
485
|
| `mcp` | `pip install humane-proxy[mcp]` | MCP server for AI agent integration (`fastmcp`) |
|
|
486
|
+
| `redis` | `pip install humane-proxy[redis]` | Redis storage backend (`redis`) |
|
|
487
|
+
| `postgres` | `pip install humane-proxy[postgres]` | PostgreSQL storage backend (`psycopg`, `psycopg_pool`) |
|
|
488
|
+
| `llamaindex` | `pip install humane-proxy[llamaindex]` | LlamaIndex native integration (`llama-index-core`) |
|
|
489
|
+
| `crewai` | `pip install humane-proxy[crewai]` | CrewAI native integration (`crewai[tools]`) |
|
|
490
|
+
| `autogen` | `pip install humane-proxy[autogen]` | AutoGen native integration (`autogen-agentchat`) |
|
|
434
491
|
| `langchain` | `pip install humane-proxy[langchain]` | LangChain adapter (MCP + `langchain-mcp-adapters`) |
|
|
435
|
-
| `all` | `pip install humane-proxy[all]` |
|
|
492
|
+
| `all` | `pip install humane-proxy[all]` | Includes ALL optional dependencies above |
|
|
436
493
|
|
|
437
494
|
---
|
|
438
495
|
|
|
@@ -1,49 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: humane-proxy
|
|
3
|
-
Version: 0.2.3
|
|
4
|
-
Summary: Lightweight, plug-and-play AI safety middleware that protects humans.
|
|
5
|
-
Author-email: Vishisht Mishra <Vishisht16@users.noreply.github.com>
|
|
6
|
-
License: Apache-2.0
|
|
7
|
-
Project-URL: Homepage, https://github.com/Vishisht16/Humane-Proxy
|
|
8
|
-
Project-URL: Repository, https://github.com/Vishisht16/Humane-Proxy
|
|
9
|
-
Project-URL: Issues, https://github.com/Vishisht16/Humane-Proxy/issues
|
|
10
|
-
Keywords: ai-safety,llm,middleware,guardrails,human-safety
|
|
11
|
-
Classifier: Development Status :: 3 - Alpha
|
|
12
|
-
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
-
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
-
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
-
Classifier: Topic :: Security
|
|
20
|
-
Requires-Python: >=3.10
|
|
21
|
-
Description-Content-Type: text/markdown
|
|
22
|
-
License-File: LICENSE
|
|
23
|
-
License-File: NOTICE
|
|
24
|
-
Requires-Dist: fastapi>=0.100.0
|
|
25
|
-
Requires-Dist: httpx>=0.24.0
|
|
26
|
-
Requires-Dist: numpy>=1.24.0
|
|
27
|
-
Requires-Dist: pyyaml>=6.0
|
|
28
|
-
Requires-Dist: uvicorn>=0.22.0
|
|
29
|
-
Requires-Dist: click>=8.0
|
|
30
|
-
Provides-Extra: dev
|
|
31
|
-
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
32
|
-
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
33
|
-
Requires-Dist: httpx; extra == "dev"
|
|
34
|
-
Provides-Extra: ml
|
|
35
|
-
Requires-Dist: sentence-transformers>=2.2.0; extra == "ml"
|
|
36
|
-
Provides-Extra: mcp
|
|
37
|
-
Requires-Dist: fastmcp>=0.4.0; extra == "mcp"
|
|
38
|
-
Provides-Extra: langchain
|
|
39
|
-
Requires-Dist: humane-proxy[mcp]; extra == "langchain"
|
|
40
|
-
Requires-Dist: langchain-mcp-adapters>=0.1.0; extra == "langchain"
|
|
41
|
-
Provides-Extra: webhooks
|
|
42
|
-
Requires-Dist: httpx>=0.24.0; extra == "webhooks"
|
|
43
|
-
Provides-Extra: all
|
|
44
|
-
Requires-Dist: humane-proxy[langchain,mcp,ml]; extra == "all"
|
|
45
|
-
Dynamic: license-file
|
|
46
|
-
|
|
47
1
|
# 🛡️ HumaneProxy
|
|
48
2
|
|
|
49
3
|
<!-- mcp-name: io.github.Vishisht16/humane-proxy -->
|
|
@@ -56,8 +10,8 @@ HumaneProxy sits between your users and any LLM. When someone expresses self-har
|
|
|
56
10
|
[](https://pypi.org/project/humane-proxy/)
|
|
57
11
|
[](LICENSE)
|
|
58
12
|
[](https://github.com/Vishisht16/Humane-Proxy/actions/workflows/tests.yaml)
|
|
59
|
-
[](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy)
|
|
60
13
|
[](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy)
|
|
14
|
+
[](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy)
|
|
61
15
|
|
|
62
16
|
---
|
|
63
17
|
|
|
@@ -234,6 +188,10 @@ HumaneProxy returns an empathetic message with crisis resources for 10+ countrie
|
|
|
234
188
|
safety:
|
|
235
189
|
categories:
|
|
236
190
|
self_harm:
|
|
191
|
+
# Self-harm escalation threshold (0.0 to 1.0).
|
|
192
|
+
# Scores below this are downgraded to safe.
|
|
193
|
+
escalate_threshold: 0.5
|
|
194
|
+
|
|
237
195
|
response_mode: "block" # default
|
|
238
196
|
|
|
239
197
|
# Optional: override the built-in message
|
|
@@ -284,6 +242,10 @@ escalation:
|
|
|
284
242
|
to:
|
|
285
243
|
- "safety-team@yourorg.com"
|
|
286
244
|
- "oncall@yourorg.com"
|
|
245
|
+
|
|
246
|
+
# Swappable Storage Backend (sqlite config default, redis/postgres optional)
|
|
247
|
+
storage:
|
|
248
|
+
backend: "sqlite" # or "redis", "postgres"
|
|
287
249
|
```
|
|
288
250
|
|
|
289
251
|
---
|
|
@@ -333,10 +295,13 @@ curl -X DELETE http://localhost:8000/admin/sessions/user-42 \
|
|
|
333
295
|
|
|
334
296
|
| Endpoint | Description |
|
|
335
297
|
|---|---|
|
|
336
|
-
| `GET /admin/
|
|
298
|
+
| `GET /admin/health` | Health check (no auth required) |
|
|
299
|
+
| `GET /admin/config` | Active config view (secrets redacted) |
|
|
300
|
+
| `GET /admin/escalations` | Paginated list, filterable by `category`, `session_id`, `date`, sortable |
|
|
301
|
+
| `GET /admin/escalations/export` | CSV export of escalations |
|
|
337
302
|
| `GET /admin/escalations/{id}` | Single escalation detail |
|
|
338
303
|
| `GET /admin/sessions/{id}/risk` | Session history + trajectory |
|
|
339
|
-
| `GET /admin/stats` | Aggregate counts
|
|
304
|
+
| `GET /admin/stats` | Aggregate counts, top sessions, hourly breakdown |
|
|
340
305
|
| `DELETE /admin/sessions/{id}` | Delete all session records |
|
|
341
306
|
|
|
342
307
|
---
|
|
@@ -361,10 +326,38 @@ Available on the [Official MCP Registry](https://registry.modelcontextprotocol.i
|
|
|
361
326
|
|
|
362
327
|
---
|
|
363
328
|
|
|
364
|
-
##
|
|
329
|
+
## AI Agent Integrations
|
|
330
|
+
|
|
331
|
+
HumaneProxy tools can be natively plugged into standard agentic frameworks:
|
|
332
|
+
|
|
333
|
+
### LlamaIndex
|
|
334
|
+
```bash
|
|
335
|
+
pip install humane-proxy[llamaindex]
|
|
336
|
+
```
|
|
337
|
+
```python
|
|
338
|
+
from humane_proxy.integrations.llamaindex import get_safety_tools
|
|
339
|
+
tools = get_safety_tools() # Native FunctionTool instances
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### CrewAI
|
|
343
|
+
```bash
|
|
344
|
+
pip install humane-proxy[crewai]
|
|
345
|
+
```
|
|
346
|
+
```python
|
|
347
|
+
from humane_proxy.integrations.crewai import get_safety_tools
|
|
348
|
+
tools = get_safety_tools() # Native BaseTool subclass instances
|
|
349
|
+
```
|
|
365
350
|
|
|
366
|
-
|
|
351
|
+
### AutoGen (AG2)
|
|
352
|
+
```bash
|
|
353
|
+
pip install humane-proxy[autogen]
|
|
354
|
+
```
|
|
355
|
+
```python
|
|
356
|
+
from humane_proxy.integrations.autogen import register_safety_tools
|
|
357
|
+
register_safety_tools(assistant, user_proxy)
|
|
358
|
+
```
|
|
367
359
|
|
|
360
|
+
### LangChain
|
|
368
361
|
```bash
|
|
369
362
|
pip install humane-proxy[langchain]
|
|
370
363
|
```
|
|
@@ -390,15 +383,17 @@ All values can be set in `humane_proxy.yaml` (project root) or via `HUMANE_PROXY
|
|
|
390
383
|
| YAML key | Env var | Default | Description |
|
|
391
384
|
|---|---|---|---|
|
|
392
385
|
| `safety.risk_threshold` | `HUMANE_PROXY_RISK_THRESHOLD` | `0.7` | Score threshold for criminal_intent escalation |
|
|
393
|
-
| `safety.
|
|
386
|
+
| `safety.categories.self_harm.escalate_threshold` | `HUMANE_PROXY_SELF_HARM_THRESHOLD` | `0.5` | Score threshold for self_harm escalation |
|
|
387
|
+
| `safety.spike_boost` | `HUMANE_PROXY_SPIKE_BOOST` | `0.25` | Score boost on trajectory spike |
|
|
394
388
|
| `server.port` | `HUMANE_PROXY_PORT` | `8000` | Proxy port |
|
|
395
|
-
| `pipeline.enabled_stages` | `HUMANE_PROXY_ENABLED_STAGES` | `[1]` | Active stages |
|
|
389
|
+
| `pipeline.enabled_stages` | `HUMANE_PROXY_ENABLED_STAGES` | `[1]` | Active stages (e.g. `1,2,3`) |
|
|
396
390
|
| `pipeline.stage1_ceiling` | `HUMANE_PROXY_STAGE1_CEILING` | `0.3` | Early exit after Stage 1 |
|
|
397
391
|
| `pipeline.stage2_ceiling` | `HUMANE_PROXY_STAGE2_CEILING` | `0.4` | Early exit after Stage 2 |
|
|
398
392
|
| `stage3.provider` | `HUMANE_PROXY_STAGE3_PROVIDER` | `"auto"` | Stage 3 provider |
|
|
399
393
|
| `stage3.timeout` | `HUMANE_PROXY_STAGE3_TIMEOUT` | `10` | Stage 3 timeout (s) |
|
|
400
394
|
| `privacy.store_message_text` | — | `false` | Store raw text (vs SHA-256 hash) |
|
|
401
|
-
| `escalation.rate_limit_max` |
|
|
395
|
+
| `escalation.rate_limit_max` | `HUMANE_PROXY_RATE_LIMIT_MAX` | `3` | Max alerts per session/window |
|
|
396
|
+
| `storage.backend` | `HUMANE_PROXY_STORAGE_BACKEND` | `"sqlite"` | `"sqlite"`, `"redis"`, `"postgres"` |
|
|
402
397
|
| `safety.categories.self_harm.response_mode` | — | `"block"` | `"block"` or `"forward"` |
|
|
403
398
|
|
|
404
399
|
---
|
|
@@ -428,11 +423,16 @@ privacy:
|
|
|
428
423
|
|
|
429
424
|
| Extra | Command | What it adds |
|
|
430
425
|
|---|---|---|
|
|
431
|
-
| *(none)* | `pip install humane-proxy` | Stage 1 heuristics +
|
|
426
|
+
| *(none)* | `pip install humane-proxy` | Stage 1 heuristics + default SQLite storage |
|
|
432
427
|
| `ml` | `pip install humane-proxy[ml]` | Stage 2 semantic embeddings (`sentence-transformers`) |
|
|
433
428
|
| `mcp` | `pip install humane-proxy[mcp]` | MCP server for AI agent integration (`fastmcp`) |
|
|
429
|
+
| `redis` | `pip install humane-proxy[redis]` | Redis storage backend (`redis`) |
|
|
430
|
+
| `postgres` | `pip install humane-proxy[postgres]` | PostgreSQL storage backend (`psycopg`, `psycopg_pool`) |
|
|
431
|
+
| `llamaindex` | `pip install humane-proxy[llamaindex]` | LlamaIndex native integration (`llama-index-core`) |
|
|
432
|
+
| `crewai` | `pip install humane-proxy[crewai]` | CrewAI native integration (`crewai[tools]`) |
|
|
433
|
+
| `autogen` | `pip install humane-proxy[autogen]` | AutoGen native integration (`autogen-agentchat`) |
|
|
434
434
|
| `langchain` | `pip install humane-proxy[langchain]` | LangChain adapter (MCP + `langchain-mcp-adapters`) |
|
|
435
|
-
| `all` | `pip install humane-proxy[all]` |
|
|
435
|
+
| `all` | `pip install humane-proxy[all]` | Includes ALL optional dependencies above |
|
|
436
436
|
|
|
437
437
|
---
|
|
438
438
|
|
|
@@ -6,7 +6,10 @@ Authentication: Bearer token from ``HUMANE_PROXY_ADMIN_KEY`` env var.
|
|
|
6
6
|
If not set, admin API is disabled (all requests → 403).
|
|
7
7
|
|
|
8
8
|
Endpoints:
|
|
9
|
+
GET /admin/health — health check (no auth)
|
|
10
|
+
GET /admin/config — active configuration (sanitised)
|
|
9
11
|
GET /admin/escalations — paginated, filterable list
|
|
12
|
+
GET /admin/escalations/export — CSV export
|
|
10
13
|
GET /admin/escalations/{id} — single record
|
|
11
14
|
GET /admin/sessions/{id}/risk — per-session trajectory
|
|
12
15
|
GET /admin/stats — aggregate counts
|
|
@@ -15,14 +18,18 @@ Endpoints:
|
|
|
15
18
|
|
|
16
19
|
from __future__ import annotations
|
|
17
20
|
|
|
21
|
+
import csv
|
|
22
|
+
import io
|
|
18
23
|
import json
|
|
19
24
|
import logging
|
|
20
25
|
import os
|
|
21
26
|
import sqlite3
|
|
27
|
+
import time
|
|
22
28
|
from datetime import datetime, timezone
|
|
23
29
|
from typing import Any
|
|
24
30
|
|
|
25
31
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
32
|
+
from fastapi.responses import StreamingResponse
|
|
26
33
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
27
34
|
|
|
28
35
|
from humane_proxy.escalation.local_db import _get_db_path
|
|
@@ -33,6 +40,8 @@ router = APIRouter(prefix="/admin", tags=["admin"])
|
|
|
33
40
|
|
|
34
41
|
_security = HTTPBearer(auto_error=False)
|
|
35
42
|
|
|
43
|
+
_start_time = time.monotonic()
|
|
44
|
+
|
|
36
45
|
|
|
37
46
|
# ---------------------------------------------------------------------------
|
|
38
47
|
# Auth dependency
|
|
@@ -81,6 +90,67 @@ def _get_conn() -> sqlite3.Connection:
|
|
|
81
90
|
return sqlite3.connect(_get_db_path(), check_same_thread=False)
|
|
82
91
|
|
|
83
92
|
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
# Health check (no auth required)
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
@router.get("/health")
|
|
98
|
+
def health_check() -> dict:
|
|
99
|
+
"""Health check — uptime, version, active stages, backend."""
|
|
100
|
+
from humane_proxy import __version__
|
|
101
|
+
from humane_proxy.config import get_config
|
|
102
|
+
|
|
103
|
+
config = get_config()
|
|
104
|
+
return {
|
|
105
|
+
"status": "healthy",
|
|
106
|
+
"version": __version__,
|
|
107
|
+
"uptime_seconds": round(time.monotonic() - _start_time, 1),
|
|
108
|
+
"enabled_stages": config.get("pipeline", {}).get("enabled_stages", [1]),
|
|
109
|
+
"storage_backend": config.get("storage", {}).get("backend", "sqlite"),
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
# Config view (secrets redacted)
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
@router.get("/config")
|
|
118
|
+
def get_active_config(_: str = Depends(_require_admin)) -> dict:
|
|
119
|
+
"""Return the active merged config with secrets redacted."""
|
|
120
|
+
import copy
|
|
121
|
+
from humane_proxy.config import get_config
|
|
122
|
+
|
|
123
|
+
config = copy.deepcopy(get_config())
|
|
124
|
+
|
|
125
|
+
# Redact sensitive values.
|
|
126
|
+
_REDACT_PATHS = [
|
|
127
|
+
("admin", "api_key"),
|
|
128
|
+
("escalation", "webhooks", "slack_url"),
|
|
129
|
+
("escalation", "webhooks", "discord_url"),
|
|
130
|
+
("escalation", "webhooks", "pagerduty_routing_key"),
|
|
131
|
+
("escalation", "webhooks", "teams_url"),
|
|
132
|
+
("storage", "redis", "url"),
|
|
133
|
+
("storage", "postgres", "dsn"),
|
|
134
|
+
]
|
|
135
|
+
for path in _REDACT_PATHS:
|
|
136
|
+
node = config
|
|
137
|
+
for part in path[:-1]:
|
|
138
|
+
node = node.get(part, {})
|
|
139
|
+
if not isinstance(node, dict):
|
|
140
|
+
break
|
|
141
|
+
else:
|
|
142
|
+
key = path[-1]
|
|
143
|
+
if key in node and node[key]:
|
|
144
|
+
node[key] = "***REDACTED***"
|
|
145
|
+
|
|
146
|
+
# Redact email password.
|
|
147
|
+
email_cfg = config.get("escalation", {}).get("webhooks", {}).get("email", {})
|
|
148
|
+
if isinstance(email_cfg, dict) and email_cfg.get("password"):
|
|
149
|
+
email_cfg["password"] = "***REDACTED***"
|
|
150
|
+
|
|
151
|
+
return config
|
|
152
|
+
|
|
153
|
+
|
|
84
154
|
# ---------------------------------------------------------------------------
|
|
85
155
|
# Routes
|
|
86
156
|
# ---------------------------------------------------------------------------
|
|
@@ -89,6 +159,10 @@ def _get_conn() -> sqlite3.Connection:
|
|
|
89
159
|
def list_escalations(
|
|
90
160
|
category: str | None = Query(None, description="Filter by category"),
|
|
91
161
|
session_id: str | None = Query(None, description="Filter by session ID"),
|
|
162
|
+
date_from: str | None = Query(None, description="Start date (ISO format, e.g. 2026-01-01)"),
|
|
163
|
+
date_to: str | None = Query(None, description="End date (ISO format, e.g. 2026-12-31)"),
|
|
164
|
+
sort_by: str = Query("timestamp", description="Sort field: timestamp, risk_score, category"),
|
|
165
|
+
sort_order: str = Query("desc", description="Sort order: asc or desc"),
|
|
92
166
|
limit: int = Query(50, ge=1, le=500),
|
|
93
167
|
offset: int = Query(0, ge=0),
|
|
94
168
|
_: str = Depends(_require_admin),
|
|
@@ -103,13 +177,31 @@ def list_escalations(
|
|
|
103
177
|
if session_id:
|
|
104
178
|
clauses.append("session_id = ?")
|
|
105
179
|
params.append(session_id)
|
|
180
|
+
if date_from:
|
|
181
|
+
try:
|
|
182
|
+
dt = datetime.fromisoformat(date_from).replace(tzinfo=timezone.utc)
|
|
183
|
+
clauses.append("timestamp >= ?")
|
|
184
|
+
params.append(dt.timestamp())
|
|
185
|
+
except ValueError:
|
|
186
|
+
raise HTTPException(400, f"Invalid date_from format: {date_from}")
|
|
187
|
+
if date_to:
|
|
188
|
+
try:
|
|
189
|
+
dt = datetime.fromisoformat(date_to).replace(tzinfo=timezone.utc)
|
|
190
|
+
clauses.append("timestamp <= ?")
|
|
191
|
+
params.append(dt.timestamp())
|
|
192
|
+
except ValueError:
|
|
193
|
+
raise HTTPException(400, f"Invalid date_to format: {date_to}")
|
|
106
194
|
|
|
107
195
|
where = ("WHERE " + " AND ".join(clauses)) if clauses else ""
|
|
108
196
|
|
|
197
|
+
allowed_sort = {"timestamp", "risk_score", "category", "session_id", "stage_reached"}
|
|
198
|
+
sort_col = sort_by if sort_by in allowed_sort else "timestamp"
|
|
199
|
+
sort_dir = "ASC" if sort_order.lower() == "asc" else "DESC"
|
|
200
|
+
|
|
109
201
|
conn = _get_conn()
|
|
110
202
|
try:
|
|
111
203
|
rows = conn.execute(
|
|
112
|
-
f"SELECT * FROM escalations {where} ORDER BY
|
|
204
|
+
f"SELECT * FROM escalations {where} ORDER BY {sort_col} {sort_dir} LIMIT ? OFFSET ?",
|
|
113
205
|
params + [limit, offset],
|
|
114
206
|
).fetchall()
|
|
115
207
|
total = conn.execute(
|
|
@@ -126,6 +218,52 @@ def list_escalations(
|
|
|
126
218
|
}
|
|
127
219
|
|
|
128
220
|
|
|
221
|
+
# ---------------------------------------------------------------------------
|
|
222
|
+
# CSV Export
|
|
223
|
+
# ---------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
@router.get("/escalations/export")
|
|
226
|
+
def export_escalations(
|
|
227
|
+
category: str | None = Query(None),
|
|
228
|
+
session_id: str | None = Query(None),
|
|
229
|
+
_: str = Depends(_require_admin),
|
|
230
|
+
) -> StreamingResponse:
|
|
231
|
+
"""Export all matching escalations as CSV."""
|
|
232
|
+
clauses: list[str] = []
|
|
233
|
+
params: list[Any] = []
|
|
234
|
+
|
|
235
|
+
if category:
|
|
236
|
+
clauses.append("category = ?")
|
|
237
|
+
params.append(category)
|
|
238
|
+
if session_id:
|
|
239
|
+
clauses.append("session_id = ?")
|
|
240
|
+
params.append(session_id)
|
|
241
|
+
|
|
242
|
+
where = ("WHERE " + " AND ".join(clauses)) if clauses else ""
|
|
243
|
+
|
|
244
|
+
conn = _get_conn()
|
|
245
|
+
try:
|
|
246
|
+
rows = conn.execute(
|
|
247
|
+
f"SELECT * FROM escalations {where} ORDER BY timestamp DESC",
|
|
248
|
+
params,
|
|
249
|
+
).fetchall()
|
|
250
|
+
finally:
|
|
251
|
+
conn.close()
|
|
252
|
+
|
|
253
|
+
output = io.StringIO()
|
|
254
|
+
writer = csv.writer(output)
|
|
255
|
+
writer.writerow(_COLS)
|
|
256
|
+
for row in rows:
|
|
257
|
+
writer.writerow(row)
|
|
258
|
+
|
|
259
|
+
output.seek(0)
|
|
260
|
+
return StreamingResponse(
|
|
261
|
+
iter([output.getvalue()]),
|
|
262
|
+
media_type="text/csv",
|
|
263
|
+
headers={"Content-Disposition": "attachment; filename=escalations.csv"},
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
|
|
129
267
|
@router.get("/escalations/{escalation_id}")
|
|
130
268
|
def get_escalation(
|
|
131
269
|
escalation_id: int,
|
|
@@ -191,7 +329,7 @@ def get_session_risk(
|
|
|
191
329
|
|
|
192
330
|
@router.get("/stats")
|
|
193
331
|
def get_stats(_: str = Depends(_require_admin)) -> dict:
|
|
194
|
-
"""Return aggregate safety statistics."""
|
|
332
|
+
"""Return aggregate safety statistics with enhanced breakdowns."""
|
|
195
333
|
conn = _get_conn()
|
|
196
334
|
try:
|
|
197
335
|
total = conn.execute("SELECT COUNT(*) FROM escalations").fetchone()[0]
|
|
@@ -205,6 +343,26 @@ def get_stats(_: str = Depends(_require_admin)) -> dict:
|
|
|
205
343
|
avg_score = conn.execute(
|
|
206
344
|
"SELECT AVG(risk_score) FROM escalations"
|
|
207
345
|
).fetchone()[0]
|
|
346
|
+
|
|
347
|
+
# Enhanced: top sessions (most flagged).
|
|
348
|
+
top_sessions = conn.execute(
|
|
349
|
+
"""SELECT session_id, COUNT(*) as cnt, AVG(risk_score) as avg_score
|
|
350
|
+
FROM escalations GROUP BY session_id ORDER BY cnt DESC LIMIT 10"""
|
|
351
|
+
).fetchall()
|
|
352
|
+
|
|
353
|
+
# Enhanced: stage distribution.
|
|
354
|
+
by_stage = conn.execute(
|
|
355
|
+
"SELECT stage_reached, COUNT(*) FROM escalations GROUP BY stage_reached"
|
|
356
|
+
).fetchall()
|
|
357
|
+
|
|
358
|
+
# Enhanced: hourly breakdown (last 24h).
|
|
359
|
+
cutoff_24h = (datetime.now(timezone.utc).timestamp()) - 86400
|
|
360
|
+
hourly = conn.execute(
|
|
361
|
+
"""SELECT strftime('%H', timestamp, 'unixepoch') as hour, COUNT(*)
|
|
362
|
+
FROM escalations WHERE timestamp >= ?
|
|
363
|
+
GROUP BY hour ORDER BY hour""",
|
|
364
|
+
(cutoff_24h,),
|
|
365
|
+
).fetchall()
|
|
208
366
|
finally:
|
|
209
367
|
conn.close()
|
|
210
368
|
|
|
@@ -213,6 +371,12 @@ def get_stats(_: str = Depends(_require_admin)) -> dict:
|
|
|
213
371
|
"by_category": dict(by_category),
|
|
214
372
|
"by_day": dict(by_day),
|
|
215
373
|
"average_risk_score": round(avg_score or 0.0, 3),
|
|
374
|
+
"top_sessions": [
|
|
375
|
+
{"session_id": s, "count": c, "avg_score": round(a or 0, 3)}
|
|
376
|
+
for s, c, a in top_sessions
|
|
377
|
+
],
|
|
378
|
+
"by_stage": dict(by_stage),
|
|
379
|
+
"hourly_last_24h": dict(hourly),
|
|
216
380
|
"generated_at": datetime.now(timezone.utc).isoformat(),
|
|
217
381
|
}
|
|
218
382
|
|