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.
Files changed (69) hide show
  1. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/PKG-INFO +69 -12
  2. humane_proxy-0.2.3/humane_proxy.egg-info/PKG-INFO → humane_proxy-0.3.0/README.md +56 -56
  3. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/__init__.py +1 -1
  4. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/api/admin.py +166 -2
  5. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/embedding_classifier.py +104 -11
  6. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/pipeline.py +14 -3
  7. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/config.py +5 -0
  8. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/config.yaml +32 -0
  9. humane_proxy-0.3.0/humane_proxy/escalation/local_db.py +68 -0
  10. humane_proxy-0.3.0/humane_proxy/integrations/autogen.py +160 -0
  11. humane_proxy-0.3.0/humane_proxy/integrations/crewai.py +131 -0
  12. humane_proxy-0.3.0/humane_proxy/integrations/llamaindex.py +138 -0
  13. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/mcp_server.py +1 -6
  14. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/middleware/interceptor.py +1 -1
  15. humane_proxy-0.3.0/humane_proxy/storage/__init__.py +15 -0
  16. humane_proxy-0.3.0/humane_proxy/storage/base.py +75 -0
  17. humane_proxy-0.3.0/humane_proxy/storage/factory.py +75 -0
  18. humane_proxy-0.3.0/humane_proxy/storage/postgres.py +212 -0
  19. humane_proxy-0.3.0/humane_proxy/storage/redis.py +193 -0
  20. humane_proxy-0.3.0/humane_proxy/storage/sqlite.py +242 -0
  21. humane_proxy-0.2.3/README.md → humane_proxy-0.3.0/humane_proxy.egg-info/PKG-INFO +113 -10
  22. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy.egg-info/SOURCES.txt +13 -0
  23. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy.egg-info/requires.txt +17 -1
  24. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/pyproject.toml +18 -2
  25. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/server.json +3 -3
  26. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_embedding_classifier.py +8 -2
  27. humane_proxy-0.3.0/tests/test_embedding_singleton.py +77 -0
  28. humane_proxy-0.3.0/tests/test_integrations_smoke.py +76 -0
  29. humane_proxy-0.3.0/tests/test_self_harm_threshold.py +51 -0
  30. humane_proxy-0.3.0/tests/test_storage_backends.py +81 -0
  31. humane_proxy-0.2.3/humane_proxy/escalation/local_db.py +0 -170
  32. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/LICENSE +0 -0
  33. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/NOTICE +0 -0
  34. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/api/__init__.py +0 -0
  35. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/__init__.py +0 -0
  36. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/heuristics.py +0 -0
  37. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/models.py +0 -0
  38. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/stage3/__init__.py +0 -0
  39. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/stage3/base.py +0 -0
  40. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/stage3/llamaguard.py +0 -0
  41. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/stage3/openai_chat.py +0 -0
  42. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/classifiers/stage3/openai_moderation.py +0 -0
  43. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/cli.py +0 -0
  44. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/escalation/__init__.py +0 -0
  45. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/escalation/router.py +0 -0
  46. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/escalation/webhooks.py +0 -0
  47. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/integrations/__init__.py +0 -0
  48. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/integrations/langchain.py +0 -0
  49. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/middleware/__init__.py +0 -0
  50. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/risk/__init__.py +0 -0
  51. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy/risk/trajectory.py +0 -0
  52. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy.egg-info/dependency_links.txt +0 -0
  53. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy.egg-info/entry_points.txt +0 -0
  54. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/humane_proxy.egg-info/top_level.txt +0 -0
  55. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/setup.cfg +0 -0
  56. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_admin_api.py +0 -0
  57. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_care_response.py +0 -0
  58. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_cli.py +0 -0
  59. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_config.py +0 -0
  60. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_enhanced_webhooks.py +0 -0
  61. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_heuristics.py +0 -0
  62. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_interceptor.py +0 -0
  63. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_local_db.py +0 -0
  64. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_models.py +0 -0
  65. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_pipeline.py +0 -0
  66. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_router.py +0 -0
  67. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_stage3.py +0 -0
  68. {humane_proxy-0.2.3 → humane_proxy-0.3.0}/tests/test_trajectory.py +0 -0
  69. {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.2.3
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
  [![Python](https://img.shields.io/pypi/pyversions/humane-proxy.svg)](https://pypi.org/project/humane-proxy/)
57
68
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
58
69
  [![Tests](https://github.com/Vishisht16/Humane-Proxy/actions/workflows/tests.yaml/badge.svg)](https://github.com/Vishisht16/Humane-Proxy/actions/workflows/tests.yaml)
59
- [![Humane-Proxy MCP server](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy/badges/card.svg)](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy)
60
70
  [![Humane-Proxy MCP server](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy/badges/score.svg)](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy)
71
+ [![Humane-Proxy MCP server](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy/badges/card.svg)](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/escalations` | Paginated list, filterable by `category`, `session_id` |
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 by category and day |
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
- ## LangChain Integration
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
- Plug HumaneProxy safety tools into any LangChain or LangGraph agent:
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.spike_boost` | | `0.25` | Score boost on trajectory spike |
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` | | `3` | Max alerts per session/window |
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 + full API + CLI |
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]` | Everything above |
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
  [![Python](https://img.shields.io/pypi/pyversions/humane-proxy.svg)](https://pypi.org/project/humane-proxy/)
57
11
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
58
12
  [![Tests](https://github.com/Vishisht16/Humane-Proxy/actions/workflows/tests.yaml/badge.svg)](https://github.com/Vishisht16/Humane-Proxy/actions/workflows/tests.yaml)
59
- [![Humane-Proxy MCP server](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy/badges/card.svg)](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy)
60
13
  [![Humane-Proxy MCP server](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy/badges/score.svg)](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy)
14
+ [![Humane-Proxy MCP server](https://glama.ai/mcp/servers/Vishisht16/Humane-Proxy/badges/card.svg)](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/escalations` | Paginated list, filterable by `category`, `session_id` |
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 by category and day |
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
- ## LangChain Integration
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
- Plug HumaneProxy safety tools into any LangChain or LangGraph agent:
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.spike_boost` | | `0.25` | Score boost on trajectory spike |
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` | | `3` | Max alerts per session/window |
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 + full API + CLI |
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]` | Everything above |
435
+ | `all` | `pip install humane-proxy[all]` | Includes ALL optional dependencies above |
436
436
 
437
437
  ---
438
438
 
@@ -16,7 +16,7 @@
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- __version__ = "0.2.3"
19
+ __version__ = "0.3.0"
20
20
 
21
21
  # ---------------------------------------------------------------------------
22
22
  # Legacy API — keep backward compatibility with existing modules that call
@@ -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 timestamp DESC LIMIT ? OFFSET ?",
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