rti-ethics-engine 2.0.0__tar.gz → 3.0.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 (38) hide show
  1. {rti_ethics_engine-2.0.0 → rti_ethics_engine-3.0.0}/.gitignore +6 -2
  2. rti_ethics_engine-3.0.0/CHANGELOG.md +31 -0
  3. rti_ethics_engine-3.0.0/PKG-INFO +213 -0
  4. rti_ethics_engine-3.0.0/README.md +160 -0
  5. {rti_ethics_engine-2.0.0 → rti_ethics_engine-3.0.0}/pyproject.toml +74 -91
  6. rti_ethics_engine-3.0.0/rti_ethics_engine/__init__.py +48 -0
  7. rti_ethics_engine-3.0.0/rti_ethics_engine/calculators.py +368 -0
  8. rti_ethics_engine-3.0.0/rti_ethics_engine/config.py +206 -0
  9. rti_ethics_engine-3.0.0/rti_ethics_engine/core.py +106 -0
  10. rti_ethics_engine-3.0.0/rti_ethics_engine/dimensions.py +143 -0
  11. rti_ethics_engine-3.0.0/rti_ethics_engine/evaluator.py +235 -0
  12. rti_ethics_engine-3.0.0/rti_ethics_engine/gatekeeper.py +132 -0
  13. rti_ethics_engine-3.0.0/rti_ethics_engine/judge.py +234 -0
  14. rti_ethics_engine-3.0.0/rti_ethics_engine/measures.py +489 -0
  15. rti_ethics_engine-3.0.0/rti_ethics_engine/meters.py +88 -0
  16. rti_ethics_engine-3.0.0/rti_ethics_engine/probes.py +453 -0
  17. rti_ethics_engine-3.0.0/rti_ethics_engine/profile.py +176 -0
  18. rti_ethics_engine-3.0.0/rti_ethics_engine/profiles/__init__.py +5 -0
  19. rti_ethics_engine-3.0.0/rti_ethics_engine/profiles/rti.py +65 -0
  20. {rti_ethics_engine-2.0.0 → rti_ethics_engine-3.0.0}/rti_ethics_engine/results.py +64 -32
  21. rti_ethics_engine-3.0.0/rti_ethics_engine/stats.py +121 -0
  22. rti_ethics_engine-3.0.0/rti_ethics_engine/store.py +217 -0
  23. rti_ethics_engine-3.0.0/rti_ethics_engine/textkit.py +164 -0
  24. rti_ethics_engine-3.0.0/rti_ethics_engine/types.py +113 -0
  25. rti_ethics_engine-3.0.0/tests/test_calculators.py +177 -0
  26. rti_ethics_engine-3.0.0/tests/test_engine.py +116 -0
  27. rti_ethics_engine-3.0.0/tests/test_integration.py +435 -0
  28. rti_ethics_engine-2.0.0/PKG-INFO +0 -166
  29. rti_ethics_engine-2.0.0/README.md +0 -108
  30. rti_ethics_engine-2.0.0/rti_ethics_engine/__init__.py +0 -180
  31. rti_ethics_engine-2.0.0/rti_ethics_engine/calculators.py +0 -1039
  32. rti_ethics_engine-2.0.0/rti_ethics_engine/config.py +0 -527
  33. rti_ethics_engine-2.0.0/rti_ethics_engine/core.py +0 -184
  34. rti_ethics_engine-2.0.0/rti_ethics_engine/dimensions.py +0 -373
  35. rti_ethics_engine-2.0.0/rti_ethics_engine/metrics.py +0 -553
  36. rti_ethics_engine-2.0.0/tests/test_engine.py +0 -205
  37. {rti_ethics_engine-2.0.0 → rti_ethics_engine-3.0.0}/LICENSE +0 -0
  38. {rti_ethics_engine-2.0.0 → rti_ethics_engine-3.0.0}/tests/__init__.py +0 -0
@@ -14,8 +14,8 @@ dist/
14
14
  downloads/
15
15
  eggs/
16
16
  .eggs/
17
- lib/
18
- lib64/
17
+ /lib/
18
+ /lib64/
19
19
  parts/
20
20
  sdist/
21
21
  var/
@@ -205,3 +205,7 @@ cython_debug/
205
205
  marimo/_static/
206
206
  marimo/_lsp/
207
207
  __marimo__/
208
+
209
+
210
+ # v3 evidence store (generated)
211
+ backend/data/ethics_evidence.db*
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ ## 3.0.0
4
+
5
+ A rewrite around one rule: **no metric is ever defaulted.** A value that cannot be measured is `None`, is listed in
6
+ `result.unmeasured`, and lowers `result.coverage`; below `min_coverage` the verdict is `INSUFFICIENT_EVIDENCE`.
7
+
8
+ ### Added
9
+ - `Evaluator`: interaction in, full 9-dimension evaluation out. Logs to a tamper-evident hash chain.
10
+ - `Interaction` / `Chunk` / `GenResult` evidence types as the only data contract.
11
+ - `DomainProfile` (+ `profiles.rti_profile`): all domain knowledge is configuration; the core is generic.
12
+ - `Auditor`: active audits (security canary attacks, paired-prompt fairness, gold-set performance, membership
13
+ inference, poisoning detection, counterfactual and explanation stability).
14
+ - `Gatekeeper`: ACTIVE / QUARANTINED / PROBATION state machine, automatic blocking, human re-admission, routing.
15
+ - `Judge`: optional LLM judge (on by default, disable with `judge=None`), structured, cached, self-grading-safe.
16
+ - `GpuMeter`: measured GPU energy and utilisation.
17
+ - `SQLiteStore`: persistent evidence, baselines, audit results, gate state.
18
+ - All 44 formulas implemented as pure functions returning `None` when undefined.
19
+ - Statistical corrections: PSI from baseline quantiles, KS critical value for sample size, Wilson/bootstrap intervals.
20
+
21
+ ### Changed (breaking)
22
+ - Weights are normalised to sum to 1 (they summed to 1.24).
23
+ - Thresholds follow `formulas.md` (SPD 0.1, PSI 0.2, accuracy 0.8, ...), not the 0.5 placeholders of 2.x.
24
+ - Layer-3 explanations are generated from real values instead of canned text.
25
+ - Environmental risk no longer saturates (compute intensity is `2 x params x tokens / output_tokens`).
26
+ - `EthicsEngine.run_protocol` accepts `None` metrics and returns `coverage` / `unmeasured`.
27
+
28
+ ### Removed
29
+ - `build_metrics_from_raw_data`, `build_default_metrics` and the `compute_*_from_data` helpers, which filled in
30
+ constants (confidence 0.9, TCR 1.0, ALC 1.0, ...). Use `Evaluator` instead.
31
+ - `MetricCalculator` and the dynamic-config loader.
@@ -0,0 +1,213 @@
1
+ Metadata-Version: 2.5
2
+ Name: rti-ethics-engine
3
+ Version: 3.0.0
4
+ Summary: Measured, domain-pluggable ethics monitoring for LLM systems: 9 dimensions, 44 formulas, drift tracking and automatic model gating. No defaulted metrics.
5
+ Project-URL: Homepage, https://github.com/Krishil-Parikh/bazinga_tsec_hacks
6
+ Project-URL: Repository, https://github.com/Krishil-Parikh/bazinga_tsec_hacks
7
+ Project-URL: Issues, https://github.com/Krishil-Parikh/bazinga_tsec_hacks/issues
8
+ Author: RTI Ethics Engine Team
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 RTI Ethics Engine Team
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: ai-ethics,audit,compliance,drift,eu-ai-act,fairness,guardrails,llm,monitoring,rag,rti
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: Intended Audience :: Science/Research
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Operating System :: OS Independent
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Programming Language :: Python :: 3.9
39
+ Classifier: Programming Language :: Python :: 3.10
40
+ Classifier: Programming Language :: Python :: 3.11
41
+ Classifier: Programming Language :: Python :: 3.12
42
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
43
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
44
+ Requires-Python: >=3.9
45
+ Requires-Dist: numpy>=1.24.0
46
+ Requires-Dist: scipy>=1.10.0
47
+ Provides-Extra: dev
48
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
49
+ Requires-Dist: pytest>=7.0; extra == 'dev'
50
+ Provides-Extra: embeddings
51
+ Requires-Dist: sentence-transformers>=2.2; extra == 'embeddings'
52
+ Description-Content-Type: text/markdown
53
+
54
+ # rti-ethics-engine
55
+
56
+ **Measured, domain-pluggable ethics monitoring for LLM systems.**
57
+
58
+ Guardrails judge one request at a time. This package watches a model *over time*: it scores every answer on nine ethical dimensions, tracks drift against a frozen baseline, probes the model actively (prompt injection, fairness, leakage, poisoning), and can **automatically block a model that crosses a threshold** until it is fixed and a human re-admits it.
59
+
60
+ Built for government chatbots (RTI/FOI) and for any company that serves several LLMs and is accountable for what they say. Nothing in the core is tied to a domain, a vendor or a dataset.
61
+
62
+ ```bash
63
+ pip install rti-ethics-engine # numpy + scipy only
64
+ pip install "rti-ethics-engine[embeddings]" # optional: sentence-transformers embeddings
65
+ ```
66
+
67
+ ## The one rule: nothing is defaulted
68
+
69
+ Every metric is computed from evidence you hand over. If a metric cannot be measured, it is `None`, it is listed under `result.unmeasured`, and it lowers `result.coverage`. The engine will not fill in a "passing" value, and it will not issue a verdict (`INSUFFICIENT_EVIDENCE` instead) until enough of the weight is measured.
70
+
71
+ ## 60-second quick start
72
+
73
+ ```python
74
+ from rti_ethics_engine import Evaluator, Interaction, Chunk, SQLiteStore
75
+
76
+ ev = Evaluator(store=SQLiteStore("ethics.db"), judge=None) # judge=None: no LLM judge
77
+
78
+ ev.begin_request("my-model") # at API ingress (enables audit-log completeness)
79
+ answer = my_model(question) # your code
80
+ out = ev.evaluate(Interaction(
81
+ query=question, response=answer, model_id="my-model",
82
+ context=[Chunk(text=c) for c in retrieved_passages], # RAG context, if any
83
+ latency_ms=812, input_tokens=420, output_tokens=96,
84
+ ))
85
+
86
+ print(out.result.final_label, out.result.s_llm, out.result.coverage)
87
+ print(out.result.unmeasured) # what could not be measured, and why that matters
88
+ out.result.print_full_audit()
89
+ ```
90
+
91
+ Only `query` and `response` are required. Every extra field you provide (token log-probs, GPU energy, session ids, retrieved context, ground-truth labels, groups) unlocks more metrics.
92
+
93
+ ## Core objects
94
+
95
+ | Object | Role |
96
+ |---|---|
97
+ | `Interaction` | One observed request/response. The only data contract. |
98
+ | `DomainProfile` | Everything domain-specific: refusal wording, legal citations, PII patterns, SLA, prices. A generic default ships; `profiles.rti_profile()` is a worked example. |
99
+ | `Evaluator` | Logs an interaction, measures all nine dimensions, runs the 3-layer protocol, returns an `Evaluation`. |
100
+ | `Auditor` (`ev.auditor`) | Active probes: security, fairness pairs, gold-set performance, membership leakage, poisoning detection, explainability stability. |
101
+ | `Gatekeeper` | Per-model ACTIVE / QUARANTINED / PROBATION state machine with human re-admission. |
102
+ | `Judge` | Optional LLM judge for semantic checks. Pluggable, and fully disableable. |
103
+ | `SQLiteStore` | Evidence store with a hash-chained, tamper-evident audit log. |
104
+ | `EthicsEngine` | The pure scoring protocol, if you want to feed it metrics yourself. |
105
+
106
+ ## How each dimension is measured
107
+
108
+ | Dimension | Metrics | Measured from |
109
+ |---|---|---|
110
+ | Fairness | SPD, DI, EOP, AOD, FNRD, refusal disparity, MI leakage, predictive parity | Decisions by group in live traffic and in paired-prompt audits; worst pairwise gap when there are >2 groups |
111
+ | Drift | PSI, KS, prediction/confidence/embedding drift | Frozen baseline vs recent window; PSI bins from baseline quantiles; KS threshold corrected for sample size |
112
+ | Environmental & cost | Cost, GPU-hours, compute intensity, cost-utility, CO2 | Tokens x price, or measured GPU energy (`GpuMeter`) x tariff; FLOPs ~ 2 x params x tokens |
113
+ | Explainability | Confidence, feature importance, counterfactual distance, explanation stability | Token log-probs (or self-consistency); retrieval attribution (or judge claim support); probe audits |
114
+ | UX | Task completion, frustration, dialog turns, cognitive load, readability | Sessions: reformulation = unresolved; Flesch reading ease |
115
+ | Legal | Lawfulness, consent validity, proportionality, DPIA coverage | Profile citation rules; PII vs personal-data requests; requested-vs-covered items; register of checks that actually ran |
116
+ | Performance | Accuracy, recall, precision, F1, group F1, long-tail accuracy | Your labelled gold set via `Auditor.run_gold` |
117
+ | Monitoring | Audit-log completeness, SLA, policy violations, incident reconstruction | Requests seen vs hash-chain-verified records; latency vs SLA; profile policy rules |
118
+ | Security | Adversarial accuracy, injection success, membership inference, poisoning detection | `Auditor.run_security` (canary attacks + your attacks), membership and poisoning audits |
119
+
120
+ The 3-layer protocol: **L1** hard gates (PASS/FAIL), **L2** risk in [0, 1] (a failed gate forces 1.0), **L3** forensic evidence generated from the real values. `S_LLM = 1 - sum(w_d * risk_d) / sum(w_d)` over the dimensions that have data. Thresholds and weights are data (`config.METRIC_SPECS`) and can be overridden per profile, JSON file or `RTI_*` environment variables.
121
+
122
+ ## Make it yours: a domain profile
123
+
124
+ ```python
125
+ from rti_ethics_engine import DomainProfile, CategoryRule, Evaluator
126
+
127
+ bank = DomainProfile(
128
+ name="retail-banking",
129
+ refusal_patterns=[r"\bcannot (?:share|disclose|help)\b", r"\bunable to assist\b"],
130
+ pii_patterns={"iban": r"\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b", "email": r"\b[\w.+-]+@[\w-]+\.\w+\b"},
131
+ required_elements={"disclaimer": r"not financial advice"},
132
+ category_rules=[CategoryRule("account_data", [r"\bbalance of\b.*\bother customer\b"], expect_refusal=True)],
133
+ sla_ms=4000,
134
+ pricing={"gpt-x": (0.0005, 0.0015)}, # USD per 1k tokens (in, out)
135
+ thresholds={"fairness.spd": 0.05}, # tighten a gate
136
+ weights={"security": 0.30}, # re-weight a dimension
137
+ )
138
+ ev = Evaluator(profile=bank, judge=None)
139
+ ```
140
+
141
+ ## Active audits
142
+
143
+ ```python
144
+ from rti_ethics_engine import GoldExample, PairedPrompt, default_attacks
145
+
146
+ def model(query, context=None): # your whole system as a callable
147
+ return answer_with_rag(query, context)
148
+
149
+ aud = ev.auditor
150
+ aud.run_security("my-model", model, benign=["How do I reset my PIN?"]) # PISR, adversarial accuracy
151
+ aud.run_gold("my-model", model, [GoldExample("Close my account", expected_decision=1)])
152
+ aud.run_fairness_pairs("my-model", model, [PairedPrompt("p1", {"A": "...", "B": "..."}, expected_decision=1)])
153
+ aud.run_membership("my-model", model, members=[...], nonmembers=[...]) # leakage
154
+ aud.run_poisoning(corpus_docs) # ingestion detector PDR
155
+ ```
156
+
157
+ Fresh audit results (default: younger than 7 days) are folded into every evaluation automatically.
158
+
159
+ ## Gate models in production
160
+
161
+ ```python
162
+ from rti_ethics_engine import Gatekeeper, GatePolicy
163
+
164
+ gate = Gatekeeper(ev.store, GatePolicy(min_obs=10, block_risk=0.55))
165
+ model = gate.select(["model-a", "model-b"], preferred="model-a") # routes around blocked models
166
+ ...
167
+ out = ev.evaluate(interaction)
168
+ gate.observe(out) # may quarantine automatically
169
+
170
+ gate.request_readmission("model-a", by="alice") # -> PROBATION: send shadow traffic
171
+ gate.probation_report("model-a")
172
+ gate.approve("model-a", approver="alice") # human sign-off; refuses if evidence is not clean
173
+ ```
174
+
175
+ Blocking is automatic; re-admission requires a named human and clean probation evidence. Re-admission thresholds are stricter than blocking thresholds, so a borderline model cannot flap.
176
+
177
+ ## The LLM judge (optional)
178
+
179
+ Some failures need meaning, not arithmetic: a paraphrased hallucination, a response that contradicts its sources, a legal exemption that does not apply to the question. `Judge` adds those checks with one structured JSON call per interaction at temperature 0, counts claims instead of free-form scoring, never grades its own model, caches by content hash, and returns `None` (not a made-up value) if it fails.
180
+
181
+ ```python
182
+ Evaluator() # default: judge from RTI_JUDGE_* env vars (Ollama, qwen3:8b)
183
+ Evaluator(judge=Judge(my_llm_callable, "judge-model"))
184
+ Evaluator(judge=None) # off: deterministic proxies are used where an honest one exists,
185
+ # judge-only metrics (claim-level faithfulness) are skipped
186
+ ```
187
+
188
+ Environment: `RTI_JUDGE_BACKEND=ollama|openai|none`, `RTI_JUDGE_MODEL`, `RTI_JUDGE_HOST`. Use a judge from a different model family than the models you audit.
189
+
190
+ ## Measured hardware
191
+
192
+ `GpuMeter` samples `nvidia-smi` during inference and returns `energy_kwh`, `gpu_util`, `power_w`; put them in `Interaction.metadata`. Without a GPU, energy-based metrics stay unmeasured unless you declare `DomainProfile.default_power_w`.
193
+
194
+ ```python
195
+ meter = GpuMeter()
196
+ with meter:
197
+ text = model(query)
198
+ interaction.metadata.update(meter.result())
199
+ ```
200
+
201
+ ## Audit trail
202
+
203
+ `SQLiteStore` hashes every record into a chain. `store.verify_chain()` detects any edit or deletion; `chain_stats_since()` underlies the audit-log-completeness and incident-reconstruction metrics. `begin_request()` at ingress makes lost records visible.
204
+
205
+ ## Honest limitations
206
+
207
+ - Fairness needs groups. Supply `Interaction.group`, a `group_fn` proxy in your profile, or run paired-prompt audits.
208
+ - Thresholds are defaults from the reference formulas (`DOCUMENTATION/formulas.md`); tune them for your domain.
209
+ - The LLM judge is an LLM: it can be wrong. Calibrate it against labelled cases before trusting its verdicts.
210
+ - Membership inference and poisoning audits measure the whole system you wrap; interpret them against how sensitive your corpus is.
211
+ - Monitoring does not transfer legal liability. It produces the evidence of reasonable care.
212
+
213
+ MIT licensed.
@@ -0,0 +1,160 @@
1
+ # rti-ethics-engine
2
+
3
+ **Measured, domain-pluggable ethics monitoring for LLM systems.**
4
+
5
+ Guardrails judge one request at a time. This package watches a model *over time*: it scores every answer on nine ethical dimensions, tracks drift against a frozen baseline, probes the model actively (prompt injection, fairness, leakage, poisoning), and can **automatically block a model that crosses a threshold** until it is fixed and a human re-admits it.
6
+
7
+ Built for government chatbots (RTI/FOI) and for any company that serves several LLMs and is accountable for what they say. Nothing in the core is tied to a domain, a vendor or a dataset.
8
+
9
+ ```bash
10
+ pip install rti-ethics-engine # numpy + scipy only
11
+ pip install "rti-ethics-engine[embeddings]" # optional: sentence-transformers embeddings
12
+ ```
13
+
14
+ ## The one rule: nothing is defaulted
15
+
16
+ Every metric is computed from evidence you hand over. If a metric cannot be measured, it is `None`, it is listed under `result.unmeasured`, and it lowers `result.coverage`. The engine will not fill in a "passing" value, and it will not issue a verdict (`INSUFFICIENT_EVIDENCE` instead) until enough of the weight is measured.
17
+
18
+ ## 60-second quick start
19
+
20
+ ```python
21
+ from rti_ethics_engine import Evaluator, Interaction, Chunk, SQLiteStore
22
+
23
+ ev = Evaluator(store=SQLiteStore("ethics.db"), judge=None) # judge=None: no LLM judge
24
+
25
+ ev.begin_request("my-model") # at API ingress (enables audit-log completeness)
26
+ answer = my_model(question) # your code
27
+ out = ev.evaluate(Interaction(
28
+ query=question, response=answer, model_id="my-model",
29
+ context=[Chunk(text=c) for c in retrieved_passages], # RAG context, if any
30
+ latency_ms=812, input_tokens=420, output_tokens=96,
31
+ ))
32
+
33
+ print(out.result.final_label, out.result.s_llm, out.result.coverage)
34
+ print(out.result.unmeasured) # what could not be measured, and why that matters
35
+ out.result.print_full_audit()
36
+ ```
37
+
38
+ Only `query` and `response` are required. Every extra field you provide (token log-probs, GPU energy, session ids, retrieved context, ground-truth labels, groups) unlocks more metrics.
39
+
40
+ ## Core objects
41
+
42
+ | Object | Role |
43
+ |---|---|
44
+ | `Interaction` | One observed request/response. The only data contract. |
45
+ | `DomainProfile` | Everything domain-specific: refusal wording, legal citations, PII patterns, SLA, prices. A generic default ships; `profiles.rti_profile()` is a worked example. |
46
+ | `Evaluator` | Logs an interaction, measures all nine dimensions, runs the 3-layer protocol, returns an `Evaluation`. |
47
+ | `Auditor` (`ev.auditor`) | Active probes: security, fairness pairs, gold-set performance, membership leakage, poisoning detection, explainability stability. |
48
+ | `Gatekeeper` | Per-model ACTIVE / QUARANTINED / PROBATION state machine with human re-admission. |
49
+ | `Judge` | Optional LLM judge for semantic checks. Pluggable, and fully disableable. |
50
+ | `SQLiteStore` | Evidence store with a hash-chained, tamper-evident audit log. |
51
+ | `EthicsEngine` | The pure scoring protocol, if you want to feed it metrics yourself. |
52
+
53
+ ## How each dimension is measured
54
+
55
+ | Dimension | Metrics | Measured from |
56
+ |---|---|---|
57
+ | Fairness | SPD, DI, EOP, AOD, FNRD, refusal disparity, MI leakage, predictive parity | Decisions by group in live traffic and in paired-prompt audits; worst pairwise gap when there are >2 groups |
58
+ | Drift | PSI, KS, prediction/confidence/embedding drift | Frozen baseline vs recent window; PSI bins from baseline quantiles; KS threshold corrected for sample size |
59
+ | Environmental & cost | Cost, GPU-hours, compute intensity, cost-utility, CO2 | Tokens x price, or measured GPU energy (`GpuMeter`) x tariff; FLOPs ~ 2 x params x tokens |
60
+ | Explainability | Confidence, feature importance, counterfactual distance, explanation stability | Token log-probs (or self-consistency); retrieval attribution (or judge claim support); probe audits |
61
+ | UX | Task completion, frustration, dialog turns, cognitive load, readability | Sessions: reformulation = unresolved; Flesch reading ease |
62
+ | Legal | Lawfulness, consent validity, proportionality, DPIA coverage | Profile citation rules; PII vs personal-data requests; requested-vs-covered items; register of checks that actually ran |
63
+ | Performance | Accuracy, recall, precision, F1, group F1, long-tail accuracy | Your labelled gold set via `Auditor.run_gold` |
64
+ | Monitoring | Audit-log completeness, SLA, policy violations, incident reconstruction | Requests seen vs hash-chain-verified records; latency vs SLA; profile policy rules |
65
+ | Security | Adversarial accuracy, injection success, membership inference, poisoning detection | `Auditor.run_security` (canary attacks + your attacks), membership and poisoning audits |
66
+
67
+ The 3-layer protocol: **L1** hard gates (PASS/FAIL), **L2** risk in [0, 1] (a failed gate forces 1.0), **L3** forensic evidence generated from the real values. `S_LLM = 1 - sum(w_d * risk_d) / sum(w_d)` over the dimensions that have data. Thresholds and weights are data (`config.METRIC_SPECS`) and can be overridden per profile, JSON file or `RTI_*` environment variables.
68
+
69
+ ## Make it yours: a domain profile
70
+
71
+ ```python
72
+ from rti_ethics_engine import DomainProfile, CategoryRule, Evaluator
73
+
74
+ bank = DomainProfile(
75
+ name="retail-banking",
76
+ refusal_patterns=[r"\bcannot (?:share|disclose|help)\b", r"\bunable to assist\b"],
77
+ pii_patterns={"iban": r"\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b", "email": r"\b[\w.+-]+@[\w-]+\.\w+\b"},
78
+ required_elements={"disclaimer": r"not financial advice"},
79
+ category_rules=[CategoryRule("account_data", [r"\bbalance of\b.*\bother customer\b"], expect_refusal=True)],
80
+ sla_ms=4000,
81
+ pricing={"gpt-x": (0.0005, 0.0015)}, # USD per 1k tokens (in, out)
82
+ thresholds={"fairness.spd": 0.05}, # tighten a gate
83
+ weights={"security": 0.30}, # re-weight a dimension
84
+ )
85
+ ev = Evaluator(profile=bank, judge=None)
86
+ ```
87
+
88
+ ## Active audits
89
+
90
+ ```python
91
+ from rti_ethics_engine import GoldExample, PairedPrompt, default_attacks
92
+
93
+ def model(query, context=None): # your whole system as a callable
94
+ return answer_with_rag(query, context)
95
+
96
+ aud = ev.auditor
97
+ aud.run_security("my-model", model, benign=["How do I reset my PIN?"]) # PISR, adversarial accuracy
98
+ aud.run_gold("my-model", model, [GoldExample("Close my account", expected_decision=1)])
99
+ aud.run_fairness_pairs("my-model", model, [PairedPrompt("p1", {"A": "...", "B": "..."}, expected_decision=1)])
100
+ aud.run_membership("my-model", model, members=[...], nonmembers=[...]) # leakage
101
+ aud.run_poisoning(corpus_docs) # ingestion detector PDR
102
+ ```
103
+
104
+ Fresh audit results (default: younger than 7 days) are folded into every evaluation automatically.
105
+
106
+ ## Gate models in production
107
+
108
+ ```python
109
+ from rti_ethics_engine import Gatekeeper, GatePolicy
110
+
111
+ gate = Gatekeeper(ev.store, GatePolicy(min_obs=10, block_risk=0.55))
112
+ model = gate.select(["model-a", "model-b"], preferred="model-a") # routes around blocked models
113
+ ...
114
+ out = ev.evaluate(interaction)
115
+ gate.observe(out) # may quarantine automatically
116
+
117
+ gate.request_readmission("model-a", by="alice") # -> PROBATION: send shadow traffic
118
+ gate.probation_report("model-a")
119
+ gate.approve("model-a", approver="alice") # human sign-off; refuses if evidence is not clean
120
+ ```
121
+
122
+ Blocking is automatic; re-admission requires a named human and clean probation evidence. Re-admission thresholds are stricter than blocking thresholds, so a borderline model cannot flap.
123
+
124
+ ## The LLM judge (optional)
125
+
126
+ Some failures need meaning, not arithmetic: a paraphrased hallucination, a response that contradicts its sources, a legal exemption that does not apply to the question. `Judge` adds those checks with one structured JSON call per interaction at temperature 0, counts claims instead of free-form scoring, never grades its own model, caches by content hash, and returns `None` (not a made-up value) if it fails.
127
+
128
+ ```python
129
+ Evaluator() # default: judge from RTI_JUDGE_* env vars (Ollama, qwen3:8b)
130
+ Evaluator(judge=Judge(my_llm_callable, "judge-model"))
131
+ Evaluator(judge=None) # off: deterministic proxies are used where an honest one exists,
132
+ # judge-only metrics (claim-level faithfulness) are skipped
133
+ ```
134
+
135
+ Environment: `RTI_JUDGE_BACKEND=ollama|openai|none`, `RTI_JUDGE_MODEL`, `RTI_JUDGE_HOST`. Use a judge from a different model family than the models you audit.
136
+
137
+ ## Measured hardware
138
+
139
+ `GpuMeter` samples `nvidia-smi` during inference and returns `energy_kwh`, `gpu_util`, `power_w`; put them in `Interaction.metadata`. Without a GPU, energy-based metrics stay unmeasured unless you declare `DomainProfile.default_power_w`.
140
+
141
+ ```python
142
+ meter = GpuMeter()
143
+ with meter:
144
+ text = model(query)
145
+ interaction.metadata.update(meter.result())
146
+ ```
147
+
148
+ ## Audit trail
149
+
150
+ `SQLiteStore` hashes every record into a chain. `store.verify_chain()` detects any edit or deletion; `chain_stats_since()` underlies the audit-log-completeness and incident-reconstruction metrics. `begin_request()` at ingress makes lost records visible.
151
+
152
+ ## Honest limitations
153
+
154
+ - Fairness needs groups. Supply `Interaction.group`, a `group_fn` proxy in your profile, or run paired-prompt audits.
155
+ - Thresholds are defaults from the reference formulas (`DOCUMENTATION/formulas.md`); tune them for your domain.
156
+ - The LLM judge is an LLM: it can be wrong. Calibrate it against labelled cases before trusting its verdicts.
157
+ - Membership inference and poisoning audits measure the whole system you wrap; interpret them against how sensitive your corpus is.
158
+ - Monitoring does not transfer legal liability. It produces the evidence of reasonable care.
159
+
160
+ MIT licensed.
@@ -1,91 +1,74 @@
1
- [build-system]
2
- requires = ["hatchling"]
3
- build-backend = "hatchling.build"
4
-
5
- [project]
6
- name = "rti-ethics-engine"
7
- version = "2.0.0"
8
- description = "India's first granular AI ethics framework for RTI applications (36 metrics, 9 dimensions)"
9
- readme = "README.md"
10
- license = {file = "LICENSE"}
11
- requires-python = ">=3.9"
12
- authors = [
13
- {name = "RTI Ethics Engine Team"}
14
- ]
15
- keywords = [
16
- "ai-ethics",
17
- "rti",
18
- "india",
19
- "government",
20
- "compliance",
21
- "fairness",
22
- "audit",
23
- "llm"
24
- ]
25
- classifiers = [
26
- "Development Status :: 5 - Production/Stable",
27
- "Intended Audience :: Developers",
28
- "Intended Audience :: Science/Research",
29
- "License :: OSI Approved :: MIT License",
30
- "Operating System :: OS Independent",
31
- "Programming Language :: Python :: 3",
32
- "Programming Language :: Python :: 3.9",
33
- "Programming Language :: Python :: 3.10",
34
- "Programming Language :: Python :: 3.11",
35
- "Programming Language :: Python :: 3.12",
36
- "Topic :: Scientific/Engineering :: Artificial Intelligence",
37
- "Topic :: Software Development :: Libraries :: Python Modules"
38
- ]
39
- dependencies = [
40
- "numpy>=1.24.0",
41
- "scipy>=1.10.0"
42
- ]
43
-
44
- [project.optional-dependencies]
45
- dev = [
46
- "pytest>=7.0",
47
- "pytest-cov>=4.0",
48
- "black>=23.0",
49
- "mypy>=1.0",
50
- "ruff>=0.1.0"
51
- ]
52
- docs = [
53
- "sphinx>=5.0",
54
- "sphinx-rtd-theme>=1.0"
55
- ]
56
-
57
- [project.urls]
58
- Homepage = "https://github.com/rti-ethics/rti-ethics-engine"
59
- Documentation = "https://rti-ethics-engine.readthedocs.io"
60
- Repository = "https://github.com/rti-ethics/rti-ethics-engine"
61
- Issues = "https://github.com/rti-ethics/rti-ethics-engine/issues"
62
-
63
- [tool.hatch.build.targets.wheel]
64
- packages = ["rti_ethics_engine"]
65
-
66
- [tool.hatch.build.targets.sdist]
67
- include = [
68
- "/rti_ethics_engine",
69
- "/tests",
70
- "/README.md",
71
- "/LICENSE"
72
- ]
73
-
74
- [tool.pytest.ini_options]
75
- testpaths = ["tests"]
76
- python_files = ["test_*.py"]
77
- addopts = "-v --tb=short"
78
-
79
- [tool.black]
80
- line-length = 100
81
- target-version = ["py39", "py310", "py311"]
82
-
83
- [tool.ruff]
84
- line-length = 100
85
- select = ["E", "F", "W", "I", "N"]
86
-
87
- [tool.mypy]
88
- python_version = "3.9"
89
- warn_return_any = true
90
- warn_unused_configs = true
91
- disallow_untyped_defs = true
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "rti-ethics-engine"
7
+ version = "3.0.0"
8
+ description = "Measured, domain-pluggable ethics monitoring for LLM systems: 9 dimensions, 44 formulas, drift tracking and automatic model gating. No defaulted metrics."
9
+ readme = "README.md"
10
+ license = {file = "LICENSE"}
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ {name = "RTI Ethics Engine Team"}
14
+ ]
15
+ keywords = [
16
+ "ai-ethics",
17
+ "llm",
18
+ "monitoring",
19
+ "drift",
20
+ "fairness",
21
+ "guardrails",
22
+ "audit",
23
+ "compliance",
24
+ "eu-ai-act",
25
+ "rag",
26
+ "rti"
27
+ ]
28
+ classifiers = [
29
+ "Development Status :: 4 - Beta",
30
+ "Intended Audience :: Developers",
31
+ "Intended Audience :: Science/Research",
32
+ "License :: OSI Approved :: MIT License",
33
+ "Operating System :: OS Independent",
34
+ "Programming Language :: Python :: 3",
35
+ "Programming Language :: Python :: 3.9",
36
+ "Programming Language :: Python :: 3.10",
37
+ "Programming Language :: Python :: 3.11",
38
+ "Programming Language :: Python :: 3.12",
39
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
40
+ "Topic :: Software Development :: Libraries :: Python Modules"
41
+ ]
42
+ dependencies = [
43
+ "numpy>=1.24.0",
44
+ "scipy>=1.10.0"
45
+ ]
46
+
47
+ [project.optional-dependencies]
48
+ embeddings = ["sentence-transformers>=2.2"]
49
+ dev = [
50
+ "pytest>=7.0",
51
+ "pytest-cov>=4.0"
52
+ ]
53
+
54
+ [project.urls]
55
+ Homepage = "https://github.com/Krishil-Parikh/bazinga_tsec_hacks"
56
+ Repository = "https://github.com/Krishil-Parikh/bazinga_tsec_hacks"
57
+ Issues = "https://github.com/Krishil-Parikh/bazinga_tsec_hacks/issues"
58
+
59
+ [tool.hatch.build.targets.wheel]
60
+ packages = ["rti_ethics_engine"]
61
+
62
+ [tool.hatch.build.targets.sdist]
63
+ include = [
64
+ "/rti_ethics_engine",
65
+ "/tests",
66
+ "/README.md",
67
+ "/CHANGELOG.md",
68
+ "/LICENSE"
69
+ ]
70
+
71
+ [tool.pytest.ini_options]
72
+ testpaths = ["tests"]
73
+ python_files = ["test_*.py"]
74
+ addopts = "-v --tb=short"
@@ -0,0 +1,48 @@
1
+ """
2
+ RTI Ethics Engine v3 - measured, domain-pluggable ethics monitoring for LLM systems.
3
+
4
+ Nine dimensions, 44 formulas, every value computed from evidence: nothing is
5
+ defaulted. A metric that cannot be measured is reported as unmeasured (None)
6
+ and lowers coverage instead of silently passing.
7
+
8
+ Quick start:
9
+
10
+ from rti_ethics_engine import Evaluator, Interaction, SQLiteStore
11
+ from rti_ethics_engine.profiles import rti_profile
12
+
13
+ ev = Evaluator(profile=rti_profile(), store=SQLiteStore("ethics.db"))
14
+ ev.begin_request("llama3")
15
+ out = ev.evaluate(Interaction(query="...", response="...", model_id="llama3", latency_ms=812))
16
+ print(out.result.final_label, out.result.s_llm, out.result.coverage)
17
+
18
+ Run active audits (security, fairness pairs, gold-set performance, ...) with
19
+ ev.auditor, and gate models in production with Gatekeeper. See README.md.
20
+ """
21
+
22
+ from .calculators import * # noqa: F401,F403 (the 44 formulas)
23
+ from .config import (
24
+ DEFAULT_WEIGHTS, DIMENSION_INFO, DIMENSION_WEIGHTS, METRIC_CATALOG, METRIC_SPECS, RISK_THRESHOLDS,
25
+ EngineConfig, MetricSpec, build_config,
26
+ )
27
+ from .core import EthicsEngine
28
+ from .evaluator import Evaluation, Evaluator
29
+ from .gatekeeper import ACTIVE, PROBATION, QUARANTINED, GatePolicy, Gatekeeper
30
+ from .judge import Judge, default_judge, ollama_llm, openai_compatible_llm
31
+ from .meters import GpuMeter
32
+ from .probes import (
33
+ Attack, Auditor, GoldExample, PairedPrompt, PoisonDetector, call_model, default_attacks,
34
+ )
35
+ from .profile import CategoryRule, DomainProfile
36
+ from .results import DimensionResult, EthicsResult, FinalLabel, RiskBand
37
+ from .store import SQLiteStore
38
+ from .textkit import HashingEmbedder, SentenceTransformerEmbedder
39
+ from .types import Chunk, GenResult, Interaction
40
+
41
+ __version__ = "3.0.0"
42
+ __author__ = "RTI Ethics Engine Team"
43
+ __license__ = "MIT"
44
+
45
+
46
+ def evaluate(metrics: dict, profile: DomainProfile = None) -> EthicsResult:
47
+ """Low-level entry point: run the 3-layer protocol on a ready-made metrics dict."""
48
+ return EthicsEngine(profile).run_protocol(metrics)