Adding an adapter¶
Adapters let RAITAP delegate to other libraries. Each family (transparency, robustness, metrics, reporting, tracking, visualisers) exposes its own registration decorator; you drop that decorator on a class, implement the abstract methods, and you are done. There is no central registry file to edit. registry_name is the only required kwarg and pyright errors at the decoration site if you forget it.
The walkthrough below uses a fictional superxai-lib transparency explainer.
1. Find the relevant module where to create the new adapter¶
RAITAP is organised into modules under src/raitap/, each owning the adapters for one family. Ideally a library fits into a single module. If it genuinely spans two, ship two adapters (one per module) and use algorithm_registry to keep their responsibilities disjoint.
2. Create the new adapter file¶
Name the file after the library (e.g. superxai_explainer.py). Then:
from __future__ import annotations
from typing import TYPE_CHECKING
from raitap import adapters
from raitap.transparency.contracts import ExplainerAlgorithmSpec, MethodFamily
from .base_explainer import AttributionOnlyExplainer
if TYPE_CHECKING:
import torch
import torch.nn as nn
@adapters.transparency(
registry_name="superxai", # CLI `+transparency=superxai` / Python `from raitap.transparency import superxai`
# extra="superxai", # uv extra name; defaults to `registry_name` (omit unless they differ, see metrics for an exception)
library="superxai-lib", # real PyPI package name; drives `self._lazy_import()`
error_patterns={ # rewrite cryptic upstream errors at call sites
r"some library footgun": "Do X instead.",
},
suppress_warnings=[ # optional library-noise filters installed at decoration time
(r"some noisy.*pattern", UserWarning, r"superxai.*"),
],
algorithm_registry={ # required kwarg, pyright errors at decoration if missing
# supertreeshap is model-agnostic (works on any backend): leave requires default empty.
"supertreeshap": ExplainerAlgorithmSpec({MethodFamily.SHAPLEY}),
},
# output_payload_kind=ExplanationPayloadKind.ATTRIBUTIONS # optional, this is the default
)
class SuperXAIExplainer(AttributionOnlyExplainer):
def __init__(self, algorithm: str, **init_kwargs):
super().__init__()
self.algorithm = algorithm
self.init_kwargs = init_kwargs
# check_backend_compat: inherited from AdapterMixin. Raises BackendIncompatibilityError
# when algorithm.requires - backend.provides is non-empty. Write zero gate code here.
# Override only for a non-capability contract, e.g. MarabouAssessor per-call setup.
def compute_attributions(
self,
model: nn.Module,
inputs: torch.Tensor,
backend=None,
**call_kwargs,
) -> torch.Tensor:
superxai = self._lazy_import()
with self._rethrow():
return getattr(superxai, self.algorithm)(model, **self.init_kwargs).attribute(
inputs, **call_kwargs
)
Base class.
AttributionOnlyExplainerprovides batching, artefact persistence, andexplain()orchestration. Other families use other bases (EmpiricalAttackAssessorfor robustness,BaseMetricComputerfor metrics, etc.); find them in files starting withbase_. The concrete adapter is just a normal class; nothing flags it as abstract (the oldabstract=Trueworkaround was removed).Decorator.
@adapters.transparency(...)is the sole entry point for registration.registry_nameis required and pyright-checked at the decoration site viaRequired[str]. Each family has its own facade attribute (adapters.robustness,adapters.metrics,adapters.reporter,adapters.tracker,visualisers.transparency,visualisers.robustness): pick the one matching your base class.Registration kwargs.
libraryis the pip name poweringself._lazy_import(): pass it when you wrap a third-party package (the usual case).extrais the uv extra surfaced in install hints and scanned byraitap.deps.inference; it defaults toregistry_nameso you only need to set it explicitly when they differ (e.g.classification_metrics+detection_metricsboth shareextra="metrics").error_patternsandsuppress_warningsare optional polish.algorithm_registry(decorator kwarg). Transparency and robustness only. Maps algorithm name to a per-algorithm semantics-hints value RAITAP tracks and reports on (ExplainerAlgorithmSpecfor transparency,AssessorAlgorithmSpecfor robustness). Required: pyright errors at the decoration site if you omit it. Missing or misnamed entries make algorithms unselectable. The decorator assigns it onto the class sotype(self).algorithm_registrystill works at runtime.output_payload_kind(decorator kwarg). Transparency only. Tells the report renderer what artefact shape the explainer emits (ATTRIBUTIONS,SALIENCY_MAP, ...). Defaults toExplanationPayloadKind.ATTRIBUTIONS; only pass it if your explainer emits something else.Backend compatibility. Inherited
check_backend_compat(fromAdapterMixin) raisesBackendIncompatibilityErrorwhenalgorithm.requires - backend.providesis non-empty. You write zero gate code. Gradient-based algorithms declarerequires={Capability.AUTOGRAD}on theirExplainerAlgorithmSpec/AssessorAlgorithmSpecentry; model-agnostic algorithms (SHAP KernelExplainer, Occlusion, FeatureAblation) leaverequiresat its default empty frozenset and run on any backend including ONNX. Overridecheck_backend_compatonly for a non-capability contract (Marabou uses it for per-call setup; auto-LiRPA callssuper()then warns on XPU). Import:from raitap.utils.errors import BackendIncompatibilityError(also re-exported fromraitap.robustnessandraitap.transparency). See Backend capabilities for what each capability means.super().__init__(). Cooperative parent init: the base class allocates buffers the framework reads later (e.g.self.attributions = None). Forgetting raisesAttributeErrordeep insideexplain(). Always call it first when overriding__init__.self._lazy_import(). Inherited fromAdapterMixin. Importslibrary(orf"{library}.{submodule}"if you passsubmodule=) at call time, keepingimport raitapcheap and letting users install RAITAP without every wrapped library. Raises a clear install-hintImportErrorif the library is missing.Backend libs (
torch,torchvision,onnxruntime) needlazy_importtoo. If your adapter file usestorch.Tensor/torch.nn/ etc, do NOT addimport torchat module top-level. Use thefrom raitap.utils.lazy import lazy_importpattern (see that module's docstring for theTYPE_CHECKING+lazy_import("torch")recipe). This preserves the bootstrap-from-zero promise:raitap.deps.bootstrap._composewalks every adapter__init__on a bare venv (no torch installed yet) to infer extras before it installs them. A top-levelimport torchbreaks the whole bootstrap. The per-familytests/test_partial_extras_safe.pypoisonstorchinsys.modulesto catch regressions immediately.self._rethrow(). Inherited context manager. Catches exceptions from the wrapped library and rewrites known-cryptic ones using yourerror_patternsmap.Abstract methods.
AttributionOnlyExplainer→compute_attributions(...).EmpiricalAttackAssessor→_default_invoke(self, ctx: AttackInvokeCtx) -> Tensor(framework dispatches viagenerate_adversarial; adapters implement only_default_invoke).BaseMetricComputer→compute() -> MetricResult. Check the base file for exact signatures.
3. Update the pyproject.toml file¶
Add the per-library extra and chain it into the module's compound extra. Use the extra name in the chain, not the PyPI name, if they differ.
[project.optional-dependencies]
# Transparency module
# ...
superxai = ["superxai-lib"]
transparency = ["raitap[shap,captum,superxai]"]
4. Update the tests¶
Tests go in tests/raitap/<module>/<subdir>/test_<name>_<entity>.py. Also add an E2E test, and update the E2E test matrix if the module has one.
Cover at minimum: registry membership, check_backend_compat, decorator wiring (the class lands in _BUILDERS["<group>"]["<name>"] and in ADAPTER_EXTRAS), and one compute_attributions / generate_adversarial / compute happy path. CI enforces module coverage.
No stub workaround needed. Concrete adapters are just concrete classes, and the decorator carries every family-required piece of metadata (algorithm_registry, output_payload_kind). A test stub is just @adapters.<family>(registry_name="_stub", algorithm_registry={...}, ...) over a minimal subclass implementing the abstract methods.
5. Update the docs¶
Add a row to docs/modules/<module>/frameworks-and-libraries.md documenting the algorithms you support (or the analogous page for non-adapter modules). YAML + Python tabs both required via config-tabs. This is what users see when they look up "does raitap support X?".
Adapter families and where to look¶
Module |
Abstract base |
Registration decorator |
Group |
Schema |
|---|---|---|---|---|
Transparency explainer |
|
|
|
|
Robustness assessor |
|
|
|
|
Metric |
|
|
|
|
Reporter |
|
|
|
|
Tracker |
|
|
|
|
Visualiser |
|
|
(no Hydra group) |
Adding a new algorithm to an existing adapter¶
If the library already has an adapter and you just want to expose a new algorithm, add an entry to that adapter's algorithm_registry decorator kwarg. Set requires={Capability.AUTOGRAD} on the hint if the algorithm needs gradients; leave requires at default empty if it is model-agnostic. No new file, no pyproject.toml change. Add a unit test that constructs the adapter with the new algorithm and asserts a successful happy-path call.
When the registration decorators aren't enough¶
A handful of Hydra shapes can't be expressed via the registration decorators alone and live as direct ConfigStore writes in src/raitap/configs/zen.py::register_zen_groups:
# @package _global_injections. E.g. thereporting=html/reporting=pdfentries push both thereportingnode and ahydra.callbacks.reporting_sweepblock into the root config so multirun report aggregation auto-wires. The decorators always write underpackage="<group>"or"<group>.<name>"; they can't reach_global_._target_: nullvariants. E.g.reporting=disabledcarries_target_: null+multirun_report: false. The decorators always target a concrete class.Custom hydra-zen
to_config=or non-dataclass nodes. Same escape hatch.
If your new adapter only needs to set _target_ + optional kwargs on its schema (the 95% case), don't touch zen.py. Use the decorator, done. If you genuinely need one of the special shapes above, add a cs.store(group=..., name=..., package=..., node=...) block in register_zen_groups after the store.add_to_hydra_store(...) flush: that order lets your specialisation overwrite the decorator-generated entry.