Skip to main content

rti-ethics-engine

Measured, domain-pluggable ethics monitoring for LLM systems.

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.

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.

pip install rti-ethics-engine                 # numpy + scipy only
pip install "rti-ethics-engine[embeddings]"   # optional: sentence-transformers embeddings

The one rule: nothing is defaulted

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.

60-second quick start

from rti_ethics_engine import Evaluator, Interaction, Chunk, SQLiteStore

ev = Evaluator(store=SQLiteStore("ethics.db"), judge=None)   # judge=None: no LLM judge

ev.begin_request("my-model")                                  # at API ingress (enables audit-log completeness)
answer = my_model(question)                                   # your code
out = ev.evaluate(Interaction(
    query=question, response=answer, model_id="my-model",
    context=[Chunk(text=c) for c in retrieved_passages],      # RAG context, if any
    latency_ms=812, input_tokens=420, output_tokens=96,
))

print(out.result.final_label, out.result.s_llm, out.result.coverage)
print(out.result.unmeasured)          # what could not be measured, and why that matters
out.result.print_full_audit()

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.

Core objects

Object Role
Interaction One observed request/response. The only data contract.
DomainProfile Everything domain-specific: refusal wording, legal citations, PII patterns, SLA, prices. A generic default ships; profiles.rti_profile() is a worked example.
Evaluator Logs an interaction, measures all nine dimensions, runs the 3-layer protocol, returns an Evaluation.
Auditor (ev.auditor) Active probes: security, fairness pairs, gold-set performance, membership leakage, poisoning detection, explainability stability.
Gatekeeper Per-model ACTIVE / QUARANTINED / PROBATION state machine with human re-admission.
Judge Optional LLM judge for semantic checks. Pluggable, and fully disableable.
SQLiteStore Evidence store with a hash-chained, tamper-evident audit log.
EthicsEngine The pure scoring protocol, if you want to feed it metrics yourself.

How each dimension is measured

Dimension Metrics Measured from
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
Drift PSI, KS, prediction/confidence/embedding drift Frozen baseline vs recent window; PSI bins from baseline quantiles; KS threshold corrected for sample size
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
Explainability Confidence, feature importance, counterfactual distance, explanation stability Token log-probs (or self-consistency); retrieval attribution (or judge claim support); probe audits
UX Task completion, frustration, dialog turns, cognitive load, readability Sessions: reformulation = unresolved; Flesch reading ease
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
Performance Accuracy, recall, precision, F1, group F1, long-tail accuracy Your labelled gold set via Auditor.run_gold
Monitoring Audit-log completeness, SLA, policy violations, incident reconstruction Requests seen vs hash-chain-verified records; latency vs SLA; profile policy rules
Security Adversarial accuracy, injection success, membership inference, poisoning detection Auditor.run_security (canary attacks + your attacks), membership and poisoning audits

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.

Make it yours: a domain profile

from rti_ethics_engine import DomainProfile, CategoryRule, Evaluator

bank = DomainProfile(
    name="retail-banking",
    refusal_patterns=[r"\bcannot (?:share|disclose|help)\b", r"\bunable to assist\b"],
    pii_patterns={"iban": r"\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b", "email": r"\b[\w.+-]+@[\w-]+\.\w+\b"},
    required_elements={"disclaimer": r"not financial advice"},
    category_rules=[CategoryRule("account_data", [r"\bbalance of\b.*\bother customer\b"], expect_refusal=True)],
    sla_ms=4000,
    pricing={"gpt-x": (0.0005, 0.0015)},           # USD per 1k tokens (in, out)
    thresholds={"fairness.spd": 0.05},             # tighten a gate
    weights={"security": 0.30},                    # re-weight a dimension
)
ev = Evaluator(profile=bank, judge=None)

Active audits

from rti_ethics_engine import GoldExample, PairedPrompt, default_attacks

def model(query, context=None):                    # your whole system as a callable
    return answer_with_rag(query, context)

aud = ev.auditor
aud.run_security("my-model", model, benign=["How do I reset my PIN?"])          # PISR, adversarial accuracy
aud.run_gold("my-model", model, [GoldExample("Close my account", expected_decision=1)])
aud.run_fairness_pairs("my-model", model, [PairedPrompt("p1", {"A": "...", "B": "..."}, expected_decision=1)])
aud.run_membership("my-model", model, members=[...], nonmembers=[...])          # leakage
aud.run_poisoning(corpus_docs)                                                  # ingestion detector PDR

Fresh audit results (default: younger than 7 days) are folded into every evaluation automatically.

Gate models in production

from rti_ethics_engine import Gatekeeper, GatePolicy

gate = Gatekeeper(ev.store, GatePolicy(min_obs=10, block_risk=0.55))
model = gate.select(["model-a", "model-b"], preferred="model-a")   # routes around blocked models
...
out = ev.evaluate(interaction)
gate.observe(out)                                  # may quarantine automatically

gate.request_readmission("model-a", by="alice")    # -> PROBATION: send shadow traffic
gate.probation_report("model-a")
gate.approve("model-a", approver="alice")          # human sign-off; refuses if evidence is not clean

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.

The LLM judge (optional)

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.

Evaluator()                                         # default: judge from RTI_JUDGE_* env vars (Ollama, qwen3:8b)
Evaluator(judge=Judge(my_llm_callable, "judge-model"))
Evaluator(judge=None)                               # off: deterministic proxies are used where an honest one exists,
                                                    # judge-only metrics (claim-level faithfulness) are skipped

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.

Measured hardware

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.

meter = GpuMeter()
with meter:
    text = model(query)
interaction.metadata.update(meter.result())

Audit trail

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.

Honest limitations

  • Fairness needs groups. Supply Interaction.group, a group_fn proxy in your profile, or run paired-prompt audits.
  • Thresholds are defaults from the reference formulas (DOCUMENTATION/formulas.md); tune them for your domain.
  • The LLM judge is an LLM: it can be wrong. Calibrate it against labelled cases before trusting its verdicts.
  • Membership inference and poisoning audits measure the whole system you wrap; interpret them against how sensitive your corpus is.
  • Monitoring does not transfer legal liability. It produces the evidence of reasonable care.

MIT licensed.

Metadata

Release files for rti-ethics-engine 3.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rti-ethics-engine 3.0.0
File Size Uploaded Provenance
rti_ethics_engine-3.0.0.tar.gz 69.3 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for rti-ethics-engine 3.0.0
File Interpreter ABI Platform
rti_ethics_engine-3.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 135.5 kB

Release files / rti_ethics_engine-3.0.0.tar.gz

Download URL rti_ethics_engine-3.0.0.tar.gz
Size 69.3 kB
Tags Source
SHA-256 checksum
How to use checksums
80a1e2924f5506a6700d3cb43f302b98451e366660a2282f89cedfa96bb65d07
BLAKE2b-256 checksum
How to use checksums
ec5faf5c2ff3f73908c843e3e2db058254ab551b913032bc919112dcbcac05a2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.0rc2

Release files / rti_ethics_engine-3.0.0-py3-none-any.whl

Download URL rti_ethics_engine-3.0.0-py3-none-any.whl
Size 66.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2f778e1f32f744b17791d2c1a24786ecec4bf87846b8282b1fc06ef4fbbdee3d
BLAKE2b-256 checksum
How to use checksums
c0493a99ea208a993000490222d92253cd4883c01e69789a75b0a021dcf674ec
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.0rc2

Security for rti-ethics-engine 3.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page