ichyra-guard 0.7.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 (48) hide show
  1. ichyra_guard-0.7.0/LICENSE +21 -0
  2. ichyra_guard-0.7.0/PKG-INFO +119 -0
  3. ichyra_guard-0.7.0/README.md +85 -0
  4. ichyra_guard-0.7.0/pyproject.toml +42 -0
  5. ichyra_guard-0.7.0/setup.cfg +4 -0
  6. ichyra_guard-0.7.0/src/ichyra/__init__.py +22 -0
  7. ichyra_guard-0.7.0/src/ichyra/core.py +164 -0
  8. ichyra_guard-0.7.0/src/ichyra/embeddings.py +54 -0
  9. ichyra_guard-0.7.0/src/ichyra/integrations/__init__.py +5 -0
  10. ichyra_guard-0.7.0/src/ichyra/integrations/crewai.py +76 -0
  11. ichyra_guard-0.7.0/src/ichyra/integrations/langchain.py +61 -0
  12. ichyra_guard-0.7.0/src/ichyra/integrations/llama_index.py +48 -0
  13. ichyra_guard-0.7.0/src/ichyra/judge.py +363 -0
  14. ichyra_guard-0.7.0/src/ichyra/lotus.py +356 -0
  15. ichyra_guard-0.7.0/src/ichyra_guard.egg-info/PKG-INFO +119 -0
  16. ichyra_guard-0.7.0/src/ichyra_guard.egg-info/SOURCES.txt +46 -0
  17. ichyra_guard-0.7.0/src/ichyra_guard.egg-info/dependency_links.txt +1 -0
  18. ichyra_guard-0.7.0/src/ichyra_guard.egg-info/requires.txt +20 -0
  19. ichyra_guard-0.7.0/src/ichyra_guard.egg-info/top_level.txt +1 -0
  20. ichyra_guard-0.7.0/tests/test_auth_real.py +56 -0
  21. ichyra_guard-0.7.0/tests/test_drift_compat.py +42 -0
  22. ichyra_guard-0.7.0/tests/test_e2e_pgvector.py +59 -0
  23. ichyra_guard-0.7.0/tests/test_endpoints.py +41 -0
  24. ichyra_guard-0.7.0/tests/test_enterprise_endpoints.py +109 -0
  25. ichyra_guard-0.7.0/tests/test_external_adapters.py +46 -0
  26. ichyra_guard-0.7.0/tests/test_facet_refiner.py +55 -0
  27. ichyra_guard-0.7.0/tests/test_governance.py +54 -0
  28. ichyra_guard-0.7.0/tests/test_graph.py +78 -0
  29. ichyra_guard-0.7.0/tests/test_guardian_heartbeat.py +71 -0
  30. ichyra_guard-0.7.0/tests/test_handshake.py +117 -0
  31. ichyra_guard-0.7.0/tests/test_hardware_root.py +22 -0
  32. ichyra_guard-0.7.0/tests/test_infra_b.py +60 -0
  33. ichyra_guard-0.7.0/tests/test_integrity_guardian.py +46 -0
  34. ichyra_guard-0.7.0/tests/test_isip_auto.py +53 -0
  35. ichyra_guard-0.7.0/tests/test_lir0_corruption.py +64 -0
  36. ichyra_guard-0.7.0/tests/test_manifold_federation.py +89 -0
  37. ichyra_guard-0.7.0/tests/test_packaging_d.py +47 -0
  38. ichyra_guard-0.7.0/tests/test_pentest_regression.py +231 -0
  39. ichyra_guard-0.7.0/tests/test_phi_navigation.py +43 -0
  40. ichyra_guard-0.7.0/tests/test_pilot_live.py +108 -0
  41. ichyra_guard-0.7.0/tests/test_policies.py +48 -0
  42. ichyra_guard-0.7.0/tests/test_revocation.py +79 -0
  43. ichyra_guard-0.7.0/tests/test_security_fixes.py +85 -0
  44. ichyra_guard-0.7.0/tests/test_tickets.py +50 -0
  45. ichyra_guard-0.7.0/tests/test_topology.py +45 -0
  46. ichyra_guard-0.7.0/tests/test_unified_integration.py +37 -0
  47. ichyra_guard-0.7.0/tests/test_verifier.py +74 -0
  48. ichyra_guard-0.7.0/tests/test_zk_proof.py +19 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tai and Marcio Santos de Lima
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,119 @@
1
+ Metadata-Version: 2.4
2
+ Name: ichyra-guard
3
+ Version: 0.7.0
4
+ Summary: Ichyra Guard — ITA vectorial + Lotus + Zero Trust + Governança 32 (avaliação ética auditável para LLMs)
5
+ Author-email: Tai and Marcio Santos de Lima <mark.iodelima@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Lotuvia-Governanca/ichyra-projeto
8
+ Project-URL: Bug Tracker, https://github.com/Lotuvia-Governanca/ichyra-projeto/issues
9
+ Keywords: ai,ethics,llm,guardrails,alignment
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: sentence-transformers>=2.2.0
18
+ Requires-Dist: numpy>=1.21.0
19
+ Provides-Extra: langchain
20
+ Requires-Dist: langchain>=0.1.0; extra == "langchain"
21
+ Requires-Dist: langchain-core>=0.1.0; extra == "langchain"
22
+ Provides-Extra: llama-index
23
+ Requires-Dist: llama-index>=0.10.0; extra == "llama-index"
24
+ Provides-Extra: all
25
+ Requires-Dist: langchain>=0.1.0; extra == "all"
26
+ Requires-Dist: llama-index>=0.10.0; extra == "all"
27
+ Requires-Dist: pgvector>=0.3.6; extra == "all"
28
+ Requires-Dist: prometheus-client>=0.20.0; extra == "all"
29
+ Requires-Dist: bleach>=6.0.0; extra == "all"
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7.0; extra == "dev"
32
+ Requires-Dist: httpx>=0.27.0; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ ![CI](https://github.com/markiolima30-coder/ichyra-ita/actions/workflows/ci.yml/badge.svg) ![Gitleaks](https://github.com/markiolima30-coder/ichyra-ita/actions/workflows/gitleaks.yml/badge.svg) ![Coverage](https://img.shields.io/badge/coverage-49%20tests-green) ![Version](https://img.shields.io/badge/version-0.6.2-blue)
36
+
37
+ # ICHYRA — Engine + ITA + Governança (README unificado)
38
+
39
+ > **BACKEND CANÔNICO DO PILOTO.** Piloto de 100 casos ao vivo (recall
40
+ > 100%, FPR 0%), Judge Groq com failover Ollama, jobs assíncronos. O
41
+ > `ichyra-engine` está arquivado como referência técnica.
42
+
43
+ > **ECOSSISTEMA ICHYRA:** `src/ichyra` (ITA vectorial `R_transform = S_d × P × V_inv` + Lotus Defense 4 camadas) + `ichyra-engine` (FastAPI Zero Trust, pgvector, OIDC, observabilidade) + `app/governance` (32 princípios) + `paper_ita.md` + auditoria 0 achados. Ver `CHANGELOG.md`.
44
+
45
+ ## Stack
46
+ - **Core:** `src/ichyra/core.py:16` `IchyraITA`, `src/ichyra/embeddings.py:4` `EmbeddingService` (`BAAI/bge-base-en-v1.5` configurável `EMBEDDING_MODEL` env, fallback `hashlib`)
47
+ - **Engine:** FastAPI 0.110 + SQLAlchemy + `pgvector Vector(384)` + `cosine_distance` (`app/services/ichyra_core.py:22`), JWT OIDC RS256 `app/auth/dependencies.py:19`, `slowapi`, `structlog`, `bleach`, `prometheus-fastapi-instrumentator`
48
+ - **Governança:** risk 0-4 `app/governance/risk_classifier.py:1`, checkpoint 10p `checkpoint.py:1`, orquestrador `orchestrator.py:12`, kill switch `kill_switch.py:1`, audit 13 campos `audit.py:1`
49
+ - **Deploy:** `docker-compose.yml:11` `pgvector/pgvector:pg16` + `healthcheck` + `prometheus/grafana` (profile `monitoring`), `alembic/versions/001_pgvector.py`, `Makefile` `make init`
50
+
51
+ ## Arquitetura unificada
52
+ ```
53
+ USER -> AUTH (OIDC/JWT) -> AI GATEWAY -> INPUT SECURITY (Security Architect)
54
+ -> LOTUS One Vehicle (stripping) -> ITA evaluate (Karmic Residue 0.20)
55
+ -> POLICY ENGINE -> TOOL PERMISSION (TTL 15min, segregação) -> OUTPUT DLP -> AUDIT -> Immunity Vector
56
+ + Orquestrador -> Motor Políticas (porteiro) -> Kill Switch middleware
57
+ ```
58
+
59
+ ## Instalação
60
+ ```bash
61
+ pip install "ichyra[all]" # langchain, llama-index, pgvector, prometheus, bleach
62
+ # ou dev:
63
+ pip install -e . -r requirements.txt
64
+ # ou engine:
65
+ pip install -r requirements.txt
66
+ ```
67
+
68
+ ## Quick Start (ITA puro)
69
+ ```python
70
+ from ichyra import IchyraITA
71
+ ita = IchyraITA() # EMBEDDING_MODEL env
72
+ res = ita.evaluate(current_context="...", proposed_action="...", feedback_history=[{"context":"...","action":"...","severity":-0.9}])
73
+ if res.magnitude > 0.4: print("bloqueado", res.magnitude)
74
+ ```
75
+
76
+ ## Quick Start com LangChain / LlamaIndex / CrewAI
77
+ ```python
78
+ from ichyra.integrations.langchain import IchyraLangChainHandler
79
+ handler = IchyraLangChainHandler(feedback_history=hist)
80
+ # ChatOpenAI(callbacks=[handler])
81
+ from ichyra.integrations.llama_index import IchyraLlamaIndexGuard
82
+ guard = IchyraLlamaIndexGuard(feedback_history=hist)
83
+ guard.filter(query, response)
84
+ from ichyra.integrations.crewai import IchyraCrewAIGuard
85
+ crew = IchyraCrewAIGuard(feedback_history=hist) # severity negativa = violação
86
+ safe_tool = crew.guard_tool(minha_tool) # bloqueia tool perigosa
87
+ veredito = crew.check(task=tarefa, output=resposta) # + remédio se bloquear
88
+ ```
89
+
90
+ ## Como rodar (engine)
91
+ ```bash
92
+ make init # gera .env com openssl rand -hex 32
93
+ docker compose up --build # api:8000, db pgvector, redis
94
+ # ou local:
95
+ uvicorn app.main:app --reload
96
+ # monitoring:
97
+ docker compose --profile monitoring up
98
+ # health: /health, /ready, /metrics, /v1/governance/prompt (auth)
99
+ # painel executivo: /dashboard (só leitura, agregados, sem PII)
100
+ ```
101
+
102
+ ## Testes, benchmark e carga
103
+ ```bash
104
+ pytest tests -q # 49 testes
105
+ python benchmarks/benchmark_100.py # BGE real: FPR 43.3% sem judge em thr=0.85 (ver BENCHMARK_RESULT.md; FPR 0% só com judge)
106
+ python benchmarks/benchmark_100.py --judge-real "BAAI/bge-base-en-v1.5" # com juiz (LLM_PROVIDER + LLM_API_KEY)
107
+ py -3.13 scripts/check_judge.py # diz se o juiz está ligado ou o que falta
108
+ py -3.13 scripts/oidc_harness.py # OIDC real 6/6 (S-01/S-09)
109
+ # carga (exige Docker + k6): .\scripts\k6_local.ps1 # mata zumbis, sobe tudo, roda, limpa
110
+ ```
111
+
112
+ ## Piloto comercial
113
+ Ver `PROPOSTA_PILOTO.md` (R$ 12 mil, 30 dias, critérios medidos).
114
+
115
+ ## Auditoria
116
+ `docs/security-audit/relatorio-auditoria-seguranca.pdf` v3 0 achados (era 8). `gitleaks.yml` bloqueia `CHANGEME_GERE_AQUI`.
117
+
118
+ ## Licença
119
+ MIT
@@ -0,0 +1,85 @@
1
+ ![CI](https://github.com/markiolima30-coder/ichyra-ita/actions/workflows/ci.yml/badge.svg) ![Gitleaks](https://github.com/markiolima30-coder/ichyra-ita/actions/workflows/gitleaks.yml/badge.svg) ![Coverage](https://img.shields.io/badge/coverage-49%20tests-green) ![Version](https://img.shields.io/badge/version-0.6.2-blue)
2
+
3
+ # ICHYRA — Engine + ITA + Governança (README unificado)
4
+
5
+ > **BACKEND CANÔNICO DO PILOTO.** Piloto de 100 casos ao vivo (recall
6
+ > 100%, FPR 0%), Judge Groq com failover Ollama, jobs assíncronos. O
7
+ > `ichyra-engine` está arquivado como referência técnica.
8
+
9
+ > **ECOSSISTEMA ICHYRA:** `src/ichyra` (ITA vectorial `R_transform = S_d × P × V_inv` + Lotus Defense 4 camadas) + `ichyra-engine` (FastAPI Zero Trust, pgvector, OIDC, observabilidade) + `app/governance` (32 princípios) + `paper_ita.md` + auditoria 0 achados. Ver `CHANGELOG.md`.
10
+
11
+ ## Stack
12
+ - **Core:** `src/ichyra/core.py:16` `IchyraITA`, `src/ichyra/embeddings.py:4` `EmbeddingService` (`BAAI/bge-base-en-v1.5` configurável `EMBEDDING_MODEL` env, fallback `hashlib`)
13
+ - **Engine:** FastAPI 0.110 + SQLAlchemy + `pgvector Vector(384)` + `cosine_distance` (`app/services/ichyra_core.py:22`), JWT OIDC RS256 `app/auth/dependencies.py:19`, `slowapi`, `structlog`, `bleach`, `prometheus-fastapi-instrumentator`
14
+ - **Governança:** risk 0-4 `app/governance/risk_classifier.py:1`, checkpoint 10p `checkpoint.py:1`, orquestrador `orchestrator.py:12`, kill switch `kill_switch.py:1`, audit 13 campos `audit.py:1`
15
+ - **Deploy:** `docker-compose.yml:11` `pgvector/pgvector:pg16` + `healthcheck` + `prometheus/grafana` (profile `monitoring`), `alembic/versions/001_pgvector.py`, `Makefile` `make init`
16
+
17
+ ## Arquitetura unificada
18
+ ```
19
+ USER -> AUTH (OIDC/JWT) -> AI GATEWAY -> INPUT SECURITY (Security Architect)
20
+ -> LOTUS One Vehicle (stripping) -> ITA evaluate (Karmic Residue 0.20)
21
+ -> POLICY ENGINE -> TOOL PERMISSION (TTL 15min, segregação) -> OUTPUT DLP -> AUDIT -> Immunity Vector
22
+ + Orquestrador -> Motor Políticas (porteiro) -> Kill Switch middleware
23
+ ```
24
+
25
+ ## Instalação
26
+ ```bash
27
+ pip install "ichyra[all]" # langchain, llama-index, pgvector, prometheus, bleach
28
+ # ou dev:
29
+ pip install -e . -r requirements.txt
30
+ # ou engine:
31
+ pip install -r requirements.txt
32
+ ```
33
+
34
+ ## Quick Start (ITA puro)
35
+ ```python
36
+ from ichyra import IchyraITA
37
+ ita = IchyraITA() # EMBEDDING_MODEL env
38
+ res = ita.evaluate(current_context="...", proposed_action="...", feedback_history=[{"context":"...","action":"...","severity":-0.9}])
39
+ if res.magnitude > 0.4: print("bloqueado", res.magnitude)
40
+ ```
41
+
42
+ ## Quick Start com LangChain / LlamaIndex / CrewAI
43
+ ```python
44
+ from ichyra.integrations.langchain import IchyraLangChainHandler
45
+ handler = IchyraLangChainHandler(feedback_history=hist)
46
+ # ChatOpenAI(callbacks=[handler])
47
+ from ichyra.integrations.llama_index import IchyraLlamaIndexGuard
48
+ guard = IchyraLlamaIndexGuard(feedback_history=hist)
49
+ guard.filter(query, response)
50
+ from ichyra.integrations.crewai import IchyraCrewAIGuard
51
+ crew = IchyraCrewAIGuard(feedback_history=hist) # severity negativa = violação
52
+ safe_tool = crew.guard_tool(minha_tool) # bloqueia tool perigosa
53
+ veredito = crew.check(task=tarefa, output=resposta) # + remédio se bloquear
54
+ ```
55
+
56
+ ## Como rodar (engine)
57
+ ```bash
58
+ make init # gera .env com openssl rand -hex 32
59
+ docker compose up --build # api:8000, db pgvector, redis
60
+ # ou local:
61
+ uvicorn app.main:app --reload
62
+ # monitoring:
63
+ docker compose --profile monitoring up
64
+ # health: /health, /ready, /metrics, /v1/governance/prompt (auth)
65
+ # painel executivo: /dashboard (só leitura, agregados, sem PII)
66
+ ```
67
+
68
+ ## Testes, benchmark e carga
69
+ ```bash
70
+ pytest tests -q # 49 testes
71
+ python benchmarks/benchmark_100.py # BGE real: FPR 43.3% sem judge em thr=0.85 (ver BENCHMARK_RESULT.md; FPR 0% só com judge)
72
+ python benchmarks/benchmark_100.py --judge-real "BAAI/bge-base-en-v1.5" # com juiz (LLM_PROVIDER + LLM_API_KEY)
73
+ py -3.13 scripts/check_judge.py # diz se o juiz está ligado ou o que falta
74
+ py -3.13 scripts/oidc_harness.py # OIDC real 6/6 (S-01/S-09)
75
+ # carga (exige Docker + k6): .\scripts\k6_local.ps1 # mata zumbis, sobe tudo, roda, limpa
76
+ ```
77
+
78
+ ## Piloto comercial
79
+ Ver `PROPOSTA_PILOTO.md` (R$ 12 mil, 30 dias, critérios medidos).
80
+
81
+ ## Auditoria
82
+ `docs/security-audit/relatorio-auditoria-seguranca.pdf` v3 0 achados (era 8). `gitleaks.yml` bloqueia `CHANGEME_GERE_AQUI`.
83
+
84
+ ## Licença
85
+ MIT
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ichyra-guard"
7
+ version = "0.7.0"
8
+ description = "Ichyra Guard — ITA vectorial + Lotus + Zero Trust + Governança 32 (avaliação ética auditável para LLMs)"
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "Tai and Marcio Santos de Lima", email = "mark.iodelima@gmail.com" },
12
+ ]
13
+ license = { text = "MIT" }
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
19
+ ]
20
+ keywords = ["ai", "ethics", "llm", "guardrails", "alignment"]
21
+ dependencies = [
22
+ "sentence-transformers>=2.2.0",
23
+ "numpy>=1.21.0"
24
+ ]
25
+ requires-python = ">=3.9"
26
+
27
+ [project.optional-dependencies]
28
+ langchain = ["langchain>=0.1.0", "langchain-core>=0.1.0"]
29
+ llama-index = ["llama-index>=0.10.0"]
30
+ all = ["langchain>=0.1.0", "llama-index>=0.10.0", "pgvector>=0.3.6", "prometheus-client>=0.20.0", "bleach>=6.0.0"]
31
+ dev = ["pytest>=7.0", "httpx>=0.27.0"]
32
+
33
+ [project.urls]
34
+ "Homepage" = "https://github.com/Lotuvia-Governanca/ichyra-projeto"
35
+ "Bug Tracker" = "https://github.com/Lotuvia-Governanca/ichyra-projeto/issues"
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["src"]
39
+
40
+ [tool.pytest.ini_options]
41
+ testpaths = ["tests"]
42
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,22 @@
1
+ from .core import IchyraITA, CorrectionResult
2
+ from .judge import LLMJudge
3
+ from .lotus import OneVehicleStripper, ImmutableAxiomVerifier, KarmicResidueCalculator, PoisonIntoMedicine
4
+
5
+ try:
6
+ from .integrations.langchain import IchyraLangChainHandler
7
+ from .integrations.llama_index import IchyraLlamaIndexGuard
8
+ except Exception:
9
+ IchyraLangChainHandler = None
10
+ IchyraLlamaIndexGuard = None
11
+
12
+ __all__ = [
13
+ "IchyraITA",
14
+ "CorrectionResult",
15
+ "LLMJudge",
16
+ "OneVehicleStripper",
17
+ "ImmutableAxiomVerifier",
18
+ "KarmicResidueCalculator",
19
+ "PoisonIntoMedicine",
20
+ "IchyraLangChainHandler",
21
+ "IchyraLlamaIndexGuard",
22
+ ]
@@ -0,0 +1,164 @@
1
+ import numpy as np
2
+ import datetime, os
3
+ from typing import List, Dict, Any, Optional
4
+ from dataclasses import dataclass
5
+ from .embeddings import EmbeddingService
6
+ from .lotus import KarmicResidueCalculator
7
+
8
+ @dataclass
9
+ class CorrectionResult:
10
+ magnitude: float
11
+ detected: bool
12
+ details: Dict[str, Any]
13
+
14
+ # Pontos de operação medidos em sweep REAL sem judge (ver BENCHMARK_RESULT.md).
15
+ # Cosseno absoluto é frágil sob anisotropia (baseline BGE ~0.8+); estes defaults
16
+ # calibrados valem até haver scoring relativo (margem/rank, requer histórico
17
+ # multi-âncora — ainda não implementado).
18
+ CALIBRATED_THRESHOLDS = {
19
+ "bge-base": 0.85, # sweep v2 real: recall 100%, FPR 43.3%
20
+ }
21
+
22
+ def calibrated_default(model_name: str) -> float:
23
+ low = (model_name or "").lower()
24
+ for key, thr in CALIBRATED_THRESHOLDS.items():
25
+ if key in low:
26
+ return thr
27
+ return 0.55
28
+
29
+ # Corte violação vs âncora safe: severity > -0.4 é âncora safe.
30
+ # Default -0.5 cai no lado violação (conservador).
31
+ SAFE_SEVERITY_ABOVE = -0.4
32
+
33
+ class IchyraITA:
34
+ def __init__(self, embedding_model: str = None):
35
+ env_model = os.getenv("EMBEDDING_MODEL", "")
36
+ self.embedding_model_name = embedding_model or env_model or "BAAI/bge-base-en-v1.5"
37
+ self.embedder = EmbeddingService(self.embedding_model_name)
38
+ # threshold via env ou ponto calibrado por modelo (sweep real)
39
+ self.default_threshold = float(os.getenv("ITA_THRESHOLD") or calibrated_default(self.embedding_model_name))
40
+
41
+ def evaluate(self,
42
+ current_context: str,
43
+ proposed_action: str,
44
+ feedback_history: List[Dict[str, Any]],
45
+ threshold: float = None,
46
+ use_llm_judge: bool = None,
47
+ relative: bool = None,
48
+ margin_threshold: float = None) -> CorrectionResult:
49
+ if threshold is None:
50
+ threshold = self.default_threshold
51
+ if use_llm_judge is None:
52
+ use_llm_judge = os.getenv("LLM_JUDGE_ENABLED", "false").lower() == "true"
53
+ if margin_threshold is None:
54
+ margin_threshold = float(os.getenv("ITA_MARGIN_THRESHOLD", "0.008"))
55
+
56
+ if not feedback_history:
57
+ return CorrectionResult(magnitude=0.0, detected=False, details={})
58
+
59
+ v_curr = self.embedder.encode(current_context, proposed_action)
60
+ now = datetime.datetime.now(datetime.timezone.utc)
61
+
62
+ # Coleta (s_d, p, record, is_safe) com decaimento kármico assintótico (Lotus L3).
63
+ scored = []
64
+ karmic_calc = KarmicResidueCalculator()
65
+
66
+ # Contagem de reincidências de violações graves no histórico
67
+ grave_violations_count = sum(1 for r in feedback_history if abs(float(r.get("severity", -0.5))) >= 0.8)
68
+
69
+ for record in feedback_history:
70
+ # Cache do embedding histórico para não recalcular N vezes
71
+ if "_cached_vec" not in record:
72
+ record["_cached_vec"] = self.embedder.encode(record["context"], record["action"])
73
+ v_orig = record["_cached_vec"]
74
+ similarity = float(np.dot(v_curr, v_orig))
75
+ s_d = similarity
76
+ p = abs(float(record.get("severity", -0.5)))
77
+
78
+ if "timestamp" in record and isinstance(record["timestamp"], datetime.datetime):
79
+ delta_days = (now - record["timestamp"]).days
80
+ decay_factor = karmic_calc.compute_decay(
81
+ delta_days=delta_days,
82
+ severity=p,
83
+ recurrence_count=grave_violations_count
84
+ )
85
+ s_d = similarity * decay_factor
86
+
87
+ is_safe = float(record.get("severity", -0.5)) > SAFE_SEVERITY_ABOVE
88
+ scored.append((s_d, p, record, is_safe))
89
+
90
+ # Modo relativo: auto quando há safe anchors; legado caso contrário.
91
+ if relative is None:
92
+ relative = any(s[3] for s in scored)
93
+ if relative:
94
+ return self._evaluate_relative(current_context, proposed_action, scored,
95
+ margin_threshold, use_llm_judge)
96
+
97
+ max_magnitude = 0.0
98
+ best_match = None
99
+ for (s_d, p, record, _is_safe) in scored:
100
+ if s_d > threshold:
101
+ p = abs(float(record.get("severity", -0.5)))
102
+ magnitude = s_d * p
103
+ # LLM Judge segunda checagem: chama LLM para confirmar se é falso-positivo
104
+ # Em produção BGE, judge filtra FP semântico fino (ex: negar vs oferecer ajuda)
105
+ if use_llm_judge:
106
+ llm_confirm = self._llm_judge(current_context, proposed_action, record)
107
+ if llm_confirm is False: # LLM diz que é falso-positivo
108
+ continue # descarta este match — não conta como violação
109
+ elif llm_confirm is True:
110
+ pass # confirma violação
111
+ if magnitude > max_magnitude:
112
+ max_magnitude = magnitude
113
+ best_match = {
114
+ "historical_context": record["context"],
115
+ "historical_action": record["action"],
116
+ "similarity": s_d,
117
+ "base_severity": p,
118
+ "embedding_model": self.embedding_model_name,
119
+ "llm_judge_used": use_llm_judge,
120
+ }
121
+
122
+ # Se LLM judge reduziu todos, detected pode ser False mesmo com similaridade alta
123
+ return CorrectionResult(
124
+ magnitude=max_magnitude,
125
+ detected=(max_magnitude > 0.4), # threshold final de magnitude (calibrado)
126
+ details=best_match or {}
127
+ )
128
+
129
+ def _evaluate_relative(self, current_context: str, proposed_action: str,
130
+ scored, margin_threshold: float,
131
+ use_llm_judge: bool) -> CorrectionResult:
132
+ """Decisão por margem: mais perto de violação conhecida que de safe conhecido."""
133
+ viol = [(s, p, r) for (s, p, r, safe) in scored if not safe]
134
+ safe = [(s, p, r) for (s, p, r, safe) in scored if safe]
135
+ if not viol:
136
+ return CorrectionResult(magnitude=0.0, detected=False,
137
+ details={"mode": "relative", "reason": "no-violation-anchors"})
138
+ s_v, p_v, rec_v = max(viol, key=lambda t: t[0])
139
+ if safe:
140
+ s_s, _, rec_s = max(safe, key=lambda t: t[0])
141
+ else:
142
+ s_s, rec_s = 0.0, None
143
+ margin = s_v - s_s
144
+ base_details = {"mode": "relative", "margin": margin, "s_viol": s_v, "s_safe": s_s,
145
+ "threshold": margin_threshold, "embedding_model": self.embedding_model_name,
146
+ "llm_judge_used": use_llm_judge,
147
+ "historical_context": rec_v["context"], "historical_action": rec_v["action"]}
148
+ if margin <= margin_threshold:
149
+ return CorrectionResult(magnitude=0.0, detected=False, details=base_details)
150
+ if use_llm_judge:
151
+ llm_confirm = self._llm_judge(current_context, proposed_action, rec_v)
152
+ if llm_confirm is False: # judge diz falso-positivo
153
+ d = dict(base_details); d["judge"] = "fp-filtered"
154
+ return CorrectionResult(magnitude=0.0, detected=False, details=d)
155
+ return CorrectionResult(magnitude=max(0.0, margin) * p_v, detected=True, details=base_details)
156
+
157
+ def _llm_judge(self, current_context: str, proposed_action: str, record: Dict[str,Any]) -> Optional[bool]:
158
+ """Avalia via LLMJudge multi-provedor (OpenAI, Claude, Ollama local)"""
159
+ try:
160
+ from .judge import LLMJudge
161
+ judge_instance = LLMJudge()
162
+ return judge_instance.judge(current_context, proposed_action, record)
163
+ except Exception:
164
+ return None
@@ -0,0 +1,54 @@
1
+ import os, numpy as np
2
+
3
+ class EmbeddingService:
4
+ """
5
+ Handles encoding with configurable production model.
6
+ Env EMBEDDING_MODEL controla: all-MiniLM-L6-v2 (rápido, FP 94%), BAAI/bge-base-en-v1.5 (produção, recomendado),
7
+ BAAI/bge-large-en-v1.5 (máx precisão, 1024 dims), intfloat/e5-large-v2.
8
+ Fallback gracioso se modelo não baixado.
9
+ """
10
+ def __init__(self, model_name: str = None):
11
+ env_model = os.getenv("EMBEDDING_MODEL", "")
12
+ self.model_name = model_name or env_model or "BAAI/bge-base-en-v1.5"
13
+ self._model = None
14
+ self._load_error = None
15
+ # SKIP_DOWNLOAD para CI/benchmark rápido — usa fallback sem baixar
16
+ if os.getenv("SKIP_DOWNLOAD","").lower() == "true":
17
+ self._load_error = "SKIP_DOWNLOAD=true"
18
+ return
19
+ try:
20
+ from sentence_transformers import SentenceTransformer
21
+ self._model = SentenceTransformer(self.model_name, device="cpu")
22
+ except Exception as e:
23
+ self._load_error = str(e)
24
+ self._model = None
25
+
26
+ def encode(self, context: str, action: str) -> np.ndarray:
27
+ text = f"contexto: {context}\nação: {action}"
28
+ if self._model is not None:
29
+ try:
30
+ # BGE requer prefixo "Represent this sentence for searching relevant passages: " para query; simplificamos
31
+ embedding = self._model.encode(text, convert_to_numpy=True, normalize_embeddings=True)
32
+ # já normalizado se normalize_embeddings=True
33
+ norm = np.linalg.norm(embedding)
34
+ if norm > 0 and abs(norm - 1.0) > 1e-3:
35
+ embedding = embedding / norm
36
+ return embedding
37
+ except Exception:
38
+ pass
39
+ # Fallback determinístico (para CI sem download)
40
+ import hashlib
41
+ h = hashlib.sha256(text.encode()).digest()
42
+ vec = np.array([b/255 for b in h[:16]], dtype=np.float32)
43
+ # expande para 384 para compat
44
+ vec = np.tile(vec, 24)[:384]
45
+ norm = np.linalg.norm(vec)
46
+ return vec / norm if norm > 0 else vec
47
+
48
+ @property
49
+ def is_real(self) -> bool:
50
+ return self._model is not None
51
+
52
+ @property
53
+ def info(self) -> dict:
54
+ return {"model": self.model_name, "real": self.is_real, "error": self._load_error}
@@ -0,0 +1,5 @@
1
+ from .langchain import IchyraLangChainHandler
2
+ from .llama_index import IchyraLlamaIndexGuard
3
+ from .crewai import IchyraCrewAIGuard
4
+
5
+ __all__ = ["IchyraLangChainHandler", "IchyraLlamaIndexGuard", "IchyraCrewAIGuard"]
@@ -0,0 +1,76 @@
1
+ """
2
+ CrewAI integration — ICHYRA como porteiro de ferramentas e saídas.
3
+ Uso:
4
+ from ichyra.integrations.crewai import IchyraCrewAIGuard
5
+ guard = IchyraCrewAIGuard(threshold=0.85)
6
+ # 1. Envolve qualquer tool do CrewAI (2 linhas):
7
+ safe_tool = guard.guard_tool(minha_tool)
8
+ # 2. Ou checa a saída do agente antes de entregar:
9
+ veredito = guard.check(task="resumir tickets", output=text)
10
+ # veredito = {"blocked": bool, "magnitude": float, "remedy": str|None}
11
+ Delegação pura para IchyraITA — sem duplicar lógica, sem depender do
12
+ pacote crewai instalado (duck-typing: aceita qualquer callable).
13
+ """
14
+ from typing import List, Dict, Any, Optional, Callable
15
+
16
+
17
+ class IchyraCrewAIGuard:
18
+ """
19
+ Porteiro ICHYRA para agentes CrewAI (ou qualquer framework de agentes).
20
+ Lazy: só importa IchyraITA no primeiro uso.
21
+ """
22
+
23
+ def __init__(self, feedback_history: List[Dict[str, Any]] = None,
24
+ threshold: float = 0.85, use_llm_judge: bool = None,
25
+ embedding_model: str = None):
26
+ self.feedback_history = feedback_history or []
27
+ self.threshold = threshold
28
+ self.use_llm_judge = use_llm_judge
29
+ self.embedding_model = embedding_model
30
+ self._ita = None
31
+
32
+ @property
33
+ def ita(self):
34
+ if self._ita is None:
35
+ from ..core import IchyraITA
36
+ self._ita = IchyraITA(
37
+ embedding_model=self.embedding_model) if self.embedding_model else IchyraITA()
38
+ return self._ita
39
+
40
+ def check(self, task: str, output: str) -> Dict[str, Any]:
41
+ """Avalia a saída do agente contra a tarefa. Retorna veredito + remédio."""
42
+ res = self.ita.evaluate(
43
+ current_context=task, proposed_action=output,
44
+ feedback_history=self.feedback_history,
45
+ threshold=self.threshold, use_llm_judge=self.use_llm_judge)
46
+ remedy = None
47
+ if res.detected:
48
+ remedy = (f"[ICHYRA] magnitude {res.magnitude:.3f} acima do "
49
+ f"limite {self.threshold}. Reavalie com empatia e "
50
+ f"proteção antes de entregar.")
51
+ return {"blocked": res.detected, "magnitude": res.magnitude,
52
+ "details": res.details, "remedy": remedy}
53
+
54
+ def guard_tool(self, fn: Callable) -> Callable:
55
+ """Envolve uma tool: avalia os argumentos antes de executar."""
56
+ def wrapped(*args, **kwargs):
57
+ textual = " ".join([str(a) for a in args] +
58
+ [f"{k}={v}" for k, v in kwargs.items()])
59
+ res = self.ita.evaluate(
60
+ current_context=f"tool:{getattr(fn, '__name__', 'tool')}",
61
+ proposed_action=textual,
62
+ feedback_history=self.feedback_history,
63
+ threshold=self.threshold, use_llm_judge=self.use_llm_judge)
64
+ if res.detected:
65
+ raise PermissionError(
66
+ f"[ICHYRA] tool bloqueada (magnitude {res.magnitude:.3f})")
67
+ return fn(*args, **kwargs)
68
+ wrapped.__name__ = getattr(fn, "__name__", "guarded_tool")
69
+ wrapped.__doc__ = fn.__doc__
70
+ return wrapped
71
+
72
+ def add_feedback(self, context: str, action: str, severity: float, timestamp=None):
73
+ import datetime
74
+ self.feedback_history.append({
75
+ "context": context, "action": action, "severity": severity,
76
+ "timestamp": timestamp or datetime.datetime.now(datetime.timezone.utc)})
@@ -0,0 +1,61 @@
1
+ """
2
+ LangChain integration — ICHYRA as a callback / guardrail
3
+ Uso:
4
+ from ichyra.integrations.langchain import IchyraLangChainHandler
5
+ handler = IchyraLangChainHandler(feedback_history=[...])
6
+ llm = ChatOpenAI(callbacks=[handler]) # ou chain com handler
7
+ # handler avalia cada LLM output e bloqueia se magnitude > threshold
8
+ Delegação pura para IchyraITA — sem duplicar lógica.
9
+ """
10
+ from typing import List, Dict, Any, Optional
11
+
12
+ class IchyraLangChainHandler:
13
+ """
14
+ LangChain callback handler que usa ICHYRA ITA como porteiro.
15
+ Compatível com LangChain Core callbacks (on_llm_end).
16
+ Lazy: só importa IchyraITA quando chamado.
17
+ """
18
+ def __init__(self, feedback_history: List[Dict[str,Any]] = None, threshold: float = 0.55, use_llm_judge: bool = None, embedding_model: str = None):
19
+ self.feedback_history = feedback_history or []
20
+ self.threshold = threshold
21
+ self.use_llm_judge = use_llm_judge
22
+ self.embedding_model = embedding_model
23
+ self._ita = None
24
+
25
+ @property
26
+ def ita(self):
27
+ if self._ita is None:
28
+ from ..core import IchyraITA
29
+ self._ita = IchyraITA(embedding_model=self.embedding_model) if self.embedding_model else IchyraITA()
30
+ return self._ita
31
+
32
+ def evaluate(self, context: str, proposed_action: str) -> Dict[str,Any]:
33
+ res = self.ita.evaluate(current_context=context, proposed_action=proposed_action, feedback_history=self.feedback_history, threshold=self.threshold, use_llm_judge=self.use_llm_judge)
34
+ return {"magnitude": res.magnitude, "blocked": res.detected, "details": res.details}
35
+
36
+ # LangChain callback hook
37
+ def on_llm_end(self, response, **kwargs):
38
+ # response.generations[0][0].text
39
+ try:
40
+ text = response.generations[0][0].text if hasattr(response, "generations") else str(response)
41
+ context = kwargs.get("context", "")
42
+ res = self.evaluate(context, text)
43
+ if res["blocked"]:
44
+ # Em prod, levanta ou reescreve; aqui apenas anexa
45
+ response._ichyra_blocked = True
46
+ response._ichyra_magnitude = res["magnitude"]
47
+ except Exception:
48
+ pass
49
+
50
+ # Para Chains / Agents: avalia antes de tool
51
+ def on_tool_start(self, serialized, input_str, **kwargs):
52
+ # avalia se tool é de alto risco; usa Security Architect se disponível
53
+ pass
54
+
55
+ def add_feedback(self, context: str, action: str, severity: float, timestamp=None):
56
+ import datetime
57
+ self.feedback_history.append({"context": context, "action": action, "severity": severity, "timestamp": timestamp or datetime.datetime.now(datetime.timezone.utc)})
58
+
59
+ def to_react_prompt(self, magnitude: float) -> str:
60
+ # Poison into Medicine: prompt de auto-correção proporcional
61
+ return f"[ICHYRA] Correção vetorial R_transform magnitude {magnitude:.3f} detectada. Reavalie sua ação com empatia e proteção, integrando a sombra (Shadow Integration)."