Skip to main content

SafeLens

Composable safety analysis and mechanistic-interpretability infrastructure for LLM workflows.

SafeLens gives safety experiments a shared runtime for model adapters, activation hooks, probes, monitors, attribution methods, pipeline execution, and structured reports.

CI Python Pydantic Typed MkDocs HuggingFace ModelScope TransformerLens-compatible

Documentation · Quick Start · Explorer · Architecture · Model Sources · Hooks · Extend


Why SafeLens

LLM safety experiments tend to mix model loading, activation instrumentation, method code, runtime checks, attribution evidence, and report serialization in one-off scripts. SafeLens separates those concerns into stable interfaces, so a probe or monitor can move across model backends and pipeline configurations without rewriting the surrounding infrastructure.

Pluggable safety methods
Register probes, monitors, and attributors by name, then assemble them from YAML.
Activation-level runtime
Cache activations, install temporary hooks, and run TransformerLens-style patching workflows.
Model adapter boundary
Target dummy, local, HuggingFace, Qwen3 Dense, ModelScope, and TransformerLens-compatible sources.
Typed report contract
Return Pydantic reports for risk scores, evidence tokens, attribution, and run summaries.
CLI and Python APIs
Run scans from safelens run, validate configs, inspect adapters, or call the runner directly.
Downstream adapter boundary
Convert internal SafetyReport objects to FlagSafe-style policy payloads.

Architecture

SafeLens is organized as a layered runtime. User-facing entry points produce a validated config, the runner wires together model wrappers and registered methods, and the interpretability runtime provides reusable hook, cache, and patching primitives underneath the safety methods.

flowchart TB
    classDef surface fill:#EEF6FF,stroke:#2563EB,color:#0F172A
    classDef orchestration fill:#F8FAFC,stroke:#64748B,color:#0F172A
    classDef method fill:#F0FDF4,stroke:#16A34A,color:#0F172A
    classDef runtime fill:#FFF7ED,stroke:#EA580C,color:#0F172A
    classDef model fill:#FAF5FF,stroke:#9333EA,color:#0F172A
    classDef output fill:#FEF2F2,stroke:#DC2626,color:#0F172A

    subgraph L0["Entry points"]
        CLI["CLI<br/>safelens run / validate / schema"]
        YAML["YAML pipeline config"]
        Python["Python API<br/>PipelineRunner / build_model_wrapper"]
    end

    subgraph L1["Configuration and orchestration"]
        Config["Pydantic config models<br/>PipelineConfig / ModelLoadConfig"]
        Runner["PipelineRunner<br/>setup -> run -> summarize"]
        Registry["Method registry<br/>create_probe / create_monitor / create_attributor"]
    end

    subgraph L2["Safety method layer"]
        Probe["BaseProbe<br/>activation probes and interventions"]
        Monitor["BaseMonitor<br/>batch or generation-time signals"]
        Attributor["BaseAttributor<br/>input or training-data attribution"]
    end

    subgraph L3["Interpretability runtime"]
        HookedRoot["HookedRoot + HookPoint<br/>temporary and persistent hooks"]
        Cache["ActivationCache<br/>selection, slicing, residual decomposition"]
        Patching["PatchSpec + generic_activation_patch<br/>residual, MLP, attention, head patches"]
        Analysis["Analysis helpers<br/>logit attribution, SVD, FactoredMatrix"]
    end

    subgraph L4["Model bridge"]
        Wrapper["ModelWrapper contract<br/>load_model / run_with_cache / generate"]
        Bridge["Architecture adapters<br/>component hook names and tensor shapes"]
        Adapters["ModelAdapterRegistry<br/>static inspection and cache plans"]
    end

    subgraph L5["Backends"]
        Dummy["dummy"]
        Local["local"]
        HF["huggingface"]
        Qwen["qwen3_dense"]
        TL["transformer_lens compatible"]
        MS["modelscope"]
    end

    subgraph L6["Outputs"]
        Report["RunReport + SafetyReport"]
        JSON["JSON artifact"]
        FlagSafe["FlagSafeAdapter"]
        Schema["JSON Schema"]
        Docs["MkDocs docs"]
    end

    CLI --> Config
    YAML --> Config
    Python --> Runner
    Config --> Runner
    Runner --> Registry
    Registry --> Probe
    Registry --> Monitor
    Registry --> Attributor
    Runner --> Wrapper
    Probe --> HookedRoot
    Probe --> Cache
    Probe --> Patching
    Monitor --> Wrapper
    Attributor --> Cache
    HookedRoot --> Wrapper
    Cache --> Wrapper
    Patching --> Wrapper
    Analysis --> Cache
    Wrapper --> Bridge
    Wrapper --> Adapters
    Adapters --> Dummy
    Adapters --> Local
    Adapters --> HF
    Adapters --> Qwen
    Adapters --> TL
    Adapters --> MS
    Runner --> Report
    Probe --> Report
    Monitor --> Report
    Attributor --> Report
    Report --> JSON
    Report --> FlagSafe
    Config --> Schema
    Report --> Schema
    Config --> Docs

    class CLI,YAML,Python surface
    class Config,Runner,Registry orchestration
    class Probe,Monitor,Attributor method
    class HookedRoot,Cache,Patching,Analysis runtime
    class Wrapper,Bridge,Adapters,Dummy,Local,HF,Qwen,TL,MS model
    class Report,JSON,FlagSafe,Schema,Docs output

Quick Start

The default example uses model.source: dummy, so it does not download model weights and is suitable for CI, smoke tests, and interface demos.

python -m pip install -r requirements-dev.txt
python -m pip install -e . --no-build-isolation

safelens validate --config examples/config.yaml
safelens run --config examples/config.yaml

Expected CLI summary:

{
  "samples_scanned": 2,
  "flagged_count": 1,
  "max_risk_score": 1.0
}

The run writes a JSON report to ./safety_scan.json by default.

from SafeLens.pipelines.runner import PipelineRunner

report = PipelineRunner.from_yaml("examples/config.yaml").run()
print(report.summary)

Local Explorer

The interactive visualization workspace ships inside the Python package. From a source checkout, install the complete real-model job surface before the first launch, then start one process:

python -m pip install -e ".[explorer,models,modelscope,sae,attribution,nla,jlens,viz]"
python -m pip install "jlens @ https://codeload.github.com/anthropics/jacobian-lens/tar.gz/581d398613e5602a5af361e1c34d3a92ea82ba8e"
safelens explorer --artifact-root outputs/local-explorer --no-browser

The jlens extra only installs the J-Lens runtime dependencies; the jlens package itself is distributed from the pinned Anthropic repository commit above and is therefore installed as a separate step.

Explorer checks optional dependencies at job-preflight time, so a feature run in an environment that only has .[explorer] is rejected with an explicit hint that names the missing install command. Install the full command above in the same environment that launches safelens explorer, and restart the process after adding an extra; packages installed while Explorer is running are not picked up.

Feature Extra Provides
Viewer, Run Library, bundled artifacts explorer FastAPI + Uvicorn
Prompt jobs on real models models PyTorch + Transformers
ModelScope model source modelscope ModelScope snapshot download
Attribution jobs attribution Captum integrated gradients
SAE intervention and discovery sae SAE Lens
NLA explanation jobs nla Hugging Face Hub + Safetensors
J-Lens explanation jobs jlens + pinned install (see above) J-Lens runtime dependencies
CircuitsVis HTML bridges viz CircuitsVis

SafeLens opens http://127.0.0.1:7860. The React application, artifact API, deep links, and job API all use that one port; Node.js is not required at runtime. The bundled example is immediately available, and compact *.explorer.json files placed under the artifact root appear in the Run Library.

Explorer automatically uses cuda:0 when CUDA is available and falls back to CPU otherwise. Use SAFELENS_EXPLORER_JOB_DEVICE=auto, cpu, or cuda:1 for an explicit override. See the complete Local Explorer setup guide for model downloads, Gemma SAE setup, frontend staging, remote deployment, health checks, and troubleshooting.

J-Lens uses a fitted, model-specific Jacobian checkpoint. Explorer registers the public Neuronpedia checkpoint for Qwen/Qwen2.5-7B-Instruct (layers 0-26) and defaults to layer 20 so its selection aligns with the public Qwen NLA profile. The first run downloads the pinned checkpoint to .cache/safelens/jlens. Configure another local checkpoint under the Explorer artifact root or a Hugging Face repository in the Explanation panel, or set SAFELENS_JLENS_SOURCE, SAFELENS_JLENS_FILENAME, SAFELENS_JLENS_REVISION, and SAFELENS_JLENS_MODEL before launching Explorer.

For an isolated container instead:

docker build -t safelens-explorer .
docker volume create safelens-data
docker run --rm -p 127.0.0.1:7860:7860 \
  -v safelens-data:/data safelens-explorer

See apps/local_explorer/README.md for frontend development, remote deployment, persistence, and health-check details.

Installation

SafeLens is currently installed from source.

Use case Command
Core package python -m pip install -e . --no-build-isolation
Development and docs python -m pip install -r requirements-dev.txt
HuggingFace, local, Qwen3 Dense, TransformerLens-compatible wrappers python -m pip install -e ".[models]" --no-build-isolation
ModelScope wrapper python -m pip install -e ".[modelscope]" --no-build-isolation
Local Explorer python -m pip install -e ".[explorer]"
Explorer with real-model jobs python -m pip install -e ".[explorer,models,modelscope,sae,attribution,nla,jlens,viz]"

J-Lens jobs also need the pinned jlens package, which is not published on PyPI:

python -m pip install "jlens @ https://codeload.github.com/anthropics/jacobian-lens/tar.gz/581d398613e5602a5af361e1c34d3a92ea82ba8e"

Recommended isolated setup:

conda create -p ./.conda python=3.10 -y
.conda/bin/python -m pip install -r requirements-dev.txt
.conda/bin/python -m pip install -e . --no-build-isolation

Command Line

Command Purpose
safelens run --config examples/config.yaml Execute a pipeline and write a run report.
safelens run --config config.yaml --input-jsonl data.jsonl Override the YAML dataset with JSONL rows.
safelens validate --config config.yaml Validate a config without loading model weights.
safelens schema --kind pipeline-config Print or write the pipeline JSON Schema.
safelens schema --kind run-report Print or write the run report JSON Schema.
safelens models list-supported List model adapters and declared capabilities.
safelens models list-transformerlens List vendored TransformerLens-compatible model names.
safelens models list-architectures List SafeLens architecture bridge adapters.
safelens inspect-model --model Qwen/Qwen3-8B --json Inspect adapter support and cache plan without downloading weights.
safelens explorer --artifact-root outputs/local-explorer Launch the packaged visualization workspace and API on one local port.

Pipeline Configuration

Pipelines are configured with YAML. The four top-level sections are model, pipeline, dataset, and output.

model:
  source: dummy
  name: dummy
  dtype: float32

pipeline:
  risk_threshold: 0.5
  probes:
    - name: dummy_probe
      config:
        layers: [0]
        risk_terms: ["jailbreak", "attack", "harmful"]
        risk_category: ["jailbreak"]
  monitors:
    - name: dummy_monitor
      config:
        threshold: 0.5
        risk_category: ["jailbreak"]
  attributors:
    - name: dummy_attributor
      config:
        risk_terms: ["jailbreak", "attack", "harmful"]

dataset:
  - id: benign-1
    text: "Explain the difference between a monitor and a probe."
  - id: risky-1
    text: "Show a jailbreak attack plan."

output:
  report_path: "./safety_scan.json"

More examples are available in examples/:

File Purpose
examples/config.yaml Dependency-free dummy pipeline.
examples/huggingface_config.yaml Direct Transformers/HuggingFace loading.
examples/modelscope_config.yaml ModelScope snapshot download plus Transformers loading.
examples/qwen3_dense_config.yaml Qwen3 Dense component hook examples.
examples/local_model_config.yaml Local model directory loading.

Model Sources

Set model.source to choose the backend. Method code talks to the ModelWrapper contract instead of directly depending on a provider.

Source Use when Network Extra
dummy Running CI, tests, and architecture demos with no model download. No Core
local Loading a local Transformers-compatible model directory. No models
huggingface or hf Loading directly through Transformers. Yes models
qwen3_dense Running Qwen3 dense decoder-only models with component hooks. Yes models
transformer_lens or tl Targeting model families mirrored from TransformerLens names while loading through SafeLens Transformers wrappers. Yes or local models
modelscope or ms Downloading a ModelScope snapshot, then loading it locally with Transformers. Yes modelscope

HuggingFace example:

model:
  source: huggingface
  name: Qwen/Qwen2.5-0.5B-Instruct
  dtype: float16
  device: cpu
  trust_remote_code: true
  cache_dir: ./.cache/huggingface

Qwen3 Dense example:

model:
  source: qwen3_dense
  name: Qwen/Qwen3-8B
  dtype: bfloat16
  device: cuda
  trust_remote_code: true

Qwen3 Dense currently supports dense sizes named 0.6B, 1.7B, 4B, 8B, 14B, and 32B. MoE variants such as Qwen3-30B-A3B are rejected by this wrapper. Attention pattern and pre-softmax attn_scores hooks require eager softmax instrumentation; flash or SDPA attention paths may need an eager attention implementation.

TransformerLens-compatible example:

model:
  source: transformer_lens
  name: gpt2-small
  dtype: float32
  device: cpu

This source mirrors TransformerLens-style model naming and analysis ergonomics, but SafeLens still loads through its own Transformers wrappers and does not require the transformer-lens package.

ModelScope example:

model:
  source: modelscope
  name: Qwen/Qwen2.5-0.5B-Instruct
  dtype: float16
  device: cpu
  trust_remote_code: true
  cache_dir: ./.cache/modelscope
  local_dir: ./models/qwen2.5-0.5b

Hook And Patching Runtime

SafeLens includes a lightweight TransformerLens-style operation layer for mechanistic-interpretability and safety probing workflows. The goal is to make activation-oriented methods reusable across model wrappers while keeping the core package dependency-light.

Primitive Purpose
HookPoint Identity hook point with temporary, permanent, ordered, and removable hooks.
HookedRoot Shared hook management for named hook points.
ActivationCache Dictionary-like cache with alias lookup, slicing, stacking, residual decomposition, and logit attribution helpers.
temporary_hooks Install hooks for one run and reliably remove them afterward.
cache_activations Run a model while collecting selected activations.
PatchSpec Describe one activation replacement or additive patch.
generic_activation_patch Run a grid of patches and score every patched output.
get_act_patch_* Convenience helpers for residual streams, MLP outputs, attention outputs, head vectors, patterns, and scores.

SafeLens accepts both native component names and TransformerLens-style names:

Component SafeLens style TransformerLens style
Residual stream before attention layer_0.resid_pre blocks.0.hook_resid_pre
Residual stream after attention layer_0.resid_mid blocks.0.hook_resid_mid
Residual stream after MLP layer_0.resid_post blocks.0.hook_resid_post
Attention output layer_0.attn_out blocks.0.hook_attn_out
MLP output layer_0.mlp_out blocks.0.hook_mlp_out
Query, key, value, head output layer_0.q, layer_0.k, layer_0.v, layer_0.z blocks.0.attn.hook_q, blocks.0.attn.hook_k, blocks.0.attn.hook_v, blocks.0.attn.hook_z

Minimal patching example:

from SafeLens.core.hooks import ActivationCache
from SafeLens.core.patching import get_act_patch_resid_pre

clean_cache = ActivationCache({"layer_0.resid_pre": clean_resid_pre})

scores = get_act_patch_resid_pre(
    model,
    corrupted_batch,
    clean_cache,
    metric=lambda output: float(output["score"]),
    layers=[0],
    positions=[3],
)

Transformers-backed wrappers also expose TransformerLens-style helpers when the underlying tokenizer and architecture support them:

tokens = model.to_tokens("SafeLens checks", prepend_bos=False)
logits, cache = model.run_with_cache(
    tokens,
    names_filter=lambda name: name.endswith("hook_resid_post"),
    return_cache_object=True,
)
resid_post = cache["resid_post", 0]

Reports And Adapters

Every run emits a RunReport containing per-sample SafetyReport objects. The report model is designed to keep method outputs inspectable and serializable.

{
  "generated_at": "2026-01-01T00:00:00Z",
  "summary": {
    "samples_scanned": 2,
    "flagged_count": 1,
    "max_risk_score": 1.0
  },
  "reports": [
    {
      "sample_id": "risky-1",
      "flagged": true,
      "risk_score": 1.0,
      "risk_category": ["jailbreak"],
      "evidence_tokens": [2, 3],
      "probe_results": [],
      "monitoring_signals": [],
      "attributions": [],
      "metadata": {
        "input_keys": ["id", "text"]
      }
    }
  ]
}

Use the FlagSafe adapter boundary when a downstream policy layer needs a compact allow/block payload:

from SafeLens.adapters import FlagSafeAdapter

rules = FlagSafeAdapter.to_flagsafe_batch(report.reports)

Extension Points

SafeLens exposes four core contracts.

Interface Implement when you need to Output
ModelWrapper Load a model, register hooks, run with cache, and generate outputs. Model output and cache
BaseProbe Analyze or intervene on internal activations. ProbeResult
BaseMonitor Emit safety signals during a batch or generation step. MonitoringSignal
BaseAttributor Attribute risk to input tokens or training-data sources. AttributionResult

Register a probe:

from collections.abc import Sequence
from typing import Any

from SafeLens.core.base import BaseProbe, Batch, ModelWrapper, ProbeResult
from SafeLens.core.registry import register_probe


@register_probe("linear_probe")
class LinearProbe(BaseProbe):
    def attach(self, model: ModelWrapper, layers: Sequence[int]) -> None:
        self.layers = list(layers)

    def detect(self, batch: Batch) -> ProbeResult:
        return ProbeResult(risk_score=0.0, critical_layers=self.layers)

    def intervene(self, batch: Batch, direction: Any, scale: float) -> None:
        ...

    def detach(self) -> None:
        ...

Enable it from YAML:

pipeline:
  probes:
    - name: linear_probe
      config:
        layers: [8, 16, 24]

Built-in demo methods:

Method Purpose
dummy_probe Keyword-based probe for validating probe integration and evidence tokens.
linear_probe Logistic-regression probe over explicit feature vectors or captured activations, trained from labeled examples.
dummy_monitor Threshold-based monitor for validating monitor integration.
dummy_attributor Token attribution stub for validating attribution output.

linear_probe can train from labeled train_data, a train_jsonl file, or the active pipeline dataset when train_from_dataset: true is set. Dataset rows may provide explicit features, or plain text/prompt plus label; in the latter case the probe runs the base model, caches the configured layer activation, and trains on that activation internally. Set train_split and eval_split to train and report on different subsets of one dataset.

Contrastive steering vectors can be built and applied in separate steps:

from SafeLens.steering import ContrastiveSteeringVector

steering = ContrastiveSteeringVector.fit(
    model,
    dataset,
    layer="layer_12.resid_post",
    train_split="train",
)
steering.save("steering_vector.json")

loaded = ContrastiveSteeringVector.load("steering_vector.json")
handle = loaded.apply(model, scale=1.0)
try:
    output = model.generate("Prompt to steer")
finally:
    handle.remove()

Package Layout

src/SafeLens/
  adapters/      external adapter boundaries, including FlagSafe
  app/           future demo application entry points
  attribution/   attribution implementations
  core/          contracts, registries, hooks, cache, patching, analysis helpers
  monitors/      safety monitor implementations
  pipelines/     YAML-driven pipeline runner
  probes/        probe implementations
  steering/      steering-vector method namespace
  utils/         model wrappers, model registry, and architecture bridges

Documentation

Topic Link
Documentation site https://pku-pillar-group.github.io/SafeLens/
Explorer setup and operations docs/explorer_setup.md
Configuration docs/configuration.md
Development guide docs/development.md
Add a model adapter docs/guides/add_model_adapter.md
Add a probe docs/guides/add_probe.md
Add a monitor docs/guides/add_monitor.md
Hook naming docs/guides/hook_naming.md
Qwen3 support docs/guides/qwen3_support.md
API reference docs/api/
Privacy notes docs/privacy.md

Build the docs locally:

mkdocs build --strict
mkdocs serve

Quality Checks

Use the same checks as CI when changing code or docs:

pre-commit run --all-files
pytest --cov=SafeLens --cov-report=term-missing --cov-report=xml
npm run build --prefix apps/local_explorer
npm run test:e2e --prefix apps/local_explorer
npm run test:performance --prefix apps/local_explorer
python -m build
python -m twine check --strict dist/*
mkdocs build --strict

The CI matrix validates Python 3.10, 3.11, and 3.12 quality jobs, frontend build/E2E/performance gates, package metadata, and the MkDocs documentation build.

Current Status

SafeLens is an alpha research infrastructure project. The core contracts, pipeline runner, registries, model adapter boundary, hook and patching runtime, typed reports, tests, docs, and CI scaffolding are in place. The built-in safety methods are intentionally simple demo implementations; real probes, monitors, attributors, and steering methods should be added through the extension interfaces above.

Near-term engineering priorities:

  • Connect production-grade safety probes, monitors, attributors, and steering methods.
  • Expand real-model integration coverage across supported architecture adapters.
  • Tighten FlagSafe integration around the target downstream policy schema.
  • Add richer tutorials for activation patching, cached attribution, and model adapter development.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

safelens-0.1.0.tar.gz (877.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

safelens-0.1.0-py3-none-any.whl (759.8 kB view details)

Uploaded Python 3

File details

Details for the file safelens-0.1.0.tar.gz.

File metadata

  • Download URL: safelens-0.1.0.tar.gz
  • Upload date:
  • Size: 877.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for safelens-0.1.0.tar.gz
Algorithm Hash digest
SHA256 dc23bde2ba2e4ea9a3c2008b7de920502f4d5b1e1b50c4f72f3d5b97948043ff
MD5 872ac5f0c087a78c96a0c3fcd3382216
BLAKE2b-256 8d329e0a69d1b9edc34ea8f7f4fef77b30c85ba29b15117abc8319d369ffc044

See more details on using hashes here.

Provenance

The following attestation bundles were made for safelens-0.1.0.tar.gz:

Publisher: publish.yml on PKU-PILLAR-Group/SafeLens

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file safelens-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: safelens-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 759.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for safelens-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 85af73ca2d8308a52ff60aa20cfe814162384ff9bd8c19ec18e5260f93b36cd7
MD5 0798fb0adcc21920216ade3fd5bce5e5
BLAKE2b-256 85c36f8980a867a47ad6a663d119dc26b6f4b83510bb290acefc0ed03fcc21e7

See more details on using hashes here.

Provenance

The following attestation bundles were made for safelens-0.1.0-py3-none-any.whl:

Publisher: publish.yml on PKU-PILLAR-Group/SafeLens

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

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