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.
- ichyra_guard-0.7.0/LICENSE +21 -0
- ichyra_guard-0.7.0/PKG-INFO +119 -0
- ichyra_guard-0.7.0/README.md +85 -0
- ichyra_guard-0.7.0/pyproject.toml +42 -0
- ichyra_guard-0.7.0/setup.cfg +4 -0
- ichyra_guard-0.7.0/src/ichyra/__init__.py +22 -0
- ichyra_guard-0.7.0/src/ichyra/core.py +164 -0
- ichyra_guard-0.7.0/src/ichyra/embeddings.py +54 -0
- ichyra_guard-0.7.0/src/ichyra/integrations/__init__.py +5 -0
- ichyra_guard-0.7.0/src/ichyra/integrations/crewai.py +76 -0
- ichyra_guard-0.7.0/src/ichyra/integrations/langchain.py +61 -0
- ichyra_guard-0.7.0/src/ichyra/integrations/llama_index.py +48 -0
- ichyra_guard-0.7.0/src/ichyra/judge.py +363 -0
- ichyra_guard-0.7.0/src/ichyra/lotus.py +356 -0
- ichyra_guard-0.7.0/src/ichyra_guard.egg-info/PKG-INFO +119 -0
- ichyra_guard-0.7.0/src/ichyra_guard.egg-info/SOURCES.txt +46 -0
- ichyra_guard-0.7.0/src/ichyra_guard.egg-info/dependency_links.txt +1 -0
- ichyra_guard-0.7.0/src/ichyra_guard.egg-info/requires.txt +20 -0
- ichyra_guard-0.7.0/src/ichyra_guard.egg-info/top_level.txt +1 -0
- ichyra_guard-0.7.0/tests/test_auth_real.py +56 -0
- ichyra_guard-0.7.0/tests/test_drift_compat.py +42 -0
- ichyra_guard-0.7.0/tests/test_e2e_pgvector.py +59 -0
- ichyra_guard-0.7.0/tests/test_endpoints.py +41 -0
- ichyra_guard-0.7.0/tests/test_enterprise_endpoints.py +109 -0
- ichyra_guard-0.7.0/tests/test_external_adapters.py +46 -0
- ichyra_guard-0.7.0/tests/test_facet_refiner.py +55 -0
- ichyra_guard-0.7.0/tests/test_governance.py +54 -0
- ichyra_guard-0.7.0/tests/test_graph.py +78 -0
- ichyra_guard-0.7.0/tests/test_guardian_heartbeat.py +71 -0
- ichyra_guard-0.7.0/tests/test_handshake.py +117 -0
- ichyra_guard-0.7.0/tests/test_hardware_root.py +22 -0
- ichyra_guard-0.7.0/tests/test_infra_b.py +60 -0
- ichyra_guard-0.7.0/tests/test_integrity_guardian.py +46 -0
- ichyra_guard-0.7.0/tests/test_isip_auto.py +53 -0
- ichyra_guard-0.7.0/tests/test_lir0_corruption.py +64 -0
- ichyra_guard-0.7.0/tests/test_manifold_federation.py +89 -0
- ichyra_guard-0.7.0/tests/test_packaging_d.py +47 -0
- ichyra_guard-0.7.0/tests/test_pentest_regression.py +231 -0
- ichyra_guard-0.7.0/tests/test_phi_navigation.py +43 -0
- ichyra_guard-0.7.0/tests/test_pilot_live.py +108 -0
- ichyra_guard-0.7.0/tests/test_policies.py +48 -0
- ichyra_guard-0.7.0/tests/test_revocation.py +79 -0
- ichyra_guard-0.7.0/tests/test_security_fixes.py +85 -0
- ichyra_guard-0.7.0/tests/test_tickets.py +50 -0
- ichyra_guard-0.7.0/tests/test_topology.py +45 -0
- ichyra_guard-0.7.0/tests/test_unified_integration.py +37 -0
- ichyra_guard-0.7.0/tests/test_verifier.py +74 -0
- 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
|
+
   
|
|
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
|
+
   
|
|
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,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,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)."
|